Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pyflakes for openSUSE:Factory 
checked in at 2022-12-15 20:16:29
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyflakes (Old)
 and      /work/SRC/openSUSE:Factory/.python-pyflakes.new.1835 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pyflakes"

Thu Dec 15 20:16:29 2022 rev:33 rq: version:2.5.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyflakes/python-pyflakes.changes  
2022-12-15 19:25:53.392290035 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-pyflakes.new.1835/python-pyflakes.changes    
    2022-12-15 20:16:31.177315362 +0100
@@ -2,11 +1,0 @@
-Tue Dec 13 16:27:28 UTC 2022 - Yogalakshmi Arunachalam <yarunacha...@suse.com>
-
-- Update to 3.0.1
-  * Fix crash on augmented assign to ``print`` builtin
-  
-- Update to 3.0.0 
-  -  Detect undefined name in variable defined by an annotated assignment
-  - Add a new error for names which are annotated but unused
-  - Remove handling of python 2.x ``# type:`` comments.  Use annotations 
instead
-
--------------------------------------------------------------------

Old:
----
  pyflakes-3.0.1.tar.gz

New:
----
  pyflakes-2.5.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pyflakes.spec ++++++
--- /var/tmp/diff_new_pack.KqrqK1/_old  2022-12-15 20:16:31.945319717 +0100
+++ /var/tmp/diff_new_pack.KqrqK1/_new  2022-12-15 20:16:31.985319943 +0100
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-pyflakes
-Version:        3.0.1
+Version:        2.5.0
 Release:        0
 Summary:        Passive checker of Python programs
 License:        MIT

++++++ pyflakes-3.0.1.tar.gz -> pyflakes-2.5.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/NEWS.rst new/pyflakes-2.5.0/NEWS.rst
--- old/pyflakes-3.0.1/NEWS.rst 2022-11-24 17:52:19.000000000 +0100
+++ new/pyflakes-2.5.0/NEWS.rst 2022-07-30 19:27:25.000000000 +0200
@@ -1,13 +1,3 @@
-3.0.1 (2022-11-24)
-
-- Fix crash on augmented assign to ``print`` builtin
-
-3.0.0 (2022-11-23)
-
-- Detect undefined name in variable defined by an annotated assignment
-- Add a new error for names which are annotated but unused
-- Remove handling of python 2.x ``# type:`` comments.  Use annotations instead
-
 2.5.0 (2022-07-30)
 
 - Drop support for EOL python 2.7 / 3.4 / 3.5
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/PKG-INFO new/pyflakes-2.5.0/PKG-INFO
--- old/pyflakes-3.0.1/PKG-INFO 2022-11-24 17:53:21.689930700 +0100
+++ new/pyflakes-2.5.0/PKG-INFO 2022-07-30 19:28:49.971378000 +0200
@@ -1,11 +1,12 @@
 Metadata-Version: 2.1
 Name: pyflakes
-Version: 3.0.1
+Version: 2.5.0
 Summary: passive checker of Python programs
 Home-page: https://github.com/PyCQA/pyflakes
 Author: A lot of people
 Author-email: code-qual...@python.org
 License: MIT
+Platform: UNKNOWN
 Classifier: Development Status :: 6 - Mature
 Classifier: Environment :: Console
 Classifier: Intended Audience :: Developers
@@ -51,11 +52,11 @@
 Useful tips:
 
 * Be sure to install it for a version of Python which is compatible
-  with your codebase: ``python#.# -m pip install pyflakes`` (for example,
-  ``python3.10 -m pip install pyflakes``)
+  with your codebase: for Python 2, ``pip2 install pyflakes`` and for
+  Python3, ``pip3 install pyflakes``.
 
-* You can also invoke Pyflakes with ``python#.# -m pyflakes .`` if you want
-  to run it for a specific python version.
+* You can also invoke Pyflakes with ``python3 -m pyflakes .`` or
+  ``python2 -m pyflakes .`` if you have it installed for both versions.
 
 * If you require more options and more flexibility, you could give a
   look to Flake8_ too.
@@ -91,7 +92,7 @@
 
 Patches may be submitted via a `GitHub pull request`_ or via the mailing list
 if you prefer. If you are comfortable doing so, please `rebase your changes`_
