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 builtins |
15 import builtins |
16 import ast |
16 import ast |
17 import bisect |
|
18 import collections |
|
19 import contextlib |
17 import contextlib |
20 import doctest |
18 import doctest |
21 import functools |
19 import functools |
22 import os |
20 import os |
23 import re |
21 import re |
24 import string |
22 import string |
25 import sys |
23 import sys |
26 import tokenize |
24 import warnings |
27 |
25 |
28 from . import messages |
26 from pyflakes import messages |
29 |
27 |
30 PY38_PLUS = sys.version_info >= (3, 8) |
28 PY38_PLUS = sys.version_info >= (3, 8) |
31 PYPY = hasattr(sys, 'pypy_version_info') |
29 PYPY = hasattr(sys, 'pypy_version_info') |
32 |
30 |
33 builtin_vars = dir(builtins) |
31 builtin_vars = dir(builtins) |
81 def _is_name_or_attr(node, name): # type: (ast.AST, str) -> bool |
79 def _is_name_or_attr(node, name): # type: (ast.AST, str) -> bool |
82 return ( |
80 return ( |
83 (isinstance(node, ast.Name) and node.id == name) or |
81 (isinstance(node, ast.Name) and node.id == name) or |
84 (isinstance(node, ast.Attribute) and node.attr == name) |
82 (isinstance(node, ast.Attribute) and node.attr == name) |
85 ) |
83 ) |
86 |
|
87 |
|
88 # https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L102-L104 |
|
89 TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*') |
|
90 # https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L1408-L1413 |
|
91 ASCII_NON_ALNUM = ''.join([chr(i) for i in range(128) if not chr(i).isalnum()]) |
|
92 TYPE_IGNORE_RE = re.compile( |
|
93 TYPE_COMMENT_RE.pattern + fr'ignore([{ASCII_NON_ALNUM}]|$)') |
|
94 # https://github.com/python/typed_ast/blob/1.4.0/ast27/Grammar/Grammar#L147 |
|
95 TYPE_FUNC_RE = re.compile(r'^(\(.*?\))\s*->\s*(.*)$') |
|
96 |
84 |
97 |
85 |
98 MAPPING_KEY_RE = re.compile(r'\(([^()]*)\)') |
86 MAPPING_KEY_RE = re.compile(r'\(([^()]*)\)') |
99 CONVERSION_FLAG_RE = re.compile('[#0+ -]*') |
87 CONVERSION_FLAG_RE = re.compile('[#0+ -]*') |
100 WIDTH_RE = re.compile(r'(?:\*|\d*)') |
88 WIDTH_RE = re.compile(r'(?:\*|\d*)') |
591 def __init__(self): |
579 def __init__(self): |
592 super().__init__() |
580 super().__init__() |
593 # Simplify: manage the special locals as globals |
581 # Simplify: manage the special locals as globals |
594 self.globals = self.alwaysUsed.copy() |
582 self.globals = self.alwaysUsed.copy() |
595 self.returnValue = None # First non-empty return |
583 self.returnValue = None # First non-empty return |
596 self.isGenerator = False # Detect a generator |
584 |
597 |
585 def unused_assignments(self): |
598 def unusedAssignments(self): |
|
599 """ |
586 """ |
600 Return a generator for the assignments which have not been used. |
587 Return a generator for the assignments which have not been used. |
601 """ |
588 """ |
602 for name, binding in self.items(): |
589 for name, binding in self.items(): |
603 if (not binding.used and |
590 if (not binding.used and |
605 name not in self.globals and |
592 name not in self.globals and |
606 not self.usesLocals and |
593 not self.usesLocals and |
607 isinstance(binding, Assignment)): |
594 isinstance(binding, Assignment)): |
608 yield name, binding |
595 yield name, binding |
609 |
596 |
|
597 def unused_annotations(self): |
|
598 """ |
|
599 Return a generator for the annotations which have not been used. |
|
600 """ |
|
601 for name, binding in self.items(): |
|
602 if not binding.used and isinstance(binding, Annotation): |
|
603 yield name, binding |
|
604 |
610 |
605 |
611 class GeneratorScope(Scope): |
606 class GeneratorScope(Scope): |
612 pass |
607 pass |
613 |
608 |
614 |
609 |
618 _annotations_future_enabled = False |
613 _annotations_future_enabled = False |
619 |
614 |
620 |
615 |
621 class DoctestScope(ModuleScope): |
616 class DoctestScope(ModuleScope): |
622 """Scope for a doctest.""" |
617 """Scope for a doctest.""" |
623 |
|
624 |
|
625 class DummyNode: |
|
626 """Used in place of an `ast.AST` to set error message positions""" |
|
627 def __init__(self, lineno, col_offset): |
|
628 self.lineno = lineno |
|
629 self.col_offset = col_offset |
|
630 |
618 |
631 |
619 |
632 class DetectClassScopedMagic: |
620 class DetectClassScopedMagic: |
633 names = dir() |
621 names = dir() |
634 |
622 |
746 with self._enter_annotation(AnnotationState.STRING): |
734 with self._enter_annotation(AnnotationState.STRING): |
747 return func(self, *args, **kwargs) |
735 return func(self, *args, **kwargs) |
748 return in_annotation_func |
736 return in_annotation_func |
749 |
737 |
750 |
738 |
751 def make_tokens(code): |
|
752 # PY3: tokenize.tokenize requires readline of bytes |
|
753 if not isinstance(code, bytes): |
|
754 code = code.encode('UTF-8') |
|
755 lines = iter(code.splitlines(True)) |
|
756 # next(lines, b'') is to prevent an error in pypy3 |
|
757 return tuple(tokenize.tokenize(lambda: next(lines, b''))) |
|
758 |
|
759 |
|
760 class _TypeableVisitor(ast.NodeVisitor): |
|
761 """Collect the line number and nodes which are deemed typeable by |
|
762 PEP 484 |
|
763 |
|
764 https://www.python.org/dev/peps/pep-0484/#type-comments |
|
765 """ |
|
766 def __init__(self): |
|
767 self.typeable_lines = [] |
|
768 self.typeable_nodes = {} |
|
769 |
|
770 def _typeable(self, node): |
|
771 # if there is more than one typeable thing on a line last one wins |
|
772 self.typeable_lines.append(node.lineno) |
|
773 self.typeable_nodes[node.lineno] = node |
|
774 |
|
775 self.generic_visit(node) |
|
776 |
|
777 visit_Assign = visit_For = visit_FunctionDef = visit_With = _typeable |
|
778 visit_AsyncFor = visit_AsyncFunctionDef = visit_AsyncWith = _typeable |
|
779 |
|
780 |
|
781 def _collect_type_comments(tree, tokens): |
|
782 visitor = _TypeableVisitor() |
|
783 visitor.visit(tree) |
|
784 |
|
785 type_comments = collections.defaultdict(list) |
|
786 for tp, text, start, _, _ in tokens: |
|
787 if ( |
|
788 tp != tokenize.COMMENT or # skip non comments |
|
789 not TYPE_COMMENT_RE.match(text) or # skip non-type comments |
|
790 TYPE_IGNORE_RE.match(text) # skip ignores |
|
791 ): |
|
792 continue |
|
793 |
|
794 # search for the typeable node at or before the line number of the |
|
795 # type comment. |
|
796 # if the bisection insertion point is before any nodes this is an |
|
797 # invalid type comment which is ignored. |
|
798 lineno, _ = start |
|
799 idx = bisect.bisect_right(visitor.typeable_lines, lineno) |
|
800 if idx == 0: |
|
801 continue |
|
802 node = visitor.typeable_nodes[visitor.typeable_lines[idx - 1]] |
|
803 type_comments[node].append((start, text)) |
|
804 |
|
805 return type_comments |
|
806 |
|
807 |
|
808 class Checker: |
739 class Checker: |
809 """ |
740 """ |
810 I check the cleanliness and sanity of Python code. |
741 I check the cleanliness and sanity of Python code. |
811 |
742 |
812 @ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements |
743 @ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements |
839 _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') |
770 _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') |
840 if _customBuiltIns: |
771 if _customBuiltIns: |
841 builtIns.update(_customBuiltIns.split(',')) |
772 builtIns.update(_customBuiltIns.split(',')) |
842 del _customBuiltIns |
773 del _customBuiltIns |
843 |
774 |
844 # TODO: file_tokens= is required to perform checks on type comments, |
|
845 # eventually make this a required positional argument. For now it |
|
846 # is defaulted to `()` for api compatibility. |
|
847 def __init__(self, tree, filename='(none)', builtins=None, |
775 def __init__(self, tree, filename='(none)', builtins=None, |
848 withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()): |
776 withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()): |
849 self._nodeHandlers = {} |
777 self._nodeHandlers = {} |
850 self._deferredFunctions = [] |
778 self._deferredFunctions = [] |
851 self._deferredAssignments = [] |
779 self._deferredAssignments = [] |
859 self.scopeStack = [Checker._ast_node_scope[type(tree)]()] |
787 self.scopeStack = [Checker._ast_node_scope[type(tree)]()] |
860 except KeyError: |
788 except KeyError: |
861 raise RuntimeError('No scope implemented for the node %r' % tree) |
789 raise RuntimeError('No scope implemented for the node %r' % tree) |
862 self.exceptHandlers = [()] |
790 self.exceptHandlers = [()] |
863 self.root = tree |
791 self.root = tree |
864 self._type_comments = _collect_type_comments(tree, file_tokens) |
|
865 for builtin in self.builtIns: |
792 for builtin in self.builtIns: |
866 self.addBinding(None, Builtin(builtin)) |
793 self.addBinding(None, Builtin(builtin)) |
867 self.handleChildren(tree) |
794 self.handleChildren(tree) |
868 self._in_deferred = True |
795 self._in_deferred = True |
869 self.runDeferred(self._deferredFunctions) |
796 self.runDeferred(self._deferredFunctions) |
876 self._deferredAssignments = None |
803 self._deferredAssignments = None |
877 del self.scopeStack[1:] |
804 del self.scopeStack[1:] |
878 self.popScope() |
805 self.popScope() |
879 self.checkDeadScopes() |
806 self.checkDeadScopes() |
880 |
807 |
|
808 if file_tokens: |
|
809 warnings.warn( |
|
810 '`file_tokens` will be removed in a future version', |
|
811 stacklevel=2, |
|
812 ) |
|
813 |
881 def deferFunction(self, callable): |
814 def deferFunction(self, callable): |
882 """ |
815 """ |
883 Schedule a function handler to be called just before completion. |
816 Schedule a function handler to be called just before completion. |
884 |
817 |
885 This is used for handling function bodies, which must be deferred |
818 This is used for handling function bodies, which must be deferred |
1140 self._nodeHandlers[node_class] = handler = getattr( |
1073 self._nodeHandlers[node_class] = handler = getattr( |
1141 self, nodeType, self._unknown_handler, |
1074 self, nodeType, self._unknown_handler, |
1142 ) |
1075 ) |
1143 return handler |
1076 return handler |
1144 |
1077 |
1145 def handleNodeLoad(self, node): |
1078 def handleNodeLoad(self, node, parent): |
1146 name = getNodeName(node) |
1079 name = getNodeName(node) |
1147 if not name: |
1080 if not name: |
1148 return |
1081 return |
1149 |
1082 |
1150 in_generators = None |
1083 in_generators = None |
1161 # iteration |
1094 # iteration |
1162 continue |
1095 continue |
1163 |
1096 |
1164 binding = scope.get(name, None) |
1097 binding = scope.get(name, None) |
1165 if isinstance(binding, Annotation) and not self._in_postponed_annotation: |
1098 if isinstance(binding, Annotation) and not self._in_postponed_annotation: |
|
1099 scope[name].used = True |
1166 continue |
1100 continue |
1167 |
1101 |
1168 if name == 'print' and isinstance(binding, Builtin): |
1102 if name == 'print' and isinstance(binding, Builtin): |
1169 parent = self.getParent(node) |
|
1170 if (isinstance(parent, ast.BinOp) and |
1103 if (isinstance(parent, ast.BinOp) and |
1171 isinstance(parent.op, ast.RShift)): |
1104 isinstance(parent.op, ast.RShift)): |
1172 self.report(messages.InvalidPrintSyntax, node) |
1105 self.report(messages.InvalidPrintSyntax, node) |
1173 |
1106 |
1174 try: |
1107 try: |
1304 return ( |
1237 return ( |
1305 self._in_annotation == AnnotationState.STRING or |
1238 self._in_annotation == AnnotationState.STRING or |
1306 self.annotationsFutureEnabled |
1239 self.annotationsFutureEnabled |
1307 ) |
1240 ) |
1308 |
1241 |
1309 def _handle_type_comments(self, node): |
|
1310 for (lineno, col_offset), comment in self._type_comments.get(node, ()): |
|
1311 comment = comment.split(':', 1)[1].strip() |
|
1312 func_match = TYPE_FUNC_RE.match(comment) |
|
1313 if func_match: |
|
1314 parts = ( |
|
1315 func_match.group(1).replace('*', ''), |
|
1316 func_match.group(2).strip(), |
|
1317 ) |
|
1318 else: |
|
1319 parts = (comment,) |
|
1320 |
|
1321 for part in parts: |
|
1322 self.deferFunction(functools.partial( |
|
1323 self.handleStringAnnotation, |
|
1324 part, DummyNode(lineno, col_offset), lineno, col_offset, |
|
1325 messages.CommentAnnotationSyntaxError, |
|
1326 )) |
|
1327 |
|
1328 def handleChildren(self, tree, omit=None): |
1242 def handleChildren(self, tree, omit=None): |
1329 self._handle_type_comments(tree) |
|
1330 for node in iter_child_nodes(tree, omit=omit): |
1243 for node in iter_child_nodes(tree, omit=omit): |
1331 self.handleNode(node, tree) |
1244 self.handleNode(node, tree) |
1332 |
1245 |
1333 def isLiteralTupleUnpacking(self, node): |
1246 def isLiteralTupleUnpacking(self, node): |
1334 if isinstance(node, ast.Assign): |
1247 if isinstance(node, ast.Assign): |
1971 """ |
1884 """ |
1972 Handle occurrence of Name (which can be a load/store/delete access.) |
1885 Handle occurrence of Name (which can be a load/store/delete access.) |
1973 """ |
1886 """ |
1974 # Locate the name in locals / function / globals scopes. |
1887 # Locate the name in locals / function / globals scopes. |
1975 if isinstance(node.ctx, ast.Load): |
1888 if isinstance(node.ctx, ast.Load): |
1976 self.handleNodeLoad(node) |
1889 self.handleNodeLoad(node, self.getParent(node)) |
1977 if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and |
1890 if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and |
1978 isinstance(node._pyflakes_parent, ast.Call)): |
1891 isinstance(node._pyflakes_parent, ast.Call)): |
1979 # we are doing locals() call in current scope |
1892 # we are doing locals() call in current scope |
1980 self.scope.usesLocals = True |
1893 self.scope.usesLocals = True |
1981 elif isinstance(node.ctx, ast.Store): |
1894 elif isinstance(node.ctx, ast.Store): |
2027 def YIELD(self, node): |
1940 def YIELD(self, node): |
2028 if isinstance(self.scope, (ClassScope, ModuleScope)): |
1941 if isinstance(self.scope, (ClassScope, ModuleScope)): |
2029 self.report(messages.YieldOutsideFunction, node) |
1942 self.report(messages.YieldOutsideFunction, node) |
2030 return |
1943 return |
2031 |
1944 |
2032 self.scope.isGenerator = True |
|
2033 self.handleNode(node.value, node) |
1945 self.handleNode(node.value, node) |
2034 |
1946 |
2035 AWAIT = YIELDFROM = YIELD |
1947 AWAIT = YIELDFROM = YIELD |
2036 |
1948 |
2037 def FUNCTIONDEF(self, node): |
1949 def FUNCTIONDEF(self, node): |
2089 |
2001 |
2090 self.pushScope() |
2002 self.pushScope() |
2091 |
2003 |
2092 self.handleChildren(node, omit=['decorator_list', 'returns']) |
2004 self.handleChildren(node, omit=['decorator_list', 'returns']) |
2093 |
2005 |
2094 def checkUnusedAssignments(): |
2006 def check_unused_assignments(): |
2095 """ |
2007 """ |
2096 Check to see if any assignments have not been used. |
2008 Check to see if any assignments have not been used. |
2097 """ |
2009 """ |
2098 for name, binding in self.scope.unusedAssignments(): |
2010 for name, binding in self.scope.unused_assignments(): |
2099 self.report(messages.UnusedVariable, binding.source, name) |
2011 self.report(messages.UnusedVariable, binding.source, name) |
2100 self.deferAssignment(checkUnusedAssignments) |
2012 |
|
2013 def check_unused_annotations(): |
|
2014 """ |
|
2015 Check to see if any annotations have not been used. |
|
2016 """ |
|
2017 for name, binding in self.scope.unused_annotations(): |
|
2018 self.report(messages.UnusedAnnotation, binding.source, name) |
|
2019 |
|
2020 self.deferAssignment(check_unused_assignments) |
|
2021 self.deferAssignment(check_unused_annotations) |
2101 |
2022 |
2102 self.popScope() |
2023 self.popScope() |
2103 |
2024 |
2104 self.deferFunction(runFunction) |
2025 self.deferFunction(runFunction) |
2105 |
2026 |
2132 self.handleNode(stmt, node) |
2053 self.handleNode(stmt, node) |
2133 self.popScope() |
2054 self.popScope() |
2134 self.addBinding(node, ClassDefinition(node.name, node)) |
2055 self.addBinding(node, ClassDefinition(node.name, node)) |
2135 |
2056 |
2136 def AUGASSIGN(self, node): |
2057 def AUGASSIGN(self, node): |
2137 self.handleNodeLoad(node.target) |
2058 self.handleNodeLoad(node.target, node) |
2138 self.handleNode(node.value, node) |
2059 self.handleNode(node.value, node) |
2139 self.handleNode(node.target, node) |
2060 self.handleNode(node.target, node) |
2140 |
2061 |
2141 def TUPLE(self, node): |
2062 def TUPLE(self, node): |
2142 if isinstance(node.ctx, ast.Store): |
2063 if isinstance(node.ctx, ast.Store): |
2270 # Restore. |
2191 # Restore. |
2271 if prev_definition: |
2192 if prev_definition: |
2272 self.scope[node.name] = prev_definition |
2193 self.scope[node.name] = prev_definition |
2273 |
2194 |
2274 def ANNASSIGN(self, node): |
2195 def ANNASSIGN(self, node): |
2275 self.handleNode(node.target, node) |
|
2276 self.handleAnnotation(node.annotation, node) |
2196 self.handleAnnotation(node.annotation, node) |
2277 # If the assignment has value, handle the *value* now. |
2197 # If the assignment has value, handle the *value* now. |
2278 if node.value: |
2198 if node.value: |
2279 # If the annotation is `TypeAlias`, handle the *value* as an annotation. |
2199 # If the annotation is `TypeAlias`, handle the *value* as an annotation. |
2280 if _is_typing(node.annotation, 'TypeAlias', self.scopeStack): |
2200 if _is_typing(node.annotation, 'TypeAlias', self.scopeStack): |
2281 self.handleAnnotation(node.value, node) |
2201 self.handleAnnotation(node.value, node) |
2282 else: |
2202 else: |
2283 self.handleNode(node.value, node) |
2203 self.handleNode(node.value, node) |
|
2204 self.handleNode(node.target, node) |
2284 |
2205 |
2285 def COMPARE(self, node): |
2206 def COMPARE(self, node): |
2286 left = node.left |
2207 left = node.left |
2287 for op, right in zip(node.ops, node.comparators): |
2208 for op, right in zip(node.ops, node.comparators): |
2288 if ( |
2209 if ( |