--- 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