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

branch
eric7
changeset 9276
e6748a5e24b9
parent 9274
86fab0c74430
child 9328
49a0a9cb2505
equal deleted inserted replaced
9275:1a7d545d3ef2 9276:e6748a5e24b9
5 5
6 """ 6 """
7 Module implementing a checker for function type annotations. 7 Module implementing a checker for function type annotations.
8 """ 8 """
9 9
10 import ast
11 import contextlib
10 import copy 12 import copy
11 import ast
12 import sys 13 import sys
13 from functools import lru_cache 14 from functools import lru_cache
14 15
15 import AstUtilities 16 import AstUtilities
16 17
38 "A204", 39 "A204",
39 "A205", 40 "A205",
40 "A206", 41 "A206",
41 ## Mixed kind of annotations 42 ## Mixed kind of annotations
42 "A301", 43 "A301",
44 ## Dynamically typed annotations
45 "A401",
43 ## Annotations Future 46 ## Annotations Future
44 "A871", 47 "A871",
48 "A872",
45 ## Annotation Coverage 49 ## Annotation Coverage
46 "A881", 50 "A881",
47 ## Annotation Complexity 51 ## Annotation Complexity
48 "A891", 52 "A891",
49 "A892", 53 "A892",
99 "A203", 103 "A203",
100 "A204", 104 "A204",
101 "A205", 105 "A205",
102 "A206", 106 "A206",
103 "A301", 107 "A301",
108 "A401",
104 ), 109 ),
105 ), 110 ),
106 (self.__checkAnnotationsFuture, ("A871",)), 111 (self.__checkAnnotationsFuture, ("A871", "A872")),
107 (self.__checkAnnotationsCoverage, ("A881",)), 112 (self.__checkAnnotationsCoverage, ("A881",)),
108 (self.__checkAnnotationComplexity, ("A891", "A892")), 113 (self.__checkAnnotationComplexity, ("A891", "A892")),
109 ] 114 ]
110 115
111 self.__checkers = [] 116 self.__checkers = []
177 check() 182 check()
178 183
179 ####################################################################### 184 #######################################################################
180 ## Annotations 185 ## Annotations
181 ## 186 ##
182 ## adapted from: flake8-annotations v2.7.0 187 ## adapted from: flake8-annotations v2.9.0
183 ####################################################################### 188 #######################################################################
184 # TODO: update to v2.9.0
185 189
186 def __checkFunctionAnnotations(self): 190 def __checkFunctionAnnotations(self):
187 """ 191 """
188 Private method to check for function annotation issues. 192 Private method to check for function annotation issues.
189 """ 193 """
200 allowUntypedNested = self.__args.get( 204 allowUntypedNested = self.__args.get(
201 "AllowUntypedNested", AnnotationsCheckerDefaultArgs["AllowUntypedNested"] 205 "AllowUntypedNested", AnnotationsCheckerDefaultArgs["AllowUntypedNested"]
202 ) 206 )
203 mypyInitReturn = self.__args.get( 207 mypyInitReturn = self.__args.get(
204 "MypyInitReturn", AnnotationsCheckerDefaultArgs["MypyInitReturn"] 208 "MypyInitReturn", AnnotationsCheckerDefaultArgs["MypyInitReturn"]
209 )
210 allowStarArgAny = self.__args.get(
211 "AllowStarArgAny", AnnotationsCheckerDefaultArgs["AllowStarArgAny"]
205 ) 212 )
206 213
207 # Store decorator lists as sets for easier lookup 214 # Store decorator lists as sets for easier lookup
208 dispatchDecorators = set( 215 dispatchDecorators = set(
209 self.__args.get( 216 self.__args.get(
248 hasTypeComment = function.hasTypeComment 255 hasTypeComment = function.hasTypeComment
249 256
250 has3107Annotation = False 257 has3107Annotation = False
251 # PEP 3107 annotations are captured by the return arg 258 # PEP 3107 annotations are captured by the return arg
252 259
260 annotatedArgs = function.getAnnotatedArguments()
261
253 # Iterate over annotated args to detect mixing of type annotations 262 # Iterate over annotated args to detect mixing of type annotations
254 # and type comments. Emit this only once per function definition 263 # and type comments. Emit this only once per function definition
255 for arg in function.getAnnotatedArguments(): 264 for arg in annotatedArgs:
256 if arg.hasTypeComment: 265 if arg.hasTypeComment:
257 hasTypeComment = True 266 hasTypeComment = True
258 267
259 if arg.has3107Annotation: 268 if arg.has3107Annotation:
260 has3107Annotation = True 269 has3107Annotation = True
262 if hasTypeComment and has3107Annotation: 271 if hasTypeComment and has3107Annotation:
263 # Short-circuit check for mixing of type comments & 272 # Short-circuit check for mixing of type comments &
264 # 3107-style annotations 273 # 3107-style annotations
265 self.__error(function.lineno - 1, function.col_offset, "A301") 274 self.__error(function.lineno - 1, function.col_offset, "A301")
266 break 275 break
276
277 # Iterate over the annotated args to look for 'typing.Any' annotations
278 # We could combine this with the above loop but I'd rather not add even
279 # more sentinels unless we'd notice a significant enough performance impact
280 for arg in annotatedArgs:
281 if arg.isDynamicallyTyped:
282 if allowStarArgAny and arg.annotationType in {
283 AnnotationType.VARARG,
284 AnnotationType.KWARG,
285 }:
286 continue
287
288 self.__error(function.lineno - 1, function.col_offset, "A401")
267 289
268 # Before we iterate over the function's missing annotations, check 290 # Before we iterate over the function's missing annotations, check
269 # to see if it's the closing function def in a series of 291 # to see if it's the closing function def in a series of
270 # `typing.overload` decorated functions. 292 # `typing.overload` decorated functions.
271 if lastOverloadDecoratedFunctionName == function.name: 293 if lastOverloadDecoratedFunctionName == function.name:
292 314
293 if ( 315 if (
294 mypyInitReturn 316 mypyInitReturn
295 and function.isClassMethod 317 and function.isClassMethod
296 and function.name == "__init__" 318 and function.name == "__init__"
297 and function.getAnnotatedArguments() 319 and annotatedArgs
298 ): 320 ):
299 # Skip recording return errors for `__init__` if at 321 # Skip recording return errors for `__init__` if at
300 # least one argument is annotated 322 # least one argument is annotated
301 continue 323 continue
302 324
417 return "A001" 439 return "A001"
418 440
419 ####################################################################### 441 #######################################################################
420 ## Annotations Coverage 442 ## Annotations Coverage
421 ## 443 ##
422 ## adapted from: flake8-annotations-coverage v0.0.5 444 ## adapted from: flake8-annotations-coverage v0.0.6
423 ####################################################################### 445 #######################################################################
424 # TODO: update to v0.0.6
425 446
426 def __checkAnnotationsCoverage(self): 447 def __checkAnnotationsCoverage(self):
427 """ 448 """
428 Private method to check for function annotation coverage. 449 Private method to check for function annotation coverage.
429 """ 450 """
444 return 465 return
445 466
446 functionDefAnnotationsInfo = [ 467 functionDefAnnotationsInfo = [
447 self.__hasTypeAnnotations(f) for f in functionDefs 468 self.__hasTypeAnnotations(f) for f in functionDefs
448 ] 469 ]
470 if not bool(functionDefAnnotationsInfo):
471 return
472
449 annotationsCoverage = int( 473 annotationsCoverage = int(
450 len(list(filter(None, functionDefAnnotationsInfo))) 474 len(list(filter(None, functionDefAnnotationsInfo)))
451 / len(functionDefAnnotationsInfo) 475 / len(functionDefAnnotationsInfo)
452 * 100 476 * 100
453 ) 477 )
486 ) 510 )
487 511
488 ####################################################################### 512 #######################################################################
489 ## Annotations Complexity 513 ## Annotations Complexity
490 ## 514 ##
491 ## adapted from: flake8-annotations-complexity v0.0.6 515 ## adapted from: flake8-annotations-complexity v0.0.7
492 ####################################################################### 516 #######################################################################
493 # TODO: update to v0.0.7
494 517
495 def __checkAnnotationComplexity(self): 518 def __checkAnnotationComplexity(self):
496 """ 519 """
497 Private method to check the type annotation complexity. 520 Private method to check the type annotation complexity.
498 """ 521 """
509 for f in ast.walk(self.__tree) 532 for f in ast.walk(self.__tree)
510 if isinstance(f, (ast.AsyncFunctionDef, ast.FunctionDef)) 533 if isinstance(f, (ast.AsyncFunctionDef, ast.FunctionDef))
511 ] 534 ]
512 for functionDef in functionDefs: 535 for functionDef in functionDefs:
513 typeAnnotations += list( 536 typeAnnotations += list(
514 filter(None, [a.annotation for a in functionDef.args.args]) 537 filter(None, (a.annotation for a in functionDef.args.args))
515 ) 538 )
516 if functionDef.returns: 539 if functionDef.returns:
517 typeAnnotations.append(functionDef.returns) 540 typeAnnotations.append(functionDef.returns)
518 typeAnnotations += [ 541 typeAnnotations += [
519 a.annotation 542 a.annotation
556 if AstUtilities.isString(annotationNode): 579 if AstUtilities.isString(annotationNode):
557 try: 580 try:
558 annotationNode = ast.parse(annotationNode.s).body[0].value 581 annotationNode = ast.parse(annotationNode.s).body[0].value
559 except (SyntaxError, IndexError): 582 except (SyntaxError, IndexError):
560 return defaultComplexity 583 return defaultComplexity
584
585 complexity = defaultComplexity
561 if isinstance(annotationNode, ast.Subscript): 586 if isinstance(annotationNode, ast.Subscript):
562 if sys.version_info >= (3, 9): 587 if sys.version_info >= (3, 9):
563 return defaultComplexity + self.__getAnnotationComplexity( 588 complexity = defaultComplexity + self.__getAnnotationComplexity(
564 annotationNode.slice 589 annotationNode.slice
565 ) 590 )
566 else: 591 else:
567 return defaultComplexity + self.__getAnnotationComplexity( 592 complexity = defaultComplexity + self.__getAnnotationComplexity(
568 annotationNode.slice.value 593 annotationNode.slice.value
569 ) 594 )
595
570 if isinstance(annotationNode, ast.Tuple): 596 if isinstance(annotationNode, ast.Tuple):
571 return max( 597 complexity = max(
572 (self.__getAnnotationComplexity(n) for n in annotationNode.elts), 598 (self.__getAnnotationComplexity(n) for n in annotationNode.elts),
573 default=defaultComplexity, 599 default=defaultComplexity,
574 ) 600 )
575 return defaultComplexity 601
602 return complexity
576 603
577 def __getAnnotationLength(self, annotationNode): 604 def __getAnnotationLength(self, annotationNode):
578 """ 605 """
579 Private method to determine the annotation length. 606 Private method to determine the annotation length.
580 607
582 length for 609 length for
583 @type ast.AST 610 @type ast.AST
584 @return annotation length 611 @return annotation length
585 @rtype = int 612 @rtype = int
586 """ 613 """
614 annotationLength = 0
587 if AstUtilities.isString(annotationNode): 615 if AstUtilities.isString(annotationNode):
588 try: 616 try:
589 annotationNode = ast.parse(annotationNode.s).body[0].value 617 annotationNode = ast.parse(annotationNode.s).body[0].value
590 except (SyntaxError, IndexError): 618 except (SyntaxError, IndexError):
591 return 0 619 return annotationLength
620
592 if isinstance(annotationNode, ast.Subscript): 621 if isinstance(annotationNode, ast.Subscript):
593 try: 622 with contextlib.suppress(AttributeError):
594 if sys.version_info >= (3, 9): 623 annotationLength = (
595 return len(annotationNode.slice.elts) 624 len(annotationNode.slice.elts)
596 else: 625 if sys.version_info >= (3, 9)
597 return len(annotationNode.slice.value.elts) 626 else len(annotationNode.slice.value.elts)
598 except AttributeError: 627 )
599 return 0 628
600 return 0 629 return annotationLength
601 630
602 ####################################################################### 631 #######################################################################
603 ## 'from __future__ import annotations' checck 632 ## 'from __future__ import annotations' checck
604 ## 633 ##
605 ## adapted from: flake8-future-annotations v0.0.4 634 ## adapted from: flake8-future-annotations v0.0.5
606 ####################################################################### 635 #######################################################################
607 # TODO: update to v0.0.5
608 636
609 def __checkAnnotationsFuture(self): 637 def __checkAnnotationsFuture(self):
610 """ 638 """
611 Private method to check the use of __future__ and typing imports. 639 Private method to check the use of __future__ and typing imports.
612 """ 640 """
613 from .AnnotationsFutureVisitor import AnnotationsFutureVisitor 641 from .AnnotationsFutureVisitor import AnnotationsFutureVisitor
642
643 forceFutureAnnotations = self.__args.get(
644 "ForceFutureAnnotations",
645 AnnotationsCheckerDefaultArgs["ForceFutureAnnotations"],
646 )
614 647
615 visitor = AnnotationsFutureVisitor() 648 visitor = AnnotationsFutureVisitor()
616 visitor.visit(self.__tree) 649 visitor.visit(self.__tree)
617 650
618 if visitor.importsFutureAnnotations() or not visitor.hasTypingImports(): 651 if visitor.importsFutureAnnotations():
619 return 652 return
620 653
621 imports = ", ".join(visitor.getTypingImports()) 654 if visitor.hasTypingImports():
622 self.__error(0, 0, "A871", imports) 655 imports = ", ".join(visitor.getTypingImports())
656 self.__error(0, 0, "A871", imports)
657 elif forceFutureAnnotations:
658 self.__error(0, 0, "A872")

eric ide

mercurial