-so they may be applied to main with a fast-forward merge, and each commit is
+so they may be applied to master with a fast-forward merge, and each commit is
 a coherent unit of work with a well-written log message.  If you are not
 comfortable with this rebase workflow, the project maintainers will be happy to
 rebase your commits for you.
@@ -111,4 +112,6 @@
 Changelog
 ---------
 
-Please see `NEWS.rst <https://github.com/PyCQA/pyflakes/blob/main/NEWS.rst>`_.
+Please see `NEWS.rst 
<https://github.com/PyCQA/pyflakes/blob/master/NEWS.rst>`_.
+
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/README.rst 
new/pyflakes-2.5.0/README.rst
--- old/pyflakes-3.0.1/README.rst       2022-11-24 17:02:51.000000000 +0100
+++ new/pyflakes-2.5.0/README.rst       2022-07-30 19:03:28.000000000 +0200
@@ -24,11 +24,11 @@
 Useful tips:
 
 * Be sure to install it for a version of Python which is compatible
-  with your codebase: ``python#.# -m pip install pyflakes`` (for example,
-  ``python3.10 -m pip install pyflakes``)
+  with your codebase: for Python 2, ``pip2 install pyflakes`` and for
+  Python3, ``pip3 install pyflakes``.
 
-* You can also invoke Pyflakes with ``python#.# -m pyflakes .`` if you want
-  to run it for a specific python version.
+* You can also invoke Pyflakes with ``python3 -m pyflakes .`` or
+  ``python2 -m pyflakes .`` if you have it installed for both versions.
 
 * If you require more options and more flexibility, you could give a
   look to Flake8_ too.
@@ -64,7 +64,7 @@
 
 Patches may be submitted via a `GitHub pull request`_ or via the mailing list
 if you prefer. If you are comfortable doing so, please `rebase your changes`_
-so they may be applied to main with a fast-forward merge, and each commit is
+so they may be applied to master with a fast-forward merge, and each commit is
 a coherent unit of work with a well-written log message.  If you are not
 comfortable with this rebase workflow, the project maintainers will be happy to
 rebase your commits for you.
@@ -84,4 +84,4 @@
 Changelog
 ---------
 
-Please see `NEWS.rst <https://github.com/PyCQA/pyflakes/blob/main/NEWS.rst>`_.
+Please see `NEWS.rst 
<https://github.com/PyCQA/pyflakes/blob/master/NEWS.rst>`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/pyflakes/__init__.py 
new/pyflakes-2.5.0/pyflakes/__init__.py
--- old/pyflakes-3.0.1/pyflakes/__init__.py     2022-11-24 17:52:30.000000000 
+0100
+++ new/pyflakes-2.5.0/pyflakes/__init__.py     2022-07-30 19:27:25.000000000 
+0200
@@ -1 +1 @@
-__version__ = '3.0.1'
+__version__ = '2.5.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/pyflakes/api.py 
new/pyflakes-2.5.0/pyflakes/api.py
--- old/pyflakes-3.0.1/pyflakes/api.py  2022-11-24 17:02:51.000000000 +0100
+++ new/pyflakes-2.5.0/pyflakes/api.py  2022-07-30 19:03:28.000000000 +0200
@@ -44,7 +44,8 @@
         reporter.unexpectedError(filename, 'problem decoding source')
         return 1
     # Okay, it's syntactically valid.  Now check it.
-    w = checker.Checker(tree, filename=filename)
+    file_tokens = checker.make_tokens(codeString)
+    w = checker.Checker(tree, file_tokens=file_tokens, filename=filename)
     w.messages.sort(key=lambda m: m.lineno)
     for warning in w.messages:
         reporter.flake(warning)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/pyflakes/checker.py 
new/pyflakes-2.5.0/pyflakes/checker.py
--- old/pyflakes-3.0.1/pyflakes/checker.py      2022-11-24 17:51:58.000000000 
+0100
+++ new/pyflakes-2.5.0/pyflakes/checker.py      2022-07-30 19:03:28.000000000 
+0200
@@ -7,6 +7,8 @@
 import __future__
 import builtins
 import ast
+import bisect
+import collections
 import contextlib
 import doctest
 import functools
@@ -14,7 +16,7 @@
 import re
 import string
 import sys
