src/eric7/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py

branch
eric7
changeset 9593
89f885d857e4
parent 9376
e143a7e7254b
child 9653
e67609152c5e
equal deleted inserted replaced
9592:2b3802c3c6d2 9593:89f885d857e4
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 (

eric ide

mercurial