src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsFunctionVisitor.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9276
e6748a5e24b9
diff -r e9e7eca7efee -r bf71ee032bb4 src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsFunctionVisitor.py
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsFunctionVisitor.py	Wed Jul 13 11:16:20 2022 +0200
+++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsFunctionVisitor.py	Wed Jul 13 14:55:47 2022 +0200
@@ -25,12 +25,20 @@
     """
     Class representing a function argument.
     """
-    def __init__(self, argname, lineno, col_offset, annotationType,
-                 hasTypeAnnotation=False, has3107Annotation=False,
-                 hasTypeComment=False):
+
+    def __init__(
+        self,
+        argname,
+        lineno,
+        col_offset,
+        annotationType,
+        hasTypeAnnotation=False,
+        has3107Annotation=False,
+        hasTypeComment=False,
+    ):
         """
         Constructor
-        
+
         @param argname name of the argument
         @type str
         @param lineno line number
@@ -56,12 +64,12 @@
         self.hasTypeAnnotation = hasTypeAnnotation
         self.has3107Annotation = has3107Annotation
         self.hasTypeComment = hasTypeComment
-    
+
     @classmethod
     def fromNode(cls, node, annotationTypeName):
         """
         Class method to create an Argument object based on the given node.
-        
+
         @param node reference to the node to be converted
         @type ast.arguments
         @param annotationTypeName name of the annotation type
@@ -88,14 +96,25 @@
     """
     Class representing a function.
     """
-    def __init__(self, name, lineno, col_offset,
-                 functionType=FunctionType.PUBLIC, isClassMethod=False,
-                 classDecoratorType=None, isReturnAnnotated=False,
-                 hasTypeComment=False, hasOnlyNoneReturns=True,
-                 isNested=False, decoratorList=None, args=None):
+
+    def __init__(
+        self,
+        name,
+        lineno,
+        col_offset,
+        functionType=FunctionType.PUBLIC,
+        isClassMethod=False,
+        classDecoratorType=None,
+        isReturnAnnotated=False,
+        hasTypeComment=False,
+        hasOnlyNoneReturns=True,
+        isNested=False,
+        decoratorList=None,
+        args=None,
+    ):
         """
         Constructor
-        
+
         @param name name of the function
         @type str
         @param lineno line number
@@ -138,24 +157,24 @@
         self.isNested = isNested
         self.decoratorList = decoratorList
         self.args = args
-    
+
     def isFullyAnnotated(self):
         """
         Public method to check, if the function definition is fully type
         annotated.
 
         Note: self.args will always include an Argument object for return.
-        
+
         @return flag indicating a fully annotated function definition
         @rtype bool
         """
         return all(arg.hasTypeAnnotation for arg in self.args)
-    
+
     def isDynamicallyTyped(self):
         """
         Public method to check, if a function definition is dynamically typed
         (i.e. completely lacking hints).
-        
+
         @return flag indicating a dynamically typed function definition
         @rtype bool
         """
@@ -165,7 +184,7 @@
         """
         Public method to provide a list of arguments with missing type
         annotations.
-        
+
         @return list of arguments with missing type annotations
         @rtype list of Argument
         """
@@ -174,26 +193,26 @@
     def getAnnotatedArguments(self):
         """
         Public method to get list of arguments with type annotations.
-        
+
         @return list of arguments with type annotations.
         @rtype list of Argument
         """
         return [arg for arg in self.args if arg.hasTypeAnnotation]
-    
+
     def hasDecorator(self, checkDecorators):
         """
         Public method to check whether the function node is decorated by any of
         the provided decorators.
-        
+
         Decorator matching is done against the provided `checkDecorators` set.
         Decorators are assumed to be either a module attribute (e.g.
         `@typing.overload`) or name (e.g. `@overload`). For the case of a
         module attribute, only the attribute is checked against
         `overload_decorators`.
-        
+
         Note: Deeper decorator imports (e.g. `a.b.overload`) are not explicitly
         supported.
-        
+
         @param checkDecorators set of decorators to check against
         @type set of str
         @return flag indicating the presence of any decorators
@@ -205,18 +224,18 @@
             return self.__decoratorChecker(decorator, checkDecorators)
         else:
             return False
