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

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9274
86fab0c74430
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
20 20
21 class AnnotationsChecker: 21 class AnnotationsChecker:
22 """ 22 """
23 Class implementing a checker for function type annotations. 23 Class implementing a checker for function type annotations.
24 """ 24 """
25
25 Codes = [ 26 Codes = [
26 ## Function Annotations 27 ## Function Annotations
27 "A001", "A002", "A003", 28 "A001",
28 29 "A002",
30 "A003",
29 ## Method Annotations 31 ## Method Annotations
30 "A101", "A102", 32 "A101",
31 33 "A102",
32 ## Return Annotations 34 ## Return Annotations
33 "A201", "A202", "A203", "A204", "A205", "A206", 35 "A201",
34 36 "A202",
37 "A203",
38 "A204",
39 "A205",
40 "A206",
35 ## Mixed kind of annotations 41 ## Mixed kind of annotations
36 "A301", 42 "A301",
37
38 ## Annotations Future 43 ## Annotations Future
39 "A871", 44 "A871",
40
41 ## Annotation Coverage 45 ## Annotation Coverage
42 "A881", 46 "A881",
43
44 ## Annotation Complexity 47 ## Annotation Complexity
45 "A891", "A892", 48 "A891",
49 "A892",
46 ] 50 ]
47 51
48 def __init__(self, source, filename, tree, select, ignore, expected, 52 def __init__(self, source, filename, tree, select, ignore, expected, repeat, args):
49 repeat, args):
50 """ 53 """
51 Constructor 54 Constructor
52 55
53 @param source source code to be checked 56 @param source source code to be checked
54 @type list of str 57 @type list of str
55 @param filename name of the source file 58 @param filename name of the source file
56 @type str 59 @type str
57 @param tree AST tree of the source code 60 @param tree AST tree of the source code
66 @type bool 69 @type bool
67 @param args dictionary of arguments for the annotation checks 70 @param args dictionary of arguments for the annotation checks
68 @type dict 71 @type dict
69 """ 72 """
70 self.__select = tuple(select) 73 self.__select = tuple(select)
71 self.__ignore = ('',) if select else tuple(ignore) 74 self.__ignore = ("",) if select else tuple(ignore)
72 self.__expected = expected[:] 75 self.__expected = expected[:]
73 self.__repeat = repeat 76 self.__repeat = repeat
74 self.__filename = filename 77 self.__filename = filename
75 self.__source = source[:] 78 self.__source = source[:]
76 self.__tree = copy.deepcopy(tree) 79 self.__tree = copy.deepcopy(tree)
77 self.__args = args 80 self.__args = args
78 81
79 # statistics counters 82 # statistics counters
80 self.counters = {} 83 self.counters = {}
81 84
82 # collection of detected errors 85 # collection of detected errors
83 self.errors = [] 86 self.errors = []
84 87
85 checkersWithCodes = [ 88 checkersWithCodes = [
86 ( 89 (
87 self.__checkFunctionAnnotations, 90 self.__checkFunctionAnnotations,
88 ("A001", "A002", "A003", "A101", "A102", 91 (
89 "A201", "A202", "A203", "A204", "A205", "A206", 92 "A001",
90 "A301", ) 93 "A002",
94 "A003",
95 "A101",
96 "A102",
97 "A201",
98 "A202",
99 "A203",
100 "A204",
101 "A205",
102 "A206",
103 "A301",
104 ),
91 ), 105 ),
92 (self.__checkAnnotationsFuture, ("A871",)), 106 (self.__checkAnnotationsFuture, ("A871",)),
93 (self.__checkAnnotationsCoverage, ("A881",)), 107 (self.__checkAnnotationsCoverage, ("A881",)),
94 (self.__checkAnnotationComplexity, ("A891", "A892")), 108 (self.__checkAnnotationComplexity, ("A891", "A892")),
95 ] 109 ]
96 110
97 self.__checkers = [] 111 self.__checkers = []
98 for checker, codes in checkersWithCodes: 112 for checker, codes in checkersWithCodes:
99 if any(not (code and self.__ignoreCode(code)) 113 if any(not (code and self.__ignoreCode(code)) for code in codes):
100 for code in codes):
101 self.__checkers.append(checker) 114 self.__checkers.append(checker)
102 115
103 def __ignoreCode(self, code): 116 def __ignoreCode(self, code):
104 """ 117 """
105 Private method to check if the message code should be ignored. 118 Private method to check if the message code should be ignored.
106 119
107 @param code message code to check for 120 @param code message code to check for
108 @type str 121 @type str
109 @return flag indicating to ignore the given code 122 @return flag indicating to ignore the given code
110 @rtype bool 123 @rtype bool
111 """ 124 """
112 return (code.startswith(self.__ignore) and 125 return code.startswith(self.__ignore) and not code.startswith(self.__select)
113 not code.startswith(self.__select)) 126
114
115 def __error(self, lineNumber, offset, code, *args): 127 def __error(self, lineNumber, offset, code, *args):
116 """ 128 """
117 Private method to record an issue. 129 Private method to record an issue.
118 130
119 @param lineNumber line number of the issue 131 @param lineNumber line number of the issue
120 @type int 132 @type int
121 @param offset position within line of the issue 133 @param offset position within line of the issue
122 @type int 134 @type int
123 @param code message code 135 @param code message code
125 @param args arguments for the message 137 @param args arguments for the message
126 @type list 138 @type list
127 """ 139 """
128 if self.__ignoreCode(code): 140 if self.__ignoreCode(code):
129 return 141 return
130 142
131 if code in self.counters: 143 if code in self.counters:
132 self.counters[code] += 1 144 self.counters[code] += 1
133 else: 145 else:
134 self.counters[code] = 1 146 self.counters[code] = 1
135 147
136 # Don't care about expected codes 148 # Don't care about expected codes
137 if code in self.__expected: 149 if code in self.__expected:
138 return 150 return
139 151
140 if code and (self.counters[code] == 1 or self.__repeat): 152 if code and (self.counters[code] == 1 or self.__repeat):
141 # record the issue with one based line number 153 # record the issue with one based line number
142 self.errors.append( 154 self.errors.append(
143 { 155 {
144 "file": self.__filename, 156 "file": self.__filename,
146 "offset": offset, 158 "offset": offset,
147 "code": code, 159 "code": code,
148 "args": args, 160 "args": args,
149 } 161 }
150 ) 162 )
151 163
152 def run(self): 164 def run(self):
153 """ 165 """
154 Public method to check the given source against annotation issues. 166 Public method to check the given source against annotation issues.
155 """ 167 """
156 if not self.__filename: 168 if not self.__filename:
157 # don't do anything, if essential data is missing 169 # don't do anything, if essential data is missing
158 return 170 return
159 171
160 if not self.__checkers: 172 if not self.__checkers:
161 # don't do anything, if no codes were selected 173 # don't do anything, if no codes were selected
162 return 174 return
163 175
164 for check in self.__checkers: 176 for check in self.__checkers:
165 check() 177 check()
166 178
167 ####################################################################### 179 #######################################################################
168 ## Annotations 180 ## Annotations
169 ## 181 ##
170 ## adapted from: flake8-annotations v2.7.0 182 ## adapted from: flake8-annotations v2.7.0
171 ####################################################################### 183 #######################################################################
172 184
173 def __checkFunctionAnnotations(self): 185 def __checkFunctionAnnotations(self):
174 """ 186 """
175 Private method to check for function annotation issues. 187 Private method to check for function annotation issues.
176 """ 188 """
177 suppressNoneReturning = self.__args.get( 189 suppressNoneReturning = self.__args.get(
178 "SuppressNoneReturning", 190 "SuppressNoneReturning",
179 AnnotationsCheckerDefaultArgs["SuppressNoneReturning"]) 191 AnnotationsCheckerDefaultArgs["SuppressNoneReturning"],
192 )
180 suppressDummyArgs = self.__args.get( 193 suppressDummyArgs = self.__args.get(
181 "SuppressDummyArgs", 194 "SuppressDummyArgs", AnnotationsCheckerDefaultArgs["SuppressDummyArgs"]
182 AnnotationsCheckerDefaultArgs["SuppressDummyArgs"]) 195 )
183 allowUntypedDefs = self.__args.get( 196 allowUntypedDefs = self.__args.get(
184 "AllowUntypedDefs", 197 "AllowUntypedDefs", AnnotationsCheckerDefaultArgs["AllowUntypedDefs"]
185 AnnotationsCheckerDefaultArgs["AllowUntypedDefs"]) 198 )
186 allowUntypedNested = self.__args.get( 199 allowUntypedNested = self.__args.get(
187 "AllowUntypedNested", 200 "AllowUntypedNested", AnnotationsCheckerDefaultArgs["AllowUntypedNested"]
188 AnnotationsCheckerDefaultArgs["AllowUntypedNested"]) 201 )
189 mypyInitReturn = self.__args.get( 202 mypyInitReturn = self.__args.get(
190 "MypyInitReturn", 203 "MypyInitReturn", AnnotationsCheckerDefaultArgs["MypyInitReturn"]
191 AnnotationsCheckerDefaultArgs["MypyInitReturn"]) 204 )
192 205
193 # Store decorator lists as sets for easier lookup 206 # Store decorator lists as sets for easier lookup
194 dispatchDecorators = set(self.__args.get( 207 dispatchDecorators = set(
195 "DispatchDecorators", 208 self.__args.get(
196 AnnotationsCheckerDefaultArgs["DispatchDecorators"])) 209 "DispatchDecorators",
197 overloadDecorators = set(self.__args.get( 210 AnnotationsCheckerDefaultArgs["DispatchDecorators"],
198 "OverloadDecorators", 211 )
199 AnnotationsCheckerDefaultArgs["OverloadDecorators"])) 212 )
200 213 overloadDecorators = set(
214 self.__args.get(
215 "OverloadDecorators",
216 AnnotationsCheckerDefaultArgs["OverloadDecorators"],
217 )
218 )
219
201 from .AnnotationsFunctionVisitor import FunctionVisitor 220 from .AnnotationsFunctionVisitor import FunctionVisitor
221
202 visitor = FunctionVisitor(self.__source) 222 visitor = FunctionVisitor(self.__source)
203 visitor.visit(self.__tree) 223 visitor.visit(self.__tree)
204 224
205 # Keep track of the last encountered function decorated by 225 # Keep track of the last encountered function decorated by
206 # `typing.overload`, if any. Per the `typing` module documentation, 226 # `typing.overload`, if any. Per the `typing` module documentation,
207 # a series of overload-decorated definitions must be followed by 227 # a series of overload-decorated definitions must be followed by
208 # exactly one non-overload-decorated definition of the same function. 228 # exactly one non-overload-decorated definition of the same function.
209 lastOverloadDecoratedFunctionName = None 229 lastOverloadDecoratedFunctionName = None
210 230
211 # Iterate over the arguments with missing type hints, by function. 231 # Iterate over the arguments with missing type hints, by function.
212 for function in visitor.functionDefinitions: 232 for function in visitor.functionDefinitions:
213 if ( 233 if function.isDynamicallyTyped() and (
214 function.isDynamicallyTyped() and 234 allowUntypedDefs or (function.isNested and allowUntypedNested)
215 (allowUntypedDefs or
216 (function.isNested and allowUntypedNested))
217 ): 235 ):
218 # Skip recording errors from dynamically typed functions 236 # Skip recording errors from dynamically typed functions
219 # or nested functions 237 # or nested functions
220 continue 238 continue
221 239
222 # Skip recording errors for configured dispatch functions, such as 240 # Skip recording errors for configured dispatch functions, such as
223 # (by default) `functools.singledispatch` and 241 # (by default) `functools.singledispatch` and
224 # `functools.singledispatchmethod` 242 # `functools.singledispatchmethod`
225 if function.hasDecorator(dispatchDecorators): 243 if function.hasDecorator(dispatchDecorators):
226 continue 244 continue
227 245
228 # Create sentinels to check for mixed hint styles 246 # Create sentinels to check for mixed hint styles
229 hasTypeComment = function.hasTypeComment 247 hasTypeComment = function.hasTypeComment
230 248
231 has3107Annotation = False 249 has3107Annotation = False
232 # PEP 3107 annotations are captured by the return arg 250 # PEP 3107 annotations are captured by the return arg
233 251
234 # Iterate over annotated args to detect mixing of type annotations 252 # Iterate over annotated args to detect mixing of type annotations
235 # and type comments. Emit this only once per function definition 253 # and type comments. Emit this only once per function definition
236 for arg in function.getAnnotatedArguments(): 254 for arg in function.getAnnotatedArguments():
237 if arg.hasTypeComment: 255 if arg.hasTypeComment:
238 hasTypeComment = True 256 hasTypeComment = True
239 257
240 if arg.has3107Annotation: 258 if arg.has3107Annotation:
241 has3107Annotation = True 259 has3107Annotation = True
242 260
243 if hasTypeComment and has3107Annotation: 261 if hasTypeComment and has3107Annotation:
244 # Short-circuit check for mixing of type comments & 262 # Short-circuit check for mixing of type comments &
245 # 3107-style annotations 263 # 3107-style annotations
246 self.__error(function.lineno - 1, function.col_offset, 264 self.__error(function.lineno - 1, function.col_offset, "A301")
247 "A301")
248 break 265 break
249 266
250 # Before we iterate over the function's missing annotations, check 267 # Before we iterate over the function's missing annotations, check
251 # to see if it's the closing function def in a series of 268 # to see if it's the closing function def in a series of
252 # `typing.overload` decorated functions. 269 # `typing.overload` decorated functions.
253 if lastOverloadDecoratedFunctionName == function.name: 270 if lastOverloadDecoratedFunctionName == function.name:
254 continue 271 continue
255 272
256 # If it's not, and it is overload decorated, store it for the next 273 # If it's not, and it is overload decorated, store it for the next
257 # iteration 274 # iteration
258 if function.hasDecorator(overloadDecorators): 275 if function.hasDecorator(overloadDecorators):
259 lastOverloadDecoratedFunctionName = function.name 276 lastOverloadDecoratedFunctionName = function.name
260 277
261 # Record explicit errors for arguments that are missing annotations 278 # Record explicit errors for arguments that are missing annotations
262 for arg in function.getMissedAnnotations(): 279 for arg in function.getMissedAnnotations():
263 if arg.argname == "return": 280 if arg.argname == "return":
264 # return annotations have multiple possible short-circuit 281 # return annotations have multiple possible short-circuit
265 # paths 282 # paths
266 if ( 283 if (
267 suppressNoneReturning and 284 suppressNoneReturning
268 not arg.hasTypeAnnotation and 285 and not arg.hasTypeAnnotation
269 function.hasOnlyNoneReturns 286 and function.hasOnlyNoneReturns
270 ): 287 ):
271 # Skip recording return errors if the function has only 288 # Skip recording return errors if the function has only
272 # `None` returns. This includes the case of no returns. 289 # `None` returns. This includes the case of no returns.
273 continue 290 continue
274 291
275 if ( 292 if (
276 mypyInitReturn and 293 mypyInitReturn
277 function.isClassMethod and 294 and function.isClassMethod
278 function.name == "__init__" and 295 and function.name == "__init__"
279 function.getAnnotatedArguments() 296 and function.getAnnotatedArguments()
280 ): 297 ):
281 # Skip recording return errors for `__init__` if at 298 # Skip recording return errors for `__init__` if at
282 # least one argument is annotated 299 # least one argument is annotated
283 continue 300 continue
284 301
285 # If the `suppressDummyArgs` flag is `True`, skip recording 302 # If the `suppressDummyArgs` flag is `True`, skip recording
286 # errors for any arguments named `_` 303 # errors for any arguments named `_`
287 if arg.argname == "_" and suppressDummyArgs: 304 if arg.argname == "_" and suppressDummyArgs:
288 continue 305 continue
289 306
290 self.__classifyError(function, arg) 307 self.__classifyError(function, arg)
291 308
292 def __classifyError(self, function, arg): 309 def __classifyError(self, function, arg):
293 """ 310 """
294 Private method to classify the missing type annotation based on the 311 Private method to classify the missing type annotation based on the
295 Function & Argument metadata. 312 Function & Argument metadata.
296 313
297 For the currently defined rules & program flow, the assumption can be 314 For the currently defined rules & program flow, the assumption can be
298 made that an argument passed to this method will match a linting error, 315 made that an argument passed to this method will match a linting error,
299 and will only match a single linting error 316 and will only match a single linting error
300 317
301 This function provides an initial classificaton, then passes relevant 318 This function provides an initial classificaton, then passes relevant
302 attributes to cached helper function(s). 319 attributes to cached helper function(s).
303 320
304 @param function reference to the Function object 321 @param function reference to the Function object
305 @type Function 322 @type Function
306 @param arg reference to the Argument object 323 @param arg reference to the Argument object
307 @type Argument 324 @type Argument
308 """ 325 """
309 # Check for return type 326 # Check for return type
310 # All return "arguments" have an explicitly defined name "return" 327 # All return "arguments" have an explicitly defined name "return"
311 if arg.argname == "return": 328 if arg.argname == "return":
312 errorCode = self.__returnErrorClassifier( 329 errorCode = self.__returnErrorClassifier(
313 function.isClassMethod, function.classDecoratorType, 330 function.isClassMethod,
314 function.functionType 331 function.classDecoratorType,
332 function.functionType,
315 ) 333 )
316 else: 334 else:
317 # Otherwise, classify function argument error 335 # Otherwise, classify function argument error
318 isFirstArg = arg == function.args[0] 336 isFirstArg = arg == function.args[0]
319 errorCode = self.__argumentErrorClassifier( 337 errorCode = self.__argumentErrorClassifier(
320 function.isClassMethod, isFirstArg, 338 function.isClassMethod,
321 function.classDecoratorType, arg.annotationType, 339 isFirstArg,
322 ) 340 function.classDecoratorType,
323 341 arg.annotationType,
342 )
343
324 if errorCode in ("A001", "A002", "A003"): 344 if errorCode in ("A001", "A002", "A003"):
325 self.__error(arg.lineno - 1, arg.col_offset, errorCode, 345 self.__error(arg.lineno - 1, arg.col_offset, errorCode, arg.argname)
326 arg.argname)
327 else: 346 else:
328 self.__error(arg.lineno - 1, arg.col_offset, errorCode) 347 self.__error(arg.lineno - 1, arg.col_offset, errorCode)
329 348
330 @lru_cache() 349 @lru_cache()
331 def __returnErrorClassifier(self, isClassMethod, classDecoratorType, 350 def __returnErrorClassifier(self, isClassMethod, classDecoratorType, functionType):
332 functionType):
333 """ 351 """
334 Private method to classify a return type annotation issue. 352 Private method to classify a return type annotation issue.
335 353
336 @param isClassMethod flag indicating a classmethod type function 354 @param isClassMethod flag indicating a classmethod type function
337 @type bool 355 @type bool
338 @param classDecoratorType type of class decorator 356 @param classDecoratorType type of class decorator
339 @type ClassDecoratorType 357 @type ClassDecoratorType
340 @param functionType type of function 358 @param functionType type of function
356 return "A203" 374 return "A203"
357 elif functionType == FunctionType.PROTECTED: 375 elif functionType == FunctionType.PROTECTED:
358 return "A202" 376 return "A202"
359 else: 377 else:
360 return "A201" 378 return "A201"
361 379
362 @lru_cache() 380 @lru_cache()
363 def __argumentErrorClassifier(self, isClassMethod, isFirstArg, 381 def __argumentErrorClassifier(
364 classDecoratorType, annotationType): 382 self, isClassMethod, isFirstArg, classDecoratorType, annotationType
383 ):
365 """ 384 """
366 Private method to classify an argument type annotation issue. 385 Private method to classify an argument type annotation issue.
367 386
368 @param isClassMethod flag indicating a classmethod type function 387 @param isClassMethod flag indicating a classmethod type function
369 @type bool 388 @type bool
370 @param isFirstArg flag indicating the first argument 389 @param isFirstArg flag indicating the first argument
371 @type bool 390 @type bool
372 @param classDecoratorType type of class decorator 391 @param classDecoratorType type of class decorator
393 elif annotationType == AnnotationType.VARARG: 412 elif annotationType == AnnotationType.VARARG:
394 return "A002" 413 return "A002"
395 else: 414 else:
396 # Combine PosOnlyArgs, Args, and KwOnlyArgs 415 # Combine PosOnlyArgs, Args, and KwOnlyArgs
397 return "A001" 416 return "A001"
398 417
399 ####################################################################### 418 #######################################################################
400 ## Annotations Coverage 419 ## Annotations Coverage
401 ## 420 ##
402 ## adapted from: flake8-annotations-coverage v0.0.5 421 ## adapted from: flake8-annotations-coverage v0.0.5
403 ####################################################################### 422 #######################################################################
404 423
405 def __checkAnnotationsCoverage(self): 424 def __checkAnnotationsCoverage(self):
406 """ 425 """
407 Private method to check for function annotation coverage. 426 Private method to check for function annotation coverage.
408 """ 427 """
409 minAnnotationsCoverage = self.__args.get( 428 minAnnotationsCoverage = self.__args.get(
410 "MinimumCoverage", 429 "MinimumCoverage", AnnotationsCheckerDefaultArgs["MinimumCoverage"]
411 AnnotationsCheckerDefaultArgs["MinimumCoverage"]) 430 )
412 if minAnnotationsCoverage == 0: 431 if minAnnotationsCoverage == 0:
413 # 0 means it is switched off 432 # 0 means it is switched off
414 return 433 return
415 434
416 functionDefs = [ 435 functionDefs = [
417 f for f in ast.walk(self.__tree) 436 f
437 for f in ast.walk(self.__tree)
418 if isinstance(f, (ast.AsyncFunctionDef, ast.FunctionDef)) 438 if isinstance(f, (ast.AsyncFunctionDef, ast.FunctionDef))
419 ] 439 ]
420 if not functionDefs: 440 if not functionDefs:
421 # no functions/methods at all 441 # no functions/methods at all
422 return 442 return
423 443
424 functionDefAnnotationsInfo = [ 444 functionDefAnnotationsInfo = [
425 self.__hasTypeAnnotations(f) for f in functionDefs 445 self.__hasTypeAnnotations(f) for f in functionDefs
426 ] 446 ]
427 annotationsCoverage = int( 447 annotationsCoverage = int(
428 len(list(filter(None, functionDefAnnotationsInfo))) / 448 len(list(filter(None, functionDefAnnotationsInfo)))
429 len(functionDefAnnotationsInfo) * 100 449 / len(functionDefAnnotationsInfo)
450 * 100
430 ) 451 )
431 if annotationsCoverage < minAnnotationsCoverage: 452 if annotationsCoverage < minAnnotationsCoverage:
432 self.__error(0, 0, "A881", annotationsCoverage) 453 self.__error(0, 0, "A881", annotationsCoverage)
433 454
434 def __hasTypeAnnotations(self, funcNode): 455 def __hasTypeAnnotations(self, funcNode):
435 """ 456 """
436 Private method to check for type annotations. 457 Private method to check for type annotations.
437 458
438 @param funcNode reference to the function definition node to be checked 459 @param funcNode reference to the function definition node to be checked
439 @type ast.AsyncFunctionDef or ast.FunctionDef 460 @type ast.AsyncFunctionDef or ast.FunctionDef
440 @return flag indicating the presence of type annotations 461 @return flag indicating the presence of type annotations
441 @rtype bool 462 @rtype bool
442 """ 463 """
443 hasReturnAnnotation = funcNode.returns is not None 464 hasReturnAnnotation = funcNode.returns is not None
444 hasArgsAnnotations = any(a for a in funcNode.args.args 465 hasArgsAnnotations = any(
445 if a.annotation is not None) 466 a for a in funcNode.args.args if a.annotation is not None
446 hasKwargsAnnotations = (funcNode.args and 467 )
447 funcNode.args.kwarg and 468 hasKwargsAnnotations = (
448 funcNode.args.kwarg.annotation is not None) 469 funcNode.args
449 hasKwonlyargsAnnotations = any(a for a in funcNode.args.kwonlyargs 470 and funcNode.args.kwarg
450 if a.annotation is not None) 471 and funcNode.args.kwarg.annotation is not None
451 472 )
452 return any((hasReturnAnnotation, hasArgsAnnotations, 473 hasKwonlyargsAnnotations = any(
453 hasKwargsAnnotations, hasKwonlyargsAnnotations)) 474 a for a in funcNode.args.kwonlyargs if a.annotation is not None
454 475 )
476
477 return any(
478 (
479 hasReturnAnnotation,
480 hasArgsAnnotations,
481 hasKwargsAnnotations,
482 hasKwonlyargsAnnotations,
483 )
484 )
485
455 ####################################################################### 486 #######################################################################
456 ## Annotations Complexity 487 ## Annotations Complexity
457 ## 488 ##
458 ## adapted from: flake8-annotations-complexity v0.0.6 489 ## adapted from: flake8-annotations-complexity v0.0.6
459 ####################################################################### 490 #######################################################################
460 491
461 def __checkAnnotationComplexity(self): 492 def __checkAnnotationComplexity(self):
462 """ 493 """
463 Private method to check the type annotation complexity. 494 Private method to check the type annotation complexity.
464 """ 495 """
465 maxAnnotationComplexity = self.__args.get( 496 maxAnnotationComplexity = self.__args.get(
466 "MaximumComplexity", 497 "MaximumComplexity", AnnotationsCheckerDefaultArgs["MaximumComplexity"]
467 AnnotationsCheckerDefaultArgs["MaximumComplexity"]) 498 )
468 maxAnnotationLength = self.__args.get( 499 maxAnnotationLength = self.__args.get(
469 "MaximumLength", AnnotationsCheckerDefaultArgs["MaximumLength"]) 500 "MaximumLength", AnnotationsCheckerDefaultArgs["MaximumLength"]
501 )
470 typeAnnotations = [] 502 typeAnnotations = []
471 503
472 functionDefs = [ 504 functionDefs = [
473 f for f in ast.walk(self.__tree) 505 f
506 for f in ast.walk(self.__tree)
474 if isinstance(f, (ast.AsyncFunctionDef, ast.FunctionDef)) 507 if isinstance(f, (ast.AsyncFunctionDef, ast.FunctionDef))
475 ] 508 ]
476 for functionDef in functionDefs: 509 for functionDef in functionDefs:
477 typeAnnotations += list(filter( 510 typeAnnotations += list(
478 None, [a.annotation for a in functionDef.args.args])) 511 filter(None, [a.annotation for a in functionDef.args.args])
512 )
479 if functionDef.returns: 513 if functionDef.returns:
480 typeAnnotations.append(functionDef.returns) 514 typeAnnotations.append(functionDef.returns)
481 typeAnnotations += [a.annotation for a in ast.walk(self.__tree) 515 typeAnnotations += [
482 if isinstance(a, ast.AnnAssign) and a.annotation] 516 a.annotation
517 for a in ast.walk(self.__tree)
518 if isinstance(a, ast.AnnAssign) and a.annotation
519 ]
483 for annotation in typeAnnotations: 520 for annotation in typeAnnotations:
484 complexity = self.__getAnnotationComplexity(annotation) 521 complexity = self.__getAnnotationComplexity(annotation)
485 if complexity > maxAnnotationComplexity: 522 if complexity > maxAnnotationComplexity:
486 self.__error(annotation.lineno - 1, annotation.col_offset, 523 self.__error(
487 "A891", complexity, maxAnnotationComplexity) 524 annotation.lineno - 1,
488 525 annotation.col_offset,
526 "A891",
527 complexity,
528 maxAnnotationComplexity,
529 )
530
489 annotationLength = self.__getAnnotationLength(annotation) 531 annotationLength = self.__getAnnotationLength(annotation)
490 if annotationLength > maxAnnotationLength: 532 if annotationLength > maxAnnotationLength:
491 self.__error(annotation.lineno - 1, annotation.col_offset, 533 self.__error(
492 "A892", annotationLength, maxAnnotationLength) 534 annotation.lineno - 1,
493 535 annotation.col_offset,
536 "A892",
537 annotationLength,
538 maxAnnotationLength,
539 )
540
494 def __getAnnotationComplexity(self, annotationNode, defaultComplexity=1): 541 def __getAnnotationComplexity(self, annotationNode, defaultComplexity=1):
495 """ 542 """
496 Private method to determine the annotation complexity. 543 Private method to determine the annotation complexity.
497 544
498 @param annotationNode reference to the node to determine the annotation 545 @param annotationNode reference to the node to determine the annotation
499 complexity for 546 complexity for
500 @type ast.AST 547 @type ast.AST
501 @param defaultComplexity default complexity value 548 @param defaultComplexity default complexity value
502 @type int 549 @type int
508 annotationNode = ast.parse(annotationNode.s).body[0].value 555 annotationNode = ast.parse(annotationNode.s).body[0].value
509 except (SyntaxError, IndexError): 556 except (SyntaxError, IndexError):
510 return defaultComplexity 557 return defaultComplexity
511 if isinstance(annotationNode, ast.Subscript): 558 if isinstance(annotationNode, ast.Subscript):
512 if sys.version_info >= (3, 9): 559 if sys.version_info >= (3, 9):
513 return (defaultComplexity + 560 return defaultComplexity + self.__getAnnotationComplexity(
514 self.__getAnnotationComplexity(annotationNode.slice)) 561 annotationNode.slice
562 )
515 else: 563 else:
516 return ( 564 return defaultComplexity + self.__getAnnotationComplexity(
517 defaultComplexity + 565 annotationNode.slice.value
518 self.__getAnnotationComplexity(annotationNode.slice.value)
519 ) 566 )
520 if isinstance(annotationNode, ast.Tuple): 567 if isinstance(annotationNode, ast.Tuple):
521 return max( 568 return max(
522 (self.__getAnnotationComplexity(n) 569 (self.__getAnnotationComplexity(n) for n in annotationNode.elts),
523 for n in annotationNode.elts), 570 default=defaultComplexity,
524 default=defaultComplexity
525 ) 571 )
526 return defaultComplexity 572 return defaultComplexity
527 573
528 def __getAnnotationLength(self, annotationNode): 574 def __getAnnotationLength(self, annotationNode):
529 """ 575 """
530 Private method to determine the annotation length. 576 Private method to determine the annotation length.
531 577
532 @param annotationNode reference to the node to determine the annotation 578 @param annotationNode reference to the node to determine the annotation
533 length for 579 length for
534 @type ast.AST 580 @type ast.AST
535 @return annotation length 581 @return annotation length
536 @rtype = int 582 @rtype = int
547 else: 593 else:
548 return len(annotationNode.slice.value.elts) 594 return len(annotationNode.slice.value.elts)
549 except AttributeError: 595 except AttributeError:
550 return 0 596 return 0
551 return 0 597 return 0
552 598
553 ####################################################################### 599 #######################################################################
554 ## 'from __future__ import annotations' checck 600 ## 'from __future__ import annotations' checck
555 ## 601 ##
556 ## adapted from: flake8-future-annotations v0.0.4 602 ## adapted from: flake8-future-annotations v0.0.4
557 ####################################################################### 603 #######################################################################
558 604
559 def __checkAnnotationsFuture(self): 605 def __checkAnnotationsFuture(self):
560 """ 606 """
561 Private method to check the use of __future__ and typing imports. 607 Private method to check the use of __future__ and typing imports.
562 """ 608 """
563 from .AnnotationsFutureVisitor import AnnotationsFutureVisitor 609 from .AnnotationsFutureVisitor import AnnotationsFutureVisitor
610
564 visitor = AnnotationsFutureVisitor() 611 visitor = AnnotationsFutureVisitor()
565 visitor.visit(self.__tree) 612 visitor.visit(self.__tree)
566 613
567 if ( 614 if visitor.importsFutureAnnotations() or not visitor.hasTypingImports():
568 visitor.importsFutureAnnotations() or
569 not visitor.hasTypingImports()
570 ):
571 return 615 return
572 616
573 imports = ", ".join(visitor.getTypingImports()) 617 imports = ", ".join(visitor.getTypingImports())
574 self.__error(0, 0, "A871", imports) 618 self.__error(0, 0, "A871", imports)

eric ide

mercurial