-import warnings
+import tokenize
 
 from pyflakes import messages
 
@@ -76,6 +78,16 @@
     )
 
 
+# 
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*)')
@@ -574,8 +586,9 @@
         # 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 unused_assignments(self):
+    def unusedAssignments(self):
         """
         Return a generator for the assignments which have not been used.
         """
@@ -587,14 +600,6 @@
                     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
@@ -610,6 +615,13 @@
     """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()
 
@@ -729,6 +741,63 @@
     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.
@@ -765,6 +834,9 @@
         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 = {}
@@ -782,6 +854,7 @@
             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)
@@ -798,12 +871,6 @@
         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.
@@ -1068,7 +1135,7 @@
         )
         return handler
 
-    def handleNodeLoad(self, node, parent):
+    def handleNodeLoad(self, node):
         name = getNodeName(node)
         if not name:
             return
@@ -1089,10 +1156,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)
@@ -1232,7 +1299,27 @@
             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)
 
@@ -1879,7 +1966,7 @@
         """
         # Locate the name in locals / function / globals scopes.
         if isinstance(node.ctx, ast.Load):
-            self.handleNodeLoad(node, self.getParent(node))
+            self.handleNodeLoad(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
@@ -1935,6 +2022,7 @@
             self.report(messages.YieldOutsideFunction, node)
             return
 
+        self.scope.isGenerator = True
         self.handleNode(node.value, node)
 
     AWAIT = YIELDFROM = YIELD
@@ -1996,22 +2084,13 @@
 
             self.handleChildren(node, omit=['decorator_list', 'returns'])
 
-            def check_unused_assignments():
+            def checkUnusedAssignments():
                 """
                 Check to see if any assignments have not been used.
                 """
-                for name, binding in self.scope.unused_assignments():
+                for name, binding in self.scope.unusedAssignments():
                     self.report(messages.UnusedVariable, binding.source, name)
-
-            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.deferAssignment(checkUnusedAssignments)
 
             self.popScope()
 
@@ -2048,7 +2127,7 @@
         self.addBinding(node, ClassDefinition(node.name, node))
 
     def AUGASSIGN(self, node):
-        self.handleNodeLoad(node.target, node)
+        self.handleNodeLoad(node.target)
         self.handleNode(node.value, node)
         self.handleNode(node.target, node)
 
@@ -2186,6 +2265,7 @@
             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:
@@ -2194,7 +2274,6 @@
                 self.handleAnnotation(node.value, node)
             else:
                 self.handleNode(node.value, node)
-        self.handleNode(node.target, node)
 
     def COMPARE(self, node):
         left = node.left
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/pyflakes/messages.py 
new/pyflakes-2.5.0/pyflakes/messages.py
--- old/pyflakes-3.0.1/pyflakes/messages.py     2022-11-24 17:02:51.000000000 
+0100
+++ new/pyflakes-2.5.0/pyflakes/messages.py     2022-07-30 19:03:28.000000000 
+0200
@@ -134,6 +134,10 @@
 class LateFutureImport(Message):
     message = 'from __future__ imports must occur at the beginning of the file'
 
+    def __init__(self, filename, loc):
+        Message.__init__(self, filename, loc)
+        self.message_args = ()
+
 
 class FutureFeatureNotDefined(Message):
     """An undefined __future__ feature name was imported."""
@@ -156,18 +160,6 @@
         self.message_args = (names,)
 
 
-class UnusedAnnotation(Message):
-    """
-    Indicates that a variable has been explicitly annotated to but not actually
-    used.
-    """
-    message = 'local variable %r is annotated but never used'
-
-    def __init__(self, filename, loc, names):
-        Message.__init__(self, filename, loc)
-        self.message_args = (names,)
-
-
 class ReturnOutsideFunction(Message):
     """
     Indicates a return statement outside of a function/method.
@@ -245,6 +237,14 @@
 
     def __init__(self, filename, loc, annotation):
         Message.__init__(self, filename, loc)
+        self.message_args = (annotation,)
+
+
+class CommentAnnotationSyntaxError(Message):
+    message = 'syntax error in type comment %r'
+
+    def __init__(self, filename, loc, annotation):
+        Message.__init__(self, filename, loc)
         self.message_args = (annotation,)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/pyflakes/test/harness.py 
new/pyflakes-2.5.0/pyflakes/test/harness.py
--- old/pyflakes-3.0.1/pyflakes/test/harness.py 2022-11-24 17:02:51.000000000 
+0100
+++ new/pyflakes-2.5.0/pyflakes/test/harness.py 2022-07-30 19:03:28.000000000 
+0200
@@ -16,10 +16,13 @@
 
     def flakes(self, input, *expectedOutputs, **kw):
         tree = ast.parse(textwrap.dedent(input))
+        file_tokens = checker.make_tokens(textwrap.dedent(input))
         if kw.get('is_segment'):
             tree = tree.body[0]
             kw.pop('is_segment')
-        w = checker.Checker(tree, withDoctest=self.withDoctest, **kw)
+        w = checker.Checker(
+            tree, file_tokens=file_tokens, withDoctest=self.withDoctest, **kw
+        )
         outputs = [type(o) for o in w.messages]
         expectedOutputs = list(expectedOutputs)
         outputs.sort(key=lambda t: t.__name__)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/pyflakes/test/test_checker.py 
new/pyflakes-2.5.0/pyflakes/test/test_checker.py
--- old/pyflakes-3.0.1/pyflakes/test/test_checker.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/pyflakes-2.5.0/pyflakes/test/test_checker.py    2022-07-30 
19:03:28.000000000 +0200
@@ -0,0 +1,184 @@
+import ast
+
+from pyflakes import checker
+from pyflakes.test.harness import TestCase
+
+
+class TypeableVisitorTests(TestCase):
+    """
+    Tests of L{_TypeableVisitor}
+    """
+
+    @staticmethod
+    def _run_visitor(s):
+        """
+        Run L{_TypeableVisitor} on the parsed source and return the visitor.
+        """
+        tree = ast.parse(s)
+        visitor = checker._TypeableVisitor()
+        visitor.visit(tree)
+        return visitor
+
+    def test_node_types(self):
+        """
+        Test that the typeable node types are collected
+        """
+        visitor = self._run_visitor(
+            """\
+x = 1  # assignment
+for x in range(1): pass  # for loop
+def f(): pass  # function definition
+with a as b: pass  # with statement
+"""
+        )
+        self.assertEqual(visitor.typeable_lines, [1, 2, 3, 4])
+        self.assertIsInstance(visitor.typeable_nodes[1], ast.Assign)
+        self.assertIsInstance(visitor.typeable_nodes[2], ast.For)
+        self.assertIsInstance(visitor.typeable_nodes[3], ast.FunctionDef)
+        self.assertIsInstance(visitor.typeable_nodes[4], ast.With)
+
+    def test_visitor_recurses(self):
+        """
+        Test the common pitfall of missing `generic_visit` in visitors by
+        ensuring that nested nodes are reported
+        """
+        visitor = self._run_visitor(
+            """\
+def f():
+    x = 1
+"""
+        )
+        self.assertEqual(visitor.typeable_lines, [1, 2])
+        self.assertIsInstance(visitor.typeable_nodes[1], ast.FunctionDef)
+        self.assertIsInstance(visitor.typeable_nodes[2], ast.Assign)
+
+    def test_py35_node_types(self):
+        """
+        Test that the PEP 492 node types are collected
+        """
+        visitor = self._run_visitor(
+            """\
+async def f():  # async def
+    async for x in y:  pass  # async for
+    async with a as b: pass  # async with
+"""
+        )
+        self.assertEqual(visitor.typeable_lines, [1, 2, 3])
+        self.assertIsInstance(visitor.typeable_nodes[1], ast.AsyncFunctionDef)
+        self.assertIsInstance(visitor.typeable_nodes[2], ast.AsyncFor)
+        self.assertIsInstance(visitor.typeable_nodes[3], ast.AsyncWith)
+
+    def test_last_node_wins(self):
+        """
+        Test that when two typeable nodes are present on a line, the last
+        typeable one wins.
+        """
+        visitor = self._run_visitor('x = 1; y = 1')
+        # detected both assignable nodes
+        self.assertEqual(visitor.typeable_lines, [1, 1])
+        # but the assignment to `y` wins
+        self.assertEqual(visitor.typeable_nodes[1].targets[0].id, 'y')
+
+
+class CollectTypeCommentsTests(TestCase):
+    """
+    Tests of L{_collect_type_comments}
+    """
+
+    @staticmethod
+    def _collect(s):
+        """
+        Run L{_collect_type_comments} on the parsed source and return the
+        mapping from nodes to comments.  The return value is converted to
+        a set: {(node_type, tuple of comments), ...}
+        """
+        tree = ast.parse(s)
+        tokens = checker.make_tokens(s)
+        ret = checker._collect_type_comments(tree, tokens)
+        return {(type(k), tuple(s for _, s in v)) for k, v in ret.items()}
+
+    def test_bytes(self):
+        """
+        Test that the function works for binary source
+        """
+        ret = self._collect(b'x = 1  # type: int')
+        self.assertSetEqual(ret, {(ast.Assign, ('# type: int',))})
+
+    def test_text(self):
+        """
+        Test that the function works for text source
+        """
+        ret = self._collect('x = 1  # type: int')
+        self.assertEqual(ret, {(ast.Assign, ('# type: int',))})
+
+    def test_non_type_comment_ignored(self):
+        """
+        Test that a non-type comment is ignored
+        """
+        ret = self._collect('x = 1  # noqa')
+        self.assertSetEqual(ret, set())
+
+    def test_type_comment_before_typeable(self):
+        """
+        Test that a type comment before something typeable is ignored.
+        """
+        ret = self._collect('# type: int\nx = 1')
+        self.assertSetEqual(ret, set())
+
+    def test_type_ignore_comment_ignored(self):
+        """
+        Test that `# type: ignore` comments are not collected.
+        """
+        ret = self._collect('x = 1  # type: ignore')
+        self.assertSetEqual(ret, set())
+
+    def test_type_ignore_with_other_things_ignored(self):
+        """
+        Test that `# type: ignore` comments with more content are also not
+        collected.
+        """
+        ret = self._collect('x = 1  # type: ignore # noqa')
+        self.assertSetEqual(ret, set())
+        ret = self._collect('x = 1  #type:ignore#noqa')
+        self.assertSetEqual(ret, set())
+
+    def test_type_comment_with_extra_still_collected(self):
+        ret = self._collect('x = 1  # type: int  # noqa')
+        self.assertSetEqual(ret, {(ast.Assign, ('# type: int  # noqa',))})
+
+    def test_type_comment_without_whitespace(self):
+        ret = self._collect('x = 1 #type:int')
+        self.assertSetEqual(ret, {(ast.Assign, ('#type:int',))})
+
+    def test_type_comment_starts_with_word_ignore(self):
+        ret = self._collect('x = 1 # type: ignore[T]')
+        self.assertSetEqual(ret, set())
+
+    def test_last_node_wins(self):
+        """
+        Test that when two typeable nodes are present on a line, the last
+        typeable one wins.
+        """
+        ret = self._collect('def f(): x = 1  # type: int')
+        self.assertSetEqual(ret, {(ast.Assign, ('# type: int',))})
+
+    def test_function_def_assigned_comments(self):
+        """
+        Test that type comments for function arguments are all attributed to
+        the function definition.
+        """
+        ret = self._collect(
+            """\
+def f(
+        a,  # type: int
+        b,  # type: str
+):
+    # type: (...) -> None
+    pass
+"""
+        )
+        expected = {(
+            ast.FunctionDef,
+            ('# type: int', '# type: str', '# type: (...) -> None'),
+        )}
+        self.assertSetEqual(ret, expected)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/pyflakes/test/test_other.py 
new/pyflakes-2.5.0/pyflakes/test/test_other.py
--- old/pyflakes-3.0.1/pyflakes/test/test_other.py      2022-11-24 
17:51:58.000000000 +0100
+++ new/pyflakes-2.5.0/pyflakes/test/test_other.py      2022-07-30 
19:03:28.000000000 +0200
@@ -2052,10 +2052,6 @@
         self.assertEqual(exc.lineno, 4)
         self.assertEqual(exc.col, 0)
 
-    def test_print_augmented_assign(self):
-        # nonsense, but shouldn't crash pyflakes
-        self.flakes('print += 1')
-
     def test_print_function_assignment(self):
         """
         A valid assignment, tested for catching false positives.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyflakes-3.0.1/pyflakes/test/test_type_annotations.py 
new/pyflakes-2.5.0/pyflakes/test/test_type_annotations.py
--- old/pyflakes-3.0.1/pyflakes/test/test_type_annotations.py   2022-11-24 
17:02:51.000000000 +0100
+++ new/pyflakes-2.5.0/pyflakes/test/test_type_annotations.py   2022-07-30 
19:03:28.000000000 +0200
@@ -17,22 +17,22 @@
         from typing import overload
 
         @overload
-        def f(s: None) -> None:
+        def f(s):  # type: (None) -> None
             pass
 
         @overload
-        def f(s: int) -> int:
+        def f(s):  # type: (int) -> int
             pass
 
         def f(s):
             return s
 
         @typing.overload
-        def g(s: None) -> None:
+        def g(s):  # type: (None) -> None
             pass
 
         @typing.overload
-        def g(s: int) -> int:
+        def g(s):  # type: (int) -> int
             pass
 
         def g(s):
@@ -46,22 +46,22 @@
         from typing_extensions import overload
 
         @overload
-        def f(s: None) -> None:
+        def f(s):  # type: (None) -> None
             pass
 
         @overload
-        def f(s: int) -> int:
+        def f(s):  # type: (int) -> int
             pass
 
         def f(s):
             return s
 
         @typing_extensions.overload
-        def g(s: None) -> None:
+        def g(s):  # type: (None) -> None
             pass
 
         @typing_extensions.overload
-        def g(s: int) -> int:
+        def g(s):  # type: (int) -> int
             pass
 
         def g(s):
@@ -74,11 +74,11 @@
         from typing import overload
 
         @overload
-        async def f(s: None) -> None:
+        async def f(s):  # type: (None) -> None
             pass
 
         @overload
-        async def f(s: int) -> int:
+        async def f(s):  # type: (int) -> int
             pass
 
         async def f(s):
@@ -92,12 +92,12 @@
 
             @dec
             @overload
-            def f(x: int) -> int:
+            def f(x):  # type: (int) -> int
                 pass
 
             @dec
             @overload
-            def f(x: str) -> str:
+            def f(x):  # type: (str) -> str
                 pass
 
             @dec
@@ -110,11 +110,11 @@
 
         class C:
             @overload
-            def f(self, x: int) -> int:
+            def f(self, x):  # type: (int) -> int
                 pass
 
             @overload
-            def f(self, x: str) -> str:
+            def f(self, x):  # type: (str) -> str
                 pass
 
             def f(self, x): return x
@@ -126,11 +126,11 @@
         import typing as t
 
         @t.overload
-        def f(s: None) -> None:
+        def f(s):  # type: (None) -> None
             pass
 
         @t.overload
-        def f(s: int) -> int:
+        def f(s):  # type: (int) -> int
             pass
 
         def f(s):
@@ -174,7 +174,7 @@
         def f():
             name: str
             age: int
-        ''', m.UnusedAnnotation, m.UnusedAnnotation)
+        ''')
         self.flakes('''
         def f():
             name: str = 'Bob'
@@ -190,7 +190,7 @@
         from typing import Any
         def f():
             a: Any
-        ''', m.UnusedAnnotation)
+        ''')
         self.flakes('''
         foo: not_a_real_type
         ''', m.UndefinedName)
@@ -298,11 +298,6 @@
         a: 'a: "A"'
         ''', m.ForwardAnnotationSyntaxError)
 