-    
+
     def __decoratorChecker(self, decorator, checkDecorators):
         """
         Private method to check the provided decorator for a match against the
         provided set of check names.
-        
+
         Decorators are assumed to be of the following form:
             * `a.name` or `a.name()`
             * `name` or `name()`
-        
+
         Note: Deeper imports (e.g. `a.b.name`) are not explicitly supported.
-        
+
         @param decorator decorator node to check
         @type ast.Attribute, ast.Call or ast.Name
         @param checkDecorators set of decorators to check against
@@ -237,24 +256,24 @@
             # `decorator.func` will be `ast.Name` or `ast.Attribute`,
             # which we can check recursively
             return self.__decoratorChecker(decorator.func, checkDecorators)
-        
+
         return None
-    
+
     @classmethod
     def fromNode(cls, node, lines, **kwargs):
         """
         Class method to create a Function object from ast.FunctionDef or
         ast.AsyncFunctionDef nodes.
-        
+
         Accept the source code, as a list of strings, in order to get the
         column where the function definition ends.
-        
+
         With exceptions, input kwargs are passed straight through to Function's
         __init__. The following kwargs will be overridden:
           * function_type
           * class_decorator_type
           * args
-        
+
         @param node reference to the function definition node
         @type ast.AsyncFunctionDef or ast.FunctionDef
         @param lines list of source code lines
@@ -266,63 +285,63 @@
         """
         # Extract function types from function name
         kwargs["functionType"] = cls.getFunctionType(node.name)
-        
+
         # Identify type of class method, if applicable
         if kwargs.get("isClassMethod", False):
             kwargs["classDecoratorType"] = cls.getClassDecoratorType(node)
-        
+
         # Store raw decorator list for use by property methods
         kwargs["decoratorList"] = node.decorator_list
-        
+
         # Instantiate empty args list here since it has no default
         kwargs["args"] = []
 
         newFunction = cls(node.name, node.lineno, node.col_offset, **kwargs)
-        
+
         # Iterate over arguments by type & add
         for argType in AST_ARG_TYPES:
             args = node.args.__getattribute__(argType)
             if args:
                 if not isinstance(args, list):
                     args = [args]
-                
+
                 newFunction.args.extend(
-                    [Argument.fromNode(arg, argType.upper())
-                     for arg in args]
+                    [Argument.fromNode(arg, argType.upper()) for arg in args]
                 )
-        
+
         # Create an Argument object for the return hint
         defEndLineno, defEndColOffset = cls.colonSeeker(node, lines)
-        returnArg = Argument("return", defEndLineno, defEndColOffset,
-                             AnnotationType.RETURN)
+        returnArg = Argument(
+            "return", defEndLineno, defEndColOffset, AnnotationType.RETURN
+        )
         if node.returns:
             returnArg.hasTypeAnnotation = True
             returnArg.has3107Annotation = True
             newFunction.isReturnAnnotated = True
-        
+
         newFunction.args.append(returnArg)
-        
+
         # Type comments in-line with input arguments are handled by the
         # Argument class. If a function-level type comment is present, attempt
         # to parse for any missed type hints.
         if node.type_comment:
             newFunction.hasTypeComment = True
             newFunction = cls.tryTypeComment(newFunction, node)
-        
+
         # Check for the presence of non-`None` returns using the special-case
         # return node visitor.
         returnVisitor = ReturnVisitor(node)
         returnVisitor.visit(node)
         newFunction.hasOnlyNoneReturns = returnVisitor.hasOnlyNoneReturns
-        
+
         return newFunction
-    
+
     @staticmethod
     def colonSeeker(node, lines):
         """
         Static method to find the line & column indices of the function
         definition's closing colon.
-        
+
         @param node reference to the function definition node
         @type ast.AsyncFunctionDef or ast.FunctionDef
         @param lines list of source code lines
@@ -332,9 +351,8 @@
         """
         # Special case single line function definitions
         if node.lineno == node.body[0].lineno:
-            return Function._singleLineColonSeeker(
-                node, lines[node.lineno - 1])
-        
+            return Function._singleLineColonSeeker(node, lines[node.lineno - 1])
+
         # With Python < 3.8, the function node includes the docstring and the
         # body does not, so we have to rewind through any docstrings, if
         # present, before looking for the def colon. We should end up with
@@ -351,24 +369,24 @@
                     if '"""' in lines[defEndLineno - 1]:
                         # Docstring has closed
                         break
-        
+
         # Once we've gotten here, we've found the line where the docstring
         # begins, so we have to step up one more line to get to the close of
         # the def.
         defEndLineno -= 1
