Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py

changeset 6184
789e88d94899
parent 6183
29384109306c
child 6188
5a6ae3be31e6
--- a/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py	Sun Mar 11 19:38:33 2018 +0100
+++ b/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py	Mon Mar 12 19:37:23 2018 +0100
@@ -45,8 +45,8 @@
         
         "M201",
         
-        "M501", "M502", "M503", "M504", "M505", 
-        "M511", "M512", "M513", 
+        "M501", "M502", "M503", "M504", "M505", "M506", "M507",
+        "M511", "M512", "M513", "M514",
         
         "M601",
         "M611", "M612", "M613",
@@ -130,7 +130,9 @@
             (self.__checkFormatString, ("M611", "M612", "M613",
                                         "M621", "M622", "M623", "M624", "M625",
                                         "M631", "M632")),
-            (self.__checkBugBear, ("M501", "M502", "M503", "M504", "M505", "M511", "M512", "M513",)),
+            (self.__checkBugBear, ("M501", "M502", "M503", "M504", "M505",
+                                   "M506", "M507",
+                                   "M511", "M512", "M513", "M514")),
             (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")),
             (self.__checkFuture, ("M701", "M702")),
             (self.__checkGettext, ("M711",)),
@@ -525,6 +527,12 @@
         """
         Private method to check, if built-ins are shadowed.
         """
+        functionDefs = [ast.FunctionDef]
+        try:
+            functionDefs.append(ast.AsyncFunctionDef)
+        except AttributeError:
+            pass
+        
         ignoreBuiltinAssignments = self.__args.get(
             "BuiltinsChecker", self.__defaultArgs["BuiltinsChecker"])
         
@@ -563,8 +571,9 @@
                             self.__error(element.lineno - 1,
                                          element.col_offset,
                                          "M131", element.id)
-            elif isinstance(node, ast.FunctionDef):
-                # function definition
+            elif any(isinstance(node, functionDef)
+                     for functionDef in functionDefs):
+                # (asynchronous) function definition
                 if sys.version_info >= (3, 0):
                     for arg in node.args.args:
                         if isinstance(arg, ast.arg) and \
@@ -640,9 +649,15 @@
             "list",
             "set",
         )
+        functionDefs = [ast.FunctionDef]
+        try:
+            functionDefs.append(ast.AsyncFunctionDef)
+        except AttributeError:
+            pass
         
         for node in ast.walk(self.__tree):
-            if isinstance(node, ast.FunctionDef):
+            if any(isinstance(node, functionDef)
+                   for functionDef in functionDefs):
                 for default in node.args.defaults:
                     if any(isinstance(default, mutableType)
                            for mutableType in mutableTypes):
@@ -716,8 +731,11 @@
         """
         visitor = BugBearVisitor()
         visitor.visit(self.__tree)
-        for node, reason in visitor.violations:
-            self.__error(node.lineno - 1, node.col_offset, reason)
+        for violation in visitor.violations:
+            node = violation[0]
+            reason = violation[1]
+            params = violation[2:]
+            self.__error(node.lineno - 1, node.col_offset, reason, *params)
 
 
 class TextVisitor(ast.NodeVisitor):
@@ -785,7 +803,7 @@
         Private method handling class and function definitions.
         
         @param node reference to the node to handle
-        @type ast.FunctionDef or ast.ClassDef
+        @type ast.FunctionDef, ast.AsyncFunctionDef or ast.ClassDef
         """
         # Manually traverse class or function definition
         # * Handle decorators normally
@@ -841,6 +859,16 @@
         # Skipped nodes: ('name', 'args', 'returns')
         self.__visitDefinition(node)
 
+    def visit_AsyncFunctionDef(self, node):
+        """
+        Public method to handle an asynchronous function definition.
+        
+        @param node reference to the node to handle
+        @type ast.AsyncFunctionDef
+        """
+        # Skipped nodes: ('name', 'args', 'returns')
+        self.__visitDefinition(node)
+
     def visit_Call(self, node):
         """
         Public method to handle a function call.
@@ -1141,6 +1169,40 @@
                     self.violations.append((node, "M505"))
                     break
     
+    def visit_Assign(self, node):
+        """
+        Public method to handle assignments.
+        
+        @param node reference to the node to be processed
+        @type ast.Assign
+        """
+        if isinstance(self.__nodeStack[-2], ast.ClassDef):
+            # By using 'hasattr' below we're ignoring starred arguments, slices
+            # and tuples for simplicity.
+            assignTargets = {t.id for t in node.targets if hasattr(t, 'id')}
+            if '__metaclass__' in assignTargets and sys.version_info >= (3, 0):
+                self.violations.append((node, "M514"))
+        
+        elif len(node.targets) == 1:
+            target = node.targets[0]
+            if isinstance(target, ast.Attribute) and \
+               isinstance(target.value, ast.Name):
+                if (target.value.id, target.attr) == ('os', 'environ'):
+                    self.violations.append((node, "M506"))
+        
+        self.generic_visit(node)
+    
+    def visit_For(self, node):
+        """
+        Public method to handle 'for' statements.
+        
+        @param node reference to the node to be processed
+        @type ast.For
+        """
+        self.__checkForM507(node)
+        
+        self.generic_visit(node)
+    
     def __checkForM502(self, node):
         """
         Private method to check the use of *strip().
@@ -1162,6 +1224,69 @@
             return          # no characters appear more than once
 
         self.violations.append((node, "M502"))
+    
+    def __checkForM507(self, node):
+        """
+        Private method to check for unused loop variables.
+        
+        @param node reference to the node to be processed
+        @type ast.For
+        """
+        targets = NameFinder()
+        targets.visit(node.target)
+        ctrlNames = set(filter(lambda s: not s.startswith('_'),
+                               targets.getNames()))
+        body = NameFinder()
+        for expr in node.body:
+            body.visit(expr)
+        usedNames = set(body.getNames())
+        for name in sorted(ctrlNames - usedNames):
+            n = targets.getNames()[name][0]
+            self.violations.append((n, "M507", name))
+
+
+class NameFinder(ast.NodeVisitor):
+    """
+    Class to extract a name out of a tree of nodes.
+    """
+    def __init__(self):
+        """
+        Constructor
+        """
+        super(NameFinder, self).__init__()
+        
+        self.__names = {}
+
+    def visit_Name(self, node):
+        """
+        Public method to handle 'Name' nodes.
+        
+        @param node reference to the node to be processed
+        @type ast.Name
+        """
+        self.__names.setdefault(node.id, []).append(node)
+
+    def visit(self, node):
+        """
+        Public method to traverse a given AST node.
+        
+        @param node AST node to be traversed
+        @type ast.Node
+        """
+        if isinstance(node, list):
+            for elem in node:
+                super(NameFinder, self).visit(elem)
+        else:
+            super(NameFinder, self).visit(node)
+    
+    def getNames(self):
+        """
+        Public method to return the extracted names and Name nodes.
+        
+        @return dictionary containing the names as keys and the list of nodes
+        @rtype dict
+        """
+        return self.__names
 
 #
 # eflag: noqa = M702

eric ide

mercurial