Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py

changeset 6882
65d1bf4b7427
parent 6645
ad476851d7e0
child 6889
334257ef9435
--- 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

eric ide

mercurial