--- a/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py Tue Mar 19 19:07:05 2019 +0100 +++ b/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py Tue Mar 19 19:32:33 2019 +0100 @@ -12,6 +12,7 @@ import re import itertools from string import Formatter +from collections import defaultdict def composeCallPath(node): @@ -60,6 +61,7 @@ "M801", "M811", "M821", "M822", + "M831", "M832", "M833", "M834", "M901", ] @@ -139,6 +141,7 @@ (self.__checkPrintStatements, ("M801",)), (self.__checkTuple, ("M811", )), (self.__checkMutableDefault, ("M821", "M822")), + (self.__checkReturn, ("M831", "M832", "M833", "M834")), ] self.__defaultArgs = { @@ -737,6 +740,17 @@ reason = violation[1] params = violation[2:] self.__error(node.lineno - 1, node.col_offset, reason, *params) + + def __checkReturn(self): + """ + Private method to check return statements. + """ + visitor = ReturnVisitor() + 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): @@ -1289,5 +1303,296 @@ """ return self.__names + +class ReturnVisitor(ast.NodeVisitor): + """ + Class implementing a node visitor to check return statements. + """ + Assigns = 'assigns' + Refs = 'refs' + Returns = 'returns' + + def __init__(self): + """ + Constructor + """ + super(ReturnVisitor, self).__init__() + + self.__stack = [] + self.violations = [] + + @property + def assigns(self): + """ + Public method to get the Assign nodes. + + @return dictionary containing the node name as key and line number + as value + @rtype dict + """ + return self.__stack[-1][ReturnVisitor.Assigns] + + @property + def refs(self): + """ + Public method to get the References nodes. + + @return dictionary containing the node name as key and line number + as value + @rtype dict + """ + return self.__stack[-1][ReturnVisitor.Refs] + + @property + def returns(self): + """ + Public method to get the Return nodes. + + @return dictionary containing the node name as key and line number + as value + @rtype dict + """ + return self.__stack[-1][ReturnVisitor.Returns] + + def __visitWithStack(self, node): + """ + Private method to traverse a given function node using a stack. + + @param node AST node to be traversed + @type ast.FunctionDef or ast.AsyncFunctionDef + """ + self.__stack.append({ + ReturnVisitor.Assigns: defaultdict(list), + ReturnVisitor.Refs: defaultdict(list), + ReturnVisitor.Returns: [] + }) + + self.generic_visit(node) + self.__checkFunction(node) + self.__stack.pop() + + def visit_FunctionDef(self, node): + """ + Public method to handle a function definition. + + @param node reference to the node to handle + @type ast.FunctionDef + """ + self.__visitWithStack(node) + + def visit_AsyncFunctionDef(self, node): + """ + Public method to handle a function definition. + + @param node reference to the node to handle + @type ast.AsyncFunctionDef + """ + self.__visitWithStack(node) + + def visit_Return(self, node): + """ + Public method to handle a return node. + + @param node reference to the node to handle + @type ast.Return + """ + self.returns.append(node) + self.generic_visit(node) + + def visit_Assign(self, node): + """ + Public method to handle an assign node. + + @param node reference to the node to handle + @type ast.Assign + """ + if not self.__stack: + return + + for target in node.targets: + self.__visitAssignTarget(target) + self.generic_visit(node.value) + + def visit_Name(self, node): + """ + Public method to handle a name node. + + @param node reference to the node to handle + @type ast.Name + """ + if self.__stack: + self.refs[node.id].append(node.lineno) + + def __visitAssignTarget(self, node): + """ + Private method to handle an assign target node. + + @param node reference to the node to handle + @type ast.AST + """ + if isinstance(node, ast.Tuple): + for elt in node.elts: + self.__visitAssignTarget(elt) + return + + if isinstance(node, ast.Name): + self.assigns[node.id].append(node.lineno) + return + + self.generic_visit(node) + + def __checkFunction(self, node): + """ + Private method to check a function definition node. + + @param node reference to the node to check + @type ast.AsyncFunctionDef or ast.FunctionDef + """ + if not self.returns or not node.body: + return + + if len(node.body) == 1 and isinstance(node.body[-1], ast.Return): + # skip functions that consist of `return None` only + return + + if not self.__resultExists(): + self.__checkUnnecessaryReturnNone() + return + + self.__checkImplicitReturnValue() + self.__checkImplicitReturn(node.body[-1]) + + for n in self.returns: + if n.value: + self.__checkUnnecessaryAssign(n.value) + + def __isNone(self, node): + """ + Private method to check, if a node value is None. + + @param node reference to the node to check + @type ast.AST + @return flag indicating the node contains a None value + """ + return isinstance(node, ast.NameConstant) and node.value is None + + def __resultExists(self): + """ + Private method to check the existance of a return result. + + @return flag indicating the existence of a return result + @rtype bool + """ + for node in self.returns: + value = node.value + if value and not self.__isNone(value): + return True + + return False + + def __checkImplicitReturnValue(self): + """ + Private method to check for implicit return values. + """ + for node in self.returns: + if not node.value: + self.violations.append((node, "M832")) + + def __checkUnnecessaryReturnNone(self): + """ + Private method to check for an unnecessary 'return None' statement. + """ + for node in self.returns: + if self.__isNone(node.value): + self.violations.append((node, "M831")) + + def __checkImplicitReturn(self, node): + """ + Private method to check for an implicit return statement. + + @param node reference to the node to check + @type ast.AST + """ + if isinstance(node, ast.If): + if not node.body or not node.orelse: + self.violations.append((node, "M833")) + return + + self.__checkImplicitReturn(node.body[-1]) + self.__checkImplicitReturn(node.orelse[-1]) + return + + if isinstance(node, ast.For) and node.orelse: + self.__checkImplicitReturn(node.orelse[-1]) + return + + if isinstance(node, ast.With): + self.__checkImplicitReturn(node.body[-1]) + return + + if not isinstance(node, + (ast.Return, ast.Raise, ast.While, ast.Try)): + self.violations.append((node, "M833")) + + def __checkUnnecessaryAssign(self, node): + """ + Private method to check for an unnecessary assign statement. + + @param node reference to the node to check + @type ast.AST + """ + if not isinstance(node, ast.Name): + return + + varname = node.id + returnLineno = node.lineno + + if varname not in self.assigns: + return + + if varname not in self.refs: + self.violations.append((node, "M834")) + return + + if self.__hasRefsBeforeNextAssign(varname, returnLineno): + return + + self.violations.append((node, "M834")) + + def __hasRefsBeforeNextAssign(self, varname, returnLineno): + """ + Private method to check for references before a following assign + statement. + + @param varname variable name to check for + @type str + @param returnLineno line number of the return statement + @type int + @return flag indicating the existence of references + @rtype bool + """ + beforeAssign = 0 + afterAssign = None + + for lineno in sorted(self.assigns[varname]): + if lineno > returnLineno: + afterAssign = lineno + break + + if lineno <= returnLineno: + beforeAssign = lineno + + for lineno in self.refs[varname]: + if lineno == returnLineno: + continue + + if afterAssign: + if beforeAssign < lineno <= afterAssign: + return True + + elif beforeAssign < lineno: + return True + + return False # # eflag: noqa = M702