diff -r 2b3802c3c6d2 -r 89f885d857e4 src/eric7/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py --- a/src/eric7/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py Thu Dec 08 15:10:49 2022 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py Thu Dec 08 16:01:34 2022 +0100 @@ -14,8 +14,6 @@ import __future__ import builtins import ast -import bisect -import collections import contextlib import doctest import functools @@ -23,9 +21,9 @@ import re import string import sys -import tokenize +import warnings -from . import messages +from pyflakes import messages PY38_PLUS = sys.version_info >= (3, 8) PYPY = hasattr(sys, 'pypy_version_info') @@ -85,16 +83,6 @@ ) -# https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L102-L104 -TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*') -# https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L1408-L1413 -ASCII_NON_ALNUM = ''.join([chr(i) for i in range(128) if not chr(i).isalnum()]) -TYPE_IGNORE_RE = re.compile( - TYPE_COMMENT_RE.pattern + fr'ignore([{ASCII_NON_ALNUM}]|$)') -# https://github.com/python/typed_ast/blob/1.4.0/ast27/Grammar/Grammar#L147 -TYPE_FUNC_RE = re.compile(r'^(\(.*?\))\s*->\s*(.*)$') - - MAPPING_KEY_RE = re.compile(r'\(([^()]*)\)') CONVERSION_FLAG_RE = re.compile('[#0+ -]*') WIDTH_RE = re.compile(r'(?:\*|\d*)') @@ -593,9 +581,8 @@ # Simplify: manage the special locals as globals self.globals = self.alwaysUsed.copy() self.returnValue = None # First non-empty return - self.isGenerator = False # Detect a generator - def unusedAssignments(self): + def unused_assignments(self): """ Return a generator for the assignments which have not been used. """ @@ -607,6 +594,14 @@ isinstance(binding, Assignment)): yield name, binding + def unused_annotations(self): + """ + Return a generator for the annotations which have not been used. + """ + for name, binding in self.items(): + if not binding.used and isinstance(binding, Annotation): + yield name, binding + class GeneratorScope(Scope): pass @@ -622,13 +617,6 @@ """Scope for a doctest.""" -class DummyNode: - """Used in place of an `ast.AST` to set error message positions""" - def __init__(self, lineno, col_offset): - self.lineno = lineno - self.col_offset = col_offset - - class DetectClassScopedMagic: names = dir() @@ -748,63 +736,6 @@ return in_annotation_func -def make_tokens(code): - # PY3: tokenize.tokenize requires readline of bytes - if not isinstance(code, bytes): - code = code.encode('UTF-8') - lines = iter(code.splitlines(True)) - # next(lines, b'') is to prevent an error in pypy3 - return tuple(tokenize.tokenize(lambda: next(lines, b''))) - - -class _TypeableVisitor(ast.NodeVisitor): - """Collect the line number and nodes which are deemed typeable by - PEP 484 - - https://www.python.org/dev/peps/pep-0484/#type-comments - """ - def __init__(self): - self.typeable_lines = [] - self.typeable_nodes = {} - - def _typeable(self, node): - # if there is more than one typeable thing on a line last one wins - self.typeable_lines.append(node.lineno) - self.typeable_nodes[node.lineno] = node - - self.generic_visit(node) - - visit_Assign = visit_For = visit_FunctionDef = visit_With = _typeable - visit_AsyncFor = visit_AsyncFunctionDef = visit_AsyncWith = _typeable - - -def _collect_type_comments(tree, tokens): - visitor = _TypeableVisitor() - visitor.visit(tree) - - type_comments = collections.defaultdict(list) - for tp, text, start, _, _ in tokens: - if ( - tp != tokenize.COMMENT or # skip non comments - not TYPE_COMMENT_RE.match(text) or # skip non-type comments - TYPE_IGNORE_RE.match(text) # skip ignores - ): - continue - - # search for the typeable node at or before the line number of the - # type comment. - # if the bisection insertion point is before any nodes this is an - # invalid type comment which is ignored. - lineno, _ = start - idx = bisect.bisect_right(visitor.typeable_lines, lineno) - if idx == 0: - continue - node = visitor.typeable_nodes[visitor.typeable_lines[idx - 1]] - type_comments[node].append((start, text)) - - return type_comments - - class Checker: """ I check the cleanliness and sanity of Python code. @@ -841,9 +772,6 @@ builtIns.update(_customBuiltIns.split(',')) del _customBuiltIns - # TODO: file_tokens= is required to perform checks on type comments, - # eventually make this a required positional argument. For now it - # is defaulted to `()` for api compatibility. def __init__(self, tree, filename='(none)', builtins=None, withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()): self._nodeHandlers = {} @@ -861,7 +789,6 @@ raise RuntimeError('No scope implemented for the node %r' % tree) self.exceptHandlers = [()] self.root = tree - self._type_comments = _collect_type_comments(tree, file_tokens) for builtin in self.builtIns: self.addBinding(None, Builtin(builtin)) self.handleChildren(tree) @@ -878,6 +805,12 @@ self.popScope() self.checkDeadScopes() + if file_tokens: + warnings.warn( + '`file_tokens` will be removed in a future version', + stacklevel=2, + ) + def deferFunction(self, callable): """ Schedule a function handler to be called just before completion. @@ -1142,7 +1075,7 @@ ) return handler - def handleNodeLoad(self, node): + def handleNodeLoad(self, node, parent): name = getNodeName(node) if not name: return @@ -1163,10 +1096,10 @@ binding = scope.get(name, None) if isinstance(binding, Annotation) and not self._in_postponed_annotation: + scope[name].used = True continue if name == 'print' and isinstance(binding, Builtin): - parent = self.getParent(node) if (isinstance(parent, ast.BinOp) and isinstance(parent.op, ast.RShift)): self.report(messages.InvalidPrintSyntax, node) @@ -1306,27 +1239,7 @@ self.annotationsFutureEnabled ) - def _handle_type_comments(self, node): - for (lineno, col_offset), comment in self._type_comments.get(node, ()): - comment = comment.split(':', 1)[1].strip() - func_match = TYPE_FUNC_RE.match(comment) - if func_match: - parts = ( - func_match.group(1).replace('*', ''), - func_match.group(2).strip(), - ) - else: - parts = (comment,) - - for part in parts: - self.deferFunction(functools.partial( - self.handleStringAnnotation, - part, DummyNode(lineno, col_offset), lineno, col_offset, - messages.CommentAnnotationSyntaxError, - )) - def handleChildren(self, tree, omit=None): - self._handle_type_comments(tree) for node in iter_child_nodes(tree, omit=omit): self.handleNode(node, tree) @@ -1973,7 +1886,7 @@ """ # Locate the name in locals / function / globals scopes. if isinstance(node.ctx, ast.Load): - self.handleNodeLoad(node) + self.handleNodeLoad(node, self.getParent(node)) if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and isinstance(node._pyflakes_parent, ast.Call)): # we are doing locals() call in current scope @@ -2029,7 +1942,6 @@ self.report(messages.YieldOutsideFunction, node) return - self.scope.isGenerator = True self.handleNode(node.value, node) AWAIT = YIELDFROM = YIELD @@ -2091,13 +2003,22 @@ self.handleChildren(node, omit=['decorator_list', 'returns']) - def checkUnusedAssignments(): + def check_unused_assignments(): """ Check to see if any assignments have not been used. """ - for name, binding in self.scope.unusedAssignments(): + for name, binding in self.scope.unused_assignments(): self.report(messages.UnusedVariable, binding.source, name) - self.deferAssignment(checkUnusedAssignments) + + def check_unused_annotations(): + """ + Check to see if any annotations have not been used. + """ + for name, binding in self.scope.unused_annotations(): + self.report(messages.UnusedAnnotation, binding.source, name) + + self.deferAssignment(check_unused_assignments) + self.deferAssignment(check_unused_annotations) self.popScope() @@ -2134,7 +2055,7 @@ self.addBinding(node, ClassDefinition(node.name, node)) def AUGASSIGN(self, node): - self.handleNodeLoad(node.target) + self.handleNodeLoad(node.target, node) self.handleNode(node.value, node) self.handleNode(node.target, node) @@ -2272,7 +2193,6 @@ self.scope[node.name] = prev_definition def ANNASSIGN(self, node): - self.handleNode(node.target, node) self.handleAnnotation(node.annotation, node) # If the assignment has value, handle the *value* now. if node.value: @@ -2281,6 +2201,7 @@ self.handleAnnotation(node.value, node) else: self.handleNode(node.value, node) + self.handleNode(node.target, node) def COMPARE(self, node): left = node.left