eric6/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py

changeset 7245
b47179fbb9d8
parent 7057
0e8d3b0c4889
child 7249
0bf517e60f54
--- a/eric6/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py	Tue Sep 17 19:23:27 2019 +0200
+++ b/eric6/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py	Tue Sep 17 19:43:17 2019 +0200
@@ -48,7 +48,9 @@
         "M131", "M132",
         
         ## Comprehensions
-        "M191", "M192", "M193", "M194",
+        "M181", "M182", "M183", "M184",
+        "M185", "M186", "M187",
+        "M191", "M192", "M193",
         "M195", "M196", "M197", "M198",
         
         ## Dictionaries with sorted keys
@@ -59,6 +61,11 @@
         "M311", "M312", "M313", "M314", "M315",
         "M321",
         
+        ## sys.version and sys.version_info usage
+        "M401", "M402", "M403",
+        "M411", "M412", "M413", "M414",
+        "M421", "M422", "M423",
+        
         ## Bugbear
         "M501", "M502", "M503", "M504", "M505", "M506", "M507", "M508",
         "M509",
@@ -161,17 +168,25 @@
             (self.__checkCoding, ("M101", "M102")),
             (self.__checkCopyright, ("M111", "M112")),
             (self.__checkBuiltins, ("M131", "M132")),
