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