-    def test_variable_annotation_references_self_name_undefined(self):
-        self.flakes("""
-        x: int = x
-        """, m.UndefinedName)
-
     def test_TypeAlias_annotations(self):
         self.flakes("""
         from typing_extensions import TypeAlias
@@ -356,10 +351,11 @@
         class Cls:
             y: int
         ''')
+        # TODO: this should print a UnusedVariable message
         self.flakes('''
         def f():
             x: int
-        ''', m.UnusedAnnotation)
+        ''')
         # This should only print one UnusedVariable message
         self.flakes('''
         def f():
@@ -367,12 +363,6 @@
             x = 3
         ''', m.UnusedVariable)
 
-    def test_unassigned_annotation_is_undefined(self):
-        self.flakes('''
-        name: str
-        print(name)
-        ''', m.UndefinedName)
-
     def test_annotated_async_def(self):
         self.flakes('''
         class c: pass
@@ -416,6 +406,115 @@
             __all__: List[str]
         ''')
 
+    def test_typeCommentsMarkImportsAsUsed(self):
+        self.flakes("""
+        from mod import A, B, C, D, E, F, G
+
+
+        def f(
+            a,  # type: A
+        ):
+            # type: (...) -> B
+            for b in a:  # type: C
+                with b as c:  # type: D
+                    d = c.x  # type: E
+                    return d
+
+
+        def g(x):  # type: (F) -> G
+            return x.y
+        """)
+
+    def test_typeCommentsFullSignature(self):
+        self.flakes("""
+        from mod import A, B, C, D
+        def f(a, b):
+            # type: (A, B[C]) -> D
+            return a + b
+        """)
+
+    def test_typeCommentsStarArgs(self):
+        self.flakes("""
+        from mod import A, B, C, D
+        def f(a, *b, **c):
+            # type: (A, *B, **C) -> D
+            return a + b
+        """)
+
+    def test_typeCommentsFullSignatureWithDocstring(self):
+        self.flakes('''
+        from mod import A, B, C, D
+        def f(a, b):
+            # type: (A, B[C]) -> D
+            """do the thing!"""
+            return a + b
+        ''')
+
+    def test_typeCommentsAdditionalComment(self):
+        self.flakes("""
+        from mod import F
+
+        x = 1 # type: F  # noqa
+        """)
+
+    def test_typeCommentsNoWhitespaceAnnotation(self):
+        self.flakes("""
+        from mod import F
+
+        x = 1  #type:F
+        """)
+
+    def test_typeCommentsInvalidDoesNotMarkAsUsed(self):
+        self.flakes("""
+        from mod import F
+
+        # type: F
+        """, m.UnusedImport)
+
+    def test_typeCommentsSyntaxError(self):
+        self.flakes("""
+        def f(x):  # type: (F[) -> None
+            pass
+        """, m.CommentAnnotationSyntaxError)
+
+    def test_typeCommentsSyntaxErrorCorrectLine(self):
+        checker = self.flakes("""\
+        x = 1
+        # type: definitely not a PEP 484 comment
+        """, m.CommentAnnotationSyntaxError)
+        self.assertEqual(checker.messages[0].lineno, 2)
+
+    def test_typeCommentsAssignedToPreviousNode(self):
+        # This test demonstrates an issue in the implementation which
+        # associates the type comment with a node above it, however the type
+        # comment isn't valid according to mypy.  If an improved approach
+        # which can detect these "invalid" type comments is implemented, this
+        # test should be removed / improved to assert that new check.
+        self.flakes("""
+        from mod import F
+        x = 1
+        # type: F
+        """)
+
+    def test_typeIgnore(self):
+        self.flakes("""
+        a = 0  # type: ignore
+        b = 0  # type: ignore[excuse]
+        c = 0  # type: ignore=excuse
+        d = 0  # type: ignore [excuse]
+        e = 0  # type: ignore whatever
+        """)
+
+    def test_typeIgnoreBogus(self):
+        self.flakes("""
+        x = 1  # type: ignored
+        """, m.UndefinedName)
+
+    def test_typeIgnoreBogusUnicode(self):
+        self.flakes("""
+        x = 2  # type: ignore\xc3
+        """, m.UndefinedName)
+
     def test_return_annotation_is_class_scope_variable(self):
         self.flakes("""
         from typing import TypeVar
@@ -605,7 +704,7 @@
             if TYPE_CHECKING:
                 from t import T
 
-            def f() -> T:
+            def f():  # type: () -> T
                 pass
         """)
         # False: the old, more-compatible approach
@@ -613,7 +712,7 @@
             if False:
                 from t import T
 
-            def f() -> T:
+            def f():  # type: () -> T
                 pass
         """)
         # some choose to assign a constant and do it that way
