Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py

changeset 4506
57666e501a42
child 4507
1a5bc1ac7c2e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py	Sat Oct 24 20:36:26 2015 +0200
@@ -0,0 +1,255 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a checker for miscellaneous checks.
+"""
+
+from __future__ import unicode_literals
+
+import sys
+import ast
+import re
+
+
+class MiscellaneousChecker(object):
+    """
+    Class implementing a checker for miscellaneous checks.
+    """
+    Codes = [
+        "M101", "M102",
+        "M111", "M112",
+        "M121",
+        "M131",
+        "M801",
+        
+        "M901",
+    ]
+
+    def __init__(self, source, filename, select, ignore, expected, repeat,
+                 args):
+        """
+        Constructor
+        
+        @param source source code to be checked (list of string)
+        @param filename name of the source file (string)
+        @param select list of selected codes (list of string)
+        @param ignore list of codes to be ignored (list of string)
+        @param expected list of expected codes (list of string)
+        @param repeat flag indicating to report each occurrence of a code
+            (boolean)
+        @param args dictionary of arguments for the miscellaneous checks (dict)
+        """
+        self.__select = tuple(select)
+        self.__ignore = ('',) if select else tuple(ignore)
+        self.__expected = expected[:]
+        self.__repeat = repeat
+        self.__filename = filename
+        self.__source = source[:]
+        self.__args = args
+        
+        self.__blindExceptRegex = re.compile(
+            r'(except:)')                               # __IGNORE_WARNING__
+        self.__pep3101FormatRegex = re.compile(
+            r'^(?:[^\'"]*[\'"][^\'"]*[\'"])*\s*%|^\s*%')
+
+        # statistics counters
+        self.counters = {}
+        
+        # collection of detected errors
+        self.errors = []
+        
+        checkersWithCodes = [
+            # TODO: fill this
+            (self.__checkCoding, ("M101", "M102")),
+            (self.__checkCopyright, ("M111", "M112")),
+            (self.__checkBlindExcept, ("M121",)),
+            (self.__checkPep3101, ("M131",)),
+            (self.__checkPrintStatements, ("M801",)),
+        ]
+        
+        self.__defaultArgs = {
+            "CodingChecker": 'latin-1, utf-8',
+            "CopyrightChecker": {
+                "MinFilesize": 0,
+                "Author": "",
+            },
+        }
+        
+        self.__checkers = []
+        for checker, codes in checkersWithCodes:
+            if any(not (code and self.__ignoreCode(code))
+                    for code in codes):
+                self.__checkers.append(checker)
+    
+    def __ignoreCode(self, code):
+        """
+        Private method to check if the message code should be ignored.
+
+        @param code message code to check for (string)
+        @return flag indicating to ignore the given code (boolean)
+        """
+        return (code.startswith(self.__ignore) and
+                not code.startswith(self.__select))
+    
+    def __error(self, lineNumber, offset, code, *args):
+        """
+        Private method to record an issue.
+        
+        @param lineNumber line number of the issue (integer)
+        @param offset position within line of the issue (integer)
+        @param code message code (string)
+        @param args arguments for the message (list)
+        """
+        if self.__ignoreCode(code):
+            return
+        
+        if code in self.counters:
+            self.counters[code] += 1
+        else:
+            self.counters[code] = 1
+        
+        # Don't care about expected codes
+        if code in self.__expected:
+            return
+        
+        if code and (self.counters[code] == 1 or self.__repeat):
+            # record the issue with one based line number
+            self.errors.append(
+                (self.__filename, lineNumber + 1, offset, (code, args)))
+    
+    def __reportInvalidSyntax(self):
+        """
+        Private method to report a syntax error.
+        """
+        exc_type, exc = sys.exc_info()[:2]
+        if len(exc.args) > 1:
+            offset = exc.args[1]
+            if len(offset) > 2:
+                offset = offset[1:3]
+        else:
+            offset = (1, 0)
+        self.__error(offset[0] - 1, offset[1] or 0,
+                     'M901', exc_type.__name__, exc.args[0])
+    
+    def run(self):
+        """
+        Public method to check the given source against miscellaneous
+        conditions.
+        """
+        if not self.__filename:
+            # don't do anything, if essential data is missing
+            return
+        
+        if not self.__checkers:
+            # don't do anything, if no codes were selected
+            return
+        
+        try:
+            self.__tree = compile(
+                ''.join(self.__source), '', 'exec', ast.PyCF_ONLY_AST)
+        except (SyntaxError, TypeError):
+            self.__reportInvalidSyntax()
+            return
+        
+        for check in self.__checkers:
+            check()
+    
+    def __checkCoding(self):
+        """
+        Private method to check the presence of a coding line and valid
+        encodings.
+        """
+        if len(self.__source) == 0:
+            return
+        
+        encodings = [e.lower().strip()
+                     for e in self.__args.get(
+                     "CodingChecker", self.__defaultArgs["CodingChecker"])
+                     .split(",")]
+        for lineno, line in enumerate(self.__source[:2]):
+            matched = re.search('coding[:=]\s*([-\w.]+)', line, re.IGNORECASE)
+            if matched:
+                if encodings and matched.group(1).lower() not in encodings:
+                    self.__error(lineno, 0, "M102", matched.group(1))
+                break
+        else:
+            self.__error(0, 0, "M101")
+    
+    def __checkCopyright(self):
+        """
+        Private method to check the presence of a copyright statement.
+        """
+        source = "".join(self.__source)
+        copyrightArgs = self.__args.get(
+            "CopyrightChecker", self.__defaultArgs["CopyrightChecker"])
+        copyrightMinFileSize = copyrightArgs.get(
+            "MinFilesize",
+            self.__defaultArgs["CopyrightChecker"]["MinFilesize"])
+        copyrightAuthor = copyrightArgs.get(
+            "Author",
+            self.__defaultArgs["CopyrightChecker"]["Author"])
+        copyrightRegexStr = \
+            r"Copyright\s+(\(C\)\s+)?(\d{{4}}\s+-\s+)?\d{{4}}\s+{author}"
+        
+        tocheck = max(1024, copyrightMinFileSize)
+        topOfSource = source[:tocheck]
+        if len(topOfSource) < copyrightMinFileSize:
+            return
+
+        copyrightRe = re.compile(copyrightRegexStr.format(author=r".*"),
+                                 re.IGNORECASE)
+        if not copyrightRe.search(topOfSource):
+            self.__error(0, 0, "M111")
+            return
+        
+        if copyrightAuthor:
+            copyrightAuthorRe = re.compile(
+                copyrightRegexStr.format(author=copyrightAuthor),
+                re.IGNORECASE)
+            if not copyrightAuthorRe.search(topOfSource):
+                self.__error(0, 0, "M112")
+    
+    def __checkBlindExcept(self):
+        """
+        Private method to check for blind except statements.
+        """
+        for lineno, line in enumerate(self.__source):
+            match = self.__blindExceptRegex.search(line)
+            if match:
+                self.__error(lineno, match.start(), "M121")
+    
+    def __checkPep3101(self):
+        """
+        Private method to check for old style string formatting.
+        """
+        for lineno, line in enumerate(self.__source):
+            match = self.__pep3101FormatRegex.search(line)
+            if match:
+                lineLen = len(line)
+                pos = line.find('%')
+                formatPos = pos
+                formatter = '%'
+                if line[pos + 1] == "(":
+                    pos = line.find(")", pos)
+                c = line[pos]
+                while c not in "diouxXeEfFgGcrs":
+                    pos += 1
+                    if pos >= lineLen:
+                        break
+                    c = line[pos]
+                if c in "diouxXeEfFgGcrs":
+                    formatter += c
+                self.__error(lineno, formatPos, "M131", formatter)
+    
+    def __checkPrintStatements(self):
+        """
+        Private method to check for print statements.
+        """
+        for node in ast.walk(self.__tree):
+            if (isinstance(node, ast.Call) and
+                getattr(node.func, 'id', None) == 'print') or \
+               (hasattr(ast, 'Print') and isinstance(node, ast.Print)):
+                self.__error(node.lineno - 1, node.col_offset, "M801")

eric ide

mercurial