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