-            (self.__checkComprehensions, ("M191", "M192", "M193", "M194",
+            (self.__checkComprehensions, ("M181", "M182", "M183", "M184",
+                                          "M185", "M186", "M187",
+                                          "M191", "M192", "M193",
                                           "M195", "M196", "M197", "M198")),
             (self.__checkDictWithSortedKeys, ("M201",)),
+            (self.__checkDateTime, ("M301", "M302", "M303", "M304", "M305",
+                                    "M306", "M307", "M308", "M311", "M312",
+                                    "M313", "M314", "M315", "M321")),
+            (self.__checkSysVersion, ("M401", "M402", "M403",
+                                      "M411", "M412", "M413", "M414",
+                                      "M421", "M422", "M423")),
+            (self.__checkBugBear, ("M501", "M502", "M503", "M504", "M505",
+                                   "M506", "M507", "M508", "M509",
+                                   "M511", "M512", "M513",
+                                   "M521", "M522", "M523", "M524")),
             (self.__checkPep3101, ("M601",)),
             (self.__checkFormatString, ("M611", "M612", "M613",
                                         "M621", "M622", "M623", "M624", "M625",
                                         "M631", "M632")),
-            (self.__checkBugBear, ("M501", "M502", "M503", "M504", "M505",
-                                   "M506", "M507", "M508", "M509",
-                                   "M511", "M512", "M513",
-                                   "M521", "M522", "M523", "M524")),
             (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")),
             (self.__checkFuture, ("M701", "M702")),
             (self.__checkGettext, ("M711",)),
@@ -181,9 +196,6 @@
             (self.__checkReturn, ("M831", "M832", "M833", "M834")),
             (self.__checkLineContinuation, ("M841",)),
             (self.__checkCommentedCode, ("M891",)),
-            (self.__checkDateTime, ("M301", "M302", "M303", "M304", "M305",
-                                    "M306", "M307", "M308", "M311", "M312",
-                                    "M313", "M314", "M315", "M321")),
         ]
         
         self.__defaultArgs = {
@@ -690,39 +702,95 @@
         Private method to check some comprehension related things.
         """
         for node in ast.walk(self.__tree):
-            if (isinstance(node, ast.Call) and
-               len(node.args) == 1 and
-               isinstance(node.func, ast.Name)):
-                if (isinstance(node.args[0], ast.GeneratorExp) and
-                        node.func.id in ('list', 'set', 'dict')):
+            if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
+                nArgs = len(node.args)
+                
+                if (
+                    nArgs == 1 and
+                    isinstance(node.args[0], ast.GeneratorExp) and
+                    node.func.id in ('list', 'set')
+                ):
                     errorCode = {
-                        "dict": "M193",
-                        "list": "M191",
-                        "set": "M192",
+                        "list": "M181",
+                        "set": "M182",
                     }[node.func.id]
                     self.__error(node.lineno - 1, node.col_offset, errorCode)
 
-                elif (isinstance(node.args[0], ast.ListComp) and
-                      node.func.id in ('set', 'dict')):
+                elif (
+                    nArgs == 1 and
+                    isinstance(node.args[0], ast.GeneratorExp) and
+                    isinstance(node.args[0].elt, ast.Tuple) and
+                    len(node.args[0].elt.elts) == 2 and
+                    node.func.id == "dict"
+                ):
+                    self.__error(node.lineno - 1, node.col_offset, "M183")
+                
+                elif (
+                    nArgs == 1 and
+                    isinstance(node.args[0], ast.ListComp) and
+                    node.func.id in ('list', 'set', 'dict')
+                ):
                     errorCode = {
-                        'dict': 'M195',
-                        'set': 'M194',
+                        'list': 'M195',
+                        'dict': 'M185',
+                        'set': 'M184',
                     }[node.func.id]
                     self.__error(node.lineno - 1, node.col_offset, errorCode)
-
-                elif (isinstance(node.args[0], ast.List) and
-                      node.func.id in ('set', 'dict')):
+                
+                elif nArgs == 1 and (
+                    isinstance(node.args[0], ast.Tuple) and
+                    node.func.id == "tuple" or
+                    isinstance(node.args[0], ast.List) and
+                    node.func.id == "list"
+                ):
                     errorCode = {
-                        'dict': 'M197',
-                        'set': 'M196',
+                        'tuple': 'M197',
+                        'list': 'M198',
+                    }[node.func.id]
+                    self.__error(node.lineno - 1, node.col_offset, errorCode,
+                                 type(node.args[0]).__name__.lower(),
+                                 node.func.id)
+                
+                elif (
+                    nArgs == 1 and
+                    isinstance(node.args[0], (ast.Tuple, ast.List)) and
+                    node.func.id in ("tuple", "list", "set", "dict")
+                ):
+                    errorCode = {
+                        "tuple": "M192",
+                        "list": "M193",
+                        "set": "M191",
+                        "dict": "M191",
                     }[node.func.id]
-                    self.__error(node.lineno - 1, node.col_offset, errorCode)
+                    self.__error(node.lineno - 1, node.col_offset, errorCode,
+                                 type(node.args[0]).__name__.lower(),
+                                 node.func.id)
 
-                elif (isinstance(node.args[0], ast.ListComp) and
-                      node.func.id in ('all', 'any', 'frozenset', 'max', 'min',
-                                       'sorted', 'sum', 'tuple',)):
-                    self.__error(node.lineno - 1, node.col_offset, "M198",
+                elif (
+                    nArgs == 1 and
+                    isinstance(node.args[0], ast.ListComp) and
+                    node.func.id in ('all', 'any', 'enumerate', 'frozenset',
+                                     'max', 'min', 'sorted', 'sum', 'tuple',)
+                ):
+                    self.__error(node.lineno - 1, node.col_offset, "M187",
                                  node.func.id)
+                
+                elif (
+                    nArgs == 0 and
+                    not any(isinstance(a, ast.Starred) for a in node.args) and
+                    not any(k.arg is None for k in node.keywords) and
+                    node.func.id in ("tuple", "list", "dict")
+                ):
+                    self.__error(node.lineno - 1, node.col_offset, "M186",
+                                 node.func.id)
+                
+                elif isinstance(node, ast.Compare) and (
+                    len(node.ops) == 1 and
+                    isinstance(node.ops[0], ast.In) and
+                    len(node.comparators) == 1 and
+                    isinstance(node.comparators[0], ast.ListComp)
+                ):
+                    self.__error(node.lineno - 1, node.col_offset, "M196")
     
     def __checkMutableDefault(self):
         """
@@ -876,6 +944,17 @@
                 node = violation[0]
                 reason = violation[1]
                 self.__error(node.lineno - 1, node.col_offset, reason)
+    
+    def __checkSysVersion(self):
+        """
+        Private method to check the use of sys.version and sys.version_info.
+        """
+        visitor = SysVersionVisitor()
+        visitor.visit(self.__tree)
+        for violation in visitor.violations:
+            node = violation[0]
+            reason = violation[1]
+            self.__error(node.lineno - 1, node.col_offset, reason)
 
 
 class TextVisitor(ast.NodeVisitor):
@@ -1967,5 +2046,192 @@
         
         self.generic_visit(node)
 
+
+class SysVersionVisitor(ast.NodeVisitor):
+    """
+    Class implementing a node visitor to check the use of sys.version and
+    sys.version_info.
+    
+    Note: This class is modelled after flake8-2020 checker.
+    """
+    def __init__(self):
+        """
+        Constructor
+        """
+        super(SysVersionVisitor, self).__init__()
+        
+        self.violations = []
+        self.__fromImports = {}
+    
+    def visit_ImportFrom(self, node):
+        """
+        Public method to handle a from ... import ... statement.
+        
+        @param node reference to the node to be processed
+        @type ast.ImportFrom
+        """
+        for alias in node.names:
+            if node.module is not None and not alias.asname:
+                self.__fromImports[alias.name] = node.module
+        
+        self.generic_visit(node)
+    
+    def __isSys(self, attr, node):
+        """
+        Private method to check for a reference to sys attribute.
+        
+        @param attr attribute name
+        @type str
+        @param node reference to the node to be checked
+        @type ast.Node
+        @return flag indicating a match
+        @rtype bool
+        """
+        match = False
+        if (
+            isinstance(node, ast.Attribute) and
+            isinstance(node.value, ast.Name) and
+            node.value.id == "sys" and
+            node.attr == attr
+        ):
+            match = True
+        elif (
+            isinstance(node, ast.Name) and
+            node.id == attr and
+            self.__fromImports.get(node.id) == "sys"
+        ):
+            match = True
+        
+        return match
+    
+    def __isSysVersionUpperSlice(self, node, n):
+        """
+        Private method to check the upper slice of sys.version.
+        
+        @param node reference to the node to be checked
+        @type ast.Node
+        @param n slice value to check against
+        @type int
+        @return flag indicating a match
+        @rtype bool
+        """
+        return (
+            self.__isSys("version", node.value) and
+            isinstance(node.slice, ast.Slice) and
+            node.slice.lower is None and
+            isinstance(node.slice.upper, ast.Num) and
+            node.slice.upper.n == n and
+            node.slice.step is None
+        )
+    
+    def visit_Subscript(self, node):
+        """
+        Public method to handle a subscript.
+        
+        @param node reference to the node to be processed
+        @type ast.Subscript
+        """
+        if self.__isSysVersionUpperSlice(node, 1):
+            self.violations.append((node.value, "M423"))
+        elif self.__isSysVersionUpperSlice(node, 3):
+            self.violations.append((node.value, "M401"))
+        elif (
+            self.__isSys('version', node.value) and
+            isinstance(node.slice, ast.Index) and
+            isinstance(node.slice.value, ast.Num) and
+            node.slice.value.n == 2
+        ):
+            self.violations.append((node.value, "M402"))
+        elif (
+            self.__isSys('version', node.value) and
+            isinstance(node.slice, ast.Index) and
+            isinstance(node.slice.value, ast.Num) and
+            node.slice.value.n == 0
+        ):
+            self.violations.append((node.value, "M421"))
+
+        self.generic_visit(node)
+    
+    def visit_Compare(self, node):
+        """
+        Public method to handle a comparison.
+        
+        @param node reference to the node to be processed
+        @type ast.Compare
+        """
+        if (
+            isinstance(node.left, ast.Subscript) and
+            self.__isSys('version_info', node.left.value) and
+            isinstance(node.left.slice, ast.Index) and
+            isinstance(node.left.slice.value, ast.Num) and
+            node.left.slice.value.n == 0 and
+            len(node.ops) == 1 and
+            isinstance(node.ops[0], ast.Eq) and
+            isinstance(node.comparators[0], ast.Num) and
+            node.comparators[0].n == 3
+        ):
+            self.violations.append((node.left, "M411"))
+        elif (
+            self.__isSys('version', node.left) and
+            len(node.ops) == 1 and
+            isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE)) and
+            isinstance(node.comparators[0], ast.Str)
+        ):
+            if len(node.comparators[0].s) == 1:
+                errorCode = "M422"
+            else:
+                errorCode = "M403"
+            self.violations.append((node.left, errorCode))
+        elif (
+            isinstance(node.left, ast.Subscript) and
+            self.__isSys('version_info', node.left.value) and
+            isinstance(node.left.slice, ast.Index) and
+            isinstance(node.left.slice.value, ast.Num) and
+            node.left.slice.value.n == 1 and
+            len(node.ops) == 1 and
+            isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE)) and
+            isinstance(node.comparators[0], ast.Num)
+        ):
+            self.violations.append((node, "M413"))
+        elif (
+            isinstance(node.left, ast.Attribute) and
+            self.__isSys('version_info', node.left.value) and
+            node.left.attr == 'minor' and
+            len(node.ops) == 1 and
+            isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE)) and
+            isinstance(node.comparators[0], ast.Num)
+        ):
+            self.violations.append((node, "M414"))
+        
+        self.generic_visit(node)
+    
+    def visit_Attribute(self, node):
+        """
+        Public method to handle an attribute.
+        
+        @param node reference to the node to be processed
+        @type ast.Attribute
+        """
+        if (
+            isinstance(node.value, ast.Name) and
+            node.value.id == 'six' and
+            node.attr == 'PY3'
+        ):
+            self.violations.append((node, "M412"))
+        
+        self.generic_visit(node)
+
+    def visit_Name(self, node):
+        """
+        Public method to handle an name.
+        
+        @param node reference to the node to be processed
+        @type ast.Name
+        """
+        if node.id == 'PY3' and self.__fromImports.get(node.id) == 'six':
+            self.violations.append((node, "M412"))
+        
+        self.generic_visit(node)
+
 #
-# eflag: noqa = M702
+# eflag: noqa = M702, M891

eric ide

mercurial