11 Implement the central Checker class. |
11 Implement the central Checker class. |
12 Also, it models the Bindings and Scopes. |
12 Also, it models the Bindings and Scopes. |
13 """ |
13 """ |
14 import __future__ |
14 import __future__ |
15 import ast |
15 import ast |
|
16 import bisect |
|
17 import collections |
16 import doctest |
18 import doctest |
|
19 import functools |
17 import os |
20 import os |
|
21 import re |
18 import sys |
22 import sys |
|
23 import tokenize |
|
24 |
|
25 from . import messages |
19 |
26 |
20 PY2 = sys.version_info < (3, 0) |
27 PY2 = sys.version_info < (3, 0) |
21 PY34 = sys.version_info < (3, 5) # Python 2.7 to 3.4 |
28 PY35_PLUS = sys.version_info >= (3, 5) # Python 3.5 and above |
|
29 PY36_PLUS = sys.version_info >= (3, 6) # Python 3.6 and above |
|
30 PY38_PLUS = sys.version_info >= (3, 8) |
22 try: |
31 try: |
23 sys.pypy_version_info |
32 sys.pypy_version_info |
24 PYPY = True |
33 PYPY = True |
25 except AttributeError: |
34 except AttributeError: |
26 PYPY = False |
35 PYPY = False |
27 |
36 |
28 builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins')) |
37 builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins')) |
29 |
38 |
30 from . import messages |
39 if PY2: |
31 |
40 tokenize_tokenize = tokenize.generate_tokens |
|
41 else: |
|
42 tokenize_tokenize = tokenize.tokenize |
32 |
43 |
33 if PY2: |
44 if PY2: |
34 def getNodeType(node_class): |
45 def getNodeType(node_class): |
35 # workaround str.upper() which is locale-dependent |
46 # workaround str.upper() which is locale-dependent |
36 return str(unicode(node_class.__name__).upper()) # __IGNORE_WARNING__ |
47 return str(unicode(node_class.__name__).upper()) # __IGNORE_WARNING__ |
60 if isinstance(n, ast.If): |
71 if isinstance(n, ast.If): |
61 return [n.body] |
72 return [n.body] |
62 if isinstance(n, ast.Try): |
73 if isinstance(n, ast.Try): |
63 return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] |
74 return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] |
64 |
75 |
65 if PY34: |
76 if PY35_PLUS: |
|
77 FOR_TYPES = (ast.For, ast.AsyncFor) |
|
78 LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor) |
|
79 else: |
|
80 FOR_TYPES = (ast.For,) |
66 LOOP_TYPES = (ast.While, ast.For) |
81 LOOP_TYPES = (ast.While, ast.For) |
67 else: |
82 |
68 LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor) |
83 # https://github.com/python/typed_ast/blob/55420396/ast27/Parser/tokenizer.c#L102-L104 |
|
84 TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*') |
|
85 # https://github.com/python/typed_ast/blob/55420396/ast27/Parser/tokenizer.c#L1400 |
|
86 TYPE_IGNORE_RE = re.compile(TYPE_COMMENT_RE.pattern + r'ignore\s*(#|$)') |
|
87 # https://github.com/python/typed_ast/blob/55420396/ast27/Grammar/Grammar#L147 |
|
88 TYPE_FUNC_RE = re.compile(r'^(\(.*?\))\s*->\s*(.*)$') |
69 |
89 |
70 |
90 |
71 class _FieldsOrder(dict): |
91 class _FieldsOrder(dict): |
72 """Fix order of AST node fields.""" |
92 """Fix order of AST node fields.""" |
73 |
93 |
100 |
120 |
101 def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): |
121 def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): |
102 """ |
122 """ |
103 Yield all direct child nodes of *node*, that is, all fields that |
123 Yield all direct child nodes of *node*, that is, all fields that |
104 are nodes and all items of fields that are lists of nodes. |
124 are nodes and all items of fields that are lists of nodes. |
|
125 |
|
126 :param node: AST node to be iterated upon |
|
127 :param omit: String or tuple of strings denoting the |
|
128 attributes of the node to be omitted from |
|
129 further parsing |
|
130 :param _fields_order: Order of AST node fields |
105 """ |
131 """ |
106 for name in _fields_order[node.__class__]: |
132 for name in _fields_order[node.__class__]: |
107 if name == omit: |
133 if omit and name in omit: |
108 continue |
134 continue |
109 field = getattr(node, name, None) |
135 field = getattr(node, name, None) |
110 if isinstance(field, ast.AST): |
136 if isinstance(field, ast.AST): |
111 yield field |
137 yield field |
112 elif isinstance(field, list): |
138 elif isinstance(field, list): |
375 """ |
413 """ |
376 A binding created by an C{__all__} assignment. If the names in the list |
414 A binding created by an C{__all__} assignment. If the names in the list |
377 can be determined statically, they will be treated as names for export and |
415 can be determined statically, they will be treated as names for export and |
378 additional checking applied to them. |
416 additional checking applied to them. |
379 |
417 |
380 The only C{__all__} assignment that can be recognized is one which takes |
418 The only recognized C{__all__} assignment via list concatenation is in the |
381 the value of a literal list containing literal strings. For example:: |
419 following format: |
382 |
420 |
383 __all__ = ["foo", "bar"] |
421 __all__ = ['a'] + ['b'] + ['c'] |
384 |
422 |
385 Names which are imported and not otherwise used but appear in the value of |
423 Names which are imported and not otherwise used but appear in the value of |
386 C{__all__} will not have an unused import warning reported for them. |
424 C{__all__} will not have an unused import warning reported for them. |
387 """ |
425 """ |
388 |
426 |
389 def __init__(self, name, source, scope): |
427 def __init__(self, name, source, scope): |
390 if '__all__' in scope and isinstance(source, ast.AugAssign): |
428 if '__all__' in scope and isinstance(source, ast.AugAssign): |
391 self.names = list(scope['__all__'].names) |
429 self.names = list(scope['__all__'].names) |
392 else: |
430 else: |
393 self.names = [] |
431 self.names = [] |
394 if isinstance(source.value, (ast.List, ast.Tuple)): |
432 |
395 for node in source.value.elts: |
433 def _add_to_names(container): |
|
434 for node in container.elts: |
396 if isinstance(node, ast.Str): |
435 if isinstance(node, ast.Str): |
397 self.names.append(node.s) |
436 self.names.append(node.s) |
|
437 |
|
438 if isinstance(source.value, (ast.List, ast.Tuple)): |
|
439 _add_to_names(source.value) |
|
440 # If concatenating lists |
|
441 elif isinstance(source.value, ast.BinOp): |
|
442 currentValue = source.value |
|
443 while isinstance(currentValue.right, ast.List): |
|
444 left = currentValue.left |
|
445 right = currentValue.right |
|
446 _add_to_names(right) |
|
447 # If more lists are being added |
|
448 if isinstance(left, ast.BinOp): |
|
449 currentValue = left |
|
450 # If just two lists are being added |
|
451 elif isinstance(left, ast.List): |
|
452 _add_to_names(left) |
|
453 # All lists accounted for - done |
|
454 break |
|
455 # If not list concatenation |
|
456 else: |
|
457 break |
398 super(ExportBinding, self).__init__(name, source) |
458 super(ExportBinding, self).__init__(name, source) |
399 |
459 |
400 |
460 |
401 class Scope(dict): |
461 class Scope(dict): |
402 importStarred = False # set to True when import * is found |
462 importStarred = False # set to True when import * is found |
430 def unusedAssignments(self): |
490 def unusedAssignments(self): |
431 """ |
491 """ |
432 Return a generator for the assignments which have not been used. |
492 Return a generator for the assignments which have not been used. |
433 """ |
493 """ |
434 for name, binding in self.items(): |
494 for name, binding in self.items(): |
435 if (not binding.used and name not in self.globals |
495 if (not binding.used and |
436 and not self.usesLocals |
496 name != '_' and # see issue #202 |
437 and isinstance(binding, Assignment)): |
497 name not in self.globals and |
|
498 not self.usesLocals and |
|
499 isinstance(binding, Assignment)): |
438 yield name, binding |
500 yield name, binding |
439 |
501 |
440 |
502 |
441 class GeneratorScope(Scope): |
503 class GeneratorScope(Scope): |
442 pass |
504 pass |
443 |
505 |
444 |
506 |
445 class ModuleScope(Scope): |
507 class ModuleScope(Scope): |
446 """Scope for a module.""" |
508 """Scope for a module.""" |
447 _futures_allowed = True |
509 _futures_allowed = True |
|
510 _annotations_future_enabled = False |
448 |
511 |
449 |
512 |
450 class DoctestScope(ModuleScope): |
513 class DoctestScope(ModuleScope): |
451 """Scope for a doctest.""" |
514 """Scope for a doctest.""" |
452 |
515 |
453 |
516 |
454 # Globally defined names which are not attributes of the builtins module, or |
517 # Globally defined names which are not attributes of the builtins module, or |
455 # are only present on some platforms. |
518 # are only present on some platforms. |
456 _MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError'] |
519 _MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError'] |
|
520 # module scope annotation will store in `__annotations__`, see also PEP 526. |
|
521 if PY36_PLUS: |
|
522 _MAGIC_GLOBALS.append('__annotations__') |
457 |
523 |
458 |
524 |
459 def getNodeName(node): |
525 def getNodeName(node): |
460 # Returns node.id, or node.name, or None |
526 # Returns node.id, or node.name, or None |
461 if hasattr(node, 'id'): # One of the many nodes with an id |
527 if hasattr(node, 'id'): # One of the many nodes with an id |
462 return node.id |
528 return node.id |
463 if hasattr(node, 'name'): # an ExceptHandler node |
529 if hasattr(node, 'name'): # an ExceptHandler node |
464 return node.name |
530 return node.name |
465 |
531 |
466 |
532 |
|
533 def is_typing_overload(value, scope): |
|
534 def is_typing_overload_decorator(node): |
|
535 return ( |
|
536 ( |
|
537 isinstance(node, ast.Name) and |
|
538 node.id in scope and |
|
539 scope[node.id].fullName == 'typing.overload' |
|
540 ) or ( |
|
541 isinstance(node, ast.Attribute) and |
|
542 isinstance(node.value, ast.Name) and |
|
543 node.value.id == 'typing' and |
|
544 node.attr == 'overload' |
|
545 ) |
|
546 ) |
|
547 |
|
548 return ( |
|
549 isinstance(value.source, ast.FunctionDef) and |
|
550 len(value.source.decorator_list) == 1 and |
|
551 is_typing_overload_decorator(value.source.decorator_list[0]) |
|
552 ) |
|
553 |
|
554 |
|
555 def make_tokens(code): |
|
556 # PY3: tokenize.tokenize requires readline of bytes |
|
557 if not isinstance(code, bytes): |
|
558 code = code.encode('UTF-8') |
|
559 lines = iter(code.splitlines(True)) |
|
560 # next(lines, b'') is to prevent an error in pypy3 |
|
561 return tuple(tokenize_tokenize(lambda: next(lines, b''))) |
|
562 |
|
563 |
|
564 class _TypeableVisitor(ast.NodeVisitor): |
|
565 """Collect the line number and nodes which are deemed typeable by |
|
566 PEP 484 |
|
567 |
|
568 https://www.python.org/dev/peps/pep-0484/#type-comments |
|
569 """ |
|
570 def __init__(self): |
|
571 self.typeable_lines = [] # type: List[int] |
|
572 self.typeable_nodes = {} # type: Dict[int, ast.AST] |
|
573 |
|
574 def _typeable(self, node): |
|
575 # if there is more than one typeable thing on a line last one wins |
|
576 self.typeable_lines.append(node.lineno) |
|
577 self.typeable_nodes[node.lineno] = node |
|
578 |
|
579 self.generic_visit(node) |
|
580 |
|
581 visit_Assign = visit_For = visit_FunctionDef = visit_With = _typeable |
|
582 visit_AsyncFor = visit_AsyncFunctionDef = visit_AsyncWith = _typeable |
|
583 |
|
584 |
|
585 def _collect_type_comments(tree, tokens): |
|
586 visitor = _TypeableVisitor() |
|
587 visitor.visit(tree) |
|
588 |
|
589 type_comments = collections.defaultdict(list) |
|
590 for tp, text, start, _, _ in tokens: |
|
591 if ( |
|
592 tp != tokenize.COMMENT or # skip non comments |
|
593 not TYPE_COMMENT_RE.match(text) or # skip non-type comments |
|
594 TYPE_IGNORE_RE.match(text) # skip ignores |
|
595 ): |
|
596 continue |
|
597 |
|
598 # search for the typeable node at or before the line number of the |
|
599 # type comment. |
|
600 # if the bisection insertion point is before any nodes this is an |
|
601 # invalid type comment which is ignored. |
|
602 lineno, _ = start |
|
603 idx = bisect.bisect_right(visitor.typeable_lines, lineno) |
|
604 if idx == 0: |
|
605 continue |
|
606 node = visitor.typeable_nodes[visitor.typeable_lines[idx - 1]] |
|
607 type_comments[node].append((start, text)) |
|
608 |
|
609 return type_comments |
|
610 |
|
611 |
467 class Checker(object): |
612 class Checker(object): |
468 """ |
613 """ |
469 I check the cleanliness and sanity of Python code. |
614 I check the cleanliness and sanity of Python code. |
470 |
615 |
471 @ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements |
616 @ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements |
485 _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') |
643 _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') |
486 if _customBuiltIns: |
644 if _customBuiltIns: |
487 builtIns.update(_customBuiltIns.split(',')) |
645 builtIns.update(_customBuiltIns.split(',')) |
488 del _customBuiltIns |
646 del _customBuiltIns |
489 |
647 |
|
648 # TODO: file_tokens= is required to perform checks on type comments, |
|
649 # eventually make this a required positional argument. For now it |
|
650 # is defaulted to `()` for api compatibility. |
490 def __init__(self, tree, filename='(none)', builtins=None, |
651 def __init__(self, tree, filename='(none)', builtins=None, |
491 withDoctest='PYFLAKES_DOCTEST' in os.environ): |
652 withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()): |
492 self._nodeHandlers = {} |
653 self._nodeHandlers = {} |
493 self._deferredFunctions = [] |
654 self._deferredFunctions = [] |
494 self._deferredAssignments = [] |
655 self._deferredAssignments = [] |
495 self.deadScopes = [] |
656 self.deadScopes = [] |
496 self.messages = [] |
657 self.messages = [] |
497 self.filename = filename |
658 self.filename = filename |
498 if builtins: |
659 if builtins: |
499 self.builtIns = self.builtIns.union(builtins) |
660 self.builtIns = self.builtIns.union(builtins) |
500 self.withDoctest = withDoctest |
661 self.withDoctest = withDoctest |
501 self.scopeStack = [ModuleScope()] |
662 try: |
|
663 self.scopeStack = [Checker._ast_node_scope[type(tree)]()] |
|
664 except KeyError: |
|
665 raise RuntimeError('No scope implemented for the node %r' % tree) |
502 self.exceptHandlers = [()] |
666 self.exceptHandlers = [()] |
503 self.root = tree |
667 self.root = tree |
|
668 self._type_comments = _collect_type_comments(tree, file_tokens) |
|
669 for builtin in self.builtIns: |
|
670 self.addBinding(None, Builtin(builtin)) |
504 self.handleChildren(tree) |
671 self.handleChildren(tree) |
505 self.runDeferred(self._deferredFunctions) |
672 self.runDeferred(self._deferredFunctions) |
506 # Set _deferredFunctions to None so that deferFunction will fail |
673 # Set _deferredFunctions to None so that deferFunction will fail |
507 # noisily if called after we've run through the deferred functions. |
674 # noisily if called after we've run through the deferred functions. |
508 self._deferredFunctions = None |
675 self._deferredFunctions = None |
594 self.report(messages.UndefinedExport, |
774 self.report(messages.UndefinedExport, |
595 scope['__all__'].source, name) |
775 scope['__all__'].source, name) |
596 |
776 |
597 # mark all import '*' as used by the undefined in __all__ |
777 # mark all import '*' as used by the undefined in __all__ |
598 if scope.importStarred: |
778 if scope.importStarred: |
|
779 from_list = [] |
599 for binding in scope.values(): |
780 for binding in scope.values(): |
600 if isinstance(binding, StarImportation): |
781 if isinstance(binding, StarImportation): |
601 binding.used = all_binding |
782 binding.used = all_binding |
|
783 from_list.append(binding.fullName) |
|
784 # report * usage, with a list of possible sources |
|
785 from_list = ', '.join(sorted(from_list)) |
|
786 for name in undefined: |
|
787 self.report(messages.ImportStarUsage, |
|
788 scope['__all__'].source, name, from_list) |
602 |
789 |
603 # Look for imported names that aren't used. |
790 # Look for imported names that aren't used. |
604 for value in scope.values(): |
791 for value in scope.values(): |
605 if isinstance(value, Importation): |
792 if isinstance(value, Importation): |
606 used = value.used or value.name in all_names |
793 used = value.used or value.name in all_names |
607 if not used: |
794 if not used: |
608 messg = messages.UnusedImport |
795 messg = messages.UnusedImport |
609 self.report(messg, value.source, str(value)) |
796 self.report(messg, value.source, str(value)) |
610 for node in value.redefined: |
797 for node in value.redefined: |
611 if isinstance(self.getParent(node), ast.For): |
798 if isinstance(self.getParent(node), FOR_TYPES): |
612 messg = messages.ImportShadowedByLoopVar |
799 messg = messages.ImportShadowedByLoopVar |
613 elif used: |
800 elif used: |
614 continue |
801 continue |
615 else: |
802 else: |
616 messg = messages.RedefinedWhileUnused |
803 messg = messages.RedefinedWhileUnused |
645 def descendantOf(self, node, ancestors, stop): |
832 def descendantOf(self, node, ancestors, stop): |
646 for a in ancestors: |
833 for a in ancestors: |
647 if self.getCommonAncestor(node, a, stop): |
834 if self.getCommonAncestor(node, a, stop): |
648 return True |
835 return True |
649 return False |
836 return False |
|
837 |
|
838 def _getAncestor(self, node, ancestor_type): |
|
839 parent = node |
|
840 while True: |
|
841 if parent is self.root: |
|
842 return None |
|
843 parent = self.getParent(parent) |
|
844 if isinstance(parent, ancestor_type): |
|
845 return parent |
|
846 |
|
847 def getScopeNode(self, node): |
|
848 return self._getAncestor(node, tuple(Checker._ast_node_scope.keys())) |
650 |
849 |
651 def differentForks(self, lnode, rnode): |
850 def differentForks(self, lnode, rnode): |
652 """True, if lnode and rnode are located on different forks of IF/TRY""" |
851 """True, if lnode and rnode are located on different forks of IF/TRY""" |
653 ancestor = self.getCommonAncestor(lnode, rnode, self.root) |
852 ancestor = self.getCommonAncestor(lnode, rnode, self.root) |
654 parts = getAlternatives(ancestor) |
853 parts = getAlternatives(ancestor) |
670 for scope in self.scopeStack[::-1]: |
869 for scope in self.scopeStack[::-1]: |
671 if value.name in scope: |
870 if value.name in scope: |
672 break |
871 break |
673 existing = scope.get(value.name) |
872 existing = scope.get(value.name) |
674 |
873 |
675 if existing and not self.differentForks(node, existing.source): |
874 if (existing and not isinstance(existing, Builtin) and |
|
875 not self.differentForks(node, existing.source)): |
676 |
876 |
677 parent_stmt = self.getParent(value.source) |
877 parent_stmt = self.getParent(value.source) |
678 if isinstance(existing, Importation) and isinstance(parent_stmt, ast.For): |
878 if isinstance(existing, Importation) and isinstance(parent_stmt, FOR_TYPES): |
679 self.report(messages.ImportShadowedByLoopVar, |
879 self.report(messages.ImportShadowedByLoopVar, |
680 node, value.name, existing.source) |
880 node, value.name, existing.source) |
681 |
881 |
682 elif scope is self.scope: |
882 elif scope is self.scope: |
683 if (isinstance(parent_stmt, ast.comprehension) and |
883 if (isinstance(parent_stmt, ast.comprehension) and |
684 not isinstance(self.getParent(existing.source), |
884 not isinstance(self.getParent(existing.source), |
685 (ast.For, ast.comprehension))): |
885 (FOR_TYPES, ast.comprehension))): |
686 self.report(messages.RedefinedInListComp, |
886 self.report(messages.RedefinedInListComp, |
687 node, value.name, existing.source) |
887 node, value.name, existing.source) |
688 elif not existing.used and value.redefines(existing): |
888 elif not existing.used and value.redefines(existing): |
689 if value.name != '_' or isinstance(existing, Importation): |
889 if value.name != '_' or isinstance(existing, Importation): |
690 self.report(messages.RedefinedWhileUnused, |
890 if not is_typing_overload(existing, self.scope): |
691 node, value.name, existing.source) |
891 self.report(messages.RedefinedWhileUnused, |
|
892 node, value.name, existing.source) |
692 |
893 |
693 elif isinstance(existing, Importation) and value.redefines(existing): |
894 elif isinstance(existing, Importation) and value.redefines(existing): |
694 existing.redefined.append(node) |
895 existing.redefined.append(node) |
695 |
896 |
696 if value.name in self.scope: |
897 if value.name in self.scope: |
724 # only generators used in a class scope can access the |
925 # only generators used in a class scope can access the |
725 # names of the class. this is skipped during the first |
926 # names of the class. this is skipped during the first |
726 # iteration |
927 # iteration |
727 continue |
928 continue |
728 |
929 |
|
930 if (name == 'print' and |
|
931 isinstance(scope.get(name, None), Builtin)): |
|
932 parent = self.getParent(node) |
|
933 if (isinstance(parent, ast.BinOp) and |
|
934 isinstance(parent.op, ast.RShift)): |
|
935 self.report(messages.InvalidPrintSyntax, node) |
|
936 |
729 try: |
937 try: |
730 scope[name].used = (self.scope, node) |
938 scope[name].used = (self.scope, node) |
|
939 |
|
940 # if the name of SubImportation is same as |
|
941 # alias of other Importation and the alias |
|
942 # is used, SubImportation also should be marked as used. |
|
943 n = scope[name] |
|
944 if isinstance(n, Importation) and n._has_alias(): |
|
945 try: |
|
946 scope[n.fullName].used = (self.scope, node) |
|
947 except KeyError: |
|
948 pass |
731 except KeyError: |
949 except KeyError: |
732 pass |
950 pass |
733 else: |
951 else: |
734 return |
952 return |
735 |
953 |
736 importStarred = importStarred or scope.importStarred |
954 importStarred = importStarred or scope.importStarred |
737 |
955 |
738 if in_generators is not False: |
956 if in_generators is not False: |
739 in_generators = isinstance(scope, GeneratorScope) |
957 in_generators = isinstance(scope, GeneratorScope) |
740 |
|
741 # look in the built-ins |
|
742 if name in self.builtIns: |
|
743 return |
|
744 |
958 |
745 if importStarred: |
959 if importStarred: |
746 from_list = [] |
960 from_list = [] |
747 |
961 |
748 for scope in self.scopeStack[-1::-1]: |
962 for scope in self.scopeStack[-1::-1]: |
784 self.report(messages.UndefinedLocal, |
1001 self.report(messages.UndefinedLocal, |
785 scope[name].used[1], name, scope[name].source) |
1002 scope[name].used[1], name, scope[name].source) |
786 break |
1003 break |
787 |
1004 |
788 parent_stmt = self.getParent(node) |
1005 parent_stmt = self.getParent(node) |
789 if isinstance(parent_stmt, (ast.For, ast.comprehension)) or ( |
1006 if isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( |
790 parent_stmt != node.parent and |
1007 parent_stmt != node.parent and |
791 not self.isLiteralTupleUnpacking(parent_stmt)): |
1008 not self.isLiteralTupleUnpacking(parent_stmt)): |
792 binding = Binding(name, node) |
1009 binding = Binding(name, node) |
793 elif name == '__all__' and isinstance(self.scope, ModuleScope): |
1010 elif name == '__all__' and isinstance(self.scope, ModuleScope): |
794 binding = ExportBinding(name, node.parent, self.scope) |
1011 binding = ExportBinding(name, node.parent, self.scope) |
|
1012 elif isinstance(getattr(node, 'ctx', None), ast.Param): |
|
1013 binding = Argument(name, self.getScopeNode(node)) |
795 else: |
1014 else: |
796 binding = Assignment(name, node) |
1015 binding = Assignment(name, node) |
797 self.addBinding(node, binding) |
1016 self.addBinding(node, binding) |
798 |
1017 |
799 def handleNodeDelete(self, node): |
1018 def handleNodeDelete(self, node): |
824 try: |
1043 try: |
825 del self.scope[name] |
1044 del self.scope[name] |
826 except KeyError: |
1045 except KeyError: |
827 self.report(messages.UndefinedName, node, name) |
1046 self.report(messages.UndefinedName, node, name) |
828 |
1047 |
|
1048 def _handle_type_comments(self, node): |
|
1049 for (lineno, col_offset), comment in self._type_comments.get(node, ()): |
|
1050 comment = comment.split(':', 1)[1].strip() |
|
1051 func_match = TYPE_FUNC_RE.match(comment) |
|
1052 if func_match: |
|
1053 parts = ( |
|
1054 func_match.group(1).replace('*', ''), |
|
1055 func_match.group(2).strip(), |
|
1056 ) |
|
1057 else: |
|
1058 parts = (comment,) |
|
1059 |
|
1060 for part in parts: |
|
1061 if PY2: |
|
1062 part = part.replace('...', 'Ellipsis') |
|
1063 self.deferFunction(functools.partial( |
|
1064 self.handleStringAnnotation, |
|
1065 part, node, lineno, col_offset, |
|
1066 messages.CommentAnnotationSyntaxError, |
|
1067 )) |
|
1068 |
829 def handleChildren(self, tree, omit=None): |
1069 def handleChildren(self, tree, omit=None): |
|
1070 self._handle_type_comments(tree) |
830 for node in iter_child_nodes(tree, omit=omit): |
1071 for node in iter_child_nodes(tree, omit=omit): |
831 self.handleNode(node, tree) |
1072 self.handleNode(node, tree) |
832 |
1073 |
833 def isLiteralTupleUnpacking(self, node): |
1074 def isLiteralTupleUnpacking(self, node): |
834 if isinstance(node, ast.Assign): |
1075 if isinstance(node, ast.Assign): |
909 # Place doctest in module scope |
1150 # Place doctest in module scope |
910 saved_stack = self.scopeStack |
1151 saved_stack = self.scopeStack |
911 self.scopeStack = [self.scopeStack[0]] |
1152 self.scopeStack = [self.scopeStack[0]] |
912 node_offset = self.offset or (0, 0) |
1153 node_offset = self.offset or (0, 0) |
913 self.pushScope(DoctestScope) |
1154 self.pushScope(DoctestScope) |
914 underscore_in_builtins = '_' in self.builtIns |
1155 self.addBinding(None, Builtin('_')) |
915 if not underscore_in_builtins: |
|
916 self.builtIns.add('_') |
|
917 for example in examples: |
1156 for example in examples: |
918 try: |
1157 try: |
919 tree = compile(example.source, "<doctest>", "exec", ast.PyCF_ONLY_AST) |
1158 tree = ast.parse(example.source, "<doctest>") |
920 except SyntaxError: |
1159 except SyntaxError: |
921 e = sys.exc_info()[1] |
1160 e = sys.exc_info()[1] |
922 if PYPY: |
1161 if PYPY: |
923 e.offset += 1 |
1162 e.offset += 1 |
924 position = (node_lineno + example.lineno + e.lineno, |
1163 position = (node_lineno + example.lineno + e.lineno, |
927 else: |
1166 else: |
928 self.offset = (node_offset[0] + node_lineno + example.lineno, |
1167 self.offset = (node_offset[0] + node_lineno + example.lineno, |
929 node_offset[1] + example.indent + 4) |
1168 node_offset[1] + example.indent + 4) |
930 self.handleChildren(tree) |
1169 self.handleChildren(tree) |
931 self.offset = node_offset |
1170 self.offset = node_offset |
932 if not underscore_in_builtins: |
|
933 self.builtIns.remove('_') |
|
934 self.popScope() |
1171 self.popScope() |
935 self.scopeStack = saved_stack |
1172 self.scopeStack = saved_stack |
|
1173 |
|
1174 def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err): |
|
1175 try: |
|
1176 tree = ast.parse(s) |
|
1177 except SyntaxError: |
|
1178 self.report(err, node, s) |
|
1179 return |
|
1180 |
|
1181 body = tree.body |
|
1182 if len(body) != 1 or not isinstance(body[0], ast.Expr): |
|
1183 self.report(err, node, s) |
|
1184 return |
|
1185 |
|
1186 parsed_annotation = tree.body[0].value |
|
1187 for descendant in ast.walk(parsed_annotation): |
|
1188 if ( |
|
1189 'lineno' in descendant._attributes and |
|
1190 'col_offset' in descendant._attributes |
|
1191 ): |
|
1192 descendant.lineno = ref_lineno |
|
1193 descendant.col_offset = ref_col_offset |
|
1194 |
|
1195 self.handleNode(parsed_annotation, node) |
936 |
1196 |
937 def handleAnnotation(self, annotation, node): |
1197 def handleAnnotation(self, annotation, node): |
938 if isinstance(annotation, ast.Str): |
1198 if isinstance(annotation, ast.Str): |
939 # Defer handling forward annotation. |
1199 # Defer handling forward annotation. |
940 def handleForwardAnnotation(): |
1200 self.deferFunction(functools.partial( |
941 try: |
1201 self.handleStringAnnotation, |
942 tree = ast.parse(annotation.s) |
1202 annotation.s, |
943 except SyntaxError: |
1203 node, |
944 self.report( |
1204 annotation.lineno, |
945 messages.ForwardAnnotationSyntaxError, |
1205 annotation.col_offset, |
946 node, |
1206 messages.ForwardAnnotationSyntaxError, |
947 annotation.s, |
1207 )) |
948 ) |
1208 elif self.annotationsFutureEnabled: |
949 return |
1209 self.deferFunction(lambda: self.handleNode(annotation, node)) |
950 |
|
951 body = tree.body |
|
952 if len(body) != 1 or not isinstance(body[0], ast.Expr): |
|
953 self.report( |
|
954 messages.ForwardAnnotationSyntaxError, |
|
955 node, |
|
956 annotation.s, |
|
957 ) |
|
958 return |
|
959 |
|
960 parsed_annotation = tree.body[0].value |
|
961 for descendant in ast.walk(parsed_annotation): |
|
962 ast.copy_location(descendant, annotation) |
|
963 |
|
964 self.handleNode(parsed_annotation, node) |
|
965 |
|
966 self.deferFunction(handleForwardAnnotation) |
|
967 else: |
1210 else: |
968 self.handleNode(annotation, node) |
1211 self.handleNode(annotation, node) |
969 |
1212 |
970 def ignore(self, node): |
1213 def ignore(self, node): |
971 pass |
1214 pass |
1099 Handle occurrence of Name (which can be a load/store/delete access.) |
1342 Handle occurrence of Name (which can be a load/store/delete access.) |
1100 """ |
1343 """ |
1101 # Locate the name in locals / function / globals scopes. |
1344 # Locate the name in locals / function / globals scopes. |
1102 if isinstance(node.ctx, (ast.Load, ast.AugLoad)): |
1345 if isinstance(node.ctx, (ast.Load, ast.AugLoad)): |
1103 self.handleNodeLoad(node) |
1346 self.handleNodeLoad(node) |
1104 if (node.id == 'locals' and isinstance(self.scope, FunctionScope) |
1347 if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and |
1105 and isinstance(node.parent, ast.Call)): |
1348 isinstance(node.parent, ast.Call)): |
1106 # we are doing locals() call in current scope |
1349 # we are doing locals() call in current scope |
1107 self.scope.usesLocals = True |
1350 self.scope.usesLocals = True |
1108 elif isinstance(node.ctx, (ast.Store, ast.AugStore)): |
1351 elif isinstance(node.ctx, (ast.Store, ast.AugStore, ast.Param)): |
1109 self.handleNodeStore(node) |
1352 self.handleNodeStore(node) |
1110 elif isinstance(node.ctx, ast.Del): |
1353 elif isinstance(node.ctx, ast.Del): |
1111 self.handleNodeDelete(node) |
1354 self.handleNodeDelete(node) |
1112 else: |
1355 else: |
1113 # must be a Param context -- this only happens for names in function |
1356 # Unknown context |
1114 # arguments, but these aren't dispatched through here |
|
1115 raise RuntimeError("Got impossible expression context: %r" % (node.ctx,)) |
1357 raise RuntimeError("Got impossible expression context: %r" % (node.ctx,)) |
1116 |
1358 |
1117 def CONTINUE(self, node): |
1359 def CONTINUE(self, node): |
1118 # Walk the tree up until we see a loop (OK), a function or class |
1360 # Walk the tree up until we see a loop (OK), a function or class |
1119 # definition (not OK), for 'continue', a finally block (not OK), or |
1361 # definition (not OK), for 'continue', a finally block (not OK), or |
1256 self.scope.returnValue) |
1491 self.scope.returnValue) |
1257 self.deferAssignment(checkReturnWithArgumentInsideGenerator) |
1492 self.deferAssignment(checkReturnWithArgumentInsideGenerator) |
1258 self.popScope() |
1493 self.popScope() |
1259 |
1494 |
1260 self.deferFunction(runFunction) |
1495 self.deferFunction(runFunction) |
|
1496 |
|
1497 def ARGUMENTS(self, node): |
|
1498 self.handleChildren(node, omit=('defaults', 'kw_defaults')) |
|
1499 if PY2: |
|
1500 scope_node = self.getScopeNode(node) |
|
1501 if node.vararg: |
|
1502 self.addBinding(node, Argument(node.vararg, scope_node)) |
|
1503 if node.kwarg: |
|
1504 self.addBinding(node, Argument(node.kwarg, scope_node)) |
|
1505 |
|
1506 def ARG(self, node): |
|
1507 self.addBinding(node, Argument(node.arg, self.getScopeNode(node))) |
1261 |
1508 |
1262 def CLASSDEF(self, node): |
1509 def CLASSDEF(self, node): |
1263 """ |
1510 """ |
1264 Check names used in a class definition, including its decorators, base |
1511 Check names used in a class definition, including its decorators, base |
1265 classes, and the body of its definition. Additionally, add its name to |
1512 classes, and the body of its definition. Additionally, add its name to |
1338 if node.module == '__future__': |
1585 if node.module == '__future__': |
1339 importation = FutureImportation(name, node, self.scope) |
1586 importation = FutureImportation(name, node, self.scope) |
1340 if alias.name not in __future__.all_feature_names: |
1587 if alias.name not in __future__.all_feature_names: |
1341 self.report(messages.FutureFeatureNotDefined, |
1588 self.report(messages.FutureFeatureNotDefined, |
1342 node, alias.name) |
1589 node, alias.name) |
|
1590 if alias.name == 'annotations': |
|
1591 self.annotationsFutureEnabled = True |
1343 elif alias.name == '*': |
1592 elif alias.name == '*': |
1344 # Only Python 2, local import * is a SyntaxWarning |
1593 # Only Python 2, local import * is a SyntaxWarning |
1345 if not PY2 and not isinstance(self.scope, ModuleScope): |
1594 if not PY2 and not isinstance(self.scope, ModuleScope): |
1346 self.report(messages.ImportStarNotPermitted, |
1595 self.report(messages.ImportStarNotPermitted, |
1347 node, module) |
1596 node, module) |