-        
+
         # Use str.rfind() to account for annotations on the same line,
         # definition closure should be the last : on the line
         defEndColOffset = lines[defEndLineno - 1].rfind(":")
-        
+
         return defEndLineno, defEndColOffset
-    
+
     @staticmethod
     def _singleLineColonSeeker(node, line):
         """
         Static method to find the line & column indices of a single line
         function definition.
-        
+
         @param node reference to the function definition node
         @type ast.AsyncFunctionDef or ast.FunctionDef
         @param line source code line
@@ -379,17 +397,17 @@
         colStart = node.col_offset
         colEnd = node.body[0].col_offset
         defEndColOffset = line.rfind(":", colStart, colEnd)
-        
+
         return node.lineno, defEndColOffset
-    
+
     @staticmethod
     def tryTypeComment(funcObj, node):
         """
         Static method to infer type hints from a function-level type comment.
-        
+
         If a function is type commented it is assumed to have a return
         annotation, otherwise Python will fail to parse the hint.
-        
+
         @param funcObj reference to the Function object
         @type Function
         @param node reference to the function definition node
@@ -399,56 +417,54 @@
         """
         hintTree = ast.parse(node.type_comment, "<func_type>", "func_type")
         hintTree = Function._maybeInjectClassArgument(hintTree, funcObj)
-        
-        for arg, hintComment in itertools.zip_longest(
-            funcObj.args, hintTree.argtypes
-        ):
+
+        for arg, hintComment in itertools.zip_longest(funcObj.args, hintTree.argtypes):
             if isinstance(hintComment, ast.Ellipsis):
                 continue
-            
+
             if arg and hintComment:
                 arg.hasTypeAnnotation = True
                 arg.hasTypeComment = True
-        
+
         # Return arg is always last
         funcObj.args[-1].hasTypeAnnotation = True
         funcObj.args[-1].hasTypeComment = True
         funcObj.isReturnAnnotated = True
-        
+
         return funcObj
-    
+
     @staticmethod
     def _maybeInjectClassArgument(hintTree, funcObj):
         """
         Static method to inject `self` or `cls` args into a type comment to
         align with PEP 3107-style annotations.
-        
+
         Because PEP 484 does not describe a method to provide partial function-
         level type comments, there is a potential for ambiguity in the context
         of both class methods and classmethods when aligning type comments to
         method arguments.
-        
+
         These two class methods, for example, should lint equivalently:
-        
+
             def bar(self, a):
                 # type: (int) -> int
                 ...
-        
+
             def bar(self, a: int) -> int
                 ...
-        
+
         When this example type comment is parsed by `ast` and then matched with
         the method's arguments, it associates the `int` hint to `self` rather
         than `a`, so a dummy hint needs to be provided in situations where
         `self` or `class` are not hinted in the type comment in order to
         achieve equivalent linting results to PEP-3107 style annotations.
-        
+
         A dummy `ast.Ellipses` constant is injected if the following criteria
         are met:
             1. The function node is either a class method or classmethod
             2. The number of hinted args is at least 1 less than the number
                of function args
-        
+
         @param hintTree parsed type hint node
         @type ast.FunctionType
         @param funcObj reference to the Function object
@@ -460,15 +476,14 @@
             # Short circuit
             return hintTree
 
-        if (
-            funcObj.classDecoratorType != ClassDecoratorType.STATICMETHOD and
-            len(hintTree.argtypes) < (len(funcObj.args) - 1)
-        ):
+        if funcObj.classDecoratorType != ClassDecoratorType.STATICMETHOD and len(
+            hintTree.argtypes
+        ) < (len(funcObj.args) - 1):
             # Subtract 1 to skip return arg
             hintTree.argtypes = [ast.Ellipsis()] + hintTree.argtypes
-        
+
         return hintTree
-    
+
     @staticmethod
     def getFunctionType(functionName):
         """
@@ -479,7 +494,7 @@
           2. Private: function name prefixed by "__"
           3. Protected: function name prefixed by "_"
           4. Public: everything else
-        
+
         @param functionName function name to be checked
         @type str
         @return type of function
@@ -493,19 +508,19 @@
             return FunctionType.PROTECTED
         else:
             return FunctionType.PUBLIC
-    
+
     @staticmethod
     def getClassDecoratorType(functionNode):
         """
         Static method to get the class method's decorator type from its
         function node.
-        
+
         Only @classmethod and @staticmethod decorators are identified; all
         other decorators are ignored
-        
+
         If @classmethod or @staticmethod decorators are not present, this
         function will return None.
