diff -r 29384109306c -r 789e88d94899 Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py --- 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