|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a node visitor for function type annotations. |
|
8 """ |
|
9 |
|
10 # |
|
11 # The visitor and associated classes are adapted from flake8-annotations v2.6.2 |
|
12 # |
|
13 |
|
14 import ast |
|
15 import itertools |
|
16 |
|
17 from .AnnotationsEnums import AnnotationType, ClassDecoratorType, FunctionType |
|
18 |
|
19 # The order of AST_ARG_TYPES must match Python's grammar |
|
20 AST_ARG_TYPES = ("posonlyargs", "args", "vararg", "kwonlyargs", "kwarg") |
|
21 |
|
22 |
|
23 class Argument: |
|
24 """ |
|
25 Class representing a function argument. |
|
26 """ |
|
27 def __init__(self, argname, lineno, col_offset, annotationType, |
|
28 hasTypeAnnotation=False, has3107Annotation=False, |
|
29 hasTypeComment=False): |
|
30 """ |
|
31 Constructor |
|
32 |
|
33 @param argname name of the argument |
|
34 @type str |
|
35 @param lineno line number |
|
36 @type int |
|
37 @param col_offset column number |
|
38 @type int |
|
39 @param annotationType type of annotation |
|
40 @type AnnotationType |
|
41 @param hasTypeAnnotation flag indicating the presence of a type |
|
42 annotation (defaults to False) |
|
43 @type bool (optional) |
|
44 @param has3107Annotation flag indicating the presence of a PEP 3107 |
|
45 annotation (defaults to False) |
|
46 @type bool (optional) |
|
47 @param hasTypeComment flag indicating the presence of a type comment |
|
48 (defaults to False) |
|
49 @type bool (optional) |
|
50 """ |
|
51 self.argname = argname |
|
52 self.lineno = lineno |
|
53 self.col_offset = col_offset |
|
54 self.annotationType = annotationType |
|
55 self.hasTypeAnnotation = hasTypeAnnotation |
|
56 self.has3107Annotation = has3107Annotation |
|
57 self.hasTypeComment = hasTypeComment |
|
58 |
|
59 @classmethod |
|
60 def fromNode(cls, node, annotationTypeName): |
|
61 """ |
|
62 Class method to create an Argument object based on the given node. |
|
63 |
|
64 @param node reference to the node to be converted |
|
65 @type ast.arguments |
|
66 @param annotationTypeName name of the annotation type |
|
67 @type str |
|
68 @return Argument object |
|
69 @rtype Argument |
|
70 """ |
|
71 annotationType = AnnotationType[annotationTypeName] |
|
72 newArg = cls(node.arg, node.lineno, node.col_offset, annotationType) |
|
73 |
|
74 newArg.hasTypeAnnotation = False |
|
75 if node.annotation: |
|
76 newArg.hasTypeAnnotation = True |
|
77 newArg.has3107Annotation = True |
|
78 |
|
79 if node.type_comment: |
|
80 newArg.hasTypeAnnotation = True |
|
81 newArg.hasTypeComment = True |
|
82 |
|
83 return newArg |
|
84 |
|
85 |
|
86 class Function: |
|
87 """ |
|
88 Class representing a function. |
|
89 """ |
|
90 def __init__(self, name, lineno, col_offset, |
|
91 functionType=FunctionType.PUBLIC, isClassMethod=False, |
|
92 classDecoratorType=None, isReturnAnnotated=False, |
|
93 hasTypeComment=False, hasOnlyNoneReturns=True, |
|
94 isNested=False, decoratorList=None, args=None): |
|
95 """ |
|
96 Constructor |
|
97 |
|
98 @param name name of the function |
|
99 @type str |
|
100 @param lineno line number |
|
101 @type int |
|
102 @param col_offset column number |
|
103 @type int |
|
104 @param functionType type of the function (defaults to |
|
105 FunctionType.PUBLIC) |
|
106 @type FunctionType (optional) |
|
107 @param isClassMethod flag indicating a class method (defaults to False) |
|
108 @type bool (optional) |
|
109 @param classDecoratorType type of a function decorator |
|
110 (defaults to None) |
|
111 @type ClassDecoratorType or None (optional) |
|
112 @param isReturnAnnotated flag indicating the presence of a return |
|
113 type annotation (defaults to False) |
|
114 @type bool (optional) |
|
115 @param hasTypeComment flag indicating the presence of a type comment |
|
116 (defaults to False) |
|
117 @type bool (optional) |
|
118 @param hasOnlyNoneReturns flag indicating only None return values |
|
119 (defaults to True) |
|
120 @type bool (optional) |
|
121 @param isNested flag indicating a nested function (defaults to False) |
|
122 @type bool (optional) |
|
123 @param decoratorList list of decorator nodes (defaults to None) |
|
124 @type list of ast.Attribute, ast.Call or ast.Name (optional) |
|
125 @param args list of arguments (defaults to None) |
|
126 @type list of Argument (optional) |
|
127 """ |
|
128 self.name = name |
|
129 self.lineno = lineno |
|
130 self.col_offset = col_offset |
|
131 self.functionType = functionType |
|
132 self.isClassMethod = isClassMethod |
|
133 self.classDecoratorType = classDecoratorType |
|
134 self.isReturnAnnotated = isReturnAnnotated |
|
135 self.hasTypeComment = hasTypeComment |
|
136 self.hasOnlyNoneReturns = hasOnlyNoneReturns |
|
137 self.isNested = isNested |
|
138 self.decoratorList = decoratorList |
|
139 self.args = args |
|
140 |
|
141 def isFullyAnnotated(self): |
|
142 """ |
|
143 Public method to check, if the function definition is fully type |
|
144 annotated. |
|
145 |
|
146 Note: self.args will always include an Argument object for return. |
|
147 |
|
148 @return flag indicating a fully annotated function definition |
|
149 @rtype bool |
|
150 """ |
|
151 return all(arg.hasTypeAnnotation for arg in self.args) |
|
152 |
|
153 def isDynamicallyTyped(self): |
|
154 """ |
|
155 Public method to check, if a function definition is dynamically typed |
|
156 (i.e. completely lacking hints). |
|
157 |
|
158 @return flag indicating a dynamically typed function definition |
|
159 @rtype bool |
|
160 """ |
|
161 return not any(arg.hasTypeAnnotation for arg in self.args) |
|
162 |
|
163 def getMissedAnnotations(self): |
|
164 """ |
|
165 Public method to provide a list of arguments with missing type |
|
166 annotations. |
|
167 |
|
168 @return list of arguments with missing type annotations |
|
169 @rtype list of Argument |
|
170 """ |
|
171 return [arg for arg in self.args if not arg.hasTypeAnnotation] |
|
172 |
|
173 def getAnnotatedArguments(self): |
|
174 """ |
|
175 Public method to get list of arguments with type annotations. |
|
176 |
|
177 @return list of arguments with type annotations. |
|
178 @rtype list of Argument |
|
179 """ |
|
180 return [arg for arg in self.args if arg.hasTypeAnnotation] |
|
181 |
|
182 def hasDecorator(self, checkDecorators): |
|
183 """ |
|
184 Public method to check whether the function node is decorated by any of |
|
185 the provided decorators. |
|
186 |
|
187 Decorator matching is done against the provided `checkDecorators` set. |
|
188 Decorators are assumed to be either a module attribute (e.g. |
|
189 `@typing.overload`) or name (e.g. `@overload`). For the case of a |
|
190 module attribute, only the attribute is checked against |
|
191 `overload_decorators`. |
|
192 |
|
193 Note: Deeper decorator imports (e.g. `a.b.overload`) are not explicitly |
|
194 supported. |
|
195 |
|
196 @param checkDecorators set of decorators to check against |
|
197 @type set of str |
|
198 @return flag indicating the presence of any decorators |
|
199 @rtype bool |
|
200 """ |
|
201 for decorator in self.decoratorList: |
|
202 # Drop to a helper to allow for simpler handling of callable |
|
203 # decorators |
|
204 return self.__decoratorChecker(decorator, checkDecorators) |
|
205 else: |
|
206 return False |
|
207 |
|
208 def __decoratorChecker(self, decorator, checkDecorators): |
|
209 """ |
|
210 Private method to check the provided decorator for a match against the |
|
211 provided set of check names. |
|
212 |
|
213 Decorators are assumed to be of the following form: |
|
214 * `a.name` or `a.name()` |
|
215 * `name` or `name()` |
|
216 |
|
217 Note: Deeper imports (e.g. `a.b.name`) are not explicitly supported. |
|
218 |
|
219 @param decorator decorator node to check |
|
220 @type ast.Attribute, ast.Call or ast.Name |
|
221 @param checkDecorators set of decorators to check against |
|
222 @type set of str |
|
223 @return flag indicating the presence of any decorators |
|
224 @rtype bool |
|
225 """ |
|
226 if isinstance(decorator, ast.Name): |
|
227 # e.g. `@overload`, where `decorator.id` will be the name |
|
228 if decorator.id in checkDecorators: |
|
229 return True |
|
230 elif isinstance(decorator, ast.Attribute): |
|
231 # e.g. `@typing.overload`, where `decorator.attr` will be the name |
|
232 if decorator.attr in checkDecorators: |
|
233 return True |
|
234 elif isinstance(decorator, ast.Call): |
|
235 # e.g. `@overload()` or `@typing.overload()`, where |
|
236 # `decorator.func` will be `ast.Name` or `ast.Attribute`, |
|
237 # which we can check recursively |
|
238 return self.__decoratorChecker(decorator.func, checkDecorators) |
|
239 |
|
240 return None |
|
241 |
|
242 @classmethod |
|
243 def fromNode(cls, node, lines, **kwargs): |
|
244 """ |
|
245 Class method to create a Function object from ast.FunctionDef or |
|
246 ast.AsyncFunctionDef nodes. |
|
247 |
|
248 Accept the source code, as a list of strings, in order to get the |
|
249 column where the function definition ends. |
|
250 |
|
251 With exceptions, input kwargs are passed straight through to Function's |
|
252 __init__. The following kwargs will be overridden: |
|
253 * function_type |
|
254 * class_decorator_type |
|
255 * args |
|
256 |
|
257 @param node reference to the function definition node |
|
258 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
259 @param lines list of source code lines |
|
260 @type list of str |
|
261 @keyparam **kwargs keyword arguments |
|
262 @type dict |
|
263 @return created Function object |
|
264 @rtype Function |
|
265 """ |
|
266 # Extract function types from function name |
|
267 kwargs["functionType"] = cls.getFunctionType(node.name) |
|
268 |
|
269 # Identify type of class method, if applicable |
|
270 if kwargs.get("isClassMethod", False): |
|
271 kwargs["classDecoratorType"] = cls.getClassDecoratorType(node) |
|
272 |
|
273 # Store raw decorator list for use by property methods |
|
274 kwargs["decoratorList"] = node.decorator_list |
|
275 |
|
276 newFunction = cls(node.name, node.lineno, node.col_offset, **kwargs) |
|
277 |
|
278 # Iterate over arguments by type & add |
|
279 newFunction.args = [] |
|
280 for argType in AST_ARG_TYPES: |
|
281 args = node.args.__getattribute__(argType) |
|
282 if args: |
|
283 if not isinstance(args, list): |
|
284 args = [args] |
|
285 |
|
286 newFunction.args.extend( |
|
287 [Argument.fromNode(arg, argType.upper()) |
|
288 for arg in args] |
|
289 ) |
|
290 |
|
291 # Create an Argument object for the return hint |
|
292 defEndLineno, defEndColOffset = cls.colonSeeker(node, lines) |
|
293 returnArg = Argument("return", defEndLineno, defEndColOffset, |
|
294 AnnotationType.RETURN) |
|
295 if node.returns: |
|
296 returnArg.hasTypeAnnotation = True |
|
297 returnArg.has3107Annotation = True |
|
298 newFunction.isReturnAnnotated = True |
|
299 |
|
300 newFunction.args.append(returnArg) |
|
301 |
|
302 # Type comments in-line with input arguments are handled by the |
|
303 # Argument class. If a function-level type comment is present, attempt |
|
304 # to parse for any missed type hints. |
|
305 if node.type_comment: |
|
306 newFunction.hasTypeComment = True |
|
307 newFunction = cls.tryTypeComment(newFunction, node) |
|
308 |
|
309 # Check for the presence of non-`None` returns using the special-case |
|
310 # return node visitor. |
|
311 returnVisitor = ReturnVisitor(node) |
|
312 returnVisitor.visit(node) |
|
313 newFunction.hasOnlyNoneReturns = returnVisitor.hasOnlyNoneReturns |
|
314 |
|
315 return newFunction |
|
316 |
|
317 @staticmethod |
|
318 def colonSeeker(node, lines): |
|
319 """ |
|
320 Static method to find the line & column indices of the function |
|
321 definition's closing colon. |
|
322 |
|
323 @param node reference to the function definition node |
|
324 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
325 @param lines list of source code lines |
|
326 @type list of str |
|
327 @return line and column offset of the colon |
|
328 @rtype tuple of (int, int) |
|
329 """ |
|
330 # Special case single line function definitions |
|
331 if node.lineno == node.body[0].lineno: |
|
332 return Function._singleLineColonSeeker( |
|
333 node, lines[node.lineno - 1]) |
|
334 |
|
335 defEndLineno = node.body[0].lineno - 1 |
|
336 |
|
337 # Use str.rfind() to account for annotations on the same line, |
|
338 # definition closure should be the last : on the line |
|
339 defEndColOffset = lines[defEndLineno - 1].rfind(":") |
|
340 |
|
341 return defEndLineno, defEndColOffset |
|
342 |
|
343 @staticmethod |
|
344 def _singleLineColonSeeker(node, line): |
|
345 """ |
|
346 Static method to find the line & column indices of a single line |
|
347 function definition. |
|
348 |
|
349 @param node reference to the function definition node |
|
350 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
351 @param line source code line |
|
352 @type str |
|
353 @return line and column offset of the colon |
|
354 @rtype tuple of (int, int) |
|
355 """ |
|
356 colStart = node.col_offset |
|
357 colEnd = node.body[0].col_offset |
|
358 defEndColOffset = line.rfind(":", colStart, colEnd) |
|
359 |
|
360 return node.lineno, defEndColOffset |
|
361 |
|
362 @staticmethod |
|
363 def tryTypeComment(funcObj, node): |
|
364 """ |
|
365 Static method to infer type hints from a function-level type comment. |
|
366 |
|
367 If a function is type commented it is assumed to have a return |
|
368 annotation, otherwise Python will fail to parse the hint. |
|
369 |
|
370 @param funcObj reference to the Function object |
|
371 @type Function |
|
372 @param node reference to the function definition node |
|
373 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
374 @return reference to the modified Function object |
|
375 @rtype Function |
|
376 """ |
|
377 hintTree = ast.parse(node.type_comment, "<func_type>", "func_type") |
|
378 hintTree = Function._maybeInjectClassArgument(hintTree, funcObj) |
|
379 |
|
380 for arg, hintComment in itertools.zip_longest( |
|
381 funcObj.args, hintTree.argtypes |
|
382 ): |
|
383 if isinstance(hintComment, ast.Ellipsis): |
|
384 continue |
|
385 |
|
386 if arg and hintComment: |
|
387 arg.hasTypeAnnotation = True |
|
388 arg.hasTypeComment = True |
|
389 |
|
390 # Return arg is always last |
|
391 funcObj.args[-1].hasTypeAnnotation = True |
|
392 funcObj.args[-1].hasTypeComment = True |
|
393 funcObj.isReturnAnnotated = True |
|
394 |
|
395 return funcObj |
|
396 |
|
397 @staticmethod |
|
398 def _maybeInjectClassArgument(hintTree, funcObj): |
|
399 """ |
|
400 Static method to inject `self` or `cls` args into a type comment to |
|
401 align with PEP 3107-style annotations. |
|
402 |
|
403 Because PEP 484 does not describe a method to provide partial function- |
|
404 level type comments, there is a potential for ambiguity in the context |
|
405 of both class methods and classmethods when aligning type comments to |
|
406 method arguments. |
|
407 |
|
408 These two class methods, for example, should lint equivalently: |
|
409 |
|
410 def bar(self, a): |
|
411 # type: (int) -> int |
|
412 ... |
|
413 |
|
414 def bar(self, a: int) -> int |
|
415 ... |
|
416 |
|
417 When this example type comment is parsed by `ast` and then matched with |
|
418 the method's arguments, it associates the `int` hint to `self` rather |
|
419 than `a`, so a dummy hint needs to be provided in situations where |
|
420 `self` or `class` are not hinted in the type comment in order to |
|
421 achieve equivalent linting results to PEP-3107 style annotations. |
|
422 |
|
423 A dummy `ast.Ellipses` constant is injected if the following criteria |
|
424 are met: |
|
425 1. The function node is either a class method or classmethod |
|
426 2. The number of hinted args is at least 1 less than the number |
|
427 of function args |
|
428 |
|
429 @param hintTree parsed type hint node |
|
430 @type ast.FunctionType |
|
431 @param funcObj reference to the Function object |
|
432 @type Function |
|
433 @return reference to the hint node |
|
434 @rtype ast.FunctionType |
|
435 """ |
|
436 if not funcObj.isClassMethod: |
|
437 # Short circuit |
|
438 return hintTree |
|
439 |
|
440 if ( |
|
441 funcObj.classDecoratorType != ClassDecoratorType.STATICMETHOD and |
|
442 len(hintTree.argtypes) < (len(funcObj.args) - 1) |
|
443 ): |
|
444 # Subtract 1 to skip return arg |
|
445 hintTree.argtypes = [ast.Ellipsis()] + hintTree.argtypes |
|
446 |
|
447 return hintTree |
|
448 |
|
449 @staticmethod |
|
450 def getFunctionType(functionName): |
|
451 """ |
|
452 Static method to determine the function's FunctionType from its name. |
|
453 |
|
454 MethodType is determined by the following priority: |
|
455 1. Special: function name prefixed & suffixed by "__" |
|
456 2. Private: function name prefixed by "__" |
|
457 3. Protected: function name prefixed by "_" |
|
458 4. Public: everything else |
|
459 |
|
460 @param functionName function name to be checked |
|
461 @type str |
|
462 @return type of function |
|
463 @rtype FunctionType |
|
464 """ |
|
465 if functionName.startswith("__") and functionName.endswith("__"): |
|
466 return FunctionType.SPECIAL |
|
467 elif functionName.startswith("__"): |
|
468 return FunctionType.PRIVATE |
|
469 elif functionName.startswith("_"): |
|
470 return FunctionType.PROTECTED |
|
471 else: |
|
472 return FunctionType.PUBLIC |
|
473 |
|
474 @staticmethod |
|
475 def getClassDecoratorType(functionNode): |
|
476 """ |
|
477 Static method to get the class method's decorator type from its |
|
478 function node. |
|
479 |
|
480 Only @classmethod and @staticmethod decorators are identified; all |
|
481 other decorators are ignored |
|
482 |
|
483 If @classmethod or @staticmethod decorators are not present, this |
|
484 function will return None. |
|
485 |
|
486 @param functionNode reference to the function definition node |
|
487 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
488 @return class decorator type |
|
489 @rtype ClassDecoratorType or None |
|
490 """ |
|
491 # @classmethod and @staticmethod will show up as ast.Name objects, |
|
492 # where callable decorators will show up as ast.Call, which we can |
|
493 # ignore |
|
494 decorators = [ |
|
495 decorator.id |
|
496 for decorator in functionNode.decorator_list |
|
497 if isinstance(decorator, ast.Name) |
|
498 ] |
|
499 |
|
500 if "classmethod" in decorators: |
|
501 return ClassDecoratorType.CLASSMETHOD |
|
502 elif "staticmethod" in decorators: |
|
503 return ClassDecoratorType.STATICMETHOD |
|
504 else: |
|
505 return None |
|
506 |
|
507 |
|
508 class FunctionVisitor(ast.NodeVisitor): |
|
509 """ |
|
510 Class implementing a node visitor to check function annotations. |
|
511 """ |
|
512 def __init__(self, lines): |
|
513 """ |
|
514 Constructor |
|
515 |
|
516 @param lines source code lines of the function |
|
517 @type list of str |
|
518 """ |
|
519 self.lines = lines |
|
520 self.functionDefinitions = [] |
|
521 self.__context = [] |
|
522 |
|
523 def switchContext(self, node): |
|
524 """ |
|
525 Public method implementing a context switcher as a generic function |
|
526 visitor in order to track function context. |
|
527 |
|
528 Without keeping track of context, it's challenging to reliably |
|
529 differentiate class methods from "regular" functions, especially in the |
|
530 case of nested classes. |
|
531 |
|
532 @param node reference to the function definition node to be analyzed |
|
533 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
534 """ |
|
535 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): |
|
536 # Check for non-empty context first to prevent IndexErrors for |
|
537 # non-nested nodes |
|
538 if self.__context: |
|
539 if isinstance(self.__context[-1], ast.ClassDef): |
|
540 # Check if current context is a ClassDef node & pass the |
|
541 # appropriate flag |
|
542 self.functionDefinitions.append( |
|
543 Function.fromNode(node, self.lines, isClassMethod=True) |
|
544 ) |
|
545 elif isinstance( |
|
546 self.__context[-1], |
|
547 (ast.FunctionDef, ast.AsyncFunctionDef) |
|
548 ): |
|
549 # Check for nested function & pass the appropriate flag |
|
550 self.functionDefinitions.append( |
|
551 Function.fromNode(node, self.lines, isNested=True) |
|
552 ) |
|
553 else: |
|
554 self.functionDefinitions.append( |
|
555 Function.fromNode(node, self.lines)) |
|
556 |
|
557 self.__context.append(node) |
|
558 self.generic_visit(node) |
|
559 self.__context.pop() |
|
560 |
|
561 visit_FunctionDef = switchContext |
|
562 visit_AsyncFunctionDef = switchContext |
|
563 visit_ClassDef = switchContext |
|
564 |
|
565 |
|
566 class ReturnVisitor(ast.NodeVisitor): |
|
567 """ |
|
568 Class implementing a node visitor to check the return statements of a |
|
569 function node. |
|
570 |
|
571 If the function node being visited has an explicit return statement of |
|
572 anything other than `None`, the `instance.hasOnlyNoneReturns` flag will |
|
573 be set to `False`. |
|
574 |
|
575 If the function node being visited has no return statement, or contains |
|
576 only return statement(s) that explicitly return `None`, the |
|
577 `instance.hasOnlyNoneReturns` flag will be set to `True`. |
|
578 |
|
579 Due to the generic visiting being done, we need to keep track of the |
|
580 context in which a non-`None` return node is found. These functions are |
|
581 added to a set that is checked to see whether nor not the parent node is |
|
582 present. |
|
583 """ |
|
584 def __init__(self, parentNode): |
|
585 """ |
|
586 Constructor |
|
587 |
|
588 @param parentNode reference to the function definition node to be |
|
589 analyzed |
|
590 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
591 """ |
|
592 self.parentNode = parentNode |
|
593 self.__context = [] |
|
594 self.__nonNoneReturnNodes = set() |
|
595 |
|
596 @property |
|
597 def hasOnlyNoneReturns(self): |
|
598 """ |
|
599 Public method indicating, that the parent node isn't in the visited |
|
600 nodes that don't return `None`. |
|
601 |
|
602 @return flag indicating, that the parent node isn't in the visited |
|
603 nodes that don't return `None` |
|
604 @rtype bool |
|
605 """ |
|
606 return self.parentNode not in self.__nonNoneReturnNodes |
|
607 |
|
608 def visit_Return(self, node): |
|
609 """ |
|
610 Public method to check each Return node to see if it returns anything |
|
611 other than `None`. |
|
612 |
|
613 If the node being visited returns anything other than `None`, its |
|
614 parent context is added to the set of non-returning child nodes of |
|
615 the parent node. |
|
616 |
|
617 @param node reference to the AST Return node |
|
618 @type ast.Return |
|
619 """ |
|
620 if node.value is not None: |
|
621 # In the event of an explicit `None` return (`return None`), the |
|
622 # node body will be an instance of either `ast.Constant` (3.8+) or |
|
623 # `ast.NameConstant`, which we need to check to see if it's |
|
624 # actually `None` |
|
625 if ( |
|
626 isinstance(node.value, (ast.Constant, ast.NameConstant)) and |
|
627 node.value.value is None |
|
628 ): |
|
629 return |
|
630 |
|
631 self.__nonNoneReturnNodes.add(self.__context[-1]) |
|
632 |
|
633 def switchContext(self, node): |
|
634 """ |
|
635 Public method implementing a context switcher as a generic function |
|
636 visitor in order to track function context. |
|
637 |
|
638 Without keeping track of context, it's challenging to reliably |
|
639 differentiate class methods from "regular" functions, especially in the |
|
640 case of nested classes. |
|
641 |
|
642 @param node reference to the function definition node to be analyzed |
|
643 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
644 """ |
|
645 self.__context.append(node) |
|
646 self.generic_visit(node) |
|
647 self.__context.pop() |
|
648 |
|
649 visit_FunctionDef = switchContext |
|
650 visit_AsyncFunctionDef = switchContext |