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

branch
eric7
changeset 9276
e6748a5e24b9
parent 9274
86fab0c74430
child 9328
49a0a9cb2505
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsChecker.py	Wed Jul 27 18:02:43 2022 +0200
+++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsChecker.py	Thu Jul 28 14:19:57 2022 +0200
@@ -7,8 +7,9 @@
 Module implementing a checker for function type annotations.
 """
 
+import ast
+import contextlib
 import copy
-import ast
 import sys
 from functools import lru_cache
 
@@ -40,8 +41,11 @@
         "A206",
         ## Mixed kind of annotations
         "A301",
+        ## Dynamically typed annotations
+        "A401",
         ## Annotations Future
         "A871",
+        "A872",
         ## Annotation Coverage
         "A881",
         ## Annotation Complexity
@@ -101,9 +105,10 @@
                     "A205",
                     "A206",
                     "A301",
+                    "A401",
                 ),
             ),
-            (self.__checkAnnotationsFuture, ("A871",)),
+            (self.__checkAnnotationsFuture, ("A871", "A872")),
             (self.__checkAnnotationsCoverage, ("A881",)),
             (self.__checkAnnotationComplexity, ("A891", "A892")),
         ]
@@ -179,9 +184,8 @@
     #######################################################################
     ## Annotations
     ##
-    ## adapted from: flake8-annotations v2.7.0
+    ## adapted from: flake8-annotations v2.9.0
     #######################################################################
-    # TODO: update to v2.9.0
 
     def __checkFunctionAnnotations(self):
         """
@@ -203,6 +207,9 @@
         mypyInitReturn = self.__args.get(
             "MypyInitReturn", AnnotationsCheckerDefaultArgs["MypyInitReturn"]
         )
+        allowStarArgAny = self.__args.get(
+            "AllowStarArgAny", AnnotationsCheckerDefaultArgs["AllowStarArgAny"]
+        )
 
         # Store decorator lists as sets for easier lookup
         dispatchDecorators = set(
@@ -250,9 +257,11 @@
             has3107Annotation = False
             # PEP 3107 annotations are captured by the return arg
 
+            annotatedArgs = function.getAnnotatedArguments()
+
             # Iterate over annotated args to detect mixing of type annotations
             # and type comments. Emit this only once per function definition
-            for arg in function.getAnnotatedArguments():
+            for arg in annotatedArgs:
                 if arg.hasTypeComment:
                     hasTypeComment = True
 
@@ -265,6 +274,19 @@
                     self.__error(function.lineno - 1, function.col_offset, "A301")
                     break
 
+            # Iterate over the annotated args to look for 'typing.Any' annotations
+            # We could combine this with the above loop but I'd rather not add even
+            # more sentinels unless we'd notice a significant enough performance impact
+            for arg in annotatedArgs:
+                if arg.isDynamicallyTyped:
+                    if allowStarArgAny and arg.annotationType in {
+                        AnnotationType.VARARG,
+                        AnnotationType.KWARG,
+                    }:
+                        continue
+
+                    self.__error(function.lineno - 1, function.col_offset, "A401")
+
             # Before we iterate over the function's missing annotations, check
             # to see if it's the closing function def in a series of
             # `typing.overload` decorated functions.
@@ -294,7 +316,7 @@
                         mypyInitReturn
                         and function.isClassMethod
                         and function.name == "__init__"
-                        and function.getAnnotatedArguments()
+                        and annotatedArgs
                     ):
                         # Skip recording return errors for `__init__` if at
                         # least one argument is annotated
@@ -419,9 +441,8 @@
     #######################################################################
     ## Annotations Coverage
     ##
-    ## adapted from: flake8-annotations-coverage v0.0.5
+    ## adapted from: flake8-annotations-coverage v0.0.6
     #######################################################################
