Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py

branch
BgService
changeset 3209
c5432abceb25
parent 3145
a9de05d4a22f
child 3228
f489068e51e8
--- a/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py	Sun Jan 05 22:45:29 2014 +0100
+++ b/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py	Wed Jan 15 22:55:52 2014 +0100
@@ -9,129 +9,181 @@
 
 from __future__ import unicode_literals
 
-try:
-    str = unicode    # __IGNORE_WARNING__
-except (NameError):
-    pass
+import sys
 
-import os
-
-from PyQt4.QtCore import QProcess, QCoreApplication
+import pep8
+from NamingStyleChecker import NamingStyleChecker
 
-from . import pep8
-from .NamingStyleChecker import NamingStyleChecker
-from .DocStyleChecker import DocStyleChecker
+# register the name checker
+pep8.register_check(NamingStyleChecker, NamingStyleChecker.Codes)
 
-import Preferences
-import Utilities
-
-from eric5config import getConfig
+from DocStyleChecker import DocStyleChecker
 
 
-class CodeStyleCheckerPy2(object):
+def initService():
     """
-    Class implementing the code style checker interface for Python 2.
+    Initialize the service and return the entry point.
+    
+    @return the entry point for the background client (function)
     """
-    def __init__(self, filename, lines, repeat=False,
-                 select="", ignore="", max_line_length=79,
-                 hang_closing=False, docType="pep257"):
+    return codeStyleCheck
+
+
+class CodeStyleCheckerReport(pep8.BaseReport):
+    """
+    Class implementing a special report to be used with our dialog.
+    """
+    def __init__(self, options):
         """
         Constructor
         
-        @param filename name of the file to check (string)
-        @param lines source of the file (list of strings) (ignored)
-        @keyparam repeat flag indicating to repeat message categories (boolean)
-        @keyparam select list of message IDs to check for
-            (comma separated string)
-        @keyparam ignore list of message IDs to ignore
-            (comma separated string)
-        @keyparam max_line_length maximum allowed line length (integer)
-        @keyparam hang_closing flag indicating to allow hanging closing
-            brackets (boolean)
-        @keyparam docType type of the documentation strings
-            (string, one of 'eric' or 'pep257')
+        @param options options for the report (optparse.Values)
         """
-        assert docType in ("eric", "pep257")
-        
-        self.errors = []
-        self.counters = {}
+        super(CodeStyleCheckerReport, self).__init__(options)
         
