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

branch
eric7
changeset 9593
89f885d857e4
parent 9376
e143a7e7254b
child 9653
e67609152c5e
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

eric ide

mercurial