-    # TODO: update to v0.0.6
 
     def __checkAnnotationsCoverage(self):
         """
@@ -446,6 +467,9 @@
         functionDefAnnotationsInfo = [
             self.__hasTypeAnnotations(f) for f in functionDefs
         ]
+        if not bool(functionDefAnnotationsInfo):
+            return
+
         annotationsCoverage = int(
             len(list(filter(None, functionDefAnnotationsInfo)))
             / len(functionDefAnnotationsInfo)
@@ -488,9 +512,8 @@
     #######################################################################
     ## Annotations Complexity
     ##
-    ## adapted from: flake8-annotations-complexity v0.0.6
+    ## adapted from: flake8-annotations-complexity v0.0.7
     #######################################################################
-    # TODO: update to v0.0.7
 
     def __checkAnnotationComplexity(self):
         """
@@ -511,7 +534,7 @@
         ]
         for functionDef in functionDefs:
             typeAnnotations += list(
-                filter(None, [a.annotation for a in functionDef.args.args])
+                filter(None, (a.annotation for a in functionDef.args.args))
             )
             if functionDef.returns:
                 typeAnnotations.append(functionDef.returns)
@@ -558,21 +581,25 @@
                 annotationNode = ast.parse(annotationNode.s).body[0].value
             except (SyntaxError, IndexError):
                 return defaultComplexity
+
+        complexity = defaultComplexity
         if isinstance(annotationNode, ast.Subscript):
             if sys.version_info >= (3, 9):
-                return defaultComplexity + self.__getAnnotationComplexity(
+                complexity = defaultComplexity + self.__getAnnotationComplexity(
                     annotationNode.slice
                 )
             else:
-                return defaultComplexity + self.__getAnnotationComplexity(
+                complexity = defaultComplexity + self.__getAnnotationComplexity(
                     annotationNode.slice.value
                 )
+
         if isinstance(annotationNode, ast.Tuple):
-            return max(
+            complexity = max(
                 (self.__getAnnotationComplexity(n) for n in annotationNode.elts),
                 default=defaultComplexity,
             )
-        return defaultComplexity
+
+        return complexity
 
     def __getAnnotationLength(self, annotationNode):
         """
@@ -584,27 +611,28 @@
         @return annotation length
         @rtype = int
         """
+        annotationLength = 0
         if AstUtilities.isString(annotationNode):
             try:
                 annotationNode = ast.parse(annotationNode.s).body[0].value
             except (SyntaxError, IndexError):
-                return 0
+                return annotationLength
+
         if isinstance(annotationNode, ast.Subscript):
-            try:
-                if sys.version_info >= (3, 9):
-                    return len(annotationNode.slice.elts)
-                else:
-                    return len(annotationNode.slice.value.elts)
-            except AttributeError:
-                return 0
-        return 0
+            with contextlib.suppress(AttributeError):
+                annotationLength = (
+                    len(annotationNode.slice.elts)
+                    if sys.version_info >= (3, 9)
+                    else len(annotationNode.slice.value.elts)
+                )
+
+        return annotationLength
 
     #######################################################################
     ## 'from __future__ import annotations' checck
     ##
-    ## adapted from: flake8-future-annotations v0.0.4
+    ## adapted from: flake8-future-annotations v0.0.5
     #######################################################################
-    # TODO: update to v0.0.5
 
     def __checkAnnotationsFuture(self):
         """
@@ -612,11 +640,19 @@
         """
         from .AnnotationsFutureVisitor import AnnotationsFutureVisitor
 
+        forceFutureAnnotations = self.__args.get(
+            "ForceFutureAnnotations",
+            AnnotationsCheckerDefaultArgs["ForceFutureAnnotations"],
+        )
+
         visitor = AnnotationsFutureVisitor()
         visitor.visit(self.__tree)
 
-        if visitor.importsFutureAnnotations() or not visitor.hasTypingImports():
+        if visitor.importsFutureAnnotations():
             return
 
-        imports = ", ".join(visitor.getTypingImports())
-        self.__error(0, 0, "A871", imports)
+        if visitor.hasTypingImports():
+            imports = ", ".join(visitor.getTypingImports())
+            self.__error(0, 0, "A871", imports)
+        elif forceFutureAnnotations:
+            self.__error(0, 0, "A872")

eric ide

mercurial