-        
+
         @param functionNode reference to the function definition node
         @type ast.AsyncFunctionDef or ast.FunctionDef
         @return class decorator type
@@ -532,28 +547,29 @@
     """
     Class implementing a node visitor to check function annotations.
     """
+
     AstFuncTypes = (ast.FunctionDef, ast.AsyncFunctionDef)
-    
+
     def __init__(self, lines):
         """
         Constructor
-        
+
         @param lines source code lines of the function
         @type list of str
         """
         self.lines = lines
         self.functionDefinitions = []
         self.__context = []
-    
+
     def switchContext(self, node):
         """
         Public method implementing a context switcher as a generic function
         visitor in order to track function context.
-        
+
         Without keeping track of context, it's challenging to reliably
         differentiate class methods from "regular" functions, especially in the
         case of nested classes.
-        
+
         @param node reference to the function definition node to be analyzed
         @type ast.AsyncFunctionDef or ast.FunctionDef
         """
@@ -567,21 +583,18 @@
                     self.functionDefinitions.append(
                         Function.fromNode(node, self.lines, isClassMethod=True)
                     )
-                elif isinstance(
-                    self.__context[-1], FunctionVisitor.AstFuncTypes
-                ):
+                elif isinstance(self.__context[-1], FunctionVisitor.AstFuncTypes):
                     # Check for nested function & pass the appropriate flag
                     self.functionDefinitions.append(
                         Function.fromNode(node, self.lines, isNested=True)
                     )
             else:
-                self.functionDefinitions.append(
-                    Function.fromNode(node, self.lines))
-        
+                self.functionDefinitions.append(Function.fromNode(node, self.lines))
+
         self.__context.append(node)
         self.generic_visit(node)
         self.__context.pop()
-    
+
     visit_FunctionDef = switchContext
     visit_AsyncFunctionDef = switchContext
     visit_ClassDef = switchContext
@@ -591,24 +604,25 @@
     """
     Class implementing a node visitor to check the return statements of a
     function node.
-    
+
     If the function node being visited has an explicit return statement of
     anything other than `None`, the `instance.hasOnlyNoneReturns` flag will
     be set to `False`.
-    
+
     If the function node being visited has no return statement, or contains
     only return statement(s) that explicitly return `None`, the
     `instance.hasOnlyNoneReturns` flag will be set to `True`.
-    
+
     Due to the generic visiting being done, we need to keep track of the
     context in which a non-`None` return node is found. These functions are
     added to a set that is checked to see whether nor not the parent node is
     present.
     """
+
     def __init__(self, parentNode):
         """
         Constructor
-        
+
         @param parentNode reference to the function definition node to be
             analyzed
         @type ast.AsyncFunctionDef or ast.FunctionDef
@@ -616,28 +630,28 @@
         self.parentNode = parentNode
         self.__context = []
         self.__nonNoneReturnNodes = set()
-    
+
     @property
     def hasOnlyNoneReturns(self):
         """
         Public method indicating, that the parent node isn't in the visited
         nodes that don't return `None`.
-        
+
         @return flag indicating, that the parent node isn't in the visited
             nodes that don't return `None`
         @rtype bool
         """
         return self.parentNode not in self.__nonNoneReturnNodes
-    
+
     def visit_Return(self, node):
         """
         Public method to check each Return node to see if it returns anything
         other than `None`.
-        
+
         If the node being visited returns anything other than `None`, its
         parent context is added to the set of non-returning child nodes of
         the parent node.
-        
+
         @param node reference to the AST Return node
         @type ast.Return
         """
@@ -647,22 +661,22 @@
             # `ast.NameConstant`, which we need to check to see if it's
             # actually `None`
             if (
-                isinstance(node.value, (ast.Constant, ast.NameConstant)) and
-                node.value.value is None
+                isinstance(node.value, (ast.Constant, ast.NameConstant))
+                and node.value.value is None
             ):
                 return
-            
+
             self.__nonNoneReturnNodes.add(self.__context[-1])
 
     def switchContext(self, node):
         """
         Public method implementing a context switcher as a generic function
         visitor in order to track function context.
-        
+
         Without keeping track of context, it's challenging to reliably
         differentiate class methods from "regular" functions, especially in the
         case of nested classes.
-        
+
         @param node reference to the function definition node to be analyzed
         @type ast.AsyncFunctionDef or ast.FunctionDef
         """

eric ide

mercurial