174 |
155 |
175 if not self.__checkers: |
156 if not self.__checkers: |
176 # don't do anything, if no codes were selected |
157 # don't do anything, if no codes were selected |
177 return |
158 return |
178 |
159 |
179 try: |
|
180 self.__tree = self.__generateTree() |
|
181 except (SyntaxError, TypeError): |
|
182 self.__reportInvalidSyntax() |
|
183 return |
|
184 |
|
185 for check in self.__checkers: |
160 for check in self.__checkers: |
186 check() |
161 check() |
187 |
162 |
|
163 ####################################################################### |
|
164 ## Annotations |
|
165 ## |
|
166 ## adapted from: flake8-annotations v2.6.2 |
|
167 ####################################################################### |
|
168 |
188 def __checkFunctionAnnotations(self): |
169 def __checkFunctionAnnotations(self): |
189 """ |
170 """ |
190 Private method to check for function annotation issues. |
171 Private method to check for function annotation issues. |
191 """ |
172 """ |
|
173 suppressNoneReturning = self.__args.get( |
|
174 "SuppressNoneReturning", |
|
175 AnnotationsCheckerDefaultArgs["SuppressNoneReturning"]) |
|
176 suppressDummyArgs = self.__args.get( |
|
177 "SuppressDummyArgs", |
|
178 AnnotationsCheckerDefaultArgs["SuppressDummyArgs"]) |
|
179 allowUntypedDefs = self.__args.get( |
|
180 "AllowUntypedDefs", |
|
181 AnnotationsCheckerDefaultArgs["AllowUntypedDefs"]) |
|
182 allowUntypedNested = self.__args.get( |
|
183 "AllowUntypedNested", |
|
184 AnnotationsCheckerDefaultArgs["AllowUntypedNested"]) |
|
185 mypyInitReturn = self.__args.get( |
|
186 "MypyInitReturn", |
|
187 AnnotationsCheckerDefaultArgs["MypyInitReturn"]) |
|
188 |
|
189 # Store decorator lists as sets for easier lookup |
|
190 dispatchDecorators = set(self.__args.get( |
|
191 "DispatchDecorators", |
|
192 AnnotationsCheckerDefaultArgs["DispatchDecorators"])) |
|
193 overloadDecorators = set(self.__args.get( |
|
194 "OverloadDecorators", |
|
195 AnnotationsCheckerDefaultArgs["OverloadDecorators"])) |
|
196 |
|
197 from .AnnotationsFunctionVisitor import FunctionVisitor |
192 visitor = FunctionVisitor(self.__source) |
198 visitor = FunctionVisitor(self.__source) |
193 visitor.visit(self.__tree) |
199 visitor.visit(self.__tree) |
194 for issue in visitor.issues: |
200 |
195 node = issue[0] |
201 # Keep track of the last encountered function decorated by |
196 reason = issue[1] |
202 # `typing.overload`, if any. Per the `typing` module documentation, |
197 params = issue[2:] |
203 # a series of overload-decorated definitions must be followed by |
198 self.__error(node.lineno - 1, node.col_offset, reason, *params) |
204 # exactly one non-overload-decorated definition of the same function. |
|
205 lastOverloadDecoratedFunctionName = None |
|
206 |
|
207 # Iterate over the arguments with missing type hints, by function. |
|
208 for function in visitor.functionDefinitions: |
|
209 if ( |
|
210 function.isDynamicallyTyped() and |
|
211 (allowUntypedDefs or |
|
212 (function.isNested and allowUntypedNested)) |
|
213 ): |
|
214 # Skip recording errors from dynamically typed functions |
|
215 # or nested functions |
|
216 continue |
|
217 |
|
218 # Skip recording errors for configured dispatch functions, such as |
|
219 # (by default) `functools.singledispatch` and |
|
220 # `functools.singledispatchmethod` |
|
221 if function.hasDecorator(dispatchDecorators): |
|
222 continue |
|
223 |
|
224 # Create sentinels to check for mixed hint styles |
|
225 hasTypeComment = function.hasTypeComment |
|
226 |
|
227 has3107Annotation = False |
|
228 # PEP 3107 annotations are captured by the return arg |
|
229 |
|
230 # Iterate over annotated args to detect mixing of type annotations |
|
231 # and type comments. Emit this only once per function definition |
|
232 for arg in function.getAnnotatedArguments(): |
|
233 if arg.hasTypeComment: |
|
234 hasTypeComment = True |
|
235 |
|
236 if arg.has3107Annotation: |
|
237 has3107Annotation = True |
|
238 |
|
239 if hasTypeComment and has3107Annotation: |
|
240 # Short-circuit check for mixing of type comments & |
|
241 # 3107-style annotations |
|
242 self.__error(function.lineno - 1, function.col_offset, |
|
243 "A301") |
|
244 break |
|
245 |
|
246 # Before we iterate over the function's missing annotations, check |
|
247 # to see if it's the closing function def in a series of |
|
248 # `typing.overload` decorated functions. |
|
249 if lastOverloadDecoratedFunctionName == function.name: |
|
250 continue |
|
251 |
|
252 # If it's not, and it is overload decorated, store it for the next |
|
253 # iteration |
|
254 if function.hasDecorator(overloadDecorators): |
|
255 lastOverloadDecoratedFunctionName = function.name |
|
256 |
|
257 # Record explicit errors for arguments that are missing annotations |
|
258 for arg in function.getMissedAnnotations(): |
|
259 if arg.argname == "return": |
|
260 # return annotations have multiple possible short-circuit |
|
261 # paths |
|
262 if ( |
|
263 suppressNoneReturning and |
|
264 not arg.hasTypeAnnotation and |
|
265 function.hasOnlyNoneReturns |
|
266 ): |
|
267 # Skip recording return errors if the function has only |
|
268 # `None` returns. This includes the case of no returns. |
|
269 continue |
|
270 |
|
271 if ( |
|
272 mypyInitReturn and |
|
273 function.isClassMethod and |
|
274 function.name == "__init__" and |
|
275 function.getAnnotatedArguments() |
|
276 ): |
|
277 # Skip recording return errors for `__init__` if at |
|
278 # least one argument is annotated |
|
279 continue |
|
280 |
|
281 # If the `suppressDummyArgs` flag is `True`, skip recording |
|
282 # errors for any arguments named `_` |
|
283 if arg.argname == "_" and suppressDummyArgs: |
|
284 continue |
|
285 |
|
286 self.__classifyError(function, arg) |
|
287 |
|
288 def __classifyError(self, function, arg): |
|
289 """ |
|
290 Private method to classify the missing type annotation based on the |
|
291 Function & Argument metadata. |
|
292 |
|
293 For the currently defined rules & program flow, the assumption can be |
|
294 made that an argument passed to this method will match a linting error, |
|
295 and will only match a single linting error |
|
296 |
|
297 This function provides an initial classificaton, then passes relevant |
|
298 attributes to cached helper function(s). |
|
299 |
|
300 @param function reference to the Function object |
|
301 @type Function |
|
302 @param arg reference to the Argument object |
|
303 @type Argument |
|
304 """ |
|
305 # Check for return type |
|
306 # All return "arguments" have an explicitly defined name "return" |
|
307 if arg.argname == "return": |
|
308 errorCode = self.__returnErrorClassifier( |
|
309 function.isClassMethod, function.classDecoratorType, |
|
310 function.functionType |
|
311 ) |
|
312 else: |
|
313 # Otherwise, classify function argument error |
|
314 isFirstArg = arg == function.args[0] |
|
315 errorCode = self.__argumentErrorClassifier( |
|
316 function.isClassMethod, isFirstArg, |
|
317 function.classDecoratorType, arg.annotationType, |
|
318 ) |
|
319 |
|
320 if errorCode in ("A001", "A002", "A003"): |
|
321 self.__error(arg.lineno - 1, arg.col_offset, errorCode, |
|
322 arg.argname) |
|
323 else: |
|
324 self.__error(arg.lineno - 1, arg.col_offset, errorCode) |
|
325 |
|
326 @lru_cache() |
|
327 def __returnErrorClassifier(self, isClassMethod, classDecoratorType, |
|
328 functionType): |
|
329 """ |
|
330 Private method to classify a return type annotation issue. |
|
331 |
|
332 @param isClassMethod flag indicating a classmethod type function |
|
333 @type bool |
|
334 @param classDecoratorType type of class decorator |
|
335 @type ClassDecoratorType |
|
336 @param functionType type of function |
|
337 @type FunctionType |
|
338 @return error code |
|
339 @rtype str |
|
340 """ |
|
341 # Decorated class methods (@classmethod, @staticmethod) have a higher |
|
342 # priority than the rest |
|
343 if isClassMethod: |
|
344 if classDecoratorType == ClassDecoratorType.CLASSMETHOD: |
|
345 return "A206" |
|
346 elif classDecoratorType == ClassDecoratorType.STATICMETHOD: |
|
347 return "A205" |
|
348 |
|
349 if functionType == FunctionType.SPECIAL: |
|
350 return "A204" |
|
351 elif functionType == FunctionType.PRIVATE: |
|
352 return "A203" |
|
353 elif functionType == FunctionType.PROTECTED: |
|
354 return "A202" |
|
355 else: |
|
356 return "A201" |
|
357 |
|
358 @lru_cache() |
|
359 def __argumentErrorClassifier(self, isClassMethod, isFirstArg, |
|
360 classDecoratorType, annotationType): |
|
361 """ |
|
362 Private method to classify an argument type annotation issue. |
|
363 |
|
364 @param isClassMethod flag indicating a classmethod type function |
|
365 @type bool |
|
366 @param isFirstArg flag indicating the first argument |
|
367 @type bool |
|
368 @param classDecoratorType type of class decorator |
|
369 @type enums.ClassDecoratorType |
|
370 @param annotationType type of annotation |
|
371 @type AnnotationType |
|
372 @return error code |
|
373 @rtype str |
|
374 """ |
|
375 # Check for regular class methods and @classmethod, @staticmethod is |
|
376 # deferred to final check |
|
377 if isClassMethod and isFirstArg: |
|
378 # The first function argument here would be an instance of self or |
|
379 # class |
|
380 if classDecoratorType == ClassDecoratorType.CLASSMETHOD: |
|
381 return "A102" |
|
382 elif classDecoratorType != ClassDecoratorType.STATICMETHOD: |
|
383 # Regular class method |
|
384 return "A101" |
|
385 |
|
386 # Check for remaining codes |
|
387 if annotationType == AnnotationType.KWARG: |
|
388 return "A003" |
|
389 elif annotationType == AnnotationType.VARARG: |
|
390 return "A002" |
|
391 else: |
|
392 # Combine PosOnlyArgs, Args, and KwOnlyArgs |
|
393 return "A001" |
|
394 |
|
395 ####################################################################### |
|
396 ## Annotations Coverage |
|
397 ## |
|
398 ## adapted from: flake8-annotations-coverage v0.0.5 |
|
399 ####################################################################### |
199 |
400 |
200 def __checkAnnotationsCoverage(self): |
401 def __checkAnnotationsCoverage(self): |
201 """ |
402 """ |
202 Private method to check for function annotation coverage. |
403 Private method to check for function annotation coverage. |
203 """ |
404 """ |
204 minAnnotationsCoverage = self.__args.get( |
405 minAnnotationsCoverage = self.__args.get( |
205 "MinimumCoverage", self.__defaultArgs["MinimumCoverage"]) |
406 "MinimumCoverage", |
|
407 AnnotationsCheckerDefaultArgs["MinimumCoverage"]) |
206 if minAnnotationsCoverage == 0: |
408 if minAnnotationsCoverage == 0: |
207 # 0 means it is switched off |
409 # 0 means it is switched off |
208 return |
410 return |
209 |
411 |
210 functionDefs = [ |
412 functionDefs = [ |
214 if not functionDefs: |
416 if not functionDefs: |
215 # no functions/methods at all |
417 # no functions/methods at all |
216 return |
418 return |
217 |
419 |
218 functionDefAnnotationsInfo = [ |
420 functionDefAnnotationsInfo = [ |
219 hasTypeAnnotations(f) for f in functionDefs |
421 self.__hasTypeAnnotations(f) for f in functionDefs |
220 ] |
422 ] |
221 annotationsCoverage = int( |
423 annotationsCoverage = int( |
222 len(list(filter(None, functionDefAnnotationsInfo))) / |
424 len(list(filter(None, functionDefAnnotationsInfo))) / |
223 len(functionDefAnnotationsInfo) * 100 |
425 len(functionDefAnnotationsInfo) * 100 |
224 ) |
426 ) |
225 if annotationsCoverage < minAnnotationsCoverage: |
427 if annotationsCoverage < minAnnotationsCoverage: |
226 self.__error(0, 0, "A881", annotationsCoverage) |
428 self.__error(0, 0, "A881", annotationsCoverage) |
227 |
429 |
|
430 def __hasTypeAnnotations(self, funcNode): |
|
431 """ |
|
432 Private method to check for type annotations. |
|
433 |
|
434 @param funcNode reference to the function definition node to be checked |
|
435 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
436 @return flag indicating the presence of type annotations |
|
437 @rtype bool |
|
438 """ |
|
439 hasReturnAnnotation = funcNode.returns is not None |
|
440 hasArgsAnnotations = any(a for a in funcNode.args.args |
|
441 if a.annotation is not None) |
|
442 hasKwargsAnnotations = (funcNode.args and |
|
443 funcNode.args.kwarg and |
|
444 funcNode.args.kwarg.annotation is not None) |
|
445 hasKwonlyargsAnnotations = any(a for a in funcNode.args.kwonlyargs |
|
446 if a.annotation is not None) |
|
447 |
|
448 return any((hasReturnAnnotation, hasArgsAnnotations, |
|
449 hasKwargsAnnotations, hasKwonlyargsAnnotations)) |
|
450 |
|
451 ####################################################################### |
|
452 ## Annotations Complexity |
|
453 ## |
|
454 ## adapted from: flake8-annotations-complexity v0.0.6 |
|
455 ####################################################################### |
|
456 |
228 def __checkAnnotationComplexity(self): |
457 def __checkAnnotationComplexity(self): |
229 """ |
458 """ |
230 Private method to check the type annotation complexity. |
459 Private method to check the type annotation complexity. |
231 """ |
460 """ |
232 maxAnnotationComplexity = self.__args.get( |
461 maxAnnotationComplexity = self.__args.get( |
233 "MaximumComplexity", self.__defaultArgs["MaximumComplexity"]) |
462 "MaximumComplexity", |
|
463 AnnotationsCheckerDefaultArgs["MaximumComplexity"]) |
|
464 maxAnnotationLength = self.__args.get( |
|
465 "MaximumLength", AnnotationsCheckerDefaultArgs["MaximumLength"]) |
234 typeAnnotations = [] |
466 typeAnnotations = [] |
235 |
467 |
236 functionDefs = [ |
468 functionDefs = [ |
237 f for f in ast.walk(self.__tree) |
469 f for f in ast.walk(self.__tree) |
238 if isinstance(f, (ast.AsyncFunctionDef, ast.FunctionDef)) |
470 if isinstance(f, (ast.AsyncFunctionDef, ast.FunctionDef)) |
243 if functionDef.returns: |
475 if functionDef.returns: |
244 typeAnnotations.append(functionDef.returns) |
476 typeAnnotations.append(functionDef.returns) |
245 typeAnnotations += [a.annotation for a in ast.walk(self.__tree) |
477 typeAnnotations += [a.annotation for a in ast.walk(self.__tree) |
246 if isinstance(a, ast.AnnAssign) and a.annotation] |
478 if isinstance(a, ast.AnnAssign) and a.annotation] |
247 for annotation in typeAnnotations: |
479 for annotation in typeAnnotations: |
248 complexity = getAnnotationComplexity(annotation) |
480 complexity = self.__getAnnotationComplexity(annotation) |
249 if complexity > maxAnnotationComplexity: |
481 if complexity > maxAnnotationComplexity: |
250 self.__error(annotation.lineno - 1, annotation.col_offset, |
482 self.__error(annotation.lineno - 1, annotation.col_offset, |
251 "A891", complexity, maxAnnotationComplexity) |
483 "A891", complexity, maxAnnotationComplexity) |
252 |
484 |
253 |
485 annotationLength = self.__getAnnotationLength(annotation) |
254 class FunctionVisitor(ast.NodeVisitor): |
486 if annotationLength > maxAnnotationLength: |
255 """ |
487 self.__error(annotation.lineno - 1, annotation.col_offset, |
256 Class implementing a node visitor to check function annotations. |
488 "A892", annotationLength, maxAnnotationLength) |
257 |
489 |
258 Note: this class is modelled after flake8-annotations checker. |
490 def __getAnnotationComplexity(self, annotationNode, defaultComplexity=1): |
259 """ |
491 """ |
260 def __init__(self, sourceLines): |
492 Private method to determine the annotation complexity. |
261 """ |
493 |
262 Constructor |
494 @param annotationNode reference to the node to determine the annotation |
263 |
495 complexity for |
264 @param sourceLines lines of source code |
496 @type ast.AST |
265 @type list of str |
497 @param defaultComplexity default complexity value |
266 """ |
498 @type int |
267 super(FunctionVisitor, self).__init__() |
499 @return annotation complexity |
268 |
500 @rtype = int |
269 self.__sourceLines = sourceLines |
501 """ |
270 |
502 if AstUtilities.isString(annotationNode): |
271 self.issues = [] |
503 try: |
272 |
504 annotationNode = ast.parse(annotationNode.s).body[0].value |
273 def visit_FunctionDef(self, node): |
505 except (SyntaxError, IndexError): |
274 """ |
506 return defaultComplexity |
275 Public method to handle a function or method definition. |
507 if isinstance(annotationNode, ast.Subscript): |
276 |
508 if sys.version_info >= (3, 9): |
277 @param node reference to the node to be processed |
509 return (defaultComplexity + |
278 @type ast.FunctionDef |
510 self.__getAnnotationComplexity(annotationNode.slice)) |
279 """ |
|
280 self.__checkFunctionNode(node) |
|
281 self.generic_visit(node) |
|
282 |
|
283 def visit_AsyncFunctionDef(self, node): |
|
284 """ |
|
285 Public method to handle an async function or method definition. |
|
286 |
|
287 @param node reference to the node to be processed |
|
288 @type ast.AsyncFunctionDef |
|
289 """ |
|
290 self.__checkFunctionNode(node) |
|
291 self.generic_visit(node) |
|
292 |
|
293 def visit_ClassDef(self, node): |
|
294 """ |
|
295 Public method to handle class definitions. |
|
296 |
|
297 @param node reference to the node to be processed |
|
298 @type ast.ClassDef |
|
299 """ |
|
300 methodNodes = [ |
|
301 childNode for childNode in node.body |
|
302 if isinstance(childNode, (ast.FunctionDef, ast.AsyncFunctionDef)) |
|
303 ] |
|
304 for methodNode in methodNodes: |
|
305 self.__checkFunctionNode(methodNode, classMethod=True) |
|
306 |
|
307 def __checkFunctionNode(self, node, classMethod=False): |
|
308 """ |
|
309 Private method to check an individual function definition node. |
|
310 |
|
311 @param node reference to the node to be processed |
|
312 @type ast.FunctionDef or ast.AsyncFunctionDef |
|
313 @param classMethod flag indicating a class method |
|
314 @type bool |
|
315 """ |
|
316 if node.name.startswith("__") and node.name.endswith("__"): |
|
317 visibilityType = "special" |
|
318 elif node.name.startswith("__"): |
|
319 visibilityType = "private" |
|
320 elif node.name.startswith("_"): |
|
321 visibilityType = "protected" |
|
322 else: |
|
323 visibilityType = "public" |
|
324 |
|
325 if classMethod: |
|
326 decorators = [ |
|
327 decorator.id for decorator in node.decorator_list |
|
328 if isinstance(decorator, ast.Name) |
|
329 ] |
|
330 if "classmethod" in decorators: |
|
331 classMethodType = "decorator" |
|
332 elif "staticmethod" in decorators: |
|
333 classMethodType = "staticmethod" |
|
334 else: |
511 else: |
335 classMethodType = "" |
512 return ( |
336 else: |
513 defaultComplexity + |
337 classMethodType = "function" |
514 self.__getAnnotationComplexity(annotationNode.slice.value) |
338 |
515 ) |
339 # check argument annotations |
516 if isinstance(annotationNode, ast.Tuple): |
340 for argType in ("args", "vararg", "kwonlyargs", "kwarg"): |
517 return max( |
341 args = node.args.__getattribute__(argType) |
518 (self.__getAnnotationComplexity(n) |
342 if args: |
519 for n in annotationNode.elts), |
343 if not isinstance(args, list): |
520 default=defaultComplexity |
344 args = [args] |
521 ) |
345 |
522 return defaultComplexity |
346 for arg in args: |
523 |
347 if not arg.annotation: |
524 def __getAnnotationLength(self, annotationNode): |
348 self.__classifyArgumentError( |
525 """ |
349 arg, argType, classMethodType) |
526 Private method to determine the annotation length. |
350 |
527 |
351 # check function return annotation |
528 @param annotationNode reference to the node to determine the annotation |
352 if not node.returns: |
529 length for |
353 lineno = node.lineno |
530 @type ast.AST |
354 colOffset = self.__sourceLines[lineno - 1].rfind(":") + 1 |
531 @return annotation length |
355 self.__classifyReturnError(classMethodType, visibilityType, |
532 @rtype = int |
356 lineno, colOffset) |
533 """ |
357 |
534 if AstUtilities.isString(annotationNode): |
358 def __classifyReturnError(self, methodType, visibilityType, lineno, |
535 try: |
359 colOffset): |
536 annotationNode = ast.parse(annotationNode.s).body[0].value |
360 """ |
537 except (SyntaxError, IndexError): |
361 Private method to classify and record a return annotation issue. |
538 return 0 |
362 |
539 if isinstance(annotationNode, ast.Subscript): |
363 @param methodType type of method/function the argument belongs to |
540 try: |
364 @type str |
541 if sys.version_info >= (3, 9): |
365 @param visibilityType visibility of the function |
542 return len(annotationNode.slice.elts) |
366 @type str |
543 else: |
367 @param lineno line number |
544 return len(annotationNode.slice.value.elts) |
368 @type int |
545 except AttributeError: |
369 @param colOffset column number |
546 return 0 |
370 @type int |
547 return 0 |
371 """ |
|
372 # create a dummy AST node to report line and column |
|
373 node = ast.AST() |
|
374 node.lineno = lineno |
|
375 node.col_offset = colOffset |
|
376 |
|
377 # now classify the issue |
|
378 if methodType == "classmethod": |
|
379 self.issues.append((node, "A206")) |
|
380 elif methodType == "staticmethod": |
|
381 self.issues.append((node, "A205")) |
|
382 elif visibilityType == "special": |
|
383 self.issues.append((node, "A204")) |
|
384 elif visibilityType == "private": |
|
385 self.issues.append((node, "A203")) |
|
386 elif visibilityType == "protected": |
|
387 self.issues.append((node, "A202")) |
|
388 else: |
|
389 self.issues.append((node, "A201")) |
|
390 |
|
391 def __classifyArgumentError(self, argNode, argType, methodType): |
|
392 """ |
|
393 Private method to classify and record an argument annotation issue. |
|
394 |
|
395 @param argNode reference to the argument node |
|
396 @type ast.arguments |
|
397 @param argType type of the argument node |
|
398 @type str |
|
399 @param methodType type of method/function the argument belongs to |
|
400 @type str |
|
401 """ |
|
402 # check class method issues |
|
403 if methodType != "function": |
|
404 if argNode.arg in ("cls", "self"): |
|
405 if methodType == "classmethod": |
|
406 self.issues.append((argNode, "A102")) |
|
407 return |
|
408 elif methodType != "staticmethod": |
|
409 self.issues.append((argNode, "A101")) |
|
410 return |
|
411 |
|
412 # check all other arguments |
|
413 if argType == "kwarg": |
|
414 self.issues.append((argNode, "A003", argNode.arg)) |
|
415 elif argType == "vararg": |
|
416 self.issues.append((argNode, "A002", argNode.arg)) |
|
417 else: |
|
418 # args and kwonlyargs |
|
419 self.issues.append((argNode, "A001", argNode.arg)) |
|
420 |
|
421 ###################################################################### |
|
422 ## some utility functions below |
|
423 ###################################################################### |
|
424 |
|
425 |
|
426 def hasTypeAnnotations(funcNode): |
|
427 """ |
|
428 Function to check for type annotations. |
|
429 |
|
430 @param funcNode reference to the function definition node to be checked |
|
431 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
432 @return flag indicating the presence of type annotations |
|
433 @rtype bool |
|
434 """ |
|
435 hasReturnAnnotation = funcNode.returns is not None |
|
436 hasArgsAnnotations = any(a for a in funcNode.args.args |
|
437 if a.annotation is not None) |
|
438 hasKwargsAnnotations = (funcNode.args and |
|
439 funcNode.args.kwarg and |
|
440 funcNode.args.kwarg.annotation is not None) |
|
441 hasKwonlyargsAnnotations = any(a for a in funcNode.args.kwonlyargs |
|
442 if a.annotation is not None) |
|
443 |
|
444 return any((hasReturnAnnotation, hasArgsAnnotations, hasKwargsAnnotations, |
|
445 hasKwonlyargsAnnotations)) |
|
446 |
|
447 |
|
448 def getAnnotationComplexity(annotationNode): |
|
449 """ |
|
450 Function to determine the annotation complexity. |
|
451 |
|
452 @param annotationNode reference to the node to determine the annotation |
|
453 complexity for |
|
454 @type ast.AST |
|
455 @return annotation complexity |
|
456 @rtype = int |
|
457 """ |
|
458 if AstUtilities.isString(annotationNode): |
|
459 annotationNode = ast.parse(annotationNode.s).body[0].value |
|
460 if isinstance(annotationNode, ast.Subscript): |
|
461 return 1 + getAnnotationComplexity(annotationNode.slice.value) |
|
462 if isinstance(annotationNode, ast.Tuple): |
|
463 return max(getAnnotationComplexity(n) for n in annotationNode.elts) |
|
464 return 1 |
|