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-08 14:14:42
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyflakes (Old)
and /work/SRC/openSUSE:Factory/.python-pyflakes.new.1835 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyflakes"
Thu Dec 8 14:14:42 2022 rev:31 rq: version:2.5.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyflakes/python-pyflakes.changes
2022-12-07 17:36:25.065016402 +0100
+++
/work/SRC/openSUSE:Factory/.python-pyflakes.new.1835/python-pyflakes.changes
2022-12-08 14:14:42.965899471 +0100
@@ -2,11 +1,0 @@
-Tue Dec 6 13:20:52 UTC 2022 - Yogalakshmi Arunachalam <[email protected]>
-
-- Update to version 3.0.1 (2022-11-24)
- * Fix crash on augmented assign to print builtin
-
-- Update to version 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
-
--------------------------------------------------------------------
Old:
----
pyflakes-3.0.1.tar.gz
New:
----
pyflakes-2.5.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-pyflakes.spec ++++++
--- /var/tmp/diff_new_pack.RHXTpf/_old 2022-12-08 14:14:43.545902574 +0100
+++ /var/tmp/diff_new_pack.RHXTpf/_new 2022-12-08 14:14:43.549902596 +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: [email protected]
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: [email protected]
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