@@ -623,7 +722,7 @@
             if MYPY:
                 from t import T
 
-            def f() -> T:
+            def f():  # type: () -> T
                 pass
         """)
 
@@ -637,7 +736,7 @@
                 Protocol = object
 
             class C(Protocol):
-                def f() -> int:
+                def f():  # type: () -> int
                     pass
         """)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/pyflakes/test/test_undefined_names.py 
new/pyflakes-2.5.0/pyflakes/test/test_undefined_names.py
--- old/pyflakes-3.0.1/pyflakes/test/test_undefined_names.py    2022-11-24 
17:18:39.000000000 +0100
+++ new/pyflakes-2.5.0/pyflakes/test/test_undefined_names.py    2022-07-30 
19:03:28.000000000 +0200
@@ -814,6 +814,7 @@
         raised.
         """
         tree = ast.parse("x = 10")
+        file_tokens = checker.make_tokens("x = 10")
         # Make it into something unrecognizable.
         tree.body[0].targets[0].ctx = object()
-        self.assertRaises(RuntimeError, checker.Checker, tree)
+        self.assertRaises(RuntimeError, checker.Checker, tree, 
file_tokens=file_tokens)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/pyflakes.egg-info/PKG-INFO 
new/pyflakes-2.5.0/pyflakes.egg-info/PKG-INFO
--- old/pyflakes-3.0.1/pyflakes.egg-info/PKG-INFO       2022-11-24 
17:53:21.000000000 +0100
+++ new/pyflakes-2.5.0/pyflakes.egg-info/PKG-INFO       2022-07-30 
19:28:49.000000000 +0200
@@ -1,11 +1,12 @@
 Metadata-Version: 2.1
 Name: pyflakes
-Version: 3.0.1
+Version: 2.5.0
 Summary: passive checker of Python programs
 Home-page: https://github.com/PyCQA/pyflakes
 Author: A lot of people
 Author-email: code-qual...@python.org
 License: MIT
+Platform: UNKNOWN
 Classifier: Development Status :: 6 - Mature
 Classifier: Environment :: Console
 Classifier: Intended Audience :: Developers
@@ -51,11 +52,11 @@
 Useful tips:
 
 * Be sure to install it for a version of Python which is compatible
-  with your codebase: ``python#.# -m pip install pyflakes`` (for example,
-  ``python3.10 -m pip install pyflakes``)
+  with your codebase: for Python 2, ``pip2 install pyflakes`` and for
+  Python3, ``pip3 install pyflakes``.
 
-* You can also invoke Pyflakes with ``python#.# -m pyflakes .`` if you want
-  to run it for a specific python version.
+* You can also invoke Pyflakes with ``python3 -m pyflakes .`` or
+  ``python2 -m pyflakes .`` if you have it installed for both versions.
 
 * If you require more options and more flexibility, you could give a
   look to Flake8_ too.
@@ -91,7 +92,7 @@
 
 Patches may be submitted via a `GitHub pull request`_ or via the mailing list
 if you prefer. If you are comfortable doing so, please `rebase your changes`_
-so they may be applied to main with a fast-forward merge, and each commit is
+so they may be applied to master with a fast-forward merge, and each commit is
 a coherent unit of work with a well-written log message.  If you are not
 comfortable with this rebase workflow, the project maintainers will be happy to
 rebase your commits for you.
@@ -111,4 +112,6 @@
 Changelog
 ---------
 
-Please see `NEWS.rst <https://github.com/PyCQA/pyflakes/blob/main/NEWS.rst>`_.
+Please see `NEWS.rst 
<https://github.com/PyCQA/pyflakes/blob/master/NEWS.rst>`_.
+
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyflakes-3.0.1/pyflakes.egg-info/SOURCES.txt 
new/pyflakes-2.5.0/pyflakes.egg-info/SOURCES.txt
--- old/pyflakes-3.0.1/pyflakes.egg-info/SOURCES.txt    2022-11-24 
17:53:21.000000000 +0100
+++ new/pyflakes-2.5.0/pyflakes.egg-info/SOURCES.txt    2022-07-30 
19:28:49.000000000 +0200
@@ -23,6 +23,7 @@
 pyflakes/test/harness.py
 pyflakes/test/test_api.py
 pyflakes/test/test_builtin.py
+pyflakes/test/test_checker.py
 pyflakes/test/test_code_segment.py
 pyflakes/test/test_dict.py
 pyflakes/test/test_doctests.py

Reply via email to