-        interpreter = Preferences.getDebugger("PythonInterpreter")
-        if interpreter == "" or not Utilities.isExecutable(interpreter):
-            self.errors.append(
-                (filename, 1, 1, QCoreApplication.translate(
-                    "CodeStyleCheckerPy2",
-                    "Python2 interpreter not configured.")))
-            return
-        
-        checker = os.path.join(getConfig('ericDir'),
-                               "UtilitiesPython2", "CodeStyleChecker.py")
-        
-        args = [checker]
-        if repeat:
-            args.append("-r")
-        if select:
-            args.append("-s")
-            args.append(select)
-        if ignore:
-            args.append("-i")
-            args.append(ignore)
-        args.append("-m")
-        args.append(str(max_line_length))
-        if hang_closing:
-            args.append("-h")
-        args.append("-d")
-        args.append(docType)
-        args.append("-f")
-        args.append(filename)
+        self.__repeat = options.repeat
+        self.errors = []
+    
+    def error_args(self, line_number, offset, code, check, *args):
+        """
+        Public method to collect the error messages.
         
-        proc = QProcess()
-        proc.setProcessChannelMode(QProcess.MergedChannels)
-        proc.start(interpreter, args)
-        finished = proc.waitForFinished(15000)
-        if finished:
-            output = \
-                str(proc.readAllStandardOutput(),
-                    Preferences.getSystem("IOEncoding"),
-                    'replace').splitlines()
-            if output[0] == "ERROR":
-                self.errors.append((filename, 1, 1, output[2]))
-                return
-            
-            if output[0] == "NO_PEP8":
-                return
-            
-            index = 0
-            while index < len(output):
-                if output[index] == "PEP8_STATISTICS":
-                    index += 1
-                    break
-                
-                fname = output[index + 1]
-                lineno = int(output[index + 2])
-                position = int(output[index + 3])
-                code = output[index + 4]
-                arglen = int(output[index + 5])
-                args = []
-                argindex = 0
-                while argindex < arglen:
-                    args.append(output[index + 6 + argindex])
-                    argindex += 1
-                index += 6 + arglen
-                
-                if code in NamingStyleChecker.Codes:
-                    text = NamingStyleChecker.getMessage(code, *args)
-                elif code in DocStyleChecker.Codes:
-                    text = DocStyleChecker.getMessage(code, *args)
+        @param line_number line number of the issue (integer)
+        @param offset position within line of the issue (integer)
+        @param code message code (string)
+        @param check reference to the checker function (function)
+        @param args arguments for the message (list)
+        @return error code (string)
+        """
+        code = super(CodeStyleCheckerReport, self).error_args(
+            line_number, offset, code, check, *args)
+        if code and (self.counters[code] == 1 or self.__repeat):
+            if code in NamingStyleChecker.Codes:
+                text = NamingStyleChecker.getMessage(code, *args)
+            else:
+                text = pep8.getMessage(code, *args)
+            self.errors.append(
+                (self.filename, line_number, offset, text)
+            )
+        return code
+
+
+def extractLineFlags(line, startComment="#", endComment=""):
+    """
+    Function to extract flags starting and ending with '__' from a line
+    comment.
+    
+    @param line line to extract flags from (string)
+    @keyparam startComment string identifying the start of the comment (string)
+    @keyparam endComment string identifying the end of a comment (string)
+    @return list containing the extracted flags (list of strings)
+    """
+    flags = []
+    
+    pos = line.rfind(startComment)
+    if pos >= 0:
+        comment = line[pos + len(startComment):].strip()
+        if endComment:
+            comment = comment.replace("endComment", "")
+        flags = [f.strip() for f in comment.split()
+                 if (f.startswith("__") and f.endswith("__"))]
+    return flags
+
+
+def codeStyleCheck(filename, source, args):
+    """
+    Do the code style check and/ or fix found errors.
+    
+    @param filename source filename (string)
+    @param source string containing the code to check (string)
+    @param args arguments used by the codeStyleCheck function (list of
+        excludeMessages (str), includeMessages (str), repeatMessages
+        (bool), fixCodes (str), noFixCodes (str), fixIssues (bool),
+        maxLineLength (int), hangClosing (bool), docType (str), errors
+        (list of str), eol (str), encoding (str))
+    @return tuple of stats (dict) and results (tuple for each found violation
+        of style (tuple of lineno (int), position (int), text (str), fixed
+            (bool), autofixing (bool), fixedMsg (str)))
+    """
+    excludeMessages, includeMessages, \
+        repeatMessages, fixCodes, noFixCodes, fixIssues, maxLineLength, \
+        hangClosing, docType, errors, eol, encoding = args
+    
+    stats = {}
+    # avoid 'Encoding declaration in unicode string' exception on Python2
+    if sys.version_info[0] == 2:
+        if encoding == 'utf-8-bom':
+            enc = 'utf-8'
+        else:
+            enc = encoding
+        source = [line.encode(enc) for line in source]
+    
+    if fixIssues:
+        from CodeStyleFixer import CodeStyleFixer
+        fixer = CodeStyleFixer(
+            filename, source, fixCodes, noFixCodes,
+            maxLineLength, True, eol)  # always fix in place
+    else:
+        fixer = None
+    
+    if not errors:
+        if includeMessages:
+            select = [s.strip() for s in
+                      includeMessages.split(',') if s.strip()]
+        else:
+            select = []
+        if excludeMessages:
+            ignore = [i.strip() for i in
+                      excludeMessages.split(',') if i.strip()]
+        else:
+            ignore = []
+        
+        # check coding style
+        styleGuide = pep8.StyleGuide(
+            reporter=CodeStyleCheckerReport,
+            repeat=repeatMessages,
+            select=select,
+            ignore=ignore,
+            max_line_length=maxLineLength,
+            hang_closing=hangClosing,
+        )
+        report = styleGuide.check_files([filename])
+        stats.update(report.counters)
+
+        # check documentation style
+        docStyleChecker = DocStyleChecker(
+            source, filename, select, ignore, [], repeatMessages,
+            maxLineLength=maxLineLength, docType=docType)
+        docStyleChecker.run()
+        stats.update(docStyleChecker.counters)
+        
+        errors = report.errors + docStyleChecker.errors
+    
+    deferredFixes = {}
+    results = []
+    for error in errors:
+        fname, lineno, position, text = error
+        if lineno > len(source):
+            lineno = len(source)
+        if "__IGNORE_WARNING__" not in \
+                extractLineFlags(source[lineno - 1].strip()):
+            if fixer:
+                res, msg, id_ = fixer.fixIssue(lineno, position, text)
+                if res == -1:
+                    itm = [lineno, position, text]
+                    deferredFixes[id_] = itm
                 else:
-                    text = pep8.getMessage(code, *args)
-                self.errors.append((fname, lineno, position, text))
-            while index < len(output):
-                code, countStr = output[index].split(None, 1)
-                self.counters[code] = int(countStr)
-                index += 1
-        else:
-            self.errors.append(
-                (filename, 1, 1, QCoreApplication.translate(
-                    "CodeStyleCheckerPy2",
-                    "Python2 interpreter did not finish within 15s.")))
+                    itm = [lineno, position, text, res == 1, True, msg]
+            else:
+                itm = [lineno, position, text, False, False, '']
+            results.append(itm)
+    
+    if fixer:
+        deferredResults = fixer.finalize()
+        for id_ in deferredResults:
+            fixed, msg = deferredResults[id_]
+            itm = deferredFixes[id_]
+            itm.extend([fixed == 1, fixed == 1, msg])
+        fixer.saveFile(encoding)
+
+    return stats, results

eric ide

mercurial