25 Class implementing a checker for function type annotations. |
25 Class implementing a checker for function type annotations. |
26 """ |
26 """ |
27 |
27 |
28 Codes = [ |
28 Codes = [ |
29 ## Function Annotations |
29 ## Function Annotations |
30 "A001", |
30 "A-001", |
31 "A002", |
31 "A-002", |
32 "A003", |
32 "A-003", |
33 ## Method Annotations |
33 ## Method Annotations |
34 "A101", |
34 "A-101", |
35 "A102", |
35 "A-102", |
36 ## Return Annotations |
36 ## Return Annotations |
37 "A201", |
37 "A-201", |
38 "A202", |
38 "A-202", |
39 "A203", |
39 "A-203", |
40 "A204", |
40 "A-204", |
41 "A205", |
41 "A-205", |
42 "A206", |
42 "A-206", |
43 ## Dynamically typed annotations |
43 ## Dynamically typed annotations |
44 "A401", |
44 "A-401", |
45 ## Type comments |
45 ## Type comments |
46 "A402", |
46 "A-402", |
47 ## Annotations Future |
47 ## Annotations Future |
48 "A871", |
48 "A-871", |
49 "A872", |
49 "A-872", |
50 "A873", |
50 "A-873", |
51 ## Annotation Coverage |
51 ## Annotation Coverage |
52 "A881", |
52 "A-881", |
53 ## Annotation Complexity |
53 ## Annotation Complexity |
54 "A891", |
54 "A-891", |
55 "A892", |
55 "A-892", |
56 ## use of typing.Union (PEP 604) |
56 ## use of typing.Union (PEP 604) |
57 "A901", |
57 "A-901", |
58 ## deprecated 'typing' symbols (PEP 585) |
58 ## deprecated 'typing' symbols (PEP 585) |
59 "A911", |
59 "A-911", |
60 ] |
60 ] |
61 |
61 |
62 def __init__(self, source, filename, tree, select, ignore, expected, repeat, args): |
62 def __init__(self, source, filename, tree, select, ignore, expected, repeat, args): |
63 """ |
63 """ |
64 Constructor |
64 Constructor |
97 |
97 |
98 checkersWithCodes = [ |
98 checkersWithCodes = [ |
99 ( |
99 ( |
100 self.__checkFunctionAnnotations, |
100 self.__checkFunctionAnnotations, |
101 ( |
101 ( |
102 "A001", |
102 "A-001", |
103 "A002", |
103 "A-002", |
104 "A003", |
104 "A-003", |
105 "A101", |
105 "A-101", |
106 "A102", |
106 "A-102", |
107 "A201", |
107 "A-201", |
108 "A202", |
108 "A-202", |
109 "A203", |
109 "A-203", |
110 "A204", |
110 "A-204", |
111 "A205", |
111 "A-205", |
112 "A206", |
112 "A-206", |
113 "A401", |
113 "A-401", |
114 "A402", |
114 "A-402", |
115 ), |
115 ), |
116 ), |
116 ), |
117 (self.__checkAnnotationsFuture, ("A871", "A872", "A873")), |
117 (self.__checkAnnotationsFuture, ("A-871", "A-872", "A-873")), |
118 (self.__checkAnnotationsCoverage, ("A881",)), |
118 (self.__checkAnnotationsCoverage, ("A-881",)), |
119 (self.__checkAnnotationComplexity, ("A891", "A892")), |
119 (self.__checkAnnotationComplexity, ("A-891", "A-892")), |
120 (self.__checkAnnotationPep604, ("A901",)), |
120 (self.__checkAnnotationPep604, ("A-901",)), |
121 (self.__checkDeprecatedTypingSymbols, ("A911",)), |
121 (self.__checkDeprecatedTypingSymbols, ("A-911",)), |
122 ] |
122 ] |
123 |
123 |
124 self.__checkers = [] |
124 self.__checkers = [] |
125 for checker, codes in checkersWithCodes: |
125 for checker, codes in checkersWithCodes: |
126 if any(not (code and self.__ignoreCode(code)) for code in codes): |
126 if any(not (code and self.__ignoreCode(code)) for code in codes): |
257 lastOverloadDecoratedFunctionName = None |
257 lastOverloadDecoratedFunctionName = None |
258 |
258 |
259 # Iterate over the arguments with missing type hints, by function. |
259 # Iterate over the arguments with missing type hints, by function. |
260 for function in visitor.functionDefinitions: |
260 for function in visitor.functionDefinitions: |
261 if function.hasTypeComment: |
261 if function.hasTypeComment: |
262 self.__error(function.lineno - 1, function.col_offset, "A402") |
262 self.__error(function.lineno - 1, function.col_offset, "A-402") |
263 |
263 |
264 if function.isDynamicallyTyped() and ( |
264 if function.isDynamicallyTyped() and ( |
265 allowUntypedDefs or (function.isNested and allowUntypedNested) |
265 allowUntypedDefs or (function.isNested and allowUntypedNested) |
266 ): |
266 ): |
267 # Skip recording errors from dynamically typed functions |
267 # Skip recording errors from dynamically typed functions |
282 AnnotationType.VARARG, |
282 AnnotationType.VARARG, |
283 AnnotationType.KWARG, |
283 AnnotationType.KWARG, |
284 }: |
284 }: |
285 continue |
285 continue |
286 |
286 |
287 self.__error(function.lineno - 1, function.col_offset, "A401") |
287 self.__error(function.lineno - 1, function.col_offset, "A-401") |
288 |
288 |
289 # Before we iterate over the function's missing annotations, check |
289 # Before we iterate over the function's missing annotations, check |
290 # to see if it's the closing function def in a series of |
290 # to see if it's the closing function def in a series of |
291 # `typing.overload` decorated functions. |
291 # `typing.overload` decorated functions. |
292 if lastOverloadDecoratedFunctionName == function.name: |
292 if lastOverloadDecoratedFunctionName == function.name: |
315 # Record explicit errors for arguments that are missing annotations |
315 # Record explicit errors for arguments that are missing annotations |
316 for arg in function.getMissedAnnotations(): |
316 for arg in function.getMissedAnnotations(): |
317 # Check for type comments here since we're not considering them as |
317 # Check for type comments here since we're not considering them as |
318 # typed args |
318 # typed args |
319 if arg.hasTypeComment: |
319 if arg.hasTypeComment: |
320 self.__error(arg.lineno - 1, arg.col_offset, "A402") |
320 self.__error(arg.lineno - 1, arg.col_offset, "A-402") |
321 |
321 |
322 if arg.argname == "return": |
322 if arg.argname == "return": |
323 # return annotations have multiple possible short-circuit |
323 # return annotations have multiple possible short-circuit |
324 # paths |
324 # paths |
325 if ( |
325 if ( |
381 isFirstArg, |
381 isFirstArg, |
382 function.classDecoratorType, |
382 function.classDecoratorType, |
383 arg.annotationType, |
383 arg.annotationType, |
384 ) |
384 ) |
385 |
385 |
386 if errorCode in ("A001", "A002", "A003"): |
386 if errorCode in ("A-001", "A-002", "A-003"): |
387 self.__error(arg.lineno - 1, arg.col_offset, errorCode, arg.argname) |
387 self.__error(arg.lineno - 1, arg.col_offset, errorCode, arg.argname) |
388 else: |
388 else: |
389 self.__error(arg.lineno - 1, arg.col_offset, errorCode) |
389 self.__error(arg.lineno - 1, arg.col_offset, errorCode) |
390 |
390 |
391 @lru_cache() # __IGNORE_WARNING_M519__ |
391 @lru_cache() # __IGNORE_WARNING_M519__ |
404 """ |
404 """ |
405 # Decorated class methods (@classmethod, @staticmethod) have a higher |
405 # Decorated class methods (@classmethod, @staticmethod) have a higher |
406 # priority than the rest |
406 # priority than the rest |
407 if isClassMethod: |
407 if isClassMethod: |
408 if classDecoratorType == ClassDecoratorType.CLASSMETHOD: |
408 if classDecoratorType == ClassDecoratorType.CLASSMETHOD: |
409 return "A206" |
409 return "A-206" |
410 elif classDecoratorType == ClassDecoratorType.STATICMETHOD: |
410 elif classDecoratorType == ClassDecoratorType.STATICMETHOD: |
411 return "A205" |
411 return "A-205" |
412 |
412 |
413 if functionType == FunctionType.SPECIAL: |
413 if functionType == FunctionType.SPECIAL: |
414 return "A204" |
414 return "A-204" |
415 elif functionType == FunctionType.PRIVATE: |
415 elif functionType == FunctionType.PRIVATE: |
416 return "A203" |
416 return "A-203" |
417 elif functionType == FunctionType.PROTECTED: |
417 elif functionType == FunctionType.PROTECTED: |
418 return "A202" |
418 return "A-202" |
419 else: |
419 else: |
420 return "A201" |
420 return "A-201" |
421 |
421 |
422 @lru_cache() # __IGNORE_WARNING_M519__ |
422 @lru_cache() # __IGNORE_WARNING_M519__ |
423 def __argumentErrorClassifier( |
423 def __argumentErrorClassifier( |
424 self, isClassMethod, isFirstArg, classDecoratorType, annotationType |
424 self, isClassMethod, isFirstArg, classDecoratorType, annotationType |
425 ): |
425 ): |
441 # deferred to final check |
441 # deferred to final check |
442 if isClassMethod and isFirstArg: |
442 if isClassMethod and isFirstArg: |
443 # The first function argument here would be an instance of self or |
443 # The first function argument here would be an instance of self or |
444 # class |
444 # class |
445 if classDecoratorType == ClassDecoratorType.CLASSMETHOD: |
445 if classDecoratorType == ClassDecoratorType.CLASSMETHOD: |
446 return "A102" |
446 return "A-102" |
447 elif classDecoratorType != ClassDecoratorType.STATICMETHOD: |
447 elif classDecoratorType != ClassDecoratorType.STATICMETHOD: |
448 # Regular class method |
448 # Regular class method |
449 return "A101" |
449 return "A-101" |
450 |
450 |
451 # Check for remaining codes |
451 # Check for remaining codes |
452 if annotationType == AnnotationType.KWARG: |
452 if annotationType == AnnotationType.KWARG: |
453 return "A003" |
453 return "A-003" |
454 elif annotationType == AnnotationType.VARARG: |
454 elif annotationType == AnnotationType.VARARG: |
455 return "A002" |
455 return "A-002" |
456 else: |
456 else: |
457 # Combine PosOnlyArgs, Args, and KwOnlyArgs |
457 # Combine PosOnlyArgs, Args, and KwOnlyArgs |
458 return "A001" |
458 return "A-001" |
459 |
459 |
460 ####################################################################### |
460 ####################################################################### |
461 ## Annotations Coverage |
461 ## Annotations Coverage |
462 ## |
462 ## |
463 ## adapted from: flake8-annotations-coverage v0.0.6 |
463 ## adapted from: flake8-annotations-coverage v0.0.6 |
493 len(list(filter(None, functionDefAnnotationsInfo))) |
493 len(list(filter(None, functionDefAnnotationsInfo))) |
494 / len(functionDefAnnotationsInfo) |
494 / len(functionDefAnnotationsInfo) |
495 * 100 |
495 * 100 |
496 ) |
496 ) |
497 if annotationsCoverage < minAnnotationsCoverage: |
497 if annotationsCoverage < minAnnotationsCoverage: |
498 self.__error(0, 0, "A881", annotationsCoverage) |
498 self.__error(0, 0, "A-881", annotationsCoverage) |
499 |
499 |
500 def __hasTypeAnnotations(self, funcNode): |
500 def __hasTypeAnnotations(self, funcNode): |
501 """ |
501 """ |
502 Private method to check for type annotations. |
502 Private method to check for type annotations. |
503 |
503 |
566 complexity = self.__getAnnotationComplexity(annotation) |
566 complexity = self.__getAnnotationComplexity(annotation) |
567 if complexity > maxAnnotationComplexity: |
567 if complexity > maxAnnotationComplexity: |
568 self.__error( |
568 self.__error( |
569 annotation.lineno - 1, |
569 annotation.lineno - 1, |
570 annotation.col_offset, |
570 annotation.col_offset, |
571 "A891", |
571 "A-891", |
572 complexity, |
572 complexity, |
573 maxAnnotationComplexity, |
573 maxAnnotationComplexity, |
574 ) |
574 ) |
575 |
575 |
576 annotationLength = self.__getAnnotationLength(annotation) |
576 annotationLength = self.__getAnnotationLength(annotation) |
577 if annotationLength > maxAnnotationLength: |
577 if annotationLength > maxAnnotationLength: |
578 self.__error( |
578 self.__error( |
579 annotation.lineno - 1, |
579 annotation.lineno - 1, |
580 annotation.col_offset, |
580 annotation.col_offset, |
581 "A892", |
581 "A-892", |
582 annotationLength, |
582 annotationLength, |
583 maxAnnotationLength, |
583 maxAnnotationLength, |
584 ) |
584 ) |
585 |
585 |
586 def __getAnnotationComplexity(self, annotationNode, defaultComplexity=1): |
586 def __getAnnotationComplexity(self, annotationNode, defaultComplexity=1): |
678 if visitor.importsFutureAnnotations(): |
678 if visitor.importsFutureAnnotations(): |
679 return |
679 return |
680 |
680 |
681 if visitor.hasTypingImports(): |
681 if visitor.hasTypingImports(): |
682 imports = ", ".join(visitor.getTypingImports()) |
682 imports = ", ".join(visitor.getTypingImports()) |
683 self.__error(0, 0, "A871", imports) |
683 self.__error(0, 0, "A-871", imports) |
684 elif forceFutureAnnotations: |
684 elif forceFutureAnnotations: |
685 self.__error(0, 0, "A872") |
685 self.__error(0, 0, "A-872") |
686 |
686 |
687 if checkFutureAnnotations and visitor.hasSimplifiedTypes(): |
687 if checkFutureAnnotations and visitor.hasSimplifiedTypes(): |
688 simplifiedTypes = ", ".join(sorted(visitor.getSimplifiedTypes())) |
688 simplifiedTypes = ", ".join(sorted(visitor.getSimplifiedTypes())) |
689 self.__error(0, 0, "A873", simplifiedTypes) |
689 self.__error(0, 0, "A-873", simplifiedTypes) |
690 |
690 |
691 ####################################################################### |
691 ####################################################################### |
692 ## check use of 'typing.Union' (see PEP 604) |
692 ## check use of 'typing.Union' (see PEP 604) |
693 ## |
693 ## |
694 ## adapted from: flake8-pep604 v1.1.0 |
694 ## adapted from: flake8-pep604 v1.1.0 |
706 |
706 |
707 visitor = AnnotationsUnionVisitor() |
707 visitor = AnnotationsUnionVisitor() |
708 visitor.visit(self.__tree) |
708 visitor.visit(self.__tree) |
709 |
709 |
710 for node in visitor.getIssues(): |
710 for node in visitor.getIssues(): |
711 self.__error(node.lineno - 1, node.col_offset, "A901") |
711 self.__error(node.lineno - 1, node.col_offset, "A-901") |
712 |
712 |
713 ####################################################################### |
713 ####################################################################### |
714 ## check use of 'typing.Union' (see PEP 604) |
714 ## check use of 'typing.Union' (see PEP 604) |
715 ## |
715 ## |
716 ## adapted from: flake8-pep585 v0.1.7 |
716 ## adapted from: flake8-pep585 v0.1.7 |
739 ) |
739 ) |
740 ) |
740 ) |
741 visitor.visit(self.__tree) |
741 visitor.visit(self.__tree) |
742 |
742 |
743 for node, (name, replacement) in visitor.getIssues(): |
743 for node, (name, replacement) in visitor.getIssues(): |
744 self.__error(node.lineno - 1, node.col_offset, "A911", name, replacement) |
744 self.__error(node.lineno - 1, node.col_offset, "A-911", name, replacement) |