././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633552776.3214808
pyflakes-2.4.0/ 0000755 0001750 0001750 00000000000 00000000000 015026 5 ustar 00asottile asottile 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/AUTHORS 0000644 0001750 0001750 00000000745 00000000000 016104 0 ustar 00asottile asottile 0000000 0000000 Contributors
------------
* Phil Frost - Former Divmod Team
* Moe Aboulkheir - Former Divmod Team
* Jean-Paul Calderone - Former Divmod Team
* Glyph Lefkowitz - Former Divmod Team
* Tristan Seligmann
* Jonathan Lange
* Georg Brandl
* Ronny Pfannschmidt
* Virgil Dupras
* Kevin Watters
* Ian Cordasco
* Florent Xicluna
* Domen Kožar
* Marcin Cieślak
* Steven Myint
* Ignas Mikalajūnas
See also the contributors list on GitHub:
https://github.com/PyCQA/pyflakes/graphs/contributors
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/LICENSE 0000644 0001750 0001750 00000002105 00000000000 016031 0 ustar 00asottile asottile 0000000 0000000 Copyright 2005-2011 Divmod, Inc.
Copyright 2013-2014 Florent Xicluna
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/MANIFEST.in 0000644 0001750 0001750 00000000111 00000000000 016555 0 ustar 00asottile asottile 0000000 0000000 include README.rst NEWS.rst
include AUTHORS LICENSE
include bin/pyflakes
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633552732.0
pyflakes-2.4.0/NEWS.rst 0000644 0001750 0001750 00000026125 00000000000 016342 0 ustar 00asottile asottile 0000000 0000000 2.4.0 (2021-10-06)
- Remove unused tracing code (``traceTree``)
- Add support for ``match`` statement
- Detect ``typing`` module attributes when imported with ``import ... as ...``
2.3.1 (2021-03-24)
- Fix regression in 2.3.0: type annotations no longer redefine imports
2.3.0 (2021-03-14)
- Recognize tuple concatenation in ``__all__`` export definitions
- Better support use of annotation-only assignments when using
``from __future__ import annotations``
- Recognize special-case typing for ``Annotated``
- Fix undefined name ``__qualname__`` in class scope
- Recognize special-cased typing for ``TypeVar``
- Errors for undefined exports in ``__all__`` are shown in a deterministic order
- Fix false positives in certain typing constructs (``TypeVar``,
``NamedTuple``, ``TypedDict``, ``cast``)
2.2.0 (2020-04-08)
- Include column information in error messages
- Fix ``@overload`` detection with other decorators and in non-global scopes
- Fix return-type annotation being a class member
- Fix assignment to ``_`` in doctests with existing ``_`` name
- Namespace attributes which are attached to ast nodes with ``_pyflakes_`` to
avoid conflicts with other libraries (notably bandit)
- Add check for f-strings without placeholders
- Add check for unused/extra/invalid ``'string literal'.format(...)``
- Add check for unused/extra/invalid ``'string literal % ...``
- Improve python shebang detection
- Allow type ignore to be followed by a code ``# type: ignore[attr-defined]``
- Add support for assignment expressions (PEP 572)
- Support ``@overload`` detection from ``typing_extensions`` as well
- Fix ``@overload`` detection for async functions
- Allow ``continue`` inside ``finally`` in python 3.8+
- Fix handling of annotations in positional-only arguments
- Make pyflakes more resistant to future syntax additions
- Fix false positives in partially quoted type annotations
- Warn about ``is`` comparison to tuples
- Fix ``Checker`` usage with async function subtrees
- Add check for ``if`` of non-empty tuple
- Switch from ``optparse`` to ``argparse``
- Fix false positives in partially quoted type annotations in unusual contexts
- Be more cautious when identifying ``Literal`` type expressions
2.1.1 (2019-02-28)
- Fix reported line number for type comment errors
- Fix typing.overload check to only check imported names
2.1.0 (2019-01-23)
- Allow intentional assignment to variables named ``_``
- Recognize ``__module__`` as a valid name in class scope
- ``pyflakes.checker.Checker`` supports checking of partial ``ast`` trees
- Detect assign-before-use for local variables which shadow builtin names
- Detect invalid ``print`` syntax using ``>>`` operator
- Treat ``async for`` the same as a ``for`` loop for introducing variables
- Add detection for list concatenation in ``__all__``
- Exempt ``@typing.overload`` from duplicate function declaration
- Importing a submodule of an ``as``-aliased ``import``-import is marked as
used
- Report undefined names from ``__all__`` as possibly coming from a ``*``
import
- Add support for changes in Python 3.8-dev
- Add support for PEP 563 (``from __future__ import annotations``)
- Include Python version and platform information in ``pyflakes --version``
- Recognize ``__annotations__`` as a valid magic global in Python 3.6+
- Mark names used in PEP 484 ``# type: ...`` comments as used
- Add check for use of ``is`` operator with ``str``, ``bytes``, and ``int``
literals
2.0.0 (2018-05-20)
- Drop support for EOL Python <2.7 and 3.2-3.3
- Check for unused exception binding in ``except:`` block
- Handle string literal type annotations
- Ignore redefinitions of ``_``, unless originally defined by import
- Support ``__class__`` without ``self`` in Python 3
- Issue an error for ``raise NotImplemented(...)``
1.6.0 (2017-08-03)
- Process function scope variable annotations for used names
- Find Python files without extensions by their shebang
1.5.0 (2017-01-09)
- Enable support for PEP 526 annotated assignments
1.4.0 (2016-12-30):
- Change formatting of ImportStarMessage to be consistent with other errors
- Support PEP 498 "f-strings"
1.3.0 (2016-09-01):
- Fix PyPy2 Windows IntegrationTests
- Check for duplicate dictionary keys
- Fix TestMain tests on Windows
- Fix "continue" and "break" checks ignoring py3.5's "async for" loop
1.2.3 (2016-05-12):
- Fix TypeError when processing relative imports
1.2.2 (2016-05-06):
- Avoid traceback when exception is del-ed in except
1.2.1 (2015-05-05):
- Fix false RedefinedWhileUnused for submodule imports
1.2.0 (2016-05-03):
- Warn against reusing exception names after the except: block on Python 3
- Improve the error messages for imports
1.1.0 (2016-03-01):
- Allow main() to accept arguments.
- Support @ matrix-multiplication operator
- Validate ``__future__`` imports
- Fix doctest scope testing
- Warn for tuple assertions which are always true
- Warn for "import \*" not at module level on Python 3
- Catch many more kinds of SyntaxErrors
- Check PEP 498 f-strings
- (and a few more sundry bugfixes)
1.0.0 (2015-09-20):
- Python 3.5 support. async/await statements in particular.
- Fix test_api.py on Windows.
- Eliminate a false UnusedImport warning when the name has been
declared "global"
0.9.2 (2015-06-17):
- Fix a traceback when a global is defined in one scope, and used in another.
0.9.1 (2015-06-09):
- Update NEWS.txt to include 0.9.0, which had been forgotten.
0.9.0 (2015-05-31):
- Exit gracefully, not with a traceback, on SIGINT and SIGPIPE.
- Fix incorrect report of undefined name when using lambda expressions in
generator expressions.
- Don't crash on DOS line endings on Windows and Python 2.6.
- Don't report an undefined name if the 'del' which caused a name to become
undefined is only conditionally executed.
- Properly handle differences in list comprehension scope in Python 3.
- Improve handling of edge cases around 'global' defined variables.
- Report an error for 'return' outside a function.
0.8.1 (2014-03-30):
- Detect the declared encoding in Python 3.
- Do not report redefinition of import in a local scope, if the
global name is used elsewhere in the module.
- Catch undefined variable in loop generator when it is also used as
loop variable.
- Report undefined name for ``(a, b) = (1, 2)`` but not for the general
unpacking ``(a, b) = func()``.
- Correctly detect when an imported module is used in default arguments
of a method, when the method and the module use the same name.
- Distribute a universal wheel file.
0.8.0 (2014-03-22):
- Adapt for the AST in Python 3.4.
- Fix caret position on SyntaxError.
- Fix crash on Python 2.x with some doctest SyntaxError.
- Add tox.ini.
- The ``PYFLAKES_NODOCTEST`` environment variable has been replaced with the
``PYFLAKES_DOCTEST`` environment variable (with the opposite meaning).
Doctest checking is now disabled by default; set the environment variable
to enable it.
- Correctly parse incremental ``__all__ += [...]``.
- Catch return with arguments inside a generator (Python <= 3.2).
- Do not complain about ``_`` in doctests.
- Drop deprecated methods ``pushFunctionScope`` and ``pushClassScope``.
0.7.3 (2013-07-02):
- Do not report undefined name for generator expression and dict or
set comprehension at class level.
- Deprecate ``Checker.pushFunctionScope`` and ``Checker.pushClassScope``:
use ``Checker.pushScope`` instead.
- Remove dependency on Unittest2 for the tests.
0.7.2 (2013-04-24):
- Fix computation of ``DoctestSyntaxError.lineno`` and ``col``.
- Add boolean attribute ``Checker.withDoctest`` to ignore doctests.
- If environment variable ``PYFLAKES_NODOCTEST`` is set, skip doctests.
- Environment variable ``PYFLAKES_BUILTINS`` accepts a comma-separated
list of additional built-in names.
0.7.1 (2013-04-23):
- File ``bin/pyflakes`` was missing in tarball generated with distribute.
- Fix reporting errors in non-ASCII filenames (Python 2.x).
0.7.0 (2013-04-17):
- Add --version and --help options.
- Support ``python -m pyflakes`` (Python 2.7 and Python 3.x).
- Add attribute ``Message.col`` to report column offset.
- Do not report redefinition of variable for a variable used in a list
comprehension in a conditional.
- Do not report redefinition of variable for generator expressions and
set or dict comprehensions.
- Do not report undefined name when the code is protected with a
``NameError`` exception handler.
- Do not report redefinition of variable when unassigning a module imported
for its side-effect.
- Support special locals like ``__tracebackhide__`` for py.test.
- Support checking doctests.
- Fix issue with Turkish locale where ``'i'.upper() == 'i'`` in Python 2.
0.6.1 (2013-01-29):
- Fix detection of variables in augmented assignments.
0.6.0 (2013-01-29):
- Support Python 3 up to 3.3, based on the pyflakes3k project.
- Preserve compatibility with Python 2.5 and all recent versions of Python.
- Support custom reporters in addition to the default Reporter.
- Allow function redefinition for modern property construction via
property.setter/deleter.
- Fix spurious redefinition warnings in conditionals.
- Do not report undefined name in ``__all__`` if import * is used.
- Add WindowsError as a known built-in name on all platforms.
- Support specifying additional built-ins in the ``Checker`` constructor.
- Don't issue Unused Variable warning when using locals() in current scope.
- Handle problems with the encoding of source files.
- Remove dependency on Twisted for the tests.
- Support ``python setup.py test`` and ``python setup.py develop``.
- Create script using setuptools ``entry_points`` to support all platforms,
including Windows.
0.5.0 (2011-09-02):
- Convert pyflakes to use newer _ast infrastructure rather than compiler.
- Support for new syntax in 2.7 (including set literals, set comprehensions,
and dictionary comprehensions).
- Make sure class names don't get bound until after class definition.
0.4.0 (2009-11-25):
- Fix reporting for certain SyntaxErrors which lack line number
information.
- Check for syntax errors more rigorously.
- Support checking names used with the class decorator syntax in versions
of Python which have it.
- Detect local variables which are bound but never used.
- Handle permission errors when trying to read source files.
- Handle problems with the encoding of source files.
- Support importing dotted names so as not to incorrectly report them as
redefined unused names.
- Support all forms of the with statement.
- Consider static ``__all__`` definitions and avoid reporting unused names
if the names are listed there.
- Fix incorrect checking of class names with respect to the names of their
bases in the class statement.
- Support the ``__path__`` global in ``__init__.py``.
0.3.0 (2009-01-30):
- Display more informative SyntaxError messages.
- Don't hang flymake with unmatched triple quotes (only report a single
line of source for a multiline syntax error).
- Recognize ``__builtins__`` as a defined name.
- Improve pyflakes support for python versions 2.3-2.5
- Support for if-else expressions and with statements.
- Warn instead of error on non-existent file paths.
- Check for ``__future__`` imports after other statements.
- Add reporting for some types of import shadowing.
- Improve reporting of unbound locals
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633552776.3214808
pyflakes-2.4.0/PKG-INFO 0000644 0001750 0001750 00000007472 00000000000 016135 0 ustar 00asottile asottile 0000000 0000000 Metadata-Version: 2.1
Name: pyflakes
Version: 2.4.0
Summary: passive checker of Python programs
Home-page: https://github.com/PyCQA/pyflakes
Author: A lot of people
Author-email: code-quality@python.org
License: MIT
Platform: UNKNOWN
Classifier: Development Status :: 6 - Mature
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development
Classifier: Topic :: Utilities
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
License-File: LICENSE
========
Pyflakes
========
A simple program which checks Python source files for errors.
Pyflakes analyzes programs and detects various errors. It works by
parsing the source file, not importing it, so it is safe to use on
modules with side effects. It's also much faster.
It is `available on PyPI `_
and it supports all active versions of Python: 2.7 and 3.4 to 3.8.
Installation
------------
It can be installed with::
$ pip install --upgrade pyflakes
Useful tips:
* Be sure to install it for a version of Python which is compatible
with your codebase: for Python 2, ``pip2 install pyflakes`` and for
Python3, ``pip3 install pyflakes``.
* 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.
Design Principles
-----------------
Pyflakes makes a simple promise: it will never complain about style,
and it will try very, very hard to never emit false positives.
Pyflakes is also faster than Pylint_. This is
largely because Pyflakes only examines the syntax tree of each file
individually. As a consequence, Pyflakes is more limited in the
types of things it can check.
If you like Pyflakes but also want stylistic checks, you want
flake8_, which combines
Pyflakes with style checks against
`PEP 8`_ and adds
per-project configuration ability.
Mailing-list
------------
Share your feedback and ideas: `subscribe to the mailing-list
`_
Contributing
------------
Issues are tracked on `GitHub `_.
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 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.
All changes should include tests and pass flake8_.
.. image:: https://github.com/PyCQA/pyflakes/workflows/Test/badge.svg
:target: https://github.com/PyCQA/pyflakes/actions
:alt: GitHub Actions build status
.. _Pylint: https://www.pylint.org/
.. _flake8: https://pypi.org/project/flake8/
.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/
.. _`rebase your changes`: https://git-scm.com/book/en/v2/Git-Branching-Rebasing
.. _`GitHub pull request`: https://github.com/PyCQA/pyflakes/pulls
Changelog
---------
Please see `NEWS.rst `_.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633476446.0
pyflakes-2.4.0/README.rst 0000644 0001750 0001750 00000005300 00000000000 016513 0 ustar 00asottile asottile 0000000 0000000 ========
Pyflakes
========
A simple program which checks Python source files for errors.
Pyflakes analyzes programs and detects various errors. It works by
parsing the source file, not importing it, so it is safe to use on
modules with side effects. It's also much faster.
It is `available on PyPI `_
and it supports all active versions of Python: 2.7 and 3.4 to 3.8.
Installation
------------
It can be installed with::
$ pip install --upgrade pyflakes
Useful tips:
* Be sure to install it for a version of Python which is compatible
with your codebase: for Python 2, ``pip2 install pyflakes`` and for
Python3, ``pip3 install pyflakes``.
* 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.
Design Principles
-----------------
Pyflakes makes a simple promise: it will never complain about style,
and it will try very, very hard to never emit false positives.
Pyflakes is also faster than Pylint_. This is
largely because Pyflakes only examines the syntax tree of each file
individually. As a consequence, Pyflakes is more limited in the
types of things it can check.
If you like Pyflakes but also want stylistic checks, you want
flake8_, which combines
Pyflakes with style checks against
`PEP 8`_ and adds
per-project configuration ability.
Mailing-list
------------
Share your feedback and ideas: `subscribe to the mailing-list
`_
Contributing
------------
Issues are tracked on `GitHub `_.
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 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.
All changes should include tests and pass flake8_.
.. image:: https://github.com/PyCQA/pyflakes/workflows/Test/badge.svg
:target: https://github.com/PyCQA/pyflakes/actions
:alt: GitHub Actions build status
.. _Pylint: https://www.pylint.org/
.. _flake8: https://pypi.org/project/flake8/
.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/
.. _`rebase your changes`: https://git-scm.com/book/en/v2/Git-Branching-Rebasing
.. _`GitHub pull request`: https://github.com/PyCQA/pyflakes/pulls
Changelog
---------
Please see `NEWS.rst `_.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633552776.3134809
pyflakes-2.4.0/bin/ 0000755 0001750 0001750 00000000000 00000000000 015576 5 ustar 00asottile asottile 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/bin/pyflakes 0000755 0001750 0001750 00000000110 00000000000 017332 0 ustar 00asottile asottile 0000000 0000000 #!/usr/bin/env python
from pyflakes.scripts.pyflakes import main
main()
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633552776.3134809
pyflakes-2.4.0/pyflakes/ 0000755 0001750 0001750 00000000000 00000000000 016644 5 ustar 00asottile asottile 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633552732.0
pyflakes-2.4.0/pyflakes/__init__.py 0000644 0001750 0001750 00000000026 00000000000 020753 0 ustar 00asottile asottile 0000000 0000000 __version__ = '2.4.0'
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/__main__.py 0000644 0001750 0001750 00000000151 00000000000 020733 0 ustar 00asottile asottile 0000000 0000000 from pyflakes.api import main
# python -m pyflakes
if __name__ == '__main__':
main(prog='pyflakes')
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/api.py 0000644 0001750 0001750 00000014767 00000000000 020006 0 ustar 00asottile asottile 0000000 0000000 """
API for the command-line I{pyflakes} tool.
"""
from __future__ import with_statement
import ast
import os
import platform
import re
import sys
from pyflakes import checker, __version__
from pyflakes import reporter as modReporter
__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main']
PYTHON_SHEBANG_REGEX = re.compile(br'^#!.*\bpython([23](\.\d+)?|w)?[dmu]?\s')
def check(codeString, filename, reporter=None):
"""
Check the Python source given by C{codeString} for flakes.
@param codeString: The Python source to check.
@type codeString: C{str}
@param filename: The name of the file the source came from, used to report
errors.
@type filename: C{str}
@param reporter: A L{Reporter} instance, where errors and warnings will be
reported.
@return: The number of warnings emitted.
@rtype: C{int}
"""
if reporter is None:
reporter = modReporter._makeDefaultReporter()
# First, compile into an AST and handle syntax errors.
try:
tree = ast.parse(codeString, filename=filename)
except SyntaxError:
value = sys.exc_info()[1]
msg = value.args[0]
(lineno, offset, text) = value.lineno, value.offset, value.text
if checker.PYPY:
if text is None:
lines = codeString.splitlines()
if len(lines) >= lineno:
text = lines[lineno - 1]
if sys.version_info >= (3, ) and isinstance(text, bytes):
try:
text = text.decode('ascii')
except UnicodeDecodeError:
text = None
offset -= 1
# If there's an encoding problem with the file, the text is None.
if text is None:
# Avoid using msg, since for the only known case, it contains a
# bogus message that claims the encoding the file declared was
# unknown.
reporter.unexpectedError(filename, 'problem decoding source')
else:
reporter.syntaxError(filename, msg, lineno, offset, text)
return 1
except Exception:
reporter.unexpectedError(filename, 'problem decoding source')
return 1
# Okay, it's syntactically valid. Now check it.
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)
return len(w.messages)
def checkPath(filename, reporter=None):
"""
Check the given path, printing out any warnings detected.
@param reporter: A L{Reporter} instance, where errors and warnings will be
reported.
@return: the number of warnings printed
"""
if reporter is None:
reporter = modReporter._makeDefaultReporter()
try:
with open(filename, 'rb') as f:
codestr = f.read()
except IOError:
msg = sys.exc_info()[1]
reporter.unexpectedError(filename, msg.args[1])
return 1
return check(codestr, filename, reporter)
def isPythonFile(filename):
"""Return True if filename points to a Python file."""
if filename.endswith('.py'):
return True
# Avoid obvious Emacs backup files
if filename.endswith("~"):
return False
max_bytes = 128
try:
with open(filename, 'rb') as f:
text = f.read(max_bytes)
if not text:
return False
except IOError:
return False
return PYTHON_SHEBANG_REGEX.match(text)
def iterSourceCode(paths):
"""
Iterate over all Python source files in C{paths}.
@param paths: A list of paths. Directories will be recursed into and
any .py files found will be yielded. Any non-directories will be
yielded as-is.
"""
for path in paths:
if os.path.isdir(path):
for dirpath, dirnames, filenames in os.walk(path):
for filename in filenames:
full_path = os.path.join(dirpath, filename)
if isPythonFile(full_path):
yield full_path
else:
yield path
def checkRecursive(paths, reporter):
"""
Recursively check all source files in C{paths}.
@param paths: A list of paths to Python source files and directories
containing Python source files.
@param reporter: A L{Reporter} where all of the warnings and errors
will be reported to.
@return: The number of warnings found.
"""
warnings = 0
for sourcePath in iterSourceCode(paths):
warnings += checkPath(sourcePath, reporter)
return warnings
def _exitOnSignal(sigName, message):
"""Handles a signal with sys.exit.
Some of these signals (SIGPIPE, for example) don't exist or are invalid on
Windows. So, ignore errors that might arise.
"""
import signal
try:
sigNumber = getattr(signal, sigName)
except AttributeError:
# the signal constants defined in the signal module are defined by
# whether the C library supports them or not. So, SIGPIPE might not
# even be defined.
return
def handler(sig, f):
sys.exit(message)
try:
signal.signal(sigNumber, handler)
except ValueError:
# It's also possible the signal is defined, but then it's invalid. In
# this case, signal.signal raises ValueError.
pass
def _get_version():
"""
Retrieve and format package version along with python version & OS used
"""
return ('%s Python %s on %s' %
(__version__, platform.python_version(), platform.system()))
def main(prog=None, args=None):
"""Entry point for the script "pyflakes"."""
import argparse
# Handle "Keyboard Interrupt" and "Broken pipe" gracefully
_exitOnSignal('SIGINT', '... stopped')
_exitOnSignal('SIGPIPE', 1)
parser = argparse.ArgumentParser(prog=prog,
description='Check Python source files for errors')
parser.add_argument('-V', '--version', action='version', version=_get_version())
parser.add_argument('path', nargs='*',
help='Path(s) of Python file(s) to check. STDIN if not given.')
args = parser.parse_args(args=args).path
reporter = modReporter._makeDefaultReporter()
if args:
warnings = checkRecursive(args, reporter)
else:
warnings = check(sys.stdin.read(), '', reporter)
raise SystemExit(warnings > 0)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633476446.0
pyflakes-2.4.0/pyflakes/checker.py 0000644 0001750 0001750 00000245237 00000000000 020637 0 ustar 00asottile asottile 0000000 0000000 """
Main module.
Implement the central Checker class.
Also, it models the Bindings and Scopes.
"""
import __future__
import ast
import bisect
import collections
import contextlib
import doctest
import functools
import os
import re
import string
import sys
import tokenize
from pyflakes import messages
PY2 = sys.version_info < (3, 0)
PY35_PLUS = sys.version_info >= (3, 5) # Python 3.5 and above
PY36_PLUS = sys.version_info >= (3, 6) # Python 3.6 and above
PY38_PLUS = sys.version_info >= (3, 8)
try:
sys.pypy_version_info
PYPY = True
except AttributeError:
PYPY = False
builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins'))
parse_format_string = string.Formatter().parse
if PY2:
tokenize_tokenize = tokenize.generate_tokens
else:
tokenize_tokenize = tokenize.tokenize
if PY2:
def getNodeType(node_class):
# workaround str.upper() which is locale-dependent
return str(unicode(node_class.__name__).upper())
def get_raise_argument(node):
return node.type
else:
def getNodeType(node_class):
return node_class.__name__.upper()
def get_raise_argument(node):
return node.exc
# Silence `pyflakes` from reporting `undefined name 'unicode'` in Python 3.
unicode = str
# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally)
if PY2:
def getAlternatives(n):
if isinstance(n, (ast.If, ast.TryFinally)):
return [n.body]
if isinstance(n, ast.TryExcept):
return [n.body + n.orelse] + [[hdl] for hdl in n.handlers]
else:
def getAlternatives(n):
if isinstance(n, ast.If):
return [n.body]
if isinstance(n, ast.Try):
return [n.body + n.orelse] + [[hdl] for hdl in n.handlers]
if PY35_PLUS:
FOR_TYPES = (ast.For, ast.AsyncFor)
LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor)
FUNCTION_TYPES = (ast.FunctionDef, ast.AsyncFunctionDef)
else:
FOR_TYPES = (ast.For,)
LOOP_TYPES = (ast.While, ast.For)
FUNCTION_TYPES = (ast.FunctionDef,)
if PY36_PLUS:
ANNASSIGN_TYPES = (ast.AnnAssign,)
else:
ANNASSIGN_TYPES = ()
if PY38_PLUS:
def _is_singleton(node): # type: (ast.AST) -> bool
return (
isinstance(node, ast.Constant) and
isinstance(node.value, (bool, type(Ellipsis), type(None)))
)
elif not PY2:
def _is_singleton(node): # type: (ast.AST) -> bool
return isinstance(node, (ast.NameConstant, ast.Ellipsis))
else:
def _is_singleton(node): # type: (ast.AST) -> bool
return (
isinstance(node, ast.Name) and
node.id in {'True', 'False', 'Ellipsis', 'None'}
)
def _is_tuple_constant(node): # type: (ast.AST) -> bool
return (
isinstance(node, ast.Tuple) and
all(_is_constant(elt) for elt in node.elts)
)
if PY38_PLUS:
def _is_constant(node):
return isinstance(node, ast.Constant) or _is_tuple_constant(node)
else:
_const_tps = (ast.Str, ast.Num)
if not PY2:
_const_tps += (ast.Bytes,)
def _is_constant(node):
return (
isinstance(node, _const_tps) or
_is_singleton(node) or
_is_tuple_constant(node)
)
def _is_const_non_singleton(node): # type: (ast.AST) -> bool
return _is_constant(node) and not _is_singleton(node)
def _is_name_or_attr(node, name): # type: (ast.Ast, str) -> bool
return (
(isinstance(node, ast.Name) and node.id == name) or
(isinstance(node, ast.Attribute) and node.attr == name)
)
# 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 + r'ignore([{}]|$)'.format(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*)')
PRECISION_RE = re.compile(r'(?:\.(?:\*|\d*))?')
LENGTH_RE = re.compile('[hlL]?')
# https://docs.python.org/3/library/stdtypes.html#old-string-formatting
VALID_CONVERSIONS = frozenset('diouxXeEfFgGcrsa%')
def _must_match(regex, string, pos):
# type: (Pattern[str], str, int) -> Match[str]
match = regex.match(string, pos)
assert match is not None
return match
def parse_percent_format(s): # type: (str) -> Tuple[PercentFormat, ...]
"""Parses the string component of a `'...' % ...` format call
Copied from https://github.com/asottile/pyupgrade at v1.20.1
"""
def _parse_inner():
# type: () -> Generator[PercentFormat, None, None]
string_start = 0
string_end = 0
in_fmt = False
i = 0
while i < len(s):
if not in_fmt:
try:
i = s.index('%', i)
except ValueError: # no more % fields!
yield s[string_start:], None
return
else:
string_end = i
i += 1
in_fmt = True
else:
key_match = MAPPING_KEY_RE.match(s, i)
if key_match:
key = key_match.group(1) # type: Optional[str]
i = key_match.end()
else:
key = None
conversion_flag_match = _must_match(CONVERSION_FLAG_RE, s, i)
conversion_flag = conversion_flag_match.group() or None
i = conversion_flag_match.end()
width_match = _must_match(WIDTH_RE, s, i)
width = width_match.group() or None
i = width_match.end()
precision_match = _must_match(PRECISION_RE, s, i)
precision = precision_match.group() or None
i = precision_match.end()
# length modifier is ignored
i = _must_match(LENGTH_RE, s, i).end()
try:
conversion = s[i]
except IndexError:
raise ValueError('end-of-string while parsing format')
i += 1
fmt = (key, conversion_flag, width, precision, conversion)
yield s[string_start:string_end], fmt
in_fmt = False
string_start = i
if in_fmt:
raise ValueError('end-of-string while parsing format')
return tuple(_parse_inner())
class _FieldsOrder(dict):
"""Fix order of AST node fields."""
def _get_fields(self, node_class):
# handle iter before target, and generators before element
fields = node_class._fields
if 'iter' in fields:
key_first = 'iter'.find
elif 'generators' in fields:
key_first = 'generators'.find
else:
key_first = 'value'.find
return tuple(sorted(fields, key=key_first, reverse=True))
def __missing__(self, node_class):
self[node_class] = fields = self._get_fields(node_class)
return fields
def counter(items):
"""
Simplest required implementation of collections.Counter. Required as 2.6
does not have Counter in collections.
"""
results = {}
for item in items:
results[item] = results.get(item, 0) + 1
return results
def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()):
"""
Yield all direct child nodes of *node*, that is, all fields that
are nodes and all items of fields that are lists of nodes.
:param node: AST node to be iterated upon
:param omit: String or tuple of strings denoting the
attributes of the node to be omitted from
further parsing
:param _fields_order: Order of AST node fields
"""
for name in _fields_order[node.__class__]:
if omit and name in omit:
continue
field = getattr(node, name, None)
if isinstance(field, ast.AST):
yield field
elif isinstance(field, list):
for item in field:
if isinstance(item, ast.AST):
yield item
def convert_to_value(item):
if isinstance(item, ast.Str):
return item.s
elif hasattr(ast, 'Bytes') and isinstance(item, ast.Bytes):
return item.s
elif isinstance(item, ast.Tuple):
return tuple(convert_to_value(i) for i in item.elts)
elif isinstance(item, ast.Num):
return item.n
elif isinstance(item, ast.Name):
result = VariableKey(item=item)
constants_lookup = {
'True': True,
'False': False,
'None': None,
}
return constants_lookup.get(
result.name,
result,
)
elif (not PY2) and isinstance(item, ast.NameConstant):
# None, True, False are nameconstants in python3, but names in 2
return item.value
else:
return UnhandledKeyType()
def is_notimplemented_name_node(node):
return isinstance(node, ast.Name) and getNodeName(node) == 'NotImplemented'
class Binding(object):
"""
Represents the binding of a value to a name.
The checker uses this to keep track of which names have been bound and
which names have not. See L{Assignment} for a special type of binding that
is checked with stricter rules.
@ivar used: pair of (L{Scope}, node) indicating the scope and
the node that this binding was last used.
"""
def __init__(self, name, source):
self.name = name
self.source = source
self.used = False
def __str__(self):
return self.name
def __repr__(self):
return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
self.name,
self.source.lineno,
id(self))
def redefines(self, other):
return isinstance(other, Definition) and self.name == other.name
class Definition(Binding):
"""
A binding that defines a function or a class.
"""
class Builtin(Definition):
"""A definition created for all Python builtins."""
def __init__(self, name):
super(Builtin, self).__init__(name, None)
def __repr__(self):
return '<%s object %r at 0x%x>' % (self.__class__.__name__,
self.name,
id(self))
class UnhandledKeyType(object):
"""
A dictionary key of a type that we cannot or do not check for duplicates.
"""
class VariableKey(object):
"""
A dictionary key which is a variable.
@ivar item: The variable AST object.
"""
def __init__(self, item):
self.name = item.id
def __eq__(self, compare):
return (
compare.__class__ == self.__class__ and
compare.name == self.name
)
def __hash__(self):
return hash(self.name)
class Importation(Definition):
"""
A binding created by an import statement.
@ivar fullName: The complete name given to the import statement,
possibly including multiple dotted components.
@type fullName: C{str}
"""
def __init__(self, name, source, full_name=None):
self.fullName = full_name or name
self.redefined = []
super(Importation, self).__init__(name, source)
def redefines(self, other):
if isinstance(other, SubmoduleImportation):
# See note in SubmoduleImportation about RedefinedWhileUnused
return self.fullName == other.fullName
return isinstance(other, Definition) and self.name == other.name
def _has_alias(self):
"""Return whether importation needs an as clause."""
return not self.fullName.split('.')[-1] == self.name
@property
def source_statement(self):
"""Generate a source statement equivalent to the import."""
if self._has_alias():
return 'import %s as %s' % (self.fullName, self.name)
else:
return 'import %s' % self.fullName
def __str__(self):
"""Return import full name with alias."""
if self._has_alias():
return self.fullName + ' as ' + self.name
else:
return self.fullName
class SubmoduleImportation(Importation):
"""
A binding created by a submodule import statement.
A submodule import is a special case where the root module is implicitly
imported, without an 'as' clause, and the submodule is also imported.
Python does not restrict which attributes of the root module may be used.
This class is only used when the submodule import is without an 'as' clause.
pyflakes handles this case by registering the root module name in the scope,
allowing any attribute of the root module to be accessed.
RedefinedWhileUnused is suppressed in `redefines` unless the submodule
name is also the same, to avoid false positives.
"""
def __init__(self, name, source):
# A dot should only appear in the name when it is a submodule import
assert '.' in name and (not source or isinstance(source, ast.Import))
package_name = name.split('.')[0]
super(SubmoduleImportation, self).__init__(package_name, source)
self.fullName = name
def redefines(self, other):
if isinstance(other, Importation):
return self.fullName == other.fullName
return super(SubmoduleImportation, self).redefines(other)
def __str__(self):
return self.fullName
@property
def source_statement(self):
return 'import ' + self.fullName
class ImportationFrom(Importation):
def __init__(self, name, source, module, real_name=None):
self.module = module
self.real_name = real_name or name
if module.endswith('.'):
full_name = module + self.real_name
else:
full_name = module + '.' + self.real_name
super(ImportationFrom, self).__init__(name, source, full_name)
def __str__(self):
"""Return import full name with alias."""
if self.real_name != self.name:
return self.fullName + ' as ' + self.name
else:
return self.fullName
@property
def source_statement(self):
if self.real_name != self.name:
return 'from %s import %s as %s' % (self.module,
self.real_name,
self.name)
else:
return 'from %s import %s' % (self.module, self.name)
class StarImportation(Importation):
"""A binding created by a 'from x import *' statement."""
def __init__(self, name, source):
super(StarImportation, self).__init__('*', source)
# Each star importation needs a unique name, and
# may not be the module name otherwise it will be deemed imported
self.name = name + '.*'
self.fullName = name
@property
def source_statement(self):
return 'from ' + self.fullName + ' import *'
def __str__(self):
# When the module ends with a ., avoid the ambiguous '..*'
if self.fullName.endswith('.'):
return self.source_statement
else:
return self.name
class FutureImportation(ImportationFrom):
"""
A binding created by a from `__future__` import statement.
`__future__` imports are implicitly used.
"""
def __init__(self, name, source, scope):
super(FutureImportation, self).__init__(name, source, '__future__')
self.used = (scope, source)
class Argument(Binding):
"""
Represents binding a name as an argument.
"""
class Assignment(Binding):
"""
Represents binding a name with an explicit assignment.
The checker will raise warnings for any Assignment that isn't used. Also,
the checker does not consider assignments in tuple/list unpacking to be
Assignments, rather it treats them as simple Bindings.
"""
class Annotation(Binding):
"""
Represents binding a name to a type without an associated value.
As long as this name is not assigned a value in another binding, it is considered
undefined for most purposes. One notable exception is using the name as a type
annotation.
"""
def redefines(self, other):
"""An Annotation doesn't define any name, so it cannot redefine one."""
return False
class FunctionDefinition(Definition):
pass
class ClassDefinition(Definition):
pass
class ExportBinding(Binding):
"""
A binding created by an C{__all__} assignment. If the names in the list
can be determined statically, they will be treated as names for export and
additional checking applied to them.
The only recognized C{__all__} assignment via list/tuple concatenation is in the
following format:
__all__ = ['a'] + ['b'] + ['c']
Names which are imported and not otherwise used but appear in the value of
C{__all__} will not have an unused import warning reported for them.
"""
def __init__(self, name, source, scope):
if '__all__' in scope and isinstance(source, ast.AugAssign):
self.names = list(scope['__all__'].names)
else:
self.names = []
def _add_to_names(container):
for node in container.elts:
if isinstance(node, ast.Str):
self.names.append(node.s)
if isinstance(source.value, (ast.List, ast.Tuple)):
_add_to_names(source.value)
# If concatenating lists or tuples
elif isinstance(source.value, ast.BinOp):
currentValue = source.value
while isinstance(currentValue.right, (ast.List, ast.Tuple)):
left = currentValue.left
right = currentValue.right
_add_to_names(right)
# If more lists are being added
if isinstance(left, ast.BinOp):
currentValue = left
# If just two lists are being added
elif isinstance(left, (ast.List, ast.Tuple)):
_add_to_names(left)
# All lists accounted for - done
break
# If not list concatenation
else:
break
super(ExportBinding, self).__init__(name, source)
class Scope(dict):
importStarred = False # set to True when import * is found
def __repr__(self):
scope_cls = self.__class__.__name__
return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self))
class ClassScope(Scope):
pass
class FunctionScope(Scope):
"""
I represent a name scope for a function.
@ivar globals: Names declared 'global' in this function.
"""
usesLocals = False
alwaysUsed = {'__tracebackhide__', '__traceback_info__',
'__traceback_supplement__'}
def __init__(self):
super(FunctionScope, self).__init__()
# Simplify: manage the special locals as globals
self.globals = self.alwaysUsed.copy()
self.returnValue = None # First non-empty return
self.isGenerator = False # Detect a generator
def unusedAssignments(self):
"""
Return a generator for the assignments which have not been used.
"""
for name, binding in self.items():
if (not binding.used and
name != '_' and # see issue #202
name not in self.globals and
not self.usesLocals and
isinstance(binding, Assignment)):
yield name, binding
class GeneratorScope(Scope):
pass
class ModuleScope(Scope):
"""Scope for a module."""
_futures_allowed = True
_annotations_future_enabled = False
class DoctestScope(ModuleScope):
"""Scope for a doctest."""
class DummyNode(object):
"""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()
# Globally defined names which are not attributes of the builtins module, or
# are only present on some platforms.
_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError']
# module scope annotation will store in `__annotations__`, see also PEP 526.
if PY36_PLUS:
_MAGIC_GLOBALS.append('__annotations__')
def getNodeName(node):
# Returns node.id, or node.name, or None
if hasattr(node, 'id'): # One of the many nodes with an id
return node.id
if hasattr(node, 'name'): # an ExceptHandler node
return node.name
if hasattr(node, 'rest'): # a MatchMapping node
return node.rest
TYPING_MODULES = frozenset(('typing', 'typing_extensions'))
def _is_typing_helper(node, is_name_match_fn, scope_stack):
"""
Internal helper to determine whether or not something is a member of a
typing module. This is used as part of working out whether we are within a
type annotation context.
Note: you probably don't want to use this function directly. Instead see the
utils below which wrap it (`_is_typing` and `_is_any_typing_member`).
"""
def _bare_name_is_attr(name):
for scope in reversed(scope_stack):
if name in scope:
return (
isinstance(scope[name], ImportationFrom) and
scope[name].module in TYPING_MODULES and
is_name_match_fn(scope[name].real_name)
)
return False
def _module_scope_is_typing(name):
for scope in reversed(scope_stack):
if name in scope:
return (
isinstance(scope[name], Importation) and
scope[name].fullName in TYPING_MODULES
)
return False
return (
(
isinstance(node, ast.Name) and
_bare_name_is_attr(node.id)
) or (
isinstance(node, ast.Attribute) and
isinstance(node.value, ast.Name) and
_module_scope_is_typing(node.value.id) and
is_name_match_fn(node.attr)
)
)
def _is_typing(node, typing_attr, scope_stack):
"""
Determine whether `node` represents the member of a typing module specified
by `typing_attr`.
This is used as part of working out whether we are within a type annotation
context.
"""
return _is_typing_helper(node, lambda x: x == typing_attr, scope_stack)
def _is_any_typing_member(node, scope_stack):
"""
Determine whether `node` represents any member of a typing module.
This is used as part of working out whether we are within a type annotation
context.
"""
return _is_typing_helper(node, lambda x: True, scope_stack)
def is_typing_overload(value, scope_stack):
return (
isinstance(value.source, FUNCTION_TYPES) and
any(
_is_typing(dec, 'overload', scope_stack)
for dec in value.source.decorator_list
)
)
class AnnotationState:
NONE = 0
STRING = 1
BARE = 2
def in_annotation(func):
@functools.wraps(func)
def in_annotation_func(self, *args, **kwargs):
with self._enter_annotation():
return func(self, *args, **kwargs)
return in_annotation_func
def in_string_annotation(func):
@functools.wraps(func)
def in_annotation_func(self, *args, **kwargs):
with self._enter_annotation(AnnotationState.STRING):
return func(self, *args, **kwargs)
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 = [] # type: List[int]
self.typeable_nodes = {} # type: Dict[int, ast.AST]
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(object):
"""
I check the cleanliness and sanity of Python code.
@ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements
of the list are two-tuples. The first element is the callable passed
to L{deferFunction}. The second element is a copy of the scope stack
at the time L{deferFunction} was called.
@ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for
callables which are deferred assignment checks.
"""
_ast_node_scope = {
ast.Module: ModuleScope,
ast.ClassDef: ClassScope,
ast.FunctionDef: FunctionScope,
ast.Lambda: FunctionScope,
ast.ListComp: GeneratorScope,
ast.SetComp: GeneratorScope,
ast.GeneratorExp: GeneratorScope,
ast.DictComp: GeneratorScope,
}
if PY35_PLUS:
_ast_node_scope[ast.AsyncFunctionDef] = FunctionScope
nodeDepth = 0
offset = None
_in_annotation = AnnotationState.NONE
_in_deferred = False
builtIns = set(builtin_vars).union(_MAGIC_GLOBALS)
_customBuiltIns = os.environ.get('PYFLAKES_BUILTINS')
if _customBuiltIns:
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 = {}
self._deferredFunctions = []
self._deferredAssignments = []
self.deadScopes = []
self.messages = []
self.filename = filename
if builtins:
self.builtIns = self.builtIns.union(builtins)
self.withDoctest = withDoctest
try:
self.scopeStack = [Checker._ast_node_scope[type(tree)]()]
except KeyError:
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)
self._in_deferred = True
self.runDeferred(self._deferredFunctions)
# Set _deferredFunctions to None so that deferFunction will fail
# noisily if called after we've run through the deferred functions.
self._deferredFunctions = None
self.runDeferred(self._deferredAssignments)
# Set _deferredAssignments to None so that deferAssignment will fail
# noisily if called after we've run through the deferred assignments.
self._deferredAssignments = None
del self.scopeStack[1:]
self.popScope()
self.checkDeadScopes()
def deferFunction(self, callable):
"""
Schedule a function handler to be called just before completion.
This is used for handling function bodies, which must be deferred
because code later in the file might modify the global scope. When
`callable` is called, the scope at the time this is called will be
restored, however it will contain any new bindings added to it.
"""
self._deferredFunctions.append((callable, self.scopeStack[:], self.offset))
def deferAssignment(self, callable):
"""
Schedule an assignment handler to be called just after deferred
function handlers.
"""
self._deferredAssignments.append((callable, self.scopeStack[:], self.offset))
def runDeferred(self, deferred):
"""
Run the callables in C{deferred} using their associated scope stack.
"""
for handler, scope, offset in deferred:
self.scopeStack = scope
self.offset = offset
handler()
def _in_doctest(self):
return (len(self.scopeStack) >= 2 and
isinstance(self.scopeStack[1], DoctestScope))
@property
def futuresAllowed(self):
if not all(isinstance(scope, ModuleScope)
for scope in self.scopeStack):
return False
return self.scope._futures_allowed
@futuresAllowed.setter
def futuresAllowed(self, value):
assert value is False
if isinstance(self.scope, ModuleScope):
self.scope._futures_allowed = False
@property
def annotationsFutureEnabled(self):
scope = self.scopeStack[0]
if not isinstance(scope, ModuleScope):
return False
return scope._annotations_future_enabled
@annotationsFutureEnabled.setter
def annotationsFutureEnabled(self, value):
assert value is True
assert isinstance(self.scope, ModuleScope)
self.scope._annotations_future_enabled = True
@property
def scope(self):
return self.scopeStack[-1]
def popScope(self):
self.deadScopes.append(self.scopeStack.pop())
def checkDeadScopes(self):
"""
Look at scopes which have been fully examined and report names in them
which were imported but unused.
"""
for scope in self.deadScopes:
# imports in classes are public members
if isinstance(scope, ClassScope):
continue
all_binding = scope.get('__all__')
if all_binding and not isinstance(all_binding, ExportBinding):
all_binding = None
if all_binding:
all_names = set(all_binding.names)
undefined = [
name for name in all_binding.names
if name not in scope
]
else:
all_names = undefined = []
if undefined:
if not scope.importStarred and \
os.path.basename(self.filename) != '__init__.py':
# Look for possible mistakes in the export list
for name in undefined:
self.report(messages.UndefinedExport,
scope['__all__'].source, name)
# mark all import '*' as used by the undefined in __all__
if scope.importStarred:
from_list = []
for binding in scope.values():
if isinstance(binding, StarImportation):
binding.used = all_binding
from_list.append(binding.fullName)
# report * usage, with a list of possible sources
from_list = ', '.join(sorted(from_list))
for name in undefined:
self.report(messages.ImportStarUsage,
scope['__all__'].source, name, from_list)
# Look for imported names that aren't used.
for value in scope.values():
if isinstance(value, Importation):
used = value.used or value.name in all_names
if not used:
messg = messages.UnusedImport
self.report(messg, value.source, str(value))
for node in value.redefined:
if isinstance(self.getParent(node), FOR_TYPES):
messg = messages.ImportShadowedByLoopVar
elif used:
continue
else:
messg = messages.RedefinedWhileUnused
self.report(messg, node, value.name, value.source)
def pushScope(self, scopeClass=FunctionScope):
self.scopeStack.append(scopeClass())
def report(self, messageClass, *args, **kwargs):
self.messages.append(messageClass(self.filename, *args, **kwargs))
def getParent(self, node):
# Lookup the first parent which is not Tuple, List or Starred
while True:
node = node._pyflakes_parent
if not hasattr(node, 'elts') and not hasattr(node, 'ctx'):
return node
def getCommonAncestor(self, lnode, rnode, stop):
if (
stop in (lnode, rnode) or
not (
hasattr(lnode, '_pyflakes_parent') and
hasattr(rnode, '_pyflakes_parent')
)
):
return None
if lnode is rnode:
return lnode
if (lnode._pyflakes_depth > rnode._pyflakes_depth):
return self.getCommonAncestor(lnode._pyflakes_parent, rnode, stop)
if (lnode._pyflakes_depth < rnode._pyflakes_depth):
return self.getCommonAncestor(lnode, rnode._pyflakes_parent, stop)
return self.getCommonAncestor(
lnode._pyflakes_parent,
rnode._pyflakes_parent,
stop,
)
def descendantOf(self, node, ancestors, stop):
for a in ancestors:
if self.getCommonAncestor(node, a, stop):
return True
return False
def _getAncestor(self, node, ancestor_type):
parent = node
while True:
if parent is self.root:
return None
parent = self.getParent(parent)
if isinstance(parent, ancestor_type):
return parent
def getScopeNode(self, node):
return self._getAncestor(node, tuple(Checker._ast_node_scope.keys()))
def differentForks(self, lnode, rnode):
"""True, if lnode and rnode are located on different forks of IF/TRY"""
ancestor = self.getCommonAncestor(lnode, rnode, self.root)
parts = getAlternatives(ancestor)
if parts:
for items in parts:
if self.descendantOf(lnode, items, ancestor) ^ \
self.descendantOf(rnode, items, ancestor):
return True
return False
def addBinding(self, node, value):
"""
Called when a binding is altered.
- `node` is the statement responsible for the change
- `value` is the new value, a Binding instance
"""
# assert value.source in (node, node._pyflakes_parent):
for scope in self.scopeStack[::-1]:
if value.name in scope:
break
existing = scope.get(value.name)
if (existing and not isinstance(existing, Builtin) and
not self.differentForks(node, existing.source)):
parent_stmt = self.getParent(value.source)
if isinstance(existing, Importation) and isinstance(parent_stmt, FOR_TYPES):
self.report(messages.ImportShadowedByLoopVar,
node, value.name, existing.source)
elif scope is self.scope:
if (isinstance(parent_stmt, ast.comprehension) and
not isinstance(self.getParent(existing.source),
(FOR_TYPES, ast.comprehension))):
self.report(messages.RedefinedInListComp,
node, value.name, existing.source)
elif not existing.used and value.redefines(existing):
if value.name != '_' or isinstance(existing, Importation):
if not is_typing_overload(existing, self.scopeStack):
self.report(messages.RedefinedWhileUnused,
node, value.name, existing.source)
elif isinstance(existing, Importation) and value.redefines(existing):
existing.redefined.append(node)
if value.name in self.scope:
# then assume the rebound name is used as a global or within a loop
value.used = self.scope[value.name].used
# don't treat annotations as assignments if there is an existing value
# in scope
if value.name not in self.scope or not isinstance(value, Annotation):
self.scope[value.name] = value
def _unknown_handler(self, node):
# this environment variable configures whether to error on unknown
# ast types.
#
# this is silent by default but the error is enabled for the pyflakes
# testsuite.
#
# this allows new syntax to be added to python without *requiring*
# changes from the pyflakes side. but will still produce an error
# in the pyflakes testsuite (so more specific handling can be added if
# needed).
if os.environ.get('PYFLAKES_ERROR_UNKNOWN'):
raise NotImplementedError('Unexpected type: {}'.format(type(node)))
else:
self.handleChildren(node)
def getNodeHandler(self, node_class):
try:
return self._nodeHandlers[node_class]
except KeyError:
nodeType = getNodeType(node_class)
self._nodeHandlers[node_class] = handler = getattr(
self, nodeType, self._unknown_handler,
)
return handler
def handleNodeLoad(self, node):
name = getNodeName(node)
if not name:
return
in_generators = None
importStarred = None
# try enclosing function scopes and global scope
for scope in self.scopeStack[-1::-1]:
if isinstance(scope, ClassScope):
if not PY2 and name == '__class__':
return
elif in_generators is False:
# only generators used in a class scope can access the
# names of the class. this is skipped during the first
# iteration
continue
binding = scope.get(name, None)
if isinstance(binding, Annotation) and not self._in_postponed_annotation:
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)
try:
scope[name].used = (self.scope, node)
# if the name of SubImportation is same as
# alias of other Importation and the alias
# is used, SubImportation also should be marked as used.
n = scope[name]
if isinstance(n, Importation) and n._has_alias():
try:
scope[n.fullName].used = (self.scope, node)
except KeyError:
pass
except KeyError:
pass
else:
return
importStarred = importStarred or scope.importStarred
if in_generators is not False:
in_generators = isinstance(scope, GeneratorScope)
if importStarred:
from_list = []
for scope in self.scopeStack[-1::-1]:
for binding in scope.values():
if isinstance(binding, StarImportation):
# mark '*' imports as used for each scope
binding.used = (self.scope, node)
from_list.append(binding.fullName)
# report * usage, with a list of possible sources
from_list = ', '.join(sorted(from_list))
self.report(messages.ImportStarUsage, node, name, from_list)
return
if name == '__path__' and os.path.basename(self.filename) == '__init__.py':
# the special name __path__ is valid only in packages
return
if name in DetectClassScopedMagic.names and isinstance(self.scope, ClassScope):
return
# protected with a NameError handler?
if 'NameError' not in self.exceptHandlers[-1]:
self.report(messages.UndefinedName, node, name)
def handleNodeStore(self, node):
name = getNodeName(node)
if not name:
return
# if the name hasn't already been defined in the current scope
if isinstance(self.scope, FunctionScope) and name not in self.scope:
# for each function or module scope above us
for scope in self.scopeStack[:-1]:
if not isinstance(scope, (FunctionScope, ModuleScope)):
continue
# if the name was defined in that scope, and the name has
# been accessed already in the current scope, and hasn't
# been declared global
used = name in scope and scope[name].used
if used and used[0] is self.scope and name not in self.scope.globals:
# then it's probably a mistake
self.report(messages.UndefinedLocal,
scope[name].used[1], name, scope[name].source)
break
parent_stmt = self.getParent(node)
if isinstance(parent_stmt, ANNASSIGN_TYPES) and parent_stmt.value is None:
binding = Annotation(name, node)
elif isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or (
parent_stmt != node._pyflakes_parent and
not self.isLiteralTupleUnpacking(parent_stmt)):
binding = Binding(name, node)
elif name == '__all__' and isinstance(self.scope, ModuleScope):
binding = ExportBinding(name, node._pyflakes_parent, self.scope)
elif PY2 and isinstance(getattr(node, 'ctx', None), ast.Param):
binding = Argument(name, self.getScopeNode(node))
else:
binding = Assignment(name, node)
self.addBinding(node, binding)
def handleNodeDelete(self, node):
def on_conditional_branch():
"""
Return `True` if node is part of a conditional body.
"""
current = getattr(node, '_pyflakes_parent', None)
while current:
if isinstance(current, (ast.If, ast.While, ast.IfExp)):
return True
current = getattr(current, '_pyflakes_parent', None)
return False
name = getNodeName(node)
if not name:
return
if on_conditional_branch():
# We cannot predict if this conditional branch is going to
# be executed.
return
if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
self.scope.globals.remove(name)
else:
try:
del self.scope[name]
except KeyError:
self.report(messages.UndefinedName, node, name)
@contextlib.contextmanager
def _enter_annotation(self, ann_type=AnnotationState.BARE):
orig, self._in_annotation = self._in_annotation, ann_type
try:
yield
finally:
self._in_annotation = orig
@property
def _in_postponed_annotation(self):
return (
self._in_annotation == AnnotationState.STRING or
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:
if PY2:
part = part.replace('...', 'Ellipsis')
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)
def isLiteralTupleUnpacking(self, node):
if isinstance(node, ast.Assign):
for child in node.targets + [node.value]:
if not hasattr(child, 'elts'):
return False
return True
def isDocstring(self, node):
"""
Determine if the given node is a docstring, as long as it is at the
correct place in the node tree.
"""
return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and
isinstance(node.value, ast.Str))
def getDocstring(self, node):
if isinstance(node, ast.Expr):
node = node.value
if not isinstance(node, ast.Str):
return (None, None)
if PYPY or PY38_PLUS:
doctest_lineno = node.lineno - 1
else:
# Computed incorrectly if the docstring has backslash
doctest_lineno = node.lineno - node.s.count('\n') - 1
return (node.s, doctest_lineno)
def handleNode(self, node, parent):
if node is None:
return
if self.offset and getattr(node, 'lineno', None) is not None:
node.lineno += self.offset[0]
node.col_offset += self.offset[1]
if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or
self.isDocstring(node)):
self.futuresAllowed = False
self.nodeDepth += 1
node._pyflakes_depth = self.nodeDepth
node._pyflakes_parent = parent
try:
handler = self.getNodeHandler(node.__class__)
handler(node)
finally:
self.nodeDepth -= 1
_getDoctestExamples = doctest.DocTestParser().get_examples
def handleDoctests(self, node):
try:
if hasattr(node, 'docstring'):
docstring = node.docstring
# This is just a reasonable guess. In Python 3.7, docstrings no
# longer have line numbers associated with them. This will be
# incorrect if there are empty lines between the beginning
# of the function and the docstring.
node_lineno = node.lineno
if hasattr(node, 'args'):
node_lineno = max([node_lineno] +
[arg.lineno for arg in node.args.args])
else:
(docstring, node_lineno) = self.getDocstring(node.body[0])
examples = docstring and self._getDoctestExamples(docstring)
except (ValueError, IndexError):
# e.g. line 6 of the docstring for has inconsistent
# leading whitespace: ...
return
if not examples:
return
# Place doctest in module scope
saved_stack = self.scopeStack
self.scopeStack = [self.scopeStack[0]]
node_offset = self.offset or (0, 0)
self.pushScope(DoctestScope)
if '_' not in self.scopeStack[0]:
self.addBinding(None, Builtin('_'))
for example in examples:
try:
tree = ast.parse(example.source, "")
except SyntaxError:
e = sys.exc_info()[1]
if PYPY:
e.offset += 1
position = (node_lineno + example.lineno + e.lineno,
example.indent + 4 + (e.offset or 0))
self.report(messages.DoctestSyntaxError, node, position)
else:
self.offset = (node_offset[0] + node_lineno + example.lineno,
node_offset[1] + example.indent + 4)
self.handleChildren(tree)
self.offset = node_offset
self.popScope()
self.scopeStack = saved_stack
@in_string_annotation
def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err):
try:
tree = ast.parse(s)
except SyntaxError:
self.report(err, node, s)
return
body = tree.body
if len(body) != 1 or not isinstance(body[0], ast.Expr):
self.report(err, node, s)
return
parsed_annotation = tree.body[0].value
for descendant in ast.walk(parsed_annotation):
if (
'lineno' in descendant._attributes and
'col_offset' in descendant._attributes
):
descendant.lineno = ref_lineno
descendant.col_offset = ref_col_offset
self.handleNode(parsed_annotation, node)
@in_annotation
def handleAnnotation(self, annotation, node):
if isinstance(annotation, ast.Str):
# Defer handling forward annotation.
self.deferFunction(functools.partial(
self.handleStringAnnotation,
annotation.s,
node,
annotation.lineno,
annotation.col_offset,
messages.ForwardAnnotationSyntaxError,
))
elif self.annotationsFutureEnabled:
fn = in_annotation(Checker.handleNode)
self.deferFunction(lambda: fn(self, annotation, node))
else:
self.handleNode(annotation, node)
def ignore(self, node):
pass
# "stmt" type nodes
DELETE = PRINT = FOR = ASYNCFOR = WHILE = WITH = WITHITEM = \
ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \
EXPR = ASSIGN = handleChildren
PASS = ignore
# "expr" type nodes
BOOLOP = UNARYOP = SET = \
REPR = ATTRIBUTE = \
STARRED = NAMECONSTANT = NAMEDEXPR = handleChildren
def SUBSCRIPT(self, node):
if _is_name_or_attr(node.value, 'Literal'):
with self._enter_annotation(AnnotationState.NONE):
self.handleChildren(node)
elif _is_name_or_attr(node.value, 'Annotated'):
self.handleNode(node.value, node)
# py39+
if isinstance(node.slice, ast.Tuple):
slice_tuple = node.slice
# = 1
):
with self._enter_annotation():
self.handleNode(node.args[0], node)
elif _is_typing(node.func, 'TypeVar', self.scopeStack):
# TypeVar("T", "int", "str")
omit += ["args"]
annotated += [arg for arg in node.args[1:]]
# TypeVar("T", bound="str")
omit += ["keywords"]
annotated += [k.value for k in node.keywords if k.arg == "bound"]
not_annotated += [
(k, ["value"] if k.arg == "bound" else None)
for k in node.keywords
]
elif _is_typing(node.func, "TypedDict", self.scopeStack):
# TypedDict("a", {"a": int})
if len(node.args) > 1 and isinstance(node.args[1], ast.Dict):
omit += ["args"]
annotated += node.args[1].values
not_annotated += [
(arg, ["values"] if i == 1 else None)
for i, arg in enumerate(node.args)
]
# TypedDict("a", a=int)
omit += ["keywords"]
annotated += [k.value for k in node.keywords]
not_annotated += [(k, ["value"]) for k in node.keywords]
elif _is_typing(node.func, "NamedTuple", self.scopeStack):
# NamedTuple("a", [("a", int)])
if (
len(node.args) > 1 and
isinstance(node.args[1], (ast.Tuple, ast.List)) and
all(isinstance(x, (ast.Tuple, ast.List)) and
len(x.elts) == 2 for x in node.args[1].elts)
):
omit += ["args"]
annotated += [elt.elts[1] for elt in node.args[1].elts]
not_annotated += [(elt.elts[0], None) for elt in node.args[1].elts]
not_annotated += [
(arg, ["elts"] if i == 1 else None)
for i, arg in enumerate(node.args)
]
not_annotated += [(elt, "elts") for elt in node.args[1].elts]
# NamedTuple("a", a=int)
omit += ["keywords"]
annotated += [k.value for k in node.keywords]
not_annotated += [(k, ["value"]) for k in node.keywords]
if omit:
with self._enter_annotation(AnnotationState.NONE):
for na_node, na_omit in not_annotated:
self.handleChildren(na_node, omit=na_omit)
self.handleChildren(node, omit=omit)
with self._enter_annotation():
for annotated_node in annotated:
self.handleNode(annotated_node, node)
else:
self.handleChildren(node)
def _handle_percent_format(self, node):
try:
placeholders = parse_percent_format(node.left.s)
except ValueError:
self.report(
messages.PercentFormatInvalidFormat,
node,
'incomplete format',
)
return
named = set()
positional_count = 0
positional = None
for _, placeholder in placeholders:
if placeholder is None:
continue
name, _, width, precision, conversion = placeholder
if conversion == '%':
continue
if conversion not in VALID_CONVERSIONS:
self.report(
messages.PercentFormatUnsupportedFormatCharacter,
node,
conversion,
)
if positional is None and conversion:
positional = name is None
for part in (width, precision):
if part is not None and '*' in part:
if not positional:
self.report(
messages.PercentFormatStarRequiresSequence,
node,
)
else:
positional_count += 1
if positional and name is not None:
self.report(
messages.PercentFormatMixedPositionalAndNamed,
node,
)
return
elif not positional and name is None:
self.report(
messages.PercentFormatMixedPositionalAndNamed,
node,
)
return
if positional:
positional_count += 1
else:
named.add(name)
if (
isinstance(node.right, (ast.List, ast.Tuple)) and
# does not have any *splats (py35+ feature)
not any(
isinstance(elt, getattr(ast, 'Starred', ()))
for elt in node.right.elts
)
):
substitution_count = len(node.right.elts)
if positional and positional_count != substitution_count:
self.report(
messages.PercentFormatPositionalCountMismatch,
node,
positional_count,
substitution_count,
)
elif not positional:
self.report(messages.PercentFormatExpectedMapping, node)
if (
isinstance(node.right, ast.Dict) and
all(isinstance(k, ast.Str) for k in node.right.keys)
):
if positional and positional_count > 1:
self.report(messages.PercentFormatExpectedSequence, node)
return
substitution_keys = {k.s for k in node.right.keys}
extra_keys = substitution_keys - named
missing_keys = named - substitution_keys
if not positional and extra_keys:
self.report(
messages.PercentFormatExtraNamedArguments,
node,
', '.join(sorted(extra_keys)),
)
if not positional and missing_keys:
self.report(
messages.PercentFormatMissingArgument,
node,
', '.join(sorted(missing_keys)),
)
def BINOP(self, node):
if (
isinstance(node.op, ast.Mod) and
isinstance(node.left, ast.Str)
):
self._handle_percent_format(node)
self.handleChildren(node)
def STR(self, node):
if self._in_annotation:
fn = functools.partial(
self.handleStringAnnotation,
node.s,
node,
node.lineno,
node.col_offset,
messages.ForwardAnnotationSyntaxError,
)
if self._in_deferred:
fn()
else:
self.deferFunction(fn)
if PY38_PLUS:
def CONSTANT(self, node):
if isinstance(node.value, str):
return self.STR(node)
else:
NUM = BYTES = ELLIPSIS = CONSTANT = ignore
# "slice" type nodes
SLICE = EXTSLICE = INDEX = handleChildren
# expression contexts are node instances too, though being constants
LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore
# same for operators
AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \
BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \
EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = \
MATMULT = ignore
def RAISE(self, node):
self.handleChildren(node)
arg = get_raise_argument(node)
if isinstance(arg, ast.Call):
if is_notimplemented_name_node(arg.func):
# Handle "raise NotImplemented(...)"
self.report(messages.RaiseNotImplemented, node)
elif is_notimplemented_name_node(arg):
# Handle "raise NotImplemented"
self.report(messages.RaiseNotImplemented, node)
# additional node types
COMPREHENSION = KEYWORD = FORMATTEDVALUE = handleChildren
_in_fstring = False
def JOINEDSTR(self, node):
if (
# the conversion / etc. flags are parsed as f-strings without
# placeholders
not self._in_fstring and
not any(isinstance(x, ast.FormattedValue) for x in node.values)
):
self.report(messages.FStringMissingPlaceholders, node)
self._in_fstring, orig = True, self._in_fstring
try:
self.handleChildren(node)
finally:
self._in_fstring = orig
def DICT(self, node):
# Complain if there are duplicate keys with different values
# If they have the same value it's not going to cause potentially
# unexpected behaviour so we'll not complain.
keys = [
convert_to_value(key) for key in node.keys
]
key_counts = counter(keys)
duplicate_keys = [
key for key, count in key_counts.items()
if count > 1
]
for key in duplicate_keys:
key_indices = [i for i, i_key in enumerate(keys) if i_key == key]
values = counter(
convert_to_value(node.values[index])
for index in key_indices
)
if any(count == 1 for value, count in values.items()):
for key_index in key_indices:
key_node = node.keys[key_index]
if isinstance(key, VariableKey):
self.report(messages.MultiValueRepeatedKeyVariable,
key_node,
key.name)
else:
self.report(
messages.MultiValueRepeatedKeyLiteral,
key_node,
key,
)
self.handleChildren(node)
def IF(self, node):
if isinstance(node.test, ast.Tuple) and node.test.elts != []:
self.report(messages.IfTuple, node)
self.handleChildren(node)
IFEXP = IF
def ASSERT(self, node):
if isinstance(node.test, ast.Tuple) and node.test.elts != []:
self.report(messages.AssertTuple, node)
self.handleChildren(node)
def GLOBAL(self, node):
"""
Keep track of globals declarations.
"""
global_scope_index = 1 if self._in_doctest() else 0
global_scope = self.scopeStack[global_scope_index]
# Ignore 'global' statement in global scope.
if self.scope is not global_scope:
# One 'global' statement can bind multiple (comma-delimited) names.
for node_name in node.names:
node_value = Assignment(node_name, node)
# Remove UndefinedName messages already reported for this name.
# TODO: if the global is not used in this scope, it does not
# become a globally defined name. See test_unused_global.
self.messages = [
m for m in self.messages if not
isinstance(m, messages.UndefinedName) or
m.message_args[0] != node_name]
# Bind name to global scope if it doesn't exist already.
global_scope.setdefault(node_name, node_value)
# Bind name to non-global scopes, but as already "used".
node_value.used = (global_scope, node)
for scope in self.scopeStack[global_scope_index + 1:]:
scope[node_name] = node_value
NONLOCAL = GLOBAL
def GENERATOREXP(self, node):
self.pushScope(GeneratorScope)
self.handleChildren(node)
self.popScope()
LISTCOMP = handleChildren if PY2 else GENERATOREXP
DICTCOMP = SETCOMP = GENERATOREXP
def NAME(self, node):
"""
Handle occurrence of Name (which can be a load/store/delete access.)
"""
# Locate the name in locals / function / globals scopes.
if isinstance(node.ctx, ast.Load):
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
self.scope.usesLocals = True
elif isinstance(node.ctx, ast.Store):
self.handleNodeStore(node)
elif PY2 and isinstance(node.ctx, ast.Param):
self.handleNodeStore(node)
elif isinstance(node.ctx, ast.Del):
self.handleNodeDelete(node)
else:
# Unknown context
raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
def CONTINUE(self, node):
# Walk the tree up until we see a loop (OK), a function or class
# definition (not OK), for 'continue', a finally block (not OK), or
# the top module scope (not OK)
n = node
while hasattr(n, '_pyflakes_parent'):
n, n_child = n._pyflakes_parent, n
if isinstance(n, LOOP_TYPES):
# Doesn't apply unless it's in the loop itself
if n_child not in n.orelse:
return
if isinstance(n, (ast.FunctionDef, ast.ClassDef)):
break
# Handle Try/TryFinally difference in Python < and >= 3.3
if hasattr(n, 'finalbody') and isinstance(node, ast.Continue):
if n_child in n.finalbody and not PY38_PLUS:
self.report(messages.ContinueInFinally, node)
return
if isinstance(node, ast.Continue):
self.report(messages.ContinueOutsideLoop, node)
else: # ast.Break
self.report(messages.BreakOutsideLoop, node)
BREAK = CONTINUE
def RETURN(self, node):
if isinstance(self.scope, (ClassScope, ModuleScope)):
self.report(messages.ReturnOutsideFunction, node)
return
if (
node.value and
hasattr(self.scope, 'returnValue') and
not self.scope.returnValue
):
self.scope.returnValue = node.value
self.handleNode(node.value, node)
def YIELD(self, node):
if isinstance(self.scope, (ClassScope, ModuleScope)):
self.report(messages.YieldOutsideFunction, node)
return
self.scope.isGenerator = True
self.handleNode(node.value, node)
AWAIT = YIELDFROM = YIELD
def FUNCTIONDEF(self, node):
for deco in node.decorator_list:
self.handleNode(deco, node)
self.LAMBDA(node)
self.addBinding(node, FunctionDefinition(node.name, node))
# doctest does not process doctest within a doctest,
# or in nested functions.
if (self.withDoctest and
not self._in_doctest() and
not isinstance(self.scope, FunctionScope)):
self.deferFunction(lambda: self.handleDoctests(node))
ASYNCFUNCTIONDEF = FUNCTIONDEF
def LAMBDA(self, node):
args = []
annotations = []
if PY2:
def addArgs(arglist):
for arg in arglist:
if isinstance(arg, ast.Tuple):
addArgs(arg.elts)
else:
args.append(arg.id)
addArgs(node.args.args)
defaults = node.args.defaults
else:
if PY38_PLUS:
for arg in node.args.posonlyargs:
args.append(arg.arg)
annotations.append(arg.annotation)
for arg in node.args.args + node.args.kwonlyargs:
args.append(arg.arg)
annotations.append(arg.annotation)
defaults = node.args.defaults + node.args.kw_defaults
# Only for Python3 FunctionDefs
is_py3_func = hasattr(node, 'returns')
for arg_name in ('vararg', 'kwarg'):
wildcard = getattr(node.args, arg_name)
if not wildcard:
continue
args.append(wildcard if PY2 else wildcard.arg)
if is_py3_func:
if PY2: # Python 2.7
argannotation = arg_name + 'annotation'
annotations.append(getattr(node.args, argannotation))
else: # Python >= 3.4
annotations.append(wildcard.annotation)
if is_py3_func:
annotations.append(node.returns)
if len(set(args)) < len(args):
for (idx, arg) in enumerate(args):
if arg in args[:idx]:
self.report(messages.DuplicateArgument, node, arg)
for annotation in annotations:
self.handleAnnotation(annotation, node)
for default in defaults:
self.handleNode(default, node)
def runFunction():
self.pushScope()
self.handleChildren(node, omit=['decorator_list', 'returns'])
def checkUnusedAssignments():
"""
Check to see if any assignments have not been used.
"""
for name, binding in self.scope.unusedAssignments():
self.report(messages.UnusedVariable, binding.source, name)
self.deferAssignment(checkUnusedAssignments)
if PY2:
def checkReturnWithArgumentInsideGenerator():
"""
Check to see if there is any return statement with
arguments but the function is a generator.
"""
if self.scope.isGenerator and self.scope.returnValue:
self.report(messages.ReturnWithArgsInsideGenerator,
self.scope.returnValue)
self.deferAssignment(checkReturnWithArgumentInsideGenerator)
self.popScope()
self.deferFunction(runFunction)
def ARGUMENTS(self, node):
self.handleChildren(node, omit=('defaults', 'kw_defaults'))
if PY2:
scope_node = self.getScopeNode(node)
if node.vararg:
self.addBinding(node, Argument(node.vararg, scope_node))
if node.kwarg:
self.addBinding(node, Argument(node.kwarg, scope_node))
def ARG(self, node):
self.addBinding(node, Argument(node.arg, self.getScopeNode(node)))
def CLASSDEF(self, node):
"""
Check names used in a class definition, including its decorators, base
classes, and the body of its definition. Additionally, add its name to
the current scope.
"""
for deco in node.decorator_list:
self.handleNode(deco, node)
for baseNode in node.bases:
self.handleNode(baseNode, node)
if not PY2:
for keywordNode in node.keywords:
self.handleNode(keywordNode, node)
self.pushScope(ClassScope)
# doctest does not process doctest within a doctest
# classes within classes are processed.
if (self.withDoctest and
not self._in_doctest() and
not isinstance(self.scope, FunctionScope)):
self.deferFunction(lambda: self.handleDoctests(node))
for stmt in node.body:
self.handleNode(stmt, node)
self.popScope()
self.addBinding(node, ClassDefinition(node.name, node))
def AUGASSIGN(self, node):
self.handleNodeLoad(node.target)
self.handleNode(node.value, node)
self.handleNode(node.target, node)
def TUPLE(self, node):
if not PY2 and isinstance(node.ctx, ast.Store):
# Python 3 advanced tuple unpacking: a, *b, c = d.
# Only one starred expression is allowed, and no more than 1<<8
# assignments are allowed before a stared expression. There is
# also a limit of 1<<24 expressions after the starred expression,
# which is impossible to test due to memory restrictions, but we
# add it here anyway
has_starred = False
star_loc = -1
for i, n in enumerate(node.elts):
if isinstance(n, ast.Starred):
if has_starred:
self.report(messages.TwoStarredExpressions, node)
# The SyntaxError doesn't distinguish two from more
# than two.
break
has_starred = True
star_loc = i
if star_loc >= 1 << 8 or len(node.elts) - star_loc - 1 >= 1 << 24:
self.report(messages.TooManyExpressionsInStarredAssignment, node)
self.handleChildren(node)
LIST = TUPLE
def IMPORT(self, node):
for alias in node.names:
if '.' in alias.name and not alias.asname:
importation = SubmoduleImportation(alias.name, node)
else:
name = alias.asname or alias.name
importation = Importation(name, node, alias.name)
self.addBinding(node, importation)
def IMPORTFROM(self, node):
if node.module == '__future__':
if not self.futuresAllowed:
self.report(messages.LateFutureImport,
node, [n.name for n in node.names])
else:
self.futuresAllowed = False
module = ('.' * node.level) + (node.module or '')
for alias in node.names:
name = alias.asname or alias.name
if node.module == '__future__':
importation = FutureImportation(name, node, self.scope)
if alias.name not in __future__.all_feature_names:
self.report(messages.FutureFeatureNotDefined,
node, alias.name)
if alias.name == 'annotations':
self.annotationsFutureEnabled = True
elif alias.name == '*':
# Only Python 2, local import * is a SyntaxWarning
if not PY2 and not isinstance(self.scope, ModuleScope):
self.report(messages.ImportStarNotPermitted,
node, module)
continue
self.scope.importStarred = True
self.report(messages.ImportStarUsed, node, module)
importation = StarImportation(module, node)
else:
importation = ImportationFrom(name, node,
module, alias.name)
self.addBinding(node, importation)
def TRY(self, node):
handler_names = []
# List the exception handlers
for i, handler in enumerate(node.handlers):
if isinstance(handler.type, ast.Tuple):
for exc_type in handler.type.elts:
handler_names.append(getNodeName(exc_type))
elif handler.type:
handler_names.append(getNodeName(handler.type))
if handler.type is None and i < len(node.handlers) - 1:
self.report(messages.DefaultExceptNotLast, handler)
# Memorize the except handlers and process the body
self.exceptHandlers.append(handler_names)
for child in node.body:
self.handleNode(child, node)
self.exceptHandlers.pop()
# Process the other nodes: "except:", "else:", "finally:"
self.handleChildren(node, omit='body')
TRYEXCEPT = TRY
def EXCEPTHANDLER(self, node):
if PY2 or node.name is None:
self.handleChildren(node)
return
# If the name already exists in the scope, modify state of existing
# binding.
if node.name in self.scope:
self.handleNodeStore(node)
# 3.x: the name of the exception, which is not a Name node, but a
# simple string, creates a local that is only bound within the scope of
# the except: block. As such, temporarily remove the existing binding
# to more accurately determine if the name is used in the except:
# block.
try:
prev_definition = self.scope.pop(node.name)
except KeyError:
prev_definition = None
self.handleNodeStore(node)
self.handleChildren(node)
# See discussion on https://github.com/PyCQA/pyflakes/pull/59
# We're removing the local name since it's being unbound after leaving
# the except: block and it's always unbound if the except: block is
# never entered. This will cause an "undefined name" error raised if
# the checked code tries to use the name afterwards.
#
# Unless it's been removed already. Then do nothing.
try:
binding = self.scope.pop(node.name)
except KeyError:
pass
else:
if not binding.used:
self.report(messages.UnusedVariable, node, node.name)
# Restore.
if prev_definition:
self.scope[node.name] = prev_definition
def ANNASSIGN(self, node):
self.handleNode(node.target, node)
self.handleAnnotation(node.annotation, node)
if node.value:
# If the assignment has value, handle the *value* now.
self.handleNode(node.value, node)
def COMPARE(self, node):
left = node.left
for op, right in zip(node.ops, node.comparators):
if (
isinstance(op, (ast.Is, ast.IsNot)) and (
_is_const_non_singleton(left) or
_is_const_non_singleton(right)
)
):
self.report(messages.IsLiteral, node)
left = right
self.handleChildren(node)
MATCH = MATCH_CASE = MATCHCLASS = MATCHOR = MATCHSEQUENCE = handleChildren
MATCHSINGLETON = MATCHVALUE = handleChildren
def _match_target(self, node):
self.handleNodeStore(node)
self.handleChildren(node)
MATCHAS = MATCHMAPPING = MATCHSTAR = _match_target
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/messages.py 0000644 0001750 0001750 00000025234 00000000000 021033 0 ustar 00asottile asottile 0000000 0000000 """
Provide the class Message and its subclasses.
"""
class Message(object):
message = ''
message_args = ()
def __init__(self, filename, loc):
self.filename = filename
self.lineno = loc.lineno
self.col = getattr(loc, 'col_offset', 0)
def __str__(self):
return '%s:%s:%s %s' % (self.filename, self.lineno, self.col+1,
self.message % self.message_args)
class UnusedImport(Message):
message = '%r imported but unused'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class RedefinedWhileUnused(Message):
message = 'redefinition of unused %r from line %r'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
self.message_args = (name, orig_loc.lineno)
class RedefinedInListComp(Message):
message = 'list comprehension redefines %r from line %r'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
self.message_args = (name, orig_loc.lineno)
class ImportShadowedByLoopVar(Message):
message = 'import %r from line %r shadowed by loop variable'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
self.message_args = (name, orig_loc.lineno)
class ImportStarNotPermitted(Message):
message = "'from %s import *' only allowed at module level"
def __init__(self, filename, loc, modname):
Message.__init__(self, filename, loc)
self.message_args = (modname,)
class ImportStarUsed(Message):
message = "'from %s import *' used; unable to detect undefined names"
def __init__(self, filename, loc, modname):
Message.__init__(self, filename, loc)
self.message_args = (modname,)
class ImportStarUsage(Message):
message = "%r may be undefined, or defined from star imports: %s"
def __init__(self, filename, loc, name, from_list):
Message.__init__(self, filename, loc)
self.message_args = (name, from_list)
class UndefinedName(Message):
message = 'undefined name %r'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class DoctestSyntaxError(Message):
message = 'syntax error in doctest'
def __init__(self, filename, loc, position=None):
Message.__init__(self, filename, loc)
if position:
(self.lineno, self.col) = position
self.message_args = ()
class UndefinedExport(Message):
message = 'undefined name %r in __all__'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class UndefinedLocal(Message):
message = 'local variable %r {0} referenced before assignment'
default = 'defined in enclosing scope on line %r'
builtin = 'defined as a builtin'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
if orig_loc is None:
self.message = self.message.format(self.builtin)
self.message_args = name
else:
self.message = self.message.format(self.default)
self.message_args = (name, orig_loc.lineno)
class DuplicateArgument(Message):
message = 'duplicate argument %r in function definition'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class MultiValueRepeatedKeyLiteral(Message):
message = 'dictionary key %r repeated with different values'
def __init__(self, filename, loc, key):
Message.__init__(self, filename, loc)
self.message_args = (key,)
class MultiValueRepeatedKeyVariable(Message):
message = 'dictionary key variable %s repeated with different values'
def __init__(self, filename, loc, key):
Message.__init__(self, filename, loc)
self.message_args = (key,)
class LateFutureImport(Message):
message = 'from __future__ imports must occur at the beginning of the file'
def __init__(self, filename, loc, names):
Message.__init__(self, filename, loc)
self.message_args = ()
class FutureFeatureNotDefined(Message):
"""An undefined __future__ feature name was imported."""
message = 'future feature %s is not defined'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class UnusedVariable(Message):
"""
Indicates that a variable has been explicitly assigned to but not actually
used.
"""
message = 'local variable %r is assigned to but never used'
def __init__(self, filename, loc, names):
Message.__init__(self, filename, loc)
self.message_args = (names,)
class ReturnWithArgsInsideGenerator(Message):
"""
Indicates a return statement with arguments inside a generator.
"""
message = '\'return\' with argument inside generator'
class ReturnOutsideFunction(Message):
"""
Indicates a return statement outside of a function/method.
"""
message = '\'return\' outside function'
class YieldOutsideFunction(Message):
"""
Indicates a yield or yield from statement outside of a function/method.
"""
message = '\'yield\' outside function'
# For whatever reason, Python gives different error messages for these two. We
# match the Python error message exactly.
class ContinueOutsideLoop(Message):
"""
Indicates a continue statement outside of a while or for loop.
"""
message = '\'continue\' not properly in loop'
class BreakOutsideLoop(Message):
"""
Indicates a break statement outside of a while or for loop.
"""
message = '\'break\' outside loop'
class ContinueInFinally(Message):
"""
Indicates a continue statement in a finally block in a while or for loop.
"""
message = '\'continue\' not supported inside \'finally\' clause'
class DefaultExceptNotLast(Message):
"""
Indicates an except: block as not the last exception handler.
"""
message = 'default \'except:\' must be last'
class TwoStarredExpressions(Message):
"""
Two or more starred expressions in an assignment (a, *b, *c = d).
"""
message = 'two starred expressions in assignment'
class TooManyExpressionsInStarredAssignment(Message):
"""
Too many expressions in an assignment with star-unpacking
"""
message = 'too many expressions in star-unpacking assignment'
class IfTuple(Message):
"""
Conditional test is a non-empty tuple literal, which are always True.
"""
message = '\'if tuple literal\' is always true, perhaps remove accidental comma?'
class AssertTuple(Message):
"""
Assertion test is a non-empty tuple literal, which are always True.
"""
message = 'assertion is always true, perhaps remove parentheses?'
class ForwardAnnotationSyntaxError(Message):
message = 'syntax error in forward annotation %r'
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,)
class RaiseNotImplemented(Message):
message = "'raise NotImplemented' should be 'raise NotImplementedError'"
class InvalidPrintSyntax(Message):
message = 'use of >> is invalid with print function'
class IsLiteral(Message):
message = 'use ==/!= to compare constant literals (str, bytes, int, float, tuple)'
class FStringMissingPlaceholders(Message):
message = 'f-string is missing placeholders'
class StringDotFormatExtraPositionalArguments(Message):
message = "'...'.format(...) has unused arguments at position(s): %s"
def __init__(self, filename, loc, extra_positions):
Message.__init__(self, filename, loc)
self.message_args = (extra_positions,)
class StringDotFormatExtraNamedArguments(Message):
message = "'...'.format(...) has unused named argument(s): %s"
def __init__(self, filename, loc, extra_keywords):
Message.__init__(self, filename, loc)
self.message_args = (extra_keywords,)
class StringDotFormatMissingArgument(Message):
message = "'...'.format(...) is missing argument(s) for placeholder(s): %s"
def __init__(self, filename, loc, missing_arguments):
Message.__init__(self, filename, loc)
self.message_args = (missing_arguments,)
class StringDotFormatMixingAutomatic(Message):
message = "'...'.format(...) mixes automatic and manual numbering"
class StringDotFormatInvalidFormat(Message):
message = "'...'.format(...) has invalid format string: %s"
def __init__(self, filename, loc, error):
Message.__init__(self, filename, loc)
self.message_args = (error,)
class PercentFormatInvalidFormat(Message):
message = "'...' %% ... has invalid format string: %s"
def __init__(self, filename, loc, error):
Message.__init__(self, filename, loc)
self.message_args = (error,)
class PercentFormatMixedPositionalAndNamed(Message):
message = "'...' %% ... has mixed positional and named placeholders"
class PercentFormatUnsupportedFormatCharacter(Message):
message = "'...' %% ... has unsupported format character %r"
def __init__(self, filename, loc, c):
Message.__init__(self, filename, loc)
self.message_args = (c,)
class PercentFormatPositionalCountMismatch(Message):
message = "'...' %% ... has %d placeholder(s) but %d substitution(s)"
def __init__(self, filename, loc, n_placeholders, n_substitutions):
Message.__init__(self, filename, loc)
self.message_args = (n_placeholders, n_substitutions)
class PercentFormatExtraNamedArguments(Message):
message = "'...' %% ... has unused named argument(s): %s"
def __init__(self, filename, loc, extra_keywords):
Message.__init__(self, filename, loc)
self.message_args = (extra_keywords,)
class PercentFormatMissingArgument(Message):
message = "'...' %% ... is missing argument(s) for placeholder(s): %s"
def __init__(self, filename, loc, missing_arguments):
Message.__init__(self, filename, loc)
self.message_args = (missing_arguments,)
class PercentFormatExpectedMapping(Message):
message = "'...' %% ... expected mapping but got sequence"
class PercentFormatExpectedSequence(Message):
message = "'...' %% ... expected sequence but got mapping"
class PercentFormatStarRequiresSequence(Message):
message = "'...' %% ... `*` specifier requires sequence"
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/reporter.py 0000644 0001750 0001750 00000005233 00000000000 021063 0 ustar 00asottile asottile 0000000 0000000 """
Provide the Reporter class.
"""
import re
import sys
class Reporter(object):
"""
Formats the results of pyflakes checks to users.
"""
def __init__(self, warningStream, errorStream):
"""
Construct a L{Reporter}.
@param warningStream: A file-like object where warnings will be
written to. The stream's C{write} method must accept unicode.
C{sys.stdout} is a good value.
@param errorStream: A file-like object where error output will be
written to. The stream's C{write} method must accept unicode.
C{sys.stderr} is a good value.
"""
self._stdout = warningStream
self._stderr = errorStream
def unexpectedError(self, filename, msg):
"""
An unexpected error occurred trying to process C{filename}.
@param filename: The path to a file that we could not process.
@ptype filename: C{unicode}
@param msg: A message explaining the problem.
@ptype msg: C{unicode}
"""
self._stderr.write("%s: %s\n" % (filename, msg))
def syntaxError(self, filename, msg, lineno, offset, text):
"""
There was a syntax error in C{filename}.
@param filename: The path to the file with the syntax error.
@ptype filename: C{unicode}
@param msg: An explanation of the syntax error.
@ptype msg: C{unicode}
@param lineno: The line number where the syntax error occurred.
@ptype lineno: C{int}
@param offset: The column on which the syntax error occurred, or None.
@ptype offset: C{int}
@param text: The source code containing the syntax error.
@ptype text: C{unicode}
"""
line = text.splitlines()[-1]
if offset is not None:
if sys.version_info < (3, 8):
offset = offset - (len(text) - len(line)) + 1
self._stderr.write('%s:%d:%d: %s\n' %
(filename, lineno, offset, msg))
else:
self._stderr.write('%s:%d: %s\n' % (filename, lineno, msg))
self._stderr.write(line)
self._stderr.write('\n')
if offset is not None:
self._stderr.write(re.sub(r'\S', ' ', line[:offset - 1]) +
"^\n")
def flake(self, message):
"""
pyflakes found something wrong with the code.
@param: A L{pyflakes.messages.Message}.
"""
self._stdout.write(str(message))
self._stdout.write('\n')
def _makeDefaultReporter():
"""
Make a reporter that can be used when no reporter is specified.
"""
return Reporter(sys.stdout, sys.stderr)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633552776.3134809
pyflakes-2.4.0/pyflakes/scripts/ 0000755 0001750 0001750 00000000000 00000000000 020333 5 ustar 00asottile asottile 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/scripts/__init__.py 0000644 0001750 0001750 00000000000 00000000000 022432 0 ustar 00asottile asottile 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/scripts/pyflakes.py 0000644 0001750 0001750 00000000437 00000000000 022527 0 ustar 00asottile asottile 0000000 0000000 """
Implementation of the command-line I{pyflakes} tool.
"""
from __future__ import absolute_import
# For backward compatibility
__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main']
from pyflakes.api import check, checkPath, checkRecursive, iterSourceCode, main
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633552776.3214808
pyflakes-2.4.0/pyflakes/test/ 0000755 0001750 0001750 00000000000 00000000000 017623 5 ustar 00asottile asottile 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/test/__init__.py 0000644 0001750 0001750 00000000000 00000000000 021722 0 ustar 00asottile asottile 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/test/harness.py 0000644 0001750 0001750 00000004544 00000000000 021647 0 ustar 00asottile asottile 0000000 0000000 import ast
import textwrap
import unittest
from pyflakes import checker
__all__ = ['TestCase', 'skip', 'skipIf']
skip = unittest.skip
skipIf = unittest.skipIf
class TestCase(unittest.TestCase):
withDoctest = False
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, 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__)
expectedOutputs.sort(key=lambda t: t.__name__)
self.assertEqual(outputs, expectedOutputs, '''\
for input:
%s
expected outputs:
%r
but got:
%s''' % (input, expectedOutputs, '\n'.join([str(o) for o in w.messages])))
return w
if not hasattr(unittest.TestCase, 'assertIs'):
def assertIs(self, expr1, expr2, msg=None):
if expr1 is not expr2:
self.fail(msg or '%r is not %r' % (expr1, expr2))
if not hasattr(unittest.TestCase, 'assertIsInstance'):
def assertIsInstance(self, obj, cls, msg=None):
"""Same as self.assertTrue(isinstance(obj, cls))."""
if not isinstance(obj, cls):
self.fail(msg or '%r is not an instance of %r' % (obj, cls))
if not hasattr(unittest.TestCase, 'assertNotIsInstance'):
def assertNotIsInstance(self, obj, cls, msg=None):
"""Same as self.assertFalse(isinstance(obj, cls))."""
if isinstance(obj, cls):
self.fail(msg or '%r is an instance of %r' % (obj, cls))
if not hasattr(unittest.TestCase, 'assertIn'):
def assertIn(self, member, container, msg=None):
"""Just like self.assertTrue(a in b)."""
if member not in container:
self.fail(msg or '%r not found in %r' % (member, container))
if not hasattr(unittest.TestCase, 'assertNotIn'):
def assertNotIn(self, member, container, msg=None):
"""Just like self.assertTrue(a not in b)."""
if member in container:
self.fail(msg or
'%r unexpectedly found in %r' % (member, container))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633476446.0
pyflakes-2.4.0/pyflakes/test/test_api.py 0000644 0001750 0001750 00000066430 00000000000 022016 0 ustar 00asottile asottile 0000000 0000000 """
Tests for L{pyflakes.scripts.pyflakes}.
"""
import contextlib
import os
import sys
import shutil
import subprocess
import tempfile
from pyflakes.messages import UnusedImport
from pyflakes.reporter import Reporter
from pyflakes.api import (
main,
checkPath,
checkRecursive,
iterSourceCode,
)
from pyflakes.test.harness import TestCase, skipIf
if sys.version_info < (3,):
from cStringIO import StringIO
else:
from io import StringIO
unichr = chr
try:
sys.pypy_version_info
PYPY = True
except AttributeError:
PYPY = False
try:
WindowsError
WIN = True
except NameError:
WIN = False
ERROR_HAS_COL_NUM = ERROR_HAS_LAST_LINE = sys.version_info >= (3, 2) or PYPY
def withStderrTo(stderr, f, *args, **kwargs):
"""
Call C{f} with C{sys.stderr} redirected to C{stderr}.
"""
(outer, sys.stderr) = (sys.stderr, stderr)
try:
return f(*args, **kwargs)
finally:
sys.stderr = outer
class Node(object):
"""
Mock an AST node.
"""
def __init__(self, lineno, col_offset=0):
self.lineno = lineno
self.col_offset = col_offset
class SysStreamCapturing(object):
"""
Context manager capturing sys.stdin, sys.stdout and sys.stderr.
The file handles are replaced with a StringIO object.
On environments that support it, the StringIO object uses newlines
set to os.linesep. Otherwise newlines are converted from \\n to
os.linesep during __exit__.
"""
def _create_StringIO(self, buffer=None):
# Python 3 has a newline argument
try:
return StringIO(buffer, newline=os.linesep)
except TypeError:
self._newline = True
# Python 2 creates an input only stream when buffer is not None
if buffer is None:
return StringIO()
else:
return StringIO(buffer)
def __init__(self, stdin):
self._newline = False
self._stdin = self._create_StringIO(stdin or '')
def __enter__(self):
self._orig_stdin = sys.stdin
self._orig_stdout = sys.stdout
self._orig_stderr = sys.stderr
sys.stdin = self._stdin
sys.stdout = self._stdout_stringio = self._create_StringIO()
sys.stderr = self._stderr_stringio = self._create_StringIO()
return self
def __exit__(self, *args):
self.output = self._stdout_stringio.getvalue()
self.error = self._stderr_stringio.getvalue()
if self._newline and os.linesep != '\n':
self.output = self.output.replace('\n', os.linesep)
self.error = self.error.replace('\n', os.linesep)
sys.stdin = self._orig_stdin
sys.stdout = self._orig_stdout
sys.stderr = self._orig_stderr
class LoggingReporter(object):
"""
Implementation of Reporter that just appends any error to a list.
"""
def __init__(self, log):
"""
Construct a C{LoggingReporter}.
@param log: A list to append log messages to.
"""
self.log = log
def flake(self, message):
self.log.append(('flake', str(message)))
def unexpectedError(self, filename, message):
self.log.append(('unexpectedError', filename, message))
def syntaxError(self, filename, msg, lineno, offset, line):
self.log.append(('syntaxError', filename, msg, lineno, offset, line))
class TestIterSourceCode(TestCase):
"""
Tests for L{iterSourceCode}.
"""
def setUp(self):
self.tempdir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.tempdir)
def makeEmptyFile(self, *parts):
assert parts
fpath = os.path.join(self.tempdir, *parts)
open(fpath, 'a').close()
return fpath
def test_emptyDirectory(self):
"""
There are no Python files in an empty directory.
"""
self.assertEqual(list(iterSourceCode([self.tempdir])), [])
def test_singleFile(self):
"""
If the directory contains one Python file, C{iterSourceCode} will find
it.
"""
childpath = self.makeEmptyFile('foo.py')
self.assertEqual(list(iterSourceCode([self.tempdir])), [childpath])
def test_onlyPythonSource(self):
"""
Files that are not Python source files are not included.
"""
self.makeEmptyFile('foo.pyc')
self.assertEqual(list(iterSourceCode([self.tempdir])), [])
def test_recurses(self):
"""
If the Python files are hidden deep down in child directories, we will
find them.
"""
os.mkdir(os.path.join(self.tempdir, 'foo'))
apath = self.makeEmptyFile('foo', 'a.py')
self.makeEmptyFile('foo', 'a.py~')
os.mkdir(os.path.join(self.tempdir, 'bar'))
bpath = self.makeEmptyFile('bar', 'b.py')
cpath = self.makeEmptyFile('c.py')
self.assertEqual(
sorted(iterSourceCode([self.tempdir])),
sorted([apath, bpath, cpath]))
def test_shebang(self):
"""
Find Python files that don't end with `.py`, but contain a Python
shebang.
"""
python = os.path.join(self.tempdir, 'a')
with open(python, 'w') as fd:
fd.write('#!/usr/bin/env python\n')
self.makeEmptyFile('b')
with open(os.path.join(self.tempdir, 'c'), 'w') as fd:
fd.write('hello\nworld\n')
python2 = os.path.join(self.tempdir, 'd')
with open(python2, 'w') as fd:
fd.write('#!/usr/bin/env python2\n')
python3 = os.path.join(self.tempdir, 'e')
with open(python3, 'w') as fd:
fd.write('#!/usr/bin/env python3\n')
pythonw = os.path.join(self.tempdir, 'f')
with open(pythonw, 'w') as fd:
fd.write('#!/usr/bin/env pythonw\n')
python3args = os.path.join(self.tempdir, 'g')
with open(python3args, 'w') as fd:
fd.write('#!/usr/bin/python3 -u\n')
python2u = os.path.join(self.tempdir, 'h')
with open(python2u, 'w') as fd:
fd.write('#!/usr/bin/python2u\n')
python3d = os.path.join(self.tempdir, 'i')
with open(python3d, 'w') as fd:
fd.write('#!/usr/local/bin/python3d\n')
python38m = os.path.join(self.tempdir, 'j')
with open(python38m, 'w') as fd:
fd.write('#! /usr/bin/env python3.8m\n')
python27 = os.path.join(self.tempdir, 'k')
with open(python27, 'w') as fd:
fd.write('#!/usr/bin/python2.7 \n')
# Should NOT be treated as Python source
notfirst = os.path.join(self.tempdir, 'l')
with open(notfirst, 'w') as fd:
fd.write('#!/bin/sh\n#!/usr/bin/python\n')
self.assertEqual(
sorted(iterSourceCode([self.tempdir])),
sorted([python, python2, python3, pythonw, python3args, python2u,
python3d, python38m, python27]))
def test_multipleDirectories(self):
"""
L{iterSourceCode} can be given multiple directories. It will recurse
into each of them.
"""
foopath = os.path.join(self.tempdir, 'foo')
barpath = os.path.join(self.tempdir, 'bar')
os.mkdir(foopath)
apath = self.makeEmptyFile('foo', 'a.py')
os.mkdir(barpath)
bpath = self.makeEmptyFile('bar', 'b.py')
self.assertEqual(
sorted(iterSourceCode([foopath, barpath])),
sorted([apath, bpath]))
def test_explicitFiles(self):
"""
If one of the paths given to L{iterSourceCode} is not a directory but
a file, it will include that in its output.
"""
epath = self.makeEmptyFile('e.py')
self.assertEqual(list(iterSourceCode([epath])),
[epath])
class TestReporter(TestCase):
"""
Tests for L{Reporter}.
"""
def test_syntaxError(self):
"""
C{syntaxError} reports that there was a syntax error in the source
file. It reports to the error stream and includes the filename, line
number, error message, actual line of source and a caret pointing to
where the error is.
"""
err = StringIO()
reporter = Reporter(None, err)
reporter.syntaxError('foo.py', 'a problem', 3,
8 if sys.version_info >= (3, 8) else 7,
'bad line of source')
self.assertEqual(
("foo.py:3:8: a problem\n"
"bad line of source\n"
" ^\n"),
err.getvalue())
def test_syntaxErrorNoOffset(self):
"""
C{syntaxError} doesn't include a caret pointing to the error if
C{offset} is passed as C{None}.
"""
err = StringIO()
reporter = Reporter(None, err)
reporter.syntaxError('foo.py', 'a problem', 3, None,
'bad line of source')
self.assertEqual(
("foo.py:3: a problem\n"
"bad line of source\n"),
err.getvalue())
def test_multiLineSyntaxError(self):
"""
If there's a multi-line syntax error, then we only report the last
line. The offset is adjusted so that it is relative to the start of
the last line.
"""
err = StringIO()
lines = [
'bad line of source',
'more bad lines of source',
]
reporter = Reporter(None, err)
reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7,
'\n'.join(lines))
column = 25 if sys.version_info >= (3, 8) else 7
self.assertEqual(
("foo.py:3:%d: a problem\n" % column +
lines[-1] + "\n" +
" " * (column - 1) + "^\n"),
err.getvalue())
def test_unexpectedError(self):
"""
C{unexpectedError} reports an error processing a source file.
"""
err = StringIO()
reporter = Reporter(None, err)
reporter.unexpectedError('source.py', 'error message')
self.assertEqual('source.py: error message\n', err.getvalue())
def test_flake(self):
"""
C{flake} reports a code warning from Pyflakes. It is exactly the
str() of a L{pyflakes.messages.Message}.
"""
out = StringIO()
reporter = Reporter(out, None)
message = UnusedImport('foo.py', Node(42), 'bar')
reporter.flake(message)
self.assertEqual(out.getvalue(), "%s\n" % (message,))
class CheckTests(TestCase):
"""
Tests for L{check} and L{checkPath} which check a file for flakes.
"""
@contextlib.contextmanager
def makeTempFile(self, content):
"""
Make a temporary file containing C{content} and return a path to it.
"""
fd, name = tempfile.mkstemp()
try:
with os.fdopen(fd, 'wb') as f:
if not hasattr(content, 'decode'):
content = content.encode('ascii')
f.write(content)
yield name
finally:
os.remove(name)
def assertHasErrors(self, path, errorList):
"""
Assert that C{path} causes errors.
@param path: A path to a file to check.
@param errorList: A list of errors expected to be printed to stderr.
"""
err = StringIO()
count = withStderrTo(err, checkPath, path)
self.assertEqual(
(count, err.getvalue()), (len(errorList), ''.join(errorList)))
def getErrors(self, path):
"""
Get any warnings or errors reported by pyflakes for the file at C{path}.
@param path: The path to a Python file on disk that pyflakes will check.
@return: C{(count, log)}, where C{count} is the number of warnings or
errors generated, and log is a list of those warnings, presented
as structured data. See L{LoggingReporter} for more details.
"""
log = []
reporter = LoggingReporter(log)
count = checkPath(path, reporter)
return count, log
def test_legacyScript(self):
from pyflakes.scripts import pyflakes as script_pyflakes
self.assertIs(script_pyflakes.checkPath, checkPath)
def test_missingTrailingNewline(self):
"""
Source which doesn't end with a newline shouldn't cause any
exception to be raised nor an error indicator to be returned by
L{check}.
"""
with self.makeTempFile("def foo():\n\tpass\n\t") as fName:
self.assertHasErrors(fName, [])
def test_checkPathNonExisting(self):
"""
L{checkPath} handles non-existing files.
"""
count, errors = self.getErrors('extremo')
self.assertEqual(count, 1)
self.assertEqual(
errors,
[('unexpectedError', 'extremo', 'No such file or directory')])
def test_multilineSyntaxError(self):
"""
Source which includes a syntax error which results in the raised
L{SyntaxError.text} containing multiple lines of source are reported
with only the last line of that source.
"""
source = """\
def foo():
'''
def bar():
pass
def baz():
'''quux'''
"""
# Sanity check - SyntaxError.text should be multiple lines, if it
# isn't, something this test was unprepared for has happened.
def evaluate(source):
exec(source)
try:
evaluate(source)
except SyntaxError:
e = sys.exc_info()[1]
if not PYPY and sys.version_info < (3, 10):
self.assertTrue(e.text.count('\n') > 1)
else:
self.fail()
with self.makeTempFile(source) as sourcePath:
if PYPY:
message = 'end of file (EOF) while scanning triple-quoted string literal'
elif sys.version_info >= (3, 10):
message = 'unterminated triple-quoted string literal (detected at line 8)' # noqa: E501
else:
message = 'invalid syntax'
if sys.version_info >= (3, 10):
column = 12
elif sys.version_info >= (3, 8):
column = 8
else:
column = 11
self.assertHasErrors(
sourcePath,
["""\
%s:8:%d: %s
'''quux'''
%s^
""" % (sourcePath, column, message, ' ' * (column - 1))])
def test_eofSyntaxError(self):
"""
The error reported for source files which end prematurely causing a
syntax error reflects the cause for the syntax error.
"""
with self.makeTempFile("def foo(") as sourcePath:
if PYPY:
msg = 'parenthesis is never closed'
elif sys.version_info >= (3, 10):
msg = "'(' was never closed"
else:
msg = 'unexpected EOF while parsing'
if PYPY:
column = 7
elif sys.version_info >= (3, 10):
column = 8
else:
column = 9
spaces = ' ' * (column - 1)
expected = '{}:1:{}: {}\ndef foo(\n{}^\n'.format(
sourcePath, column, msg, spaces
)
self.assertHasErrors(sourcePath, [expected])
def test_eofSyntaxErrorWithTab(self):
"""
The error reported for source files which end prematurely causing a
syntax error reflects the cause for the syntax error.
"""
with self.makeTempFile("if True:\n\tfoo =") as sourcePath:
column = 6 if PYPY else 7
last_line = '\t ^' if PYPY else '\t ^'
self.assertHasErrors(
sourcePath,
["""\
%s:2:%s: invalid syntax
\tfoo =
%s
""" % (sourcePath, column, last_line)])
def test_nonDefaultFollowsDefaultSyntaxError(self):
"""
Source which has a non-default argument following a default argument
should include the line number of the syntax error. However these
exceptions do not include an offset.
"""
source = """\
def foo(bar=baz, bax):
pass
"""
with self.makeTempFile(source) as sourcePath:
if ERROR_HAS_LAST_LINE:
if PYPY:
column = 7
elif sys.version_info >= (3, 10):
column = 18
elif sys.version_info >= (3, 9):
column = 21
elif sys.version_info >= (3, 8):
column = 9
else:
column = 8
last_line = ' ' * (column - 1) + '^\n'
columnstr = '%d:' % column
else:
last_line = columnstr = ''
self.assertHasErrors(
sourcePath,
["""\
%s:1:%s non-default argument follows default argument
def foo(bar=baz, bax):
%s""" % (sourcePath, columnstr, last_line)])
def test_nonKeywordAfterKeywordSyntaxError(self):
"""
Source which has a non-keyword argument after a keyword argument should
include the line number of the syntax error. However these exceptions
do not include an offset.
"""
source = """\
foo(bar=baz, bax)
"""
with self.makeTempFile(source) as sourcePath:
if ERROR_HAS_LAST_LINE:
if PYPY:
column = 12
elif sys.version_info >= (3, 9):
column = 17
elif sys.version_info >= (3, 8):
column = 14
else:
column = 13
last_line = ' ' * (column - 1) + '^\n'
columnstr = '%d:' % column
else:
last_line = columnstr = ''
if sys.version_info >= (3, 5):
message = 'positional argument follows keyword argument'
else:
message = 'non-keyword arg after keyword arg'
self.assertHasErrors(
sourcePath,
["""\
%s:1:%s %s
foo(bar=baz, bax)
%s""" % (sourcePath, columnstr, message, last_line)])
def test_invalidEscape(self):
"""
The invalid escape syntax raises ValueError in Python 2
"""
ver = sys.version_info
# ValueError: invalid \x escape
with self.makeTempFile(r"foo = '\xyz'") as sourcePath:
if ver < (3,):
decoding_error = "%s: problem decoding source\n" % (sourcePath,)
else:
position_end = 1
if PYPY:
column = 5
elif ver >= (3, 9):
column = 13
else:
column = 7
# Column has been "fixed" since 3.2.4 and 3.3.1
if ver < (3, 2, 4) or ver[:3] == (3, 3, 0):
position_end = 2
if ERROR_HAS_LAST_LINE:
last_line = '%s^\n' % (' ' * (column - 1))
else:
last_line = ''
decoding_error = """\
%s:1:%d: (unicode error) 'unicodeescape' codec can't decode bytes \
in position 0-%d: truncated \\xXX escape
foo = '\\xyz'
%s""" % (sourcePath, column, position_end, last_line)
self.assertHasErrors(
sourcePath, [decoding_error])
@skipIf(sys.platform == 'win32', 'unsupported on Windows')
def test_permissionDenied(self):
"""
If the source file is not readable, this is reported on standard
error.
"""
if os.getuid() == 0:
self.skipTest('root user can access all files regardless of '
'permissions')
with self.makeTempFile('') as sourcePath:
os.chmod(sourcePath, 0)
count, errors = self.getErrors(sourcePath)
self.assertEqual(count, 1)
self.assertEqual(
errors,
[('unexpectedError', sourcePath, "Permission denied")])
def test_pyflakesWarning(self):
"""
If the source file has a pyflakes warning, this is reported as a
'flake'.
"""
with self.makeTempFile("import foo") as sourcePath:
count, errors = self.getErrors(sourcePath)
self.assertEqual(count, 1)
self.assertEqual(
errors, [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))])
def test_encodedFileUTF8(self):
"""
If source file declares the correct encoding, no error is reported.
"""
SNOWMAN = unichr(0x2603)
source = ("""\
# coding: utf-8
x = "%s"
""" % SNOWMAN).encode('utf-8')
with self.makeTempFile(source) as sourcePath:
self.assertHasErrors(sourcePath, [])
def test_CRLFLineEndings(self):
"""
Source files with Windows CR LF line endings are parsed successfully.
"""
with self.makeTempFile("x = 42\r\n") as sourcePath:
self.assertHasErrors(sourcePath, [])
def test_misencodedFileUTF8(self):
"""
If a source file contains bytes which cannot be decoded, this is
reported on stderr.
"""
SNOWMAN = unichr(0x2603)
source = ("""\
# coding: ascii
x = "%s"
""" % SNOWMAN).encode('utf-8')
with self.makeTempFile(source) as sourcePath:
if PYPY and sys.version_info < (3, ):
message = ('\'ascii\' codec can\'t decode byte 0xe2 '
'in position 21: ordinal not in range(128)')
result = """\
%s:0:0: %s
x = "\xe2\x98\x83"
^\n""" % (sourcePath, message)
else:
message = 'problem decoding source'
result = "%s: problem decoding source\n" % (sourcePath,)
self.assertHasErrors(
sourcePath, [result])
def test_misencodedFileUTF16(self):
"""
If a source file contains bytes which cannot be decoded, this is
reported on stderr.
"""
SNOWMAN = unichr(0x2603)
source = ("""\
# coding: ascii
x = "%s"
""" % SNOWMAN).encode('utf-16')
with self.makeTempFile(source) as sourcePath:
self.assertHasErrors(
sourcePath, ["%s: problem decoding source\n" % (sourcePath,)])
def test_checkRecursive(self):
"""
L{checkRecursive} descends into each directory, finding Python files
and reporting problems.
"""
tempdir = tempfile.mkdtemp()
try:
os.mkdir(os.path.join(tempdir, 'foo'))
file1 = os.path.join(tempdir, 'foo', 'bar.py')
with open(file1, 'wb') as fd:
fd.write("import baz\n".encode('ascii'))
file2 = os.path.join(tempdir, 'baz.py')
with open(file2, 'wb') as fd:
fd.write("import contraband".encode('ascii'))
log = []
reporter = LoggingReporter(log)
warnings = checkRecursive([tempdir], reporter)
self.assertEqual(warnings, 2)
self.assertEqual(
sorted(log),
sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))),
('flake',
str(UnusedImport(file2, Node(1), 'contraband')))]))
finally:
shutil.rmtree(tempdir)
class IntegrationTests(TestCase):
"""
Tests of the pyflakes script that actually spawn the script.
"""
def setUp(self):
self.tempdir = tempfile.mkdtemp()
self.tempfilepath = os.path.join(self.tempdir, 'temp')
def tearDown(self):
shutil.rmtree(self.tempdir)
def getPyflakesBinary(self):
"""
Return the path to the pyflakes binary.
"""
import pyflakes
package_dir = os.path.dirname(pyflakes.__file__)
return os.path.join(package_dir, '..', 'bin', 'pyflakes')
def runPyflakes(self, paths, stdin=None):
"""
Launch a subprocess running C{pyflakes}.
@param paths: Command-line arguments to pass to pyflakes.
@param stdin: Text to use as stdin.
@return: C{(returncode, stdout, stderr)} of the completed pyflakes
process.
"""
env = dict(os.environ)
env['PYTHONPATH'] = os.pathsep.join(sys.path)
command = [sys.executable, self.getPyflakesBinary()]
command.extend(paths)
if stdin:
p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = p.communicate(stdin.encode('ascii'))
else:
p = subprocess.Popen(command, env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = p.communicate()
rv = p.wait()
if sys.version_info >= (3,):
stdout = stdout.decode('utf-8')
stderr = stderr.decode('utf-8')
return (stdout, stderr, rv)
def test_goodFile(self):
"""
When a Python source file is all good, the return code is zero and no
messages are printed to either stdout or stderr.
"""
open(self.tempfilepath, 'a').close()
d = self.runPyflakes([self.tempfilepath])
self.assertEqual(d, ('', '', 0))
def test_fileWithFlakes(self):
"""
When a Python source file has warnings, the return code is non-zero
and the warnings are printed to stdout.
"""
with open(self.tempfilepath, 'wb') as fd:
fd.write("import contraband\n".encode('ascii'))
d = self.runPyflakes([self.tempfilepath])
expected = UnusedImport(self.tempfilepath, Node(1), 'contraband')
self.assertEqual(d, ("%s%s" % (expected, os.linesep), '', 1))
def test_errors_io(self):
"""
When pyflakes finds errors with the files it's given, (if they don't
exist, say), then the return code is non-zero and the errors are
printed to stderr.
"""
d = self.runPyflakes([self.tempfilepath])
error_msg = '%s: No such file or directory%s' % (self.tempfilepath,
os.linesep)
self.assertEqual(d, ('', error_msg, 1))
def test_errors_syntax(self):
"""
When pyflakes finds errors with the files it's given, (if they don't
exist, say), then the return code is non-zero and the errors are
printed to stderr.
"""
with open(self.tempfilepath, 'wb') as fd:
fd.write("import".encode('ascii'))
d = self.runPyflakes([self.tempfilepath])
error_msg = '{0}:1:{2}: invalid syntax{1}import{1} {3}^{1}'.format(
self.tempfilepath, os.linesep, 6 if PYPY else 7, '' if PYPY else ' ')
self.assertEqual(d, ('', error_msg, 1))
def test_readFromStdin(self):
"""
If no arguments are passed to C{pyflakes} then it reads from stdin.
"""
d = self.runPyflakes([], stdin='import contraband')
expected = UnusedImport('', Node(1), 'contraband')
self.assertEqual(d, ("%s%s" % (expected, os.linesep), '', 1))
class TestMain(IntegrationTests):
"""
Tests of the pyflakes main function.
"""
def runPyflakes(self, paths, stdin=None):
try:
with SysStreamCapturing(stdin) as capture:
main(args=paths)
except SystemExit as e:
self.assertIsInstance(e.code, bool)
rv = int(e.code)
return (capture.output, capture.error, rv)
else:
raise RuntimeError('SystemExit not raised')
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/test/test_builtin.py 0000644 0001750 0001750 00000001547 00000000000 022711 0 ustar 00asottile asottile 0000000 0000000 """
Tests for detecting redefinition of builtins.
"""
from sys import version_info
from pyflakes import messages as m
from pyflakes.test.harness import TestCase, skipIf
class TestBuiltins(TestCase):
def test_builtin_unbound_local(self):
self.flakes('''
def foo():
a = range(1, 10)
range = a
return range
foo()
print(range)
''', m.UndefinedLocal)
def test_global_shadowing_builtin(self):
self.flakes('''
def f():
global range
range = None
print(range)
f()
''')
@skipIf(version_info >= (3,), 'not an UnboundLocalError in Python 3')
def test_builtin_in_comprehension(self):
self.flakes('''
def f():
[range for range in range(1, 10)]
f()
''', m.UndefinedLocal)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/test/test_checker.py 0000644 0001750 0001750 00000013576 00000000000 022654 0 ustar 00asottile asottile 0000000 0000000 import ast
import sys
from pyflakes import checker
from pyflakes.test.harness import TestCase, skipIf
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)
@skipIf(sys.version_info < (3, 5), 'async syntax introduced in py35')
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(u'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)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/test/test_code_segment.py 0000644 0001750 0001750 00000010756 00000000000 023701 0 ustar 00asottile asottile 0000000 0000000 from sys import version_info
from pyflakes import messages as m
from pyflakes.checker import (FunctionScope, ClassScope, ModuleScope,
Argument, FunctionDefinition, Assignment)
from pyflakes.test.harness import TestCase, skipIf
class TestCodeSegments(TestCase):
"""
Tests for segments of a module
"""
def test_function_segment(self):
self.flakes('''
def foo():
def bar():
pass
''', is_segment=True)
self.flakes('''
def foo():
def bar():
x = 0
''', m.UnusedVariable, is_segment=True)
def test_class_segment(self):
self.flakes('''
class Foo:
class Bar:
pass
''', is_segment=True)
self.flakes('''
class Foo:
def bar():
x = 0
''', m.UnusedVariable, is_segment=True)
def test_scope_class(self):
checker = self.flakes('''
class Foo:
x = 0
def bar(a, b=1, *d, **e):
pass
''', is_segment=True)
scopes = checker.deadScopes
module_scopes = [
scope for scope in scopes if scope.__class__ is ModuleScope]
class_scopes = [
scope for scope in scopes if scope.__class__ is ClassScope]
function_scopes = [
scope for scope in scopes if scope.__class__ is FunctionScope]
# Ensure module scope is not present because we are analysing
# the inner contents of Foo
self.assertEqual(len(module_scopes), 0)
self.assertEqual(len(class_scopes), 1)
self.assertEqual(len(function_scopes), 1)
class_scope = class_scopes[0]
function_scope = function_scopes[0]
self.assertIsInstance(class_scope, ClassScope)
self.assertIsInstance(function_scope, FunctionScope)
self.assertIn('x', class_scope)
self.assertIn('bar', class_scope)
self.assertIn('a', function_scope)
self.assertIn('b', function_scope)
self.assertIn('d', function_scope)
self.assertIn('e', function_scope)
self.assertIsInstance(class_scope['bar'], FunctionDefinition)
self.assertIsInstance(class_scope['x'], Assignment)
self.assertIsInstance(function_scope['a'], Argument)
self.assertIsInstance(function_scope['b'], Argument)
self.assertIsInstance(function_scope['d'], Argument)
self.assertIsInstance(function_scope['e'], Argument)
def test_scope_function(self):
checker = self.flakes('''
def foo(a, b=1, *d, **e):
def bar(f, g=1, *h, **i):
pass
''', is_segment=True)
scopes = checker.deadScopes
module_scopes = [
scope for scope in scopes if scope.__class__ is ModuleScope]
function_scopes = [
scope for scope in scopes if scope.__class__ is FunctionScope]
# Ensure module scope is not present because we are analysing
# the inner contents of foo
self.assertEqual(len(module_scopes), 0)
self.assertEqual(len(function_scopes), 2)
function_scope_foo = function_scopes[1]
function_scope_bar = function_scopes[0]
self.assertIsInstance(function_scope_foo, FunctionScope)
self.assertIsInstance(function_scope_bar, FunctionScope)
self.assertIn('a', function_scope_foo)
self.assertIn('b', function_scope_foo)
self.assertIn('d', function_scope_foo)
self.assertIn('e', function_scope_foo)
self.assertIn('bar', function_scope_foo)
self.assertIn('f', function_scope_bar)
self.assertIn('g', function_scope_bar)
self.assertIn('h', function_scope_bar)
self.assertIn('i', function_scope_bar)
self.assertIsInstance(function_scope_foo['bar'], FunctionDefinition)
self.assertIsInstance(function_scope_foo['a'], Argument)
self.assertIsInstance(function_scope_foo['b'], Argument)
self.assertIsInstance(function_scope_foo['d'], Argument)
self.assertIsInstance(function_scope_foo['e'], Argument)
self.assertIsInstance(function_scope_bar['f'], Argument)
self.assertIsInstance(function_scope_bar['g'], Argument)
self.assertIsInstance(function_scope_bar['h'], Argument)
self.assertIsInstance(function_scope_bar['i'], Argument)
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_scope_async_function(self):
self.flakes('async def foo(): pass', is_segment=True)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/test/test_dict.py 0000644 0001750 0001750 00000013642 00000000000 022165 0 ustar 00asottile asottile 0000000 0000000 """
Tests for dict duplicate keys Pyflakes behavior.
"""
from sys import version_info
from pyflakes import messages as m
from pyflakes.test.harness import TestCase, skipIf
class Test(TestCase):
def test_duplicate_keys(self):
self.flakes(
"{'yes': 1, 'yes': 2}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
@skipIf(version_info < (3,),
"bytes and strings with same 'value' are not equal in python3")
def test_duplicate_keys_bytes_vs_unicode_py3(self):
self.flakes("{b'a': 1, u'a': 2}")
@skipIf(version_info < (3,),
"bytes and strings with same 'value' are not equal in python3")
def test_duplicate_values_bytes_vs_unicode_py3(self):
self.flakes(
"{1: b'a', 1: u'a'}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
@skipIf(version_info >= (3,),
"bytes and strings with same 'value' are equal in python2")
def test_duplicate_keys_bytes_vs_unicode_py2(self):
self.flakes(
"{b'a': 1, u'a': 2}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
@skipIf(version_info >= (3,),
"bytes and strings with same 'value' are equal in python2")
def test_duplicate_values_bytes_vs_unicode_py2(self):
self.flakes("{1: b'a', 1: u'a'}")
def test_multiple_duplicate_keys(self):
self.flakes(
"{'yes': 1, 'yes': 2, 'no': 2, 'no': 3}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_keys_in_function(self):
self.flakes(
'''
def f(thing):
pass
f({'yes': 1, 'yes': 2})
''',
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_keys_in_lambda(self):
self.flakes(
"lambda x: {(0,1): 1, (0,1): 2}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_keys_tuples(self):
self.flakes(
"{(0,1): 1, (0,1): 2}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_keys_tuples_int_and_float(self):
self.flakes(
"{(0,1): 1, (0,1.0): 2}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_keys_ints(self):
self.flakes(
"{1: 1, 1: 2}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_keys_bools(self):
self.flakes(
"{True: 1, True: 2}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_keys_bools_false(self):
# Needed to ensure 2.x correctly coerces these from variables
self.flakes(
"{False: 1, False: 2}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_keys_none(self):
self.flakes(
"{None: 1, None: 2}",
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_variable_keys(self):
self.flakes(
'''
a = 1
{a: 1, a: 2}
''',
m.MultiValueRepeatedKeyVariable,
m.MultiValueRepeatedKeyVariable,
)
def test_duplicate_variable_values(self):
self.flakes(
'''
a = 1
b = 2
{1: a, 1: b}
''',
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_variable_values_same_value(self):
# Current behaviour is not to look up variable values. This is to
# confirm that.
self.flakes(
'''
a = 1
b = 1
{1: a, 1: b}
''',
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_duplicate_key_float_and_int(self):
"""
These do look like different values, but when it comes to their use as
keys, they compare as equal and so are actually duplicates.
The literal dict {1: 1, 1.0: 1} actually becomes {1.0: 1}.
"""
self.flakes(
'''
{1: 1, 1.0: 2}
''',
m.MultiValueRepeatedKeyLiteral,
m.MultiValueRepeatedKeyLiteral,
)
def test_no_duplicate_key_error_same_value(self):
self.flakes('''
{'yes': 1, 'yes': 1}
''')
def test_no_duplicate_key_errors(self):
self.flakes('''
{'yes': 1, 'no': 2}
''')
def test_no_duplicate_keys_tuples_same_first_element(self):
self.flakes("{(0,1): 1, (0,2): 1}")
def test_no_duplicate_key_errors_func_call(self):
self.flakes('''
def test(thing):
pass
test({True: 1, None: 2, False: 1})
''')
def test_no_duplicate_key_errors_bool_or_none(self):
self.flakes("{True: 1, None: 2, False: 1}")
def test_no_duplicate_key_errors_ints(self):
self.flakes('''
{1: 1, 2: 1}
''')
def test_no_duplicate_key_errors_vars(self):
self.flakes('''
test = 'yes'
rest = 'yes'
{test: 1, rest: 2}
''')
def test_no_duplicate_key_errors_tuples(self):
self.flakes('''
{(0,1): 1, (0,2): 1}
''')
def test_no_duplicate_key_errors_instance_attributes(self):
self.flakes('''
class Test():
pass
f = Test()
f.a = 1
{f.a: 1, f.a: 1}
''')
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/test/test_doctests.py 0000644 0001750 0001750 00000031611 00000000000 023066 0 ustar 00asottile asottile 0000000 0000000 import sys
import textwrap
from pyflakes import messages as m
from pyflakes.checker import (
DoctestScope,
FunctionScope,
ModuleScope,
)
from pyflakes.test.test_other import Test as TestOther
from pyflakes.test.test_imports import Test as TestImports
from pyflakes.test.test_undefined_names import Test as TestUndefinedNames
from pyflakes.test.harness import TestCase, skip
try:
sys.pypy_version_info
PYPY = True
except AttributeError:
PYPY = False
class _DoctestMixin(object):
withDoctest = True
def doctestify(self, input):
lines = []
for line in textwrap.dedent(input).splitlines():
if line.strip() == '':
pass
elif (line.startswith(' ') or
line.startswith('except:') or
line.startswith('except ') or
line.startswith('finally:') or
line.startswith('else:') or
line.startswith('elif ') or
(lines and lines[-1].startswith(('>>> @', '... @')))):
line = "... %s" % line
else:
line = ">>> %s" % line
lines.append(line)
doctestificator = textwrap.dedent('''\
def doctest_something():
"""
%s
"""
''')
return doctestificator % "\n ".join(lines)
def flakes(self, input, *args, **kw):
return super(_DoctestMixin, self).flakes(self.doctestify(input), *args, **kw)
class Test(TestCase):
withDoctest = True
def test_scope_class(self):
"""Check that a doctest is given a DoctestScope."""
checker = self.flakes("""
m = None
def doctest_stuff():
'''
>>> d = doctest_stuff()
'''
f = m
return f
""")
scopes = checker.deadScopes
module_scopes = [
scope for scope in scopes if scope.__class__ is ModuleScope]
doctest_scopes = [
scope for scope in scopes if scope.__class__ is DoctestScope]
function_scopes = [
scope for scope in scopes if scope.__class__ is FunctionScope]
self.assertEqual(len(module_scopes), 1)
self.assertEqual(len(doctest_scopes), 1)
module_scope = module_scopes[0]
doctest_scope = doctest_scopes[0]
self.assertIsInstance(doctest_scope, DoctestScope)
self.assertIsInstance(doctest_scope, ModuleScope)
self.assertNotIsInstance(doctest_scope, FunctionScope)
self.assertNotIsInstance(module_scope, DoctestScope)
self.assertIn('m', module_scope)
self.assertIn('doctest_stuff', module_scope)
self.assertIn('d', doctest_scope)
self.assertEqual(len(function_scopes), 1)
self.assertIn('f', function_scopes[0])
def test_nested_doctest_ignored(self):
"""Check that nested doctests are ignored."""
checker = self.flakes("""
m = None
def doctest_stuff():
'''
>>> def function_in_doctest():
... \"\"\"
... >>> ignored_undefined_name
... \"\"\"
... df = m
... return df
...
>>> function_in_doctest()
'''
f = m
return f
""")
scopes = checker.deadScopes
module_scopes = [
scope for scope in scopes if scope.__class__ is ModuleScope]
doctest_scopes = [
scope for scope in scopes if scope.__class__ is DoctestScope]
function_scopes = [
scope for scope in scopes if scope.__class__ is FunctionScope]
self.assertEqual(len(module_scopes), 1)
self.assertEqual(len(doctest_scopes), 1)
module_scope = module_scopes[0]
doctest_scope = doctest_scopes[0]
self.assertIn('m', module_scope)
self.assertIn('doctest_stuff', module_scope)
self.assertIn('function_in_doctest', doctest_scope)
self.assertEqual(len(function_scopes), 2)
self.assertIn('f', function_scopes[0])
self.assertIn('df', function_scopes[1])
def test_global_module_scope_pollution(self):
"""Check that global in doctest does not pollute module scope."""
checker = self.flakes("""
def doctest_stuff():
'''
>>> def function_in_doctest():
... global m
... m = 50
... df = 10
... m = df
...
>>> function_in_doctest()
'''
f = 10
return f
""")
scopes = checker.deadScopes
module_scopes = [
scope for scope in scopes if scope.__class__ is ModuleScope]
doctest_scopes = [
scope for scope in scopes if scope.__class__ is DoctestScope]
function_scopes = [
scope for scope in scopes if scope.__class__ is FunctionScope]
self.assertEqual(len(module_scopes), 1)
self.assertEqual(len(doctest_scopes), 1)
module_scope = module_scopes[0]
doctest_scope = doctest_scopes[0]
self.assertIn('doctest_stuff', module_scope)
self.assertIn('function_in_doctest', doctest_scope)
self.assertEqual(len(function_scopes), 2)
self.assertIn('f', function_scopes[0])
self.assertIn('df', function_scopes[1])
self.assertIn('m', function_scopes[1])
self.assertNotIn('m', module_scope)
def test_global_undefined(self):
self.flakes("""
global m
def doctest_stuff():
'''
>>> m
'''
""", m.UndefinedName)
def test_nested_class(self):
"""Doctest within nested class are processed."""
self.flakes("""
class C:
class D:
'''
>>> m
'''
def doctest_stuff(self):
'''
>>> m
'''
return 1
""", m.UndefinedName, m.UndefinedName)
def test_ignore_nested_function(self):
"""Doctest module does not process doctest in nested functions."""
# 'syntax error' would cause a SyntaxError if the doctest was processed.
# However doctest does not find doctest in nested functions
# (https://bugs.python.org/issue1650090). If nested functions were
# processed, this use of m should cause UndefinedName, and the
# name inner_function should probably exist in the doctest scope.
self.flakes("""
def doctest_stuff():
def inner_function():
'''
>>> syntax error
>>> inner_function()
1
>>> m
'''
return 1
m = inner_function()
return m
""")
def test_inaccessible_scope_class(self):
"""Doctest may not access class scope."""
self.flakes("""
class C:
def doctest_stuff(self):
'''
>>> m
'''
return 1
m = 1
""", m.UndefinedName)
def test_importBeforeDoctest(self):
self.flakes("""
import foo
def doctest_stuff():
'''
>>> foo
'''
""")
@skip("todo")
def test_importBeforeAndInDoctest(self):
self.flakes('''
import foo
def doctest_stuff():
"""
>>> import foo
>>> foo
"""
foo
''', m.RedefinedWhileUnused)
def test_importInDoctestAndAfter(self):
self.flakes('''
def doctest_stuff():
"""
>>> import foo
>>> foo
"""
import foo
foo()
''')
def test_offsetInDoctests(self):
exc = self.flakes('''
def doctest_stuff():
"""
>>> x # line 5
"""
''', m.UndefinedName).messages[0]
self.assertEqual(exc.lineno, 5)
self.assertEqual(exc.col, 12)
def test_offsetInLambdasInDoctests(self):
exc = self.flakes('''
def doctest_stuff():
"""
>>> lambda: x # line 5
"""
''', m.UndefinedName).messages[0]
self.assertEqual(exc.lineno, 5)
self.assertEqual(exc.col, 20)
def test_offsetAfterDoctests(self):
exc = self.flakes('''
def doctest_stuff():
"""
>>> x = 5
"""
x
''', m.UndefinedName).messages[0]
self.assertEqual(exc.lineno, 8)
self.assertEqual(exc.col, 0)
def test_syntaxErrorInDoctest(self):
exceptions = self.flakes(
'''
def doctest_stuff():
"""
>>> from # line 4
>>> fortytwo = 42
>>> except Exception:
"""
''',
m.DoctestSyntaxError,
m.DoctestSyntaxError,
m.DoctestSyntaxError).messages
exc = exceptions[0]
self.assertEqual(exc.lineno, 4)
if PYPY:
self.assertEqual(exc.col, 27)
elif sys.version_info >= (3, 8):
self.assertEqual(exc.col, 18)
else:
self.assertEqual(exc.col, 26)
# PyPy error column offset is 0,
# for the second and third line of the doctest
# i.e. at the beginning of the line
exc = exceptions[1]
self.assertEqual(exc.lineno, 5)
if PYPY:
self.assertEqual(exc.col, 14)
else:
self.assertEqual(exc.col, 16)
exc = exceptions[2]
self.assertEqual(exc.lineno, 6)
if PYPY:
self.assertEqual(exc.col, 14)
elif sys.version_info >= (3, 8):
self.assertEqual(exc.col, 13)
else:
self.assertEqual(exc.col, 18)
def test_indentationErrorInDoctest(self):
exc = self.flakes('''
def doctest_stuff():
"""
>>> if True:
... pass
"""
''', m.DoctestSyntaxError).messages[0]
self.assertEqual(exc.lineno, 5)
if PYPY:
self.assertEqual(exc.col, 14)
elif sys.version_info >= (3, 8):
self.assertEqual(exc.col, 13)
else:
self.assertEqual(exc.col, 16)
def test_offsetWithMultiLineArgs(self):
(exc1, exc2) = self.flakes(
'''
def doctest_stuff(arg1,
arg2,
arg3):
"""
>>> assert
>>> this
"""
''',
m.DoctestSyntaxError,
m.UndefinedName).messages
self.assertEqual(exc1.lineno, 6)
if PYPY:
self.assertEqual(exc1.col, 20)
else:
self.assertEqual(exc1.col, 19)
self.assertEqual(exc2.lineno, 7)
self.assertEqual(exc2.col, 12)
def test_doctestCanReferToFunction(self):
self.flakes("""
def foo():
'''
>>> foo
'''
""")
def test_doctestCanReferToClass(self):
self.flakes("""
class Foo():
'''
>>> Foo
'''
def bar(self):
'''
>>> Foo
'''
""")
def test_noOffsetSyntaxErrorInDoctest(self):
exceptions = self.flakes(
'''
def buildurl(base, *args, **kwargs):
"""
>>> buildurl('/blah.php', ('a', '&'), ('b', '=')
'/blah.php?a=%26&b=%3D'
>>> buildurl('/blah.php', a='&', 'b'='=')
'/blah.php?b=%3D&a=%26'
"""
pass
''',
m.DoctestSyntaxError,
m.DoctestSyntaxError).messages
exc = exceptions[0]
self.assertEqual(exc.lineno, 4)
exc = exceptions[1]
self.assertEqual(exc.lineno, 6)
def test_singleUnderscoreInDoctest(self):
self.flakes('''
def func():
"""A docstring
>>> func()
1
>>> _
1
"""
return 1
''')
def test_globalUnderscoreInDoctest(self):
self.flakes("""
from gettext import ugettext as _
def doctest_stuff():
'''
>>> pass
'''
""", m.UnusedImport)
class TestOther(_DoctestMixin, TestOther):
"""Run TestOther with each test wrapped in a doctest."""
class TestImports(_DoctestMixin, TestImports):
"""Run TestImports with each test wrapped in a doctest."""
class TestUndefinedNames(_DoctestMixin, TestUndefinedNames):
"""Run TestUndefinedNames with each test wrapped in a doctest."""
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633476446.0
pyflakes-2.4.0/pyflakes/test/test_imports.py 0000644 0001750 0001750 00000103447 00000000000 022742 0 ustar 00asottile asottile 0000000 0000000 from sys import version_info
from pyflakes import messages as m
from pyflakes.checker import (
FutureImportation,
Importation,
ImportationFrom,
StarImportation,
SubmoduleImportation,
)
from pyflakes.test.harness import TestCase, skip, skipIf
class TestImportationObject(TestCase):
def test_import_basic(self):
binding = Importation('a', None, 'a')
assert binding.source_statement == 'import a'
assert str(binding) == 'a'
def test_import_as(self):
binding = Importation('c', None, 'a')
assert binding.source_statement == 'import a as c'
assert str(binding) == 'a as c'
def test_import_submodule(self):
binding = SubmoduleImportation('a.b', None)
assert binding.source_statement == 'import a.b'
assert str(binding) == 'a.b'
def test_import_submodule_as(self):
# A submodule import with an as clause is not a SubmoduleImportation
binding = Importation('c', None, 'a.b')
assert binding.source_statement == 'import a.b as c'
assert str(binding) == 'a.b as c'
def test_import_submodule_as_source_name(self):
binding = Importation('a', None, 'a.b')
assert binding.source_statement == 'import a.b as a'
assert str(binding) == 'a.b as a'
def test_importfrom_relative(self):
binding = ImportationFrom('a', None, '.', 'a')
assert binding.source_statement == 'from . import a'
assert str(binding) == '.a'
def test_importfrom_relative_parent(self):
binding = ImportationFrom('a', None, '..', 'a')
assert binding.source_statement == 'from .. import a'
assert str(binding) == '..a'
def test_importfrom_relative_with_module(self):
binding = ImportationFrom('b', None, '..a', 'b')
assert binding.source_statement == 'from ..a import b'
assert str(binding) == '..a.b'
def test_importfrom_relative_with_module_as(self):
binding = ImportationFrom('c', None, '..a', 'b')
assert binding.source_statement == 'from ..a import b as c'
assert str(binding) == '..a.b as c'
def test_importfrom_member(self):
binding = ImportationFrom('b', None, 'a', 'b')
assert binding.source_statement == 'from a import b'
assert str(binding) == 'a.b'
def test_importfrom_submodule_member(self):
binding = ImportationFrom('c', None, 'a.b', 'c')
assert binding.source_statement == 'from a.b import c'
assert str(binding) == 'a.b.c'
def test_importfrom_member_as(self):
binding = ImportationFrom('c', None, 'a', 'b')
assert binding.source_statement == 'from a import b as c'
assert str(binding) == 'a.b as c'
def test_importfrom_submodule_member_as(self):
binding = ImportationFrom('d', None, 'a.b', 'c')
assert binding.source_statement == 'from a.b import c as d'
assert str(binding) == 'a.b.c as d'
def test_importfrom_star(self):
binding = StarImportation('a.b', None)
assert binding.source_statement == 'from a.b import *'
assert str(binding) == 'a.b.*'
def test_importfrom_star_relative(self):
binding = StarImportation('.b', None)
assert binding.source_statement == 'from .b import *'
assert str(binding) == '.b.*'
def test_importfrom_future(self):
binding = FutureImportation('print_function', None, None)
assert binding.source_statement == 'from __future__ import print_function'
assert str(binding) == '__future__.print_function'
def test_unusedImport_underscore(self):
"""
The magic underscore var should be reported as unused when used as an
import alias.
"""
self.flakes('import fu as _', m.UnusedImport)
class Test(TestCase):
def test_unusedImport(self):
self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport)
self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport)
def test_unusedImport_relative(self):
self.flakes('from . import fu', m.UnusedImport)
self.flakes('from . import fu as baz', m.UnusedImport)
self.flakes('from .. import fu', m.UnusedImport)
self.flakes('from ... import fu', m.UnusedImport)
self.flakes('from .. import fu as baz', m.UnusedImport)
self.flakes('from .bar import fu', m.UnusedImport)
self.flakes('from ..bar import fu', m.UnusedImport)
self.flakes('from ...bar import fu', m.UnusedImport)
self.flakes('from ...bar import fu as baz', m.UnusedImport)
checker = self.flakes('from . import fu', m.UnusedImport)
error = checker.messages[0]
assert error.message == '%r imported but unused'
assert error.message_args == ('.fu', )
checker = self.flakes('from . import fu as baz', m.UnusedImport)
error = checker.messages[0]
assert error.message == '%r imported but unused'
assert error.message_args == ('.fu as baz', )
def test_aliasedImport(self):
self.flakes('import fu as FU, bar as FU',
m.RedefinedWhileUnused, m.UnusedImport)
self.flakes('from moo import fu as FU, bar as FU',
m.RedefinedWhileUnused, m.UnusedImport)
def test_aliasedImportShadowModule(self):
"""Imported aliases can shadow the source of the import."""
self.flakes('from moo import fu as moo; moo')
self.flakes('import fu as fu; fu')
self.flakes('import fu.bar as fu; fu')
def test_usedImport(self):
self.flakes('import fu; print(fu)')
self.flakes('from baz import fu; print(fu)')
self.flakes('import fu; del fu')
def test_usedImport_relative(self):
self.flakes('from . import fu; assert fu')
self.flakes('from .bar import fu; assert fu')
self.flakes('from .. import fu; assert fu')
self.flakes('from ..bar import fu as baz; assert baz')
def test_redefinedWhileUnused(self):
self.flakes('import fu; fu = 3', m.RedefinedWhileUnused)
self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused)
self.flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused)
def test_redefinedIf(self):
"""
Test that importing a module twice within an if
block does raise a warning.
"""
self.flakes('''
i = 2
if i==1:
import os
import os
os.path''', m.RedefinedWhileUnused)
def test_redefinedIfElse(self):
"""
Test that importing a module twice in if
and else blocks does not raise a warning.
"""
self.flakes('''
i = 2
if i==1:
import os
else:
import os
os.path''')
def test_redefinedTry(self):
"""
Test that importing a module twice in a try block
does raise a warning.
"""
self.flakes('''
try:
import os
import os
except:
pass
os.path''', m.RedefinedWhileUnused)
def test_redefinedTryExcept(self):
"""
Test that importing a module twice in a try
and except block does not raise a warning.
"""
self.flakes('''
try:
import os
except:
import os
os.path''')
def test_redefinedTryNested(self):
"""
Test that importing a module twice using a nested
try/except and if blocks does not issue a warning.
"""
self.flakes('''
try:
if True:
if True:
import os
except:
import os
os.path''')
def test_redefinedTryExceptMulti(self):
self.flakes("""
try:
from aa import mixer
except AttributeError:
from bb import mixer
except RuntimeError:
from cc import mixer
except:
from dd import mixer
mixer(123)
""")
def test_redefinedTryElse(self):
self.flakes("""
try:
from aa import mixer
except ImportError:
pass
else:
from bb import mixer
mixer(123)
""", m.RedefinedWhileUnused)
def test_redefinedTryExceptElse(self):
self.flakes("""
try:
import funca
except ImportError:
from bb import funca
from bb import funcb
else:
from bbb import funcb
print(funca, funcb)
""")
def test_redefinedTryExceptFinally(self):
self.flakes("""
try:
from aa import a
except ImportError:
from bb import a
finally:
a = 42
print(a)
""")
def test_redefinedTryExceptElseFinally(self):
self.flakes("""
try:
import b
except ImportError:
b = Ellipsis
from bb import a
else:
from aa import a
finally:
a = 42
print(a, b)
""")
def test_redefinedByFunction(self):
self.flakes('''
import fu
def fu():
pass
''', m.RedefinedWhileUnused)
def test_redefinedInNestedFunction(self):
"""
Test that shadowing a global name with a nested function definition
generates a warning.
"""
self.flakes('''
import fu
def bar():
def baz():
def fu():
pass
''', m.RedefinedWhileUnused, m.UnusedImport)
def test_redefinedInNestedFunctionTwice(self):
"""
Test that shadowing a global name with a nested function definition
generates a warning.
"""
self.flakes('''
import fu
def bar():
import fu
def baz():
def fu():
pass
''',
m.RedefinedWhileUnused, m.RedefinedWhileUnused,
m.UnusedImport, m.UnusedImport)
def test_redefinedButUsedLater(self):
"""
Test that a global import which is redefined locally,
but used later in another scope does not generate a warning.
"""
self.flakes('''
import unittest, transport
class GetTransportTestCase(unittest.TestCase):
def test_get_transport(self):
transport = 'transport'
self.assertIsNotNone(transport)
class TestTransportMethodArgs(unittest.TestCase):
def test_send_defaults(self):
transport.Transport()
''')
def test_redefinedByClass(self):
self.flakes('''
import fu
class fu:
pass
''', m.RedefinedWhileUnused)
def test_redefinedBySubclass(self):
"""
If an imported name is redefined by a class statement which also uses
that name in the bases list, no warning is emitted.
"""
self.flakes('''
from fu import bar
class bar(bar):
pass
''')
def test_redefinedInClass(self):
"""
Test that shadowing a global with a class attribute does not produce a
warning.
"""
self.flakes('''
import fu
class bar:
fu = 1
print(fu)
''')
def test_importInClass(self):
"""
Test that import within class is a locally scoped attribute.
"""
self.flakes('''
class bar:
import fu
''')
self.flakes('''
class bar:
import fu
fu
''', m.UndefinedName)
def test_usedInFunction(self):
self.flakes('''
import fu
def fun():
print(fu)
''')
def test_shadowedByParameter(self):
self.flakes('''
import fu
def fun(fu):
print(fu)
''', m.UnusedImport, m.RedefinedWhileUnused)
self.flakes('''
import fu
def fun(fu):
print(fu)
print(fu)
''')
def test_newAssignment(self):
self.flakes('fu = None')
def test_usedInGetattr(self):
self.flakes('import fu; fu.bar.baz')
self.flakes('import fu; "bar".fu.baz', m.UnusedImport)
def test_usedInSlice(self):
self.flakes('import fu; print(fu.bar[1:])')
def test_usedInIfBody(self):
self.flakes('''
import fu
if True: print(fu)
''')
def test_usedInIfConditional(self):
self.flakes('''
import fu
if fu: pass
''')
def test_usedInElifConditional(self):
self.flakes('''
import fu
if False: pass
elif fu: pass
''')
def test_usedInElse(self):
self.flakes('''
import fu
if False: pass
else: print(fu)
''')
def test_usedInCall(self):
self.flakes('import fu; fu.bar()')
def test_usedInClass(self):
self.flakes('''
import fu
class bar:
bar = fu
''')
def test_usedInClassBase(self):
self.flakes('''
import fu
class bar(object, fu.baz):
pass
''')
def test_notUsedInNestedScope(self):
self.flakes('''
import fu
def bleh():
pass
print(fu)
''')
def test_usedInFor(self):
self.flakes('''
import fu
for bar in range(9):
print(fu)
''')
def test_usedInForElse(self):
self.flakes('''
import fu
for bar in range(10):
pass
else:
print(fu)
''')
def test_redefinedByFor(self):
self.flakes('''
import fu
for fu in range(2):
pass
''', m.ImportShadowedByLoopVar)
def test_shadowedByFor(self):
"""
Test that shadowing a global name with a for loop variable generates a
warning.
"""
self.flakes('''
import fu
fu.bar()
for fu in ():
pass
''', m.ImportShadowedByLoopVar)
def test_shadowedByForDeep(self):
"""
Test that shadowing a global name with a for loop variable nested in a
tuple unpack generates a warning.
"""
self.flakes('''
import fu
fu.bar()
for (x, y, z, (a, b, c, (fu,))) in ():
pass
''', m.ImportShadowedByLoopVar)
# Same with a list instead of a tuple
self.flakes('''
import fu
fu.bar()
for [x, y, z, (a, b, c, (fu,))] in ():
pass
''', m.ImportShadowedByLoopVar)
def test_usedInReturn(self):
self.flakes('''
import fu
def fun():
return fu
''')
def test_usedInOperators(self):
self.flakes('import fu; 3 + fu.bar')
self.flakes('import fu; 3 % fu.bar')
self.flakes('import fu; 3 - fu.bar')
self.flakes('import fu; 3 * fu.bar')
self.flakes('import fu; 3 ** fu.bar')
self.flakes('import fu; 3 / fu.bar')
self.flakes('import fu; 3 // fu.bar')
self.flakes('import fu; -fu.bar')
self.flakes('import fu; ~fu.bar')
self.flakes('import fu; 1 == fu.bar')
self.flakes('import fu; 1 | fu.bar')
self.flakes('import fu; 1 & fu.bar')
self.flakes('import fu; 1 ^ fu.bar')
self.flakes('import fu; 1 >> fu.bar')
self.flakes('import fu; 1 << fu.bar')
def test_usedInAssert(self):
self.flakes('import fu; assert fu.bar')
def test_usedInSubscript(self):
self.flakes('import fu; fu.bar[1]')
def test_usedInLogic(self):
self.flakes('import fu; fu and False')
self.flakes('import fu; fu or False')
self.flakes('import fu; not fu.bar')
def test_usedInList(self):
self.flakes('import fu; [fu]')
def test_usedInTuple(self):
self.flakes('import fu; (fu,)')
def test_usedInTry(self):
self.flakes('''
import fu
try: fu
except: pass
''')
def test_usedInExcept(self):
self.flakes('''
import fu
try: fu
except: pass
''')
def test_redefinedByExcept(self):
expected = [m.RedefinedWhileUnused]
if version_info >= (3,):
# The exc variable is unused inside the exception handler.
expected.append(m.UnusedVariable)
self.flakes('''
import fu
try: pass
except Exception as fu: pass
''', *expected)
def test_usedInRaise(self):
self.flakes('''
import fu
raise fu.bar
''')
def test_usedInYield(self):
self.flakes('''
import fu
def gen():
yield fu
''')
def test_usedInDict(self):
self.flakes('import fu; {fu:None}')
self.flakes('import fu; {1:fu}')
def test_usedInParameterDefault(self):
self.flakes('''
import fu
def f(bar=fu):
pass
''')
def test_usedInAttributeAssign(self):
self.flakes('import fu; fu.bar = 1')
def test_usedInKeywordArg(self):
self.flakes('import fu; fu.bar(stuff=fu)')
def test_usedInAssignment(self):
self.flakes('import fu; bar=fu')
self.flakes('import fu; n=0; n+=fu')
def test_usedInListComp(self):
self.flakes('import fu; [fu for _ in range(1)]')
self.flakes('import fu; [1 for _ in range(1) if fu]')
@skipIf(version_info >= (3,),
'in Python 3 list comprehensions execute in a separate scope')
def test_redefinedByListComp(self):
self.flakes('import fu; [1 for fu in range(1)]',
m.RedefinedInListComp)
def test_usedInTryFinally(self):
self.flakes('''
import fu
try: pass
finally: fu
''')
self.flakes('''
import fu
try: fu
finally: pass
''')
def test_usedInWhile(self):
self.flakes('''
import fu
while 0:
fu
''')
self.flakes('''
import fu
while fu: pass
''')
def test_usedInGlobal(self):
"""
A 'global' statement shadowing an unused import should not prevent it
from being reported.
"""
self.flakes('''
import fu
def f(): global fu
''', m.UnusedImport)
def test_usedAndGlobal(self):
"""
A 'global' statement shadowing a used import should not cause it to be
reported as unused.
"""
self.flakes('''
import foo
def f(): global foo
def g(): foo.is_used()
''')
def test_assignedToGlobal(self):
"""
Binding an import to a declared global should not cause it to be
reported as unused.
"""
self.flakes('''
def f(): global foo; import foo
def g(): foo.is_used()
''')
@skipIf(version_info >= (3,), 'deprecated syntax')
def test_usedInBackquote(self):
self.flakes('import fu; `fu`')
def test_usedInExec(self):
if version_info < (3,):
exec_stmt = 'exec "print 1" in fu.bar'
else:
exec_stmt = 'exec("print(1)", fu.bar)'
self.flakes('import fu; %s' % exec_stmt)
def test_usedInLambda(self):
self.flakes('import fu; lambda: fu')
def test_shadowedByLambda(self):
self.flakes('import fu; lambda fu: fu',
m.UnusedImport, m.RedefinedWhileUnused)
self.flakes('import fu; lambda fu: fu\nfu()')
def test_usedInSliceObj(self):
self.flakes('import fu; "meow"[::fu]')
def test_unusedInNestedScope(self):
self.flakes('''
def bar():
import fu
fu
''', m.UnusedImport, m.UndefinedName)
def test_methodsDontUseClassScope(self):
self.flakes('''
class bar:
import fu
def fun(self):
fu
''', m.UndefinedName)
def test_nestedFunctionsNestScope(self):
self.flakes('''
def a():
def b():
fu
import fu
''')
def test_nestedClassAndFunctionScope(self):
self.flakes('''
def a():
import fu
class b:
def c(self):
print(fu)
''')
def test_importStar(self):
"""Use of import * at module level is reported."""
self.flakes('from fu import *', m.ImportStarUsed, m.UnusedImport)
self.flakes('''
try:
from fu import *
except:
pass
''', m.ImportStarUsed, m.UnusedImport)
checker = self.flakes('from fu import *',
m.ImportStarUsed, m.UnusedImport)
error = checker.messages[0]
assert error.message.startswith("'from %s import *' used; unable ")
assert error.message_args == ('fu', )
error = checker.messages[1]
assert error.message == '%r imported but unused'
assert error.message_args == ('fu.*', )
def test_importStar_relative(self):
"""Use of import * from a relative import is reported."""
self.flakes('from .fu import *', m.ImportStarUsed, m.UnusedImport)
self.flakes('''
try:
from .fu import *
except:
pass
''', m.ImportStarUsed, m.UnusedImport)
checker = self.flakes('from .fu import *',
m.ImportStarUsed, m.UnusedImport)
error = checker.messages[0]
assert error.message.startswith("'from %s import *' used; unable ")
assert error.message_args == ('.fu', )
error = checker.messages[1]
assert error.message == '%r imported but unused'
assert error.message_args == ('.fu.*', )
checker = self.flakes('from .. import *',
m.ImportStarUsed, m.UnusedImport)
error = checker.messages[0]
assert error.message.startswith("'from %s import *' used; unable ")
assert error.message_args == ('..', )
error = checker.messages[1]
assert error.message == '%r imported but unused'
assert error.message_args == ('from .. import *', )
@skipIf(version_info < (3,),
'import * below module level is a warning on Python 2')
def test_localImportStar(self):
"""import * is only allowed at module level."""
self.flakes('''
def a():
from fu import *
''', m.ImportStarNotPermitted)
self.flakes('''
class a:
from fu import *
''', m.ImportStarNotPermitted)
checker = self.flakes('''
class a:
from .. import *
''', m.ImportStarNotPermitted)
error = checker.messages[0]
assert error.message == "'from %s import *' only allowed at module level"
assert error.message_args == ('..', )
@skipIf(version_info > (3,),
'import * below module level is an error on Python 3')
def test_importStarNested(self):
"""All star imports are marked as used by an undefined variable."""
self.flakes('''
from fu import *
def f():
from bar import *
x
''', m.ImportStarUsed, m.ImportStarUsed, m.ImportStarUsage)
def test_packageImport(self):
"""
If a dotted name is imported and used, no warning is reported.
"""
self.flakes('''
import fu.bar
fu.bar
''')
def test_unusedPackageImport(self):
"""
If a dotted name is imported and not used, an unused import warning is
reported.
"""
self.flakes('import fu.bar', m.UnusedImport)
def test_duplicateSubmoduleImport(self):
"""
If a submodule of a package is imported twice, an unused import warning
and a redefined while unused warning are reported.
"""
self.flakes('''
import fu.bar, fu.bar
fu.bar
''', m.RedefinedWhileUnused)
self.flakes('''
import fu.bar
import fu.bar
fu.bar
''', m.RedefinedWhileUnused)
def test_differentSubmoduleImport(self):
"""
If two different submodules of a package are imported, no duplicate
import warning is reported for the package.
"""
self.flakes('''
import fu.bar, fu.baz
fu.bar, fu.baz
''')
self.flakes('''
import fu.bar
import fu.baz
fu.bar, fu.baz
''')
def test_used_package_with_submodule_import(self):
"""
Usage of package marks submodule imports as used.
"""
self.flakes('''
import fu
import fu.bar
fu.x
''')
self.flakes('''
import fu.bar
import fu
fu.x
''')
def test_used_package_with_submodule_import_of_alias(self):
"""
Usage of package by alias marks submodule imports as used.
"""
self.flakes('''
import foo as f
import foo.bar
f.bar.do_something()
''')
self.flakes('''
import foo as f
import foo.bar.blah
f.bar.blah.do_something()
''')
def test_unused_package_with_submodule_import(self):
"""
When a package and its submodule are imported, only report once.
"""
checker = self.flakes('''
import fu
import fu.bar
''', m.UnusedImport)
error = checker.messages[0]
assert error.message == '%r imported but unused'
assert error.message_args == ('fu.bar', )
assert error.lineno == 5 if self.withDoctest else 3
def test_assignRHSFirst(self):
self.flakes('import fu; fu = fu')
self.flakes('import fu; fu, bar = fu')
self.flakes('import fu; [fu, bar] = fu')
self.flakes('import fu; fu += fu')
def test_tryingMultipleImports(self):
self.flakes('''
try:
import fu
except ImportError:
import bar as fu
fu
''')
def test_nonGlobalDoesNotRedefine(self):
self.flakes('''
import fu
def a():
fu = 3
return fu
fu
''')
def test_functionsRunLater(self):
self.flakes('''
def a():
fu
import fu
''')
def test_functionNamesAreBoundNow(self):
self.flakes('''
import fu
def fu():
fu
fu
''', m.RedefinedWhileUnused)
def test_ignoreNonImportRedefinitions(self):
self.flakes('a = 1; a = 2')
@skip("todo")
def test_importingForImportError(self):
self.flakes('''
try:
import fu
except ImportError:
pass
''')
def test_importedInClass(self):
"""Imports in class scope can be used through self."""
self.flakes('''
class c:
import i
def __init__(self):
self.i
''')
def test_importUsedInMethodDefinition(self):
"""
Method named 'foo' with default args referring to module named 'foo'.
"""
self.flakes('''
import foo
class Thing(object):
def foo(self, parser=foo.parse_foo):
pass
''')
def test_futureImport(self):
"""__future__ is special."""
self.flakes('from __future__ import division')
self.flakes('''
"docstring is allowed before future import"
from __future__ import division
''')
def test_futureImportFirst(self):
"""
__future__ imports must come before anything else.
"""
self.flakes('''
x = 5
from __future__ import division
''', m.LateFutureImport)
self.flakes('''
from foo import bar
from __future__ import division
bar
''', m.LateFutureImport)
def test_futureImportUsed(self):
"""__future__ is special, but names are injected in the namespace."""
self.flakes('''
from __future__ import division
from __future__ import print_function
assert print_function is not division
''')
def test_futureImportUndefined(self):
"""Importing undefined names from __future__ fails."""
self.flakes('''
from __future__ import print_statement
''', m.FutureFeatureNotDefined)
def test_futureImportStar(self):
"""Importing '*' from __future__ fails."""
self.flakes('''
from __future__ import *
''', m.FutureFeatureNotDefined)
class TestSpecialAll(TestCase):
"""
Tests for suppression of unused import warnings by C{__all__}.
"""
def test_ignoredInFunction(self):
"""
An C{__all__} definition does not suppress unused import warnings in a
function scope.
"""
self.flakes('''
def foo():
import bar
__all__ = ["bar"]
''', m.UnusedImport, m.UnusedVariable)
def test_ignoredInClass(self):
"""
An C{__all__} definition in a class does not suppress unused import warnings.
"""
self.flakes('''
import bar
class foo:
__all__ = ["bar"]
''', m.UnusedImport)
def test_warningSuppressed(self):
"""
If a name is imported and unused but is named in C{__all__}, no warning
is reported.
"""
self.flakes('''
import foo
__all__ = ["foo"]
''')
self.flakes('''
import foo
__all__ = ("foo",)
''')
def test_augmentedAssignment(self):
"""
The C{__all__} variable is defined incrementally.
"""
self.flakes('''
import a
import c
__all__ = ['a']
__all__ += ['b']
if 1 < 3:
__all__ += ['c', 'd']
''', m.UndefinedExport, m.UndefinedExport)
def test_list_concatenation_assignment(self):
"""
The C{__all__} variable is defined through list concatenation.
"""
self.flakes('''
import sys
__all__ = ['a'] + ['b'] + ['c']
''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport)
def test_tuple_concatenation_assignment(self):
"""
The C{__all__} variable is defined through tuple concatenation.
"""
self.flakes('''
import sys
__all__ = ('a',) + ('b',) + ('c',)
''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport)
def test_all_with_attributes(self):
self.flakes('''
from foo import bar
__all__ = [bar.__name__]
''')
def test_all_with_names(self):
# not actually valid, but shouldn't produce a crash
self.flakes('''
from foo import bar
__all__ = [bar]
''')
def test_all_with_attributes_added(self):
self.flakes('''
from foo import bar
from bar import baz
__all__ = [bar.__name__] + [baz.__name__]
''')
def test_all_mixed_attributes_and_strings(self):
self.flakes('''
from foo import bar
from foo import baz
__all__ = ['bar', baz.__name__]
''')
def test_unboundExported(self):
"""
If C{__all__} includes a name which is not bound, a warning is emitted.
"""
self.flakes('''
__all__ = ["foo"]
''', m.UndefinedExport)
# Skip this in __init__.py though, since the rules there are a little
# different.
for filename in ["foo/__init__.py", "__init__.py"]:
self.flakes('''
__all__ = ["foo"]
''', filename=filename)
def test_importStarExported(self):
"""
Report undefined if import * is used
"""
self.flakes('''
from math import *
__all__ = ['sin', 'cos']
csc(1)
''', m.ImportStarUsed, m.ImportStarUsage, m.ImportStarUsage, m.ImportStarUsage)
def test_importStarNotExported(self):
"""Report unused import when not needed to satisfy __all__."""
self.flakes('''
from foolib import *
a = 1
__all__ = ['a']
''', m.ImportStarUsed, m.UnusedImport)
def test_usedInGenExp(self):
"""
Using a global in a generator expression results in no warnings.
"""
self.flakes('import fu; (fu for _ in range(1))')
self.flakes('import fu; (1 for _ in range(1) if fu)')
def test_redefinedByGenExp(self):
"""
Re-using a global name as the loop variable for a generator
expression results in a redefinition warning.
"""
self.flakes('import fu; (1 for fu in range(1))',
m.RedefinedWhileUnused, m.UnusedImport)
def test_usedAsDecorator(self):
"""
Using a global name in a decorator statement results in no warnings,
but using an undefined name in a decorator statement results in an
undefined name warning.
"""
self.flakes('''
from interior import decorate
@decorate
def f():
return "hello"
''')
self.flakes('''
from interior import decorate
@decorate('value')
def f():
return "hello"
''')
self.flakes('''
@decorate
def f():
return "hello"
''', m.UndefinedName)
def test_usedAsClassDecorator(self):
"""
Using an imported name as a class decorator results in no warnings,
but using an undefined name as a class decorator results in an
undefined name warning.
"""
self.flakes('''
from interior import decorate
@decorate
class foo:
pass
''')
self.flakes('''
from interior import decorate
@decorate("foo")
class bar:
pass
''')
self.flakes('''
@decorate
class foo:
pass
''', m.UndefinedName)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/test/test_is_literal.py 0000644 0001750 0001750 00000010735 00000000000 023371 0 ustar 00asottile asottile 0000000 0000000 from pyflakes.messages import IsLiteral
from pyflakes.test.harness import TestCase
class Test(TestCase):
def test_is_str(self):
self.flakes("""
x = 'foo'
if x is 'foo':
pass
""", IsLiteral)
def test_is_bytes(self):
self.flakes("""
x = b'foo'
if x is b'foo':
pass
""", IsLiteral)
def test_is_unicode(self):
self.flakes("""
x = u'foo'
if x is u'foo':
pass
""", IsLiteral)
def test_is_int(self):
self.flakes("""
x = 10
if x is 10:
pass
""", IsLiteral)
def test_is_true(self):
self.flakes("""
x = True
if x is True:
pass
""")
def test_is_false(self):
self.flakes("""
x = False
if x is False:
pass
""")
def test_is_not_str(self):
self.flakes("""
x = 'foo'
if x is not 'foo':
pass
""", IsLiteral)
def test_is_not_bytes(self):
self.flakes("""
x = b'foo'
if x is not b'foo':
pass
""", IsLiteral)
def test_is_not_unicode(self):
self.flakes("""
x = u'foo'
if x is not u'foo':
pass
""", IsLiteral)
def test_is_not_int(self):
self.flakes("""
x = 10
if x is not 10:
pass
""", IsLiteral)
def test_is_not_true(self):
self.flakes("""
x = True
if x is not True:
pass
""")
def test_is_not_false(self):
self.flakes("""
x = False
if x is not False:
pass
""")
def test_left_is_str(self):
self.flakes("""
x = 'foo'
if 'foo' is x:
pass
""", IsLiteral)
def test_left_is_bytes(self):
self.flakes("""
x = b'foo'
if b'foo' is x:
pass
""", IsLiteral)
def test_left_is_unicode(self):
self.flakes("""
x = u'foo'
if u'foo' is x:
pass
""", IsLiteral)
def test_left_is_int(self):
self.flakes("""
x = 10
if 10 is x:
pass
""", IsLiteral)
def test_left_is_true(self):
self.flakes("""
x = True
if True is x:
pass
""")
def test_left_is_false(self):
self.flakes("""
x = False
if False is x:
pass
""")
def test_left_is_not_str(self):
self.flakes("""
x = 'foo'
if 'foo' is not x:
pass
""", IsLiteral)
def test_left_is_not_bytes(self):
self.flakes("""
x = b'foo'
if b'foo' is not x:
pass
""", IsLiteral)
def test_left_is_not_unicode(self):
self.flakes("""
x = u'foo'
if u'foo' is not x:
pass
""", IsLiteral)
def test_left_is_not_int(self):
self.flakes("""
x = 10
if 10 is not x:
pass
""", IsLiteral)
def test_left_is_not_true(self):
self.flakes("""
x = True
if True is not x:
pass
""")
def test_left_is_not_false(self):
self.flakes("""
x = False
if False is not x:
pass
""")
def test_chained_operators_is_true(self):
self.flakes("""
x = 5
if x is True < 4:
pass
""")
def test_chained_operators_is_str(self):
self.flakes("""
x = 5
if x is 'foo' < 4:
pass
""", IsLiteral)
def test_chained_operators_is_true_end(self):
self.flakes("""
x = 5
if 4 < x is True:
pass
""")
def test_chained_operators_is_str_end(self):
self.flakes("""
x = 5
if 4 < x is 'foo':
pass
""", IsLiteral)
def test_is_tuple_constant(self):
self.flakes('''\
x = 5
if x is ():
pass
''', IsLiteral)
def test_is_tuple_constant_containing_constants(self):
self.flakes('''\
x = 5
if x is (1, '2', True, (1.5, ())):
pass
''', IsLiteral)
def test_is_tuple_containing_variables_ok(self):
# a bit nonsensical, but does not trigger a SyntaxWarning
self.flakes('''\
x = 5
if x is (x,):
pass
''')
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633476446.0
pyflakes-2.4.0/pyflakes/test/test_match.py 0000644 0001750 0001750 00000004061 00000000000 022331 0 ustar 00asottile asottile 0000000 0000000 from sys import version_info
from pyflakes.test.harness import TestCase, skipIf
@skipIf(version_info < (3, 10), "Python >= 3.10 only")
class TestMatch(TestCase):
def test_match_bindings(self):
self.flakes('''
def f():
x = 1
match x:
case 1 as y:
print(f'matched as {y}')
''')
self.flakes('''
def f():
x = [1, 2, 3]
match x:
case [1, y, 3]:
print(f'matched {y}')
''')
self.flakes('''
def f():
x = {'foo': 1}
match x:
case {'foo': y}:
print(f'matched {y}')
''')
def test_match_pattern_matched_class(self):
self.flakes('''
from a import B
match 1:
case B(x=1) as y:
print(f'matched {y}')
''')
self.flakes('''
from a import B
match 1:
case B(a, x=z) as y:
print(f'matched {y} {a} {z}')
''')
def test_match_placeholder(self):
self.flakes('''
def f():
match 1:
case _:
print('catchall!')
''')
def test_match_singleton(self):
self.flakes('''
match 1:
case True:
print('true')
''')
def test_match_or_pattern(self):
self.flakes('''
match 1:
case 1 | 2:
print('one or two')
''')
def test_match_star(self):
self.flakes('''
x = [1, 2, 3]
match x:
case [1, *y]:
print(f'captured: {y}')
''')
def test_match_double_star(self):
self.flakes('''
x = {'foo': 'bar', 'baz': 'womp'}
match x:
case {'foo': k1, **rest}:
print(f'{k1=} {rest=}')
''')
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/test/test_other.py 0000644 0001750 0001750 00000151706 00000000000 022367 0 ustar 00asottile asottile 0000000 0000000 """
Tests for various Pyflakes behavior.
"""
from sys import version_info
from pyflakes import messages as m
from pyflakes.test.harness import TestCase, skip, skipIf
class Test(TestCase):
def test_duplicateArgs(self):
self.flakes('def fu(bar, bar): pass', m.DuplicateArgument)
def test_localReferencedBeforeAssignment(self):
self.flakes('''
a = 1
def f():
a; a=1
f()
''', m.UndefinedLocal, m.UnusedVariable)
@skipIf(version_info >= (3,),
'in Python 3 list comprehensions execute in a separate scope')
def test_redefinedInListComp(self):
"""
Test that shadowing a variable in a list comprehension raises
a warning.
"""
self.flakes('''
a = 1
[1 for a, b in [(1, 2)]]
''', m.RedefinedInListComp)
self.flakes('''
class A:
a = 1
[1 for a, b in [(1, 2)]]
''', m.RedefinedInListComp)
self.flakes('''
def f():
a = 1
[1 for a, b in [(1, 2)]]
''', m.RedefinedInListComp)
self.flakes('''
[1 for a, b in [(1, 2)]]
[1 for a, b in [(1, 2)]]
''')
self.flakes('''
for a, b in [(1, 2)]:
pass
[1 for a, b in [(1, 2)]]
''')
def test_redefinedInGenerator(self):
"""
Test that reusing a variable in a generator does not raise
a warning.
"""
self.flakes('''
a = 1
(1 for a, b in [(1, 2)])
''')
self.flakes('''
class A:
a = 1
list(1 for a, b in [(1, 2)])
''')
self.flakes('''
def f():
a = 1
(1 for a, b in [(1, 2)])
''', m.UnusedVariable)
self.flakes('''
(1 for a, b in [(1, 2)])
(1 for a, b in [(1, 2)])
''')
self.flakes('''
for a, b in [(1, 2)]:
pass
(1 for a, b in [(1, 2)])
''')
def test_redefinedInSetComprehension(self):
"""
Test that reusing a variable in a set comprehension does not raise
a warning.
"""
self.flakes('''
a = 1
{1 for a, b in [(1, 2)]}
''')
self.flakes('''
class A:
a = 1
{1 for a, b in [(1, 2)]}
''')
self.flakes('''
def f():
a = 1
{1 for a, b in [(1, 2)]}
''', m.UnusedVariable)
self.flakes('''
{1 for a, b in [(1, 2)]}
{1 for a, b in [(1, 2)]}
''')
self.flakes('''
for a, b in [(1, 2)]:
pass
{1 for a, b in [(1, 2)]}
''')
def test_redefinedInDictComprehension(self):
"""
Test that reusing a variable in a dict comprehension does not raise
a warning.
"""
self.flakes('''
a = 1
{1: 42 for a, b in [(1, 2)]}
''')
self.flakes('''
class A:
a = 1
{1: 42 for a, b in [(1, 2)]}
''')
self.flakes('''
def f():
a = 1
{1: 42 for a, b in [(1, 2)]}
''', m.UnusedVariable)
self.flakes('''
{1: 42 for a, b in [(1, 2)]}
{1: 42 for a, b in [(1, 2)]}
''')
self.flakes('''
for a, b in [(1, 2)]:
pass
{1: 42 for a, b in [(1, 2)]}
''')
def test_redefinedFunction(self):
"""
Test that shadowing a function definition with another one raises a
warning.
"""
self.flakes('''
def a(): pass
def a(): pass
''', m.RedefinedWhileUnused)
def test_redefinedUnderscoreFunction(self):
"""
Test that shadowing a function definition named with underscore doesn't
raise anything.
"""
self.flakes('''
def _(): pass
def _(): pass
''')
def test_redefinedUnderscoreImportation(self):
"""
Test that shadowing an underscore importation raises a warning.
"""
self.flakes('''
from .i18n import _
def _(): pass
''', m.RedefinedWhileUnused)
def test_redefinedClassFunction(self):
"""
Test that shadowing a function definition in a class suite with another
one raises a warning.
"""
self.flakes('''
class A:
def a(): pass
def a(): pass
''', m.RedefinedWhileUnused)
def test_redefinedIfElseFunction(self):
"""
Test that shadowing a function definition twice in an if
and else block does not raise a warning.
"""
self.flakes('''
if True:
def a(): pass
else:
def a(): pass
''')
def test_redefinedIfFunction(self):
"""
Test that shadowing a function definition within an if block
raises a warning.
"""
self.flakes('''
if True:
def a(): pass
def a(): pass
''', m.RedefinedWhileUnused)
def test_redefinedTryExceptFunction(self):
"""
Test that shadowing a function definition twice in try
and except block does not raise a warning.
"""
self.flakes('''
try:
def a(): pass
except:
def a(): pass
''')
def test_redefinedTryFunction(self):
"""
Test that shadowing a function definition within a try block
raises a warning.
"""
self.flakes('''
try:
def a(): pass
def a(): pass
except:
pass
''', m.RedefinedWhileUnused)
def test_redefinedIfElseInListComp(self):
"""
Test that shadowing a variable in a list comprehension in
an if and else block does not raise a warning.
"""
self.flakes('''
if False:
a = 1
else:
[a for a in '12']
''')
@skipIf(version_info >= (3,),
'in Python 3 list comprehensions execute in a separate scope')
def test_redefinedElseInListComp(self):
"""
Test that shadowing a variable in a list comprehension in
an else (or if) block raises a warning.
"""
self.flakes('''
if False:
pass
else:
a = 1
[a for a in '12']
''', m.RedefinedInListComp)
def test_functionDecorator(self):
"""
Test that shadowing a function definition with a decorated version of
that function does not raise a warning.
"""
self.flakes('''
from somewhere import somedecorator
def a(): pass
a = somedecorator(a)
''')
def test_classFunctionDecorator(self):
"""
Test that shadowing a function definition in a class suite with a
decorated version of that function does not raise a warning.
"""
self.flakes('''
class A:
def a(): pass
a = classmethod(a)
''')
def test_modernProperty(self):
self.flakes("""
class A:
@property
def t(self):
pass
@t.setter
def t(self, value):
pass
@t.deleter
def t(self):
pass
""")
def test_unaryPlus(self):
"""Don't die on unary +."""
self.flakes('+1')
def test_undefinedBaseClass(self):
"""
If a name in the base list of a class definition is undefined, a
warning is emitted.
"""
self.flakes('''
class foo(foo):
pass
''', m.UndefinedName)
def test_classNameUndefinedInClassBody(self):
"""
If a class name is used in the body of that class's definition and
the name is not already defined, a warning is emitted.
"""
self.flakes('''
class foo:
foo
''', m.UndefinedName)
def test_classNameDefinedPreviously(self):
"""
If a class name is used in the body of that class's definition and
the name was previously defined in some other way, no warning is
emitted.
"""
self.flakes('''
foo = None
class foo:
foo
''')
def test_classRedefinition(self):
"""
If a class is defined twice in the same module, a warning is emitted.
"""
self.flakes('''
class Foo:
pass
class Foo:
pass
''', m.RedefinedWhileUnused)
def test_functionRedefinedAsClass(self):
"""
If a function is redefined as a class, a warning is emitted.
"""
self.flakes('''
def Foo():
pass
class Foo:
pass
''', m.RedefinedWhileUnused)
def test_classRedefinedAsFunction(self):
"""
If a class is redefined as a function, a warning is emitted.
"""
self.flakes('''
class Foo:
pass
def Foo():
pass
''', m.RedefinedWhileUnused)
def test_classWithReturn(self):
"""
If a return is used inside a class, a warning is emitted.
"""
self.flakes('''
class Foo(object):
return
''', m.ReturnOutsideFunction)
def test_moduleWithReturn(self):
"""
If a return is used at the module level, a warning is emitted.
"""
self.flakes('''
return
''', m.ReturnOutsideFunction)
def test_classWithYield(self):
"""
If a yield is used inside a class, a warning is emitted.
"""
self.flakes('''
class Foo(object):
yield
''', m.YieldOutsideFunction)
def test_moduleWithYield(self):
"""
If a yield is used at the module level, a warning is emitted.
"""
self.flakes('''
yield
''', m.YieldOutsideFunction)
@skipIf(version_info < (3, 3), "Python >= 3.3 only")
def test_classWithYieldFrom(self):
"""
If a yield from is used inside a class, a warning is emitted.
"""
self.flakes('''
class Foo(object):
yield from range(10)
''', m.YieldOutsideFunction)
@skipIf(version_info < (3, 3), "Python >= 3.3 only")
def test_moduleWithYieldFrom(self):
"""
If a yield from is used at the module level, a warning is emitted.
"""
self.flakes('''
yield from range(10)
''', m.YieldOutsideFunction)
def test_continueOutsideLoop(self):
self.flakes('''
continue
''', m.ContinueOutsideLoop)
self.flakes('''
def f():
continue
''', m.ContinueOutsideLoop)
self.flakes('''
while True:
pass
else:
continue
''', m.ContinueOutsideLoop)
self.flakes('''
while True:
pass
else:
if 1:
if 2:
continue
''', m.ContinueOutsideLoop)
self.flakes('''
while True:
def f():
continue
''', m.ContinueOutsideLoop)
self.flakes('''
while True:
class A:
continue
''', m.ContinueOutsideLoop)
def test_continueInsideLoop(self):
self.flakes('''
while True:
continue
''')
self.flakes('''
for i in range(10):
continue
''')
self.flakes('''
while True:
if 1:
continue
''')
self.flakes('''
for i in range(10):
if 1:
continue
''')
self.flakes('''
while True:
while True:
pass
else:
continue
else:
pass
''')
self.flakes('''
while True:
try:
pass
finally:
while True:
continue
''')
@skipIf(version_info > (3, 8), "Python <= 3.8 only")
def test_continueInFinally(self):
# 'continue' inside 'finally' is a special syntax error
# that is removed in 3.8
self.flakes('''
while True:
try:
pass
finally:
continue
''', m.ContinueInFinally)
self.flakes('''
while True:
try:
pass
finally:
if 1:
if 2:
continue
''', m.ContinueInFinally)
# Even when not in a loop, this is the error Python gives
self.flakes('''
try:
pass
finally:
continue
''', m.ContinueInFinally)
def test_breakOutsideLoop(self):
self.flakes('''
break
''', m.BreakOutsideLoop)
self.flakes('''
def f():
break
''', m.BreakOutsideLoop)
self.flakes('''
while True:
pass
else:
break
''', m.BreakOutsideLoop)
self.flakes('''
while True:
pass
else:
if 1:
if 2:
break
''', m.BreakOutsideLoop)
self.flakes('''
while True:
def f():
break
''', m.BreakOutsideLoop)
self.flakes('''
while True:
class A:
break
''', m.BreakOutsideLoop)
self.flakes('''
try:
pass
finally:
break
''', m.BreakOutsideLoop)
def test_breakInsideLoop(self):
self.flakes('''
while True:
break
''')
self.flakes('''
for i in range(10):
break
''')
self.flakes('''
while True:
if 1:
break
''')
self.flakes('''
for i in range(10):
if 1:
break
''')
self.flakes('''
while True:
while True:
pass
else:
break
else:
pass
''')
self.flakes('''
while True:
try:
pass
finally:
while True:
break
''')
self.flakes('''
while True:
try:
pass
finally:
break
''')
self.flakes('''
while True:
try:
pass
finally:
if 1:
if 2:
break
''')
def test_defaultExceptLast(self):
"""
A default except block should be last.
YES:
try:
...
except Exception:
...
except:
...
NO:
try:
...
except:
...
except Exception:
...
"""
self.flakes('''
try:
pass
except ValueError:
pass
''')
self.flakes('''
try:
pass
except ValueError:
pass
except:
pass
''')
self.flakes('''
try:
pass
except:
pass
''')
self.flakes('''
try:
pass
except ValueError:
pass
else:
pass
''')
self.flakes('''
try:
pass
except:
pass
else:
pass
''')
self.flakes('''
try:
pass
except ValueError:
pass
except:
pass
else:
pass
''')
def test_defaultExceptNotLast(self):
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
except:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
except:
pass
except ValueError:
pass
''', m.DefaultExceptNotLast, m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
else:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except:
pass
else:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
except:
pass
else:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
except:
pass
except ValueError:
pass
else:
pass
''', m.DefaultExceptNotLast, m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
finally:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except:
pass
finally:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
except:
pass
finally:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
except:
pass
except ValueError:
pass
finally:
pass
''', m.DefaultExceptNotLast, m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
else:
pass
finally:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except:
pass
else:
pass
finally:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
except:
pass
else:
pass
finally:
pass
''', m.DefaultExceptNotLast)
self.flakes('''
try:
pass
except:
pass
except ValueError:
pass
except:
pass
except ValueError:
pass
else:
pass
finally:
pass
''', m.DefaultExceptNotLast, m.DefaultExceptNotLast)
@skipIf(version_info < (3,), "Python 3 only")
def test_starredAssignmentNoError(self):
"""
Python 3 extended iterable unpacking
"""
self.flakes('''
a, *b = range(10)
''')
self.flakes('''
*a, b = range(10)
''')
self.flakes('''
a, *b, c = range(10)
''')
self.flakes('''
(a, *b) = range(10)
''')
self.flakes('''
(*a, b) = range(10)
''')
self.flakes('''
(a, *b, c) = range(10)
''')
self.flakes('''
[a, *b] = range(10)
''')
self.flakes('''
[*a, b] = range(10)
''')
self.flakes('''
[a, *b, c] = range(10)
''')
# Taken from test_unpack_ex.py in the cPython source
s = ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \
", *rest = range(1<<8)"
self.flakes(s)
s = "(" + ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \
", *rest) = range(1<<8)"
self.flakes(s)
s = "[" + ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \
", *rest] = range(1<<8)"
self.flakes(s)
@skipIf(version_info < (3, ), "Python 3 only")
def test_starredAssignmentErrors(self):
"""
SyntaxErrors (not encoded in the ast) surrounding Python 3 extended
iterable unpacking
"""
# Taken from test_unpack_ex.py in the cPython source
s = ", ".join("a%d" % i for i in range(1 << 8)) + \
", *rest = range(1<<8 + 1)"
self.flakes(s, m.TooManyExpressionsInStarredAssignment)
s = "(" + ", ".join("a%d" % i for i in range(1 << 8)) + \
", *rest) = range(1<<8 + 1)"
self.flakes(s, m.TooManyExpressionsInStarredAssignment)
s = "[" + ", ".join("a%d" % i for i in range(1 << 8)) + \
", *rest] = range(1<<8 + 1)"
self.flakes(s, m.TooManyExpressionsInStarredAssignment)
s = ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \
", *rest = range(1<<8 + 2)"
self.flakes(s, m.TooManyExpressionsInStarredAssignment)
s = "(" + ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \
", *rest) = range(1<<8 + 2)"
self.flakes(s, m.TooManyExpressionsInStarredAssignment)
s = "[" + ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \
", *rest] = range(1<<8 + 2)"
self.flakes(s, m.TooManyExpressionsInStarredAssignment)
# No way we can actually test this!
# s = "*rest, " + ", ".join("a%d" % i for i in range(1<<24)) + \
# ", *rest = range(1<<24 + 1)"
# self.flakes(s, m.TooManyExpressionsInStarredAssignment)
self.flakes('''
a, *b, *c = range(10)
''', m.TwoStarredExpressions)
self.flakes('''
a, *b, c, *d = range(10)
''', m.TwoStarredExpressions)
self.flakes('''
*a, *b, *c = range(10)
''', m.TwoStarredExpressions)
self.flakes('''
(a, *b, *c) = range(10)
''', m.TwoStarredExpressions)
self.flakes('''
(a, *b, c, *d) = range(10)
''', m.TwoStarredExpressions)
self.flakes('''
(*a, *b, *c) = range(10)
''', m.TwoStarredExpressions)
self.flakes('''
[a, *b, *c] = range(10)
''', m.TwoStarredExpressions)
self.flakes('''
[a, *b, c, *d] = range(10)
''', m.TwoStarredExpressions)
self.flakes('''
[*a, *b, *c] = range(10)
''', m.TwoStarredExpressions)
@skip("todo: Too hard to make this warn but other cases stay silent")
def test_doubleAssignment(self):
"""
If a variable is re-assigned to without being used, no warning is
emitted.
"""
self.flakes('''
x = 10
x = 20
''', m.RedefinedWhileUnused)
def test_doubleAssignmentConditionally(self):
"""
If a variable is re-assigned within a conditional, no warning is
emitted.
"""
self.flakes('''
x = 10
if True:
x = 20
''')
def test_doubleAssignmentWithUse(self):
"""
If a variable is re-assigned to after being used, no warning is
emitted.
"""
self.flakes('''
x = 10
y = x * 2
x = 20
''')
def test_comparison(self):
"""
If a defined name is used on either side of any of the six comparison
operators, no warning is emitted.
"""
self.flakes('''
x = 10
y = 20
x < y
x <= y
x == y
x != y
x >= y
x > y
''')
def test_identity(self):
"""
If a defined name is used on either side of an identity test, no
warning is emitted.
"""
self.flakes('''
x = 10
y = 20
x is y
x is not y
''')
def test_containment(self):
"""
If a defined name is used on either side of a containment test, no
warning is emitted.
"""
self.flakes('''
x = 10
y = 20
x in y
x not in y
''')
def test_loopControl(self):
"""
break and continue statements are supported.
"""
self.flakes('''
for x in [1, 2]:
break
''')
self.flakes('''
for x in [1, 2]:
continue
''')
def test_ellipsis(self):
"""
Ellipsis in a slice is supported.
"""
self.flakes('''
[1, 2][...]
''')
def test_extendedSlice(self):
"""
Extended slices are supported.
"""
self.flakes('''
x = 3
[1, 2][x,:]
''')
def test_varAugmentedAssignment(self):
"""
Augmented assignment of a variable is supported.
We don't care about var refs.
"""
self.flakes('''
foo = 0
foo += 1
''')
def test_attrAugmentedAssignment(self):
"""
Augmented assignment of attributes is supported.
We don't care about attr refs.
"""
self.flakes('''
foo = None
foo.bar += foo.baz
''')
def test_globalDeclaredInDifferentScope(self):
"""
A 'global' can be declared in one scope and reused in another.
"""
self.flakes('''
def f(): global foo
def g(): foo = 'anything'; foo.is_used()
''')
def test_function_arguments(self):
"""
Test to traverse ARG and ARGUMENT handler
"""
self.flakes('''
def foo(a, b):
pass
''')
self.flakes('''
def foo(a, b, c=0):
pass
''')
self.flakes('''
def foo(a, b, c=0, *args):
pass
''')
self.flakes('''
def foo(a, b, c=0, *args, **kwargs):
pass
''')
@skipIf(version_info < (3, 3), "Python >= 3.3 only")
def test_function_arguments_python3(self):
self.flakes('''
def foo(a, b, c=0, *args, d=0, **kwargs):
pass
''')
class TestUnusedAssignment(TestCase):
"""
Tests for warning about unused assignments.
"""
def test_unusedVariable(self):
"""
Warn when a variable in a function is assigned a value that's never
used.
"""
self.flakes('''
def a():
b = 1
''', m.UnusedVariable)
def test_unusedUnderscoreVariable(self):
"""
Don't warn when the magic "_" (underscore) variable is unused.
See issue #202.
"""
self.flakes('''
def a(unused_param):
_ = unused_param
''')
def test_unusedVariableAsLocals(self):
"""
Using locals() it is perfectly valid to have unused variables
"""
self.flakes('''
def a():
b = 1
return locals()
''')
def test_unusedVariableNoLocals(self):
"""
Using locals() in wrong scope should not matter
"""
self.flakes('''
def a():
locals()
def a():
b = 1
return
''', m.UnusedVariable)
@skip("todo: Difficult because it doesn't apply in the context of a loop")
def test_unusedReassignedVariable(self):
"""
Shadowing a used variable can still raise an UnusedVariable warning.
"""
self.flakes('''
def a():
b = 1
b.foo()
b = 2
''', m.UnusedVariable)
def test_variableUsedInLoop(self):
"""
Shadowing a used variable cannot raise an UnusedVariable warning in the
context of a loop.
"""
self.flakes('''
def a():
b = True
while b:
b = False
''')
def test_assignToGlobal(self):
"""
Assigning to a global and then not using that global is perfectly
acceptable. Do not mistake it for an unused local variable.
"""
self.flakes('''
b = 0
def a():
global b
b = 1
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_assignToNonlocal(self):
"""
Assigning to a nonlocal and then not using that binding is perfectly
acceptable. Do not mistake it for an unused local variable.
"""
self.flakes('''
b = b'0'
def a():
nonlocal b
b = b'1'
''')
def test_assignToMember(self):
"""
Assigning to a member of another object and then not using that member
variable is perfectly acceptable. Do not mistake it for an unused
local variable.
"""
# XXX: Adding this test didn't generate a failure. Maybe not
# necessary?
self.flakes('''
class b:
pass
def a():
b.foo = 1
''')
def test_assignInForLoop(self):
"""
Don't warn when a variable in a for loop is assigned to but not used.
"""
self.flakes('''
def f():
for i in range(10):
pass
''')
def test_assignInListComprehension(self):
"""
Don't warn when a variable in a list comprehension is
assigned to but not used.
"""
self.flakes('''
def f():
[None for i in range(10)]
''')
def test_generatorExpression(self):
"""
Don't warn when a variable in a generator expression is
assigned to but not used.
"""
self.flakes('''
def f():
(None for i in range(10))
''')
def test_assignmentInsideLoop(self):
"""
Don't warn when a variable assignment occurs lexically after its use.
"""
self.flakes('''
def f():
x = None
for i in range(10):
if i > 2:
return x
x = i * 2
''')
def test_tupleUnpacking(self):
"""
Don't warn when a variable included in tuple unpacking is unused. It's
very common for variables in a tuple unpacking assignment to be unused
in good Python code, so warning will only create false positives.
"""
self.flakes('''
def f(tup):
(x, y) = tup
''')
self.flakes('''
def f():
(x, y) = 1, 2
''', m.UnusedVariable, m.UnusedVariable)
self.flakes('''
def f():
(x, y) = coords = 1, 2
if x > 1:
print(coords)
''')
self.flakes('''
def f():
(x, y) = coords = 1, 2
''', m.UnusedVariable)
self.flakes('''
def f():
coords = (x, y) = 1, 2
''', m.UnusedVariable)
def test_listUnpacking(self):
"""
Don't warn when a variable included in list unpacking is unused.
"""
self.flakes('''
def f(tup):
[x, y] = tup
''')
self.flakes('''
def f():
[x, y] = [1, 2]
''', m.UnusedVariable, m.UnusedVariable)
def test_closedOver(self):
"""
Don't warn when the assignment is used in an inner function.
"""
self.flakes('''
def barMaker():
foo = 5
def bar():
return foo
return bar
''')
def test_doubleClosedOver(self):
"""
Don't warn when the assignment is used in an inner function, even if
that inner function itself is in an inner function.
"""
self.flakes('''
def barMaker():
foo = 5
def bar():
def baz():
return foo
return bar
''')
def test_tracebackhideSpecialVariable(self):
"""
Do not warn about unused local variable __tracebackhide__, which is
a special variable for py.test.
"""
self.flakes("""
def helper():
__tracebackhide__ = True
""")
def test_ifexp(self):
"""
Test C{foo if bar else baz} statements.
"""
self.flakes("a = 'moo' if True else 'oink'")
self.flakes("a = foo if True else 'oink'", m.UndefinedName)
self.flakes("a = 'moo' if True else bar", m.UndefinedName)
def test_if_tuple(self):
"""
Test C{if (foo,)} conditions.
"""
self.flakes("""if (): pass""")
self.flakes("""
if (
True
):
pass
""")
self.flakes("""
if (
True,
):
pass
""", m.IfTuple)
self.flakes("""
x = 1 if (
True,
) else 2
""", m.IfTuple)
def test_withStatementNoNames(self):
"""
No warnings are emitted for using inside or after a nameless C{with}
statement a name defined beforehand.
"""
self.flakes('''
from __future__ import with_statement
bar = None
with open("foo"):
bar
bar
''')
def test_withStatementSingleName(self):
"""
No warnings are emitted for using a name defined by a C{with} statement
within the suite or afterwards.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as bar:
bar
bar
''')
def test_withStatementAttributeName(self):
"""
No warnings are emitted for using an attribute as the target of a
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo.bar:
pass
''')
def test_withStatementSubscript(self):
"""
No warnings are emitted for using a subscript as the target of a
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo[0]:
pass
''')
def test_withStatementSubscriptUndefined(self):
"""
An undefined name warning is emitted if the subscript used as the
target of a C{with} statement is not defined.
"""
self.flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo[bar]:
pass
''', m.UndefinedName)
def test_withStatementTupleNames(self):
"""
No warnings are emitted for using any of the tuple of names defined by
a C{with} statement within the suite or afterwards.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as (bar, baz):
bar, baz
bar, baz
''')
def test_withStatementListNames(self):
"""
No warnings are emitted for using any of the list of names defined by a
C{with} statement within the suite or afterwards.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as [bar, baz]:
bar, baz
bar, baz
''')
def test_withStatementComplicatedTarget(self):
"""
If the target of a C{with} statement uses any or all of the valid forms
for that part of the grammar (See
U{http://docs.python.org/reference/compound_stmts.html#the-with-statement}),
the names involved are checked both for definedness and any bindings
created are respected in the suite of the statement and afterwards.
"""
self.flakes('''
from __future__ import with_statement
c = d = e = g = h = i = None
with open('foo') as [(a, b), c[d], e.f, g[h:i]]:
a, b, c, d, e, g, h, i
a, b, c, d, e, g, h, i
''')
def test_withStatementSingleNameUndefined(self):
"""
An undefined name warning is emitted if the name first defined by a
C{with} statement is used before the C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
bar
with open('foo') as bar:
pass
''', m.UndefinedName)
def test_withStatementTupleNamesUndefined(self):
"""
An undefined name warning is emitted if a name first defined by the
tuple-unpacking form of the C{with} statement is used before the
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
baz
with open('foo') as (bar, baz):
pass
''', m.UndefinedName)
def test_withStatementSingleNameRedefined(self):
"""
A redefined name warning is emitted if a name bound by an import is
rebound by the name defined by a C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import bar
with open('foo') as bar:
pass
''', m.RedefinedWhileUnused)
def test_withStatementTupleNamesRedefined(self):
"""
A redefined name warning is emitted if a name bound by an import is
rebound by one of the names defined by the tuple-unpacking form of a
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import bar
with open('foo') as (bar, baz):
pass
''', m.RedefinedWhileUnused)
def test_withStatementUndefinedInside(self):
"""
An undefined name warning is emitted if a name is used inside the
body of a C{with} statement without first being bound.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as bar:
baz
''', m.UndefinedName)
def test_withStatementNameDefinedInBody(self):
"""
A name defined in the body of a C{with} statement can be used after
the body ends without warning.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as bar:
baz = 10
baz
''')
def test_withStatementUndefinedInExpression(self):
"""
An undefined name warning is emitted if a name in the I{test}
expression of a C{with} statement is undefined.
"""
self.flakes('''
from __future__ import with_statement
with bar as baz:
pass
''', m.UndefinedName)
self.flakes('''
from __future__ import with_statement
with bar as bar:
pass
''', m.UndefinedName)
def test_dictComprehension(self):
"""
Dict comprehensions are properly handled.
"""
self.flakes('''
a = {1: x for x in range(10)}
''')
def test_setComprehensionAndLiteral(self):
"""
Set comprehensions are properly handled.
"""
self.flakes('''
a = {1, 2, 3}
b = {x for x in range(10)}
''')
def test_exceptionUsedInExcept(self):
self.flakes('''
try: pass
except Exception as e: e
''')
self.flakes('''
def download_review():
try: pass
except Exception as e: e
''')
@skipIf(version_info < (3,),
"In Python 2 exception names stay bound after the exception handler")
def test_exceptionUnusedInExcept(self):
self.flakes('''
try: pass
except Exception as e: pass
''', m.UnusedVariable)
def test_exceptionUnusedInExceptInFunction(self):
self.flakes('''
def download_review():
try: pass
except Exception as e: pass
''', m.UnusedVariable)
def test_exceptWithoutNameInFunction(self):
"""
Don't issue false warning when an unnamed exception is used.
Previously, there would be a false warning, but only when the
try..except was in a function
"""
self.flakes('''
import tokenize
def foo():
try: pass
except tokenize.TokenError: pass
''')
def test_exceptWithoutNameInFunctionTuple(self):
"""
Don't issue false warning when an unnamed exception is used.
This example catches a tuple of exception types.
"""
self.flakes('''
import tokenize
def foo():
try: pass
except (tokenize.TokenError, IndentationError): pass
''')
def test_augmentedAssignmentImportedFunctionCall(self):
"""
Consider a function that is called on the right part of an
augassign operation to be used.
"""
self.flakes('''
from foo import bar
baz = 0
baz += bar()
''')
def test_assert_without_message(self):
"""An assert without a message is not an error."""
self.flakes('''
a = 1
assert a
''')
def test_assert_with_message(self):
"""An assert with a message is not an error."""
self.flakes('''
a = 1
assert a, 'x'
''')
def test_assert_tuple(self):
"""An assert of a non-empty tuple is always True."""
self.flakes('''
assert (False, 'x')
assert (False, )
''', m.AssertTuple, m.AssertTuple)
def test_assert_tuple_empty(self):
"""An assert of an empty tuple is always False."""
self.flakes('''
assert ()
''')
def test_assert_static(self):
"""An assert of a static value is not an error."""
self.flakes('''
assert True
assert 1
''')
@skipIf(version_info < (3, 3), 'new in Python 3.3')
def test_yieldFromUndefined(self):
"""
Test C{yield from} statement
"""
self.flakes('''
def bar():
yield from foo()
''', m.UndefinedName)
@skipIf(version_info < (3, 6), 'new in Python 3.6')
def test_f_string(self):
"""Test PEP 498 f-strings are treated as a usage."""
self.flakes('''
baz = 0
print(f'\x7b4*baz\N{RIGHT CURLY BRACKET}')
''')
@skipIf(version_info < (3, 8), 'new in Python 3.8')
def test_assign_expr(self):
"""Test PEP 572 assignment expressions are treated as usage / write."""
self.flakes('''
from foo import y
print(x := y)
print(x)
''')
class TestStringFormatting(TestCase):
@skipIf(version_info < (3, 6), 'new in Python 3.6')
def test_f_string_without_placeholders(self):
self.flakes("f'foo'", m.FStringMissingPlaceholders)
self.flakes('''
f"""foo
bar
"""
''', m.FStringMissingPlaceholders)
self.flakes('''
print(
f'foo'
f'bar'
)
''', m.FStringMissingPlaceholders)
# this is an "escaped placeholder" but not a placeholder
self.flakes("f'{{}}'", m.FStringMissingPlaceholders)
# ok: f-string with placeholders
self.flakes('''
x = 5
print(f'{x}')
''')
# ok: f-string with format specifiers
self.flakes('''
x = 'a' * 90
print(f'{x:.8}')
''')
# ok: f-string with multiple format specifiers
self.flakes('''
x = y = 5
print(f'{x:>2} {y:>2}')
''')
def test_invalid_dot_format_calls(self):
self.flakes('''
'{'.format(1)
''', m.StringDotFormatInvalidFormat)
self.flakes('''
'{} {1}'.format(1, 2)
''', m.StringDotFormatMixingAutomatic)
self.flakes('''
'{0} {}'.format(1, 2)
''', m.StringDotFormatMixingAutomatic)
self.flakes('''
'{}'.format(1, 2)
''', m.StringDotFormatExtraPositionalArguments)
self.flakes('''
'{}'.format(1, bar=2)
''', m.StringDotFormatExtraNamedArguments)
self.flakes('''
'{} {}'.format(1)
''', m.StringDotFormatMissingArgument)
self.flakes('''
'{2}'.format()
''', m.StringDotFormatMissingArgument)
self.flakes('''
'{bar}'.format()
''', m.StringDotFormatMissingArgument)
# too much string recursion (placeholder-in-placeholder)
self.flakes('''
'{:{:{}}}'.format(1, 2, 3)
''', m.StringDotFormatInvalidFormat)
# ok: dotted / bracketed names need to handle the param differently
self.flakes("'{.__class__}'.format('')")
self.flakes("'{foo[bar]}'.format(foo={'bar': 'barv'})")
# ok: placeholder-placeholders
self.flakes('''
print('{:{}} {}'.format(1, 15, 2))
''')
# ok: not a placeholder-placeholder
self.flakes('''
print('{:2}'.format(1))
''')
# ok: not mixed automatic
self.flakes('''
'{foo}-{}'.format(1, foo=2)
''')
# ok: we can't determine statically the format args
self.flakes('''
a = ()
"{}".format(*a)
''')
self.flakes('''
k = {}
"{foo}".format(**k)
''')
def test_invalid_percent_format_calls(self):
self.flakes('''
'%(foo)' % {'foo': 'bar'}
''', m.PercentFormatInvalidFormat)
self.flakes('''
'%s %(foo)s' % {'foo': 'bar'}
''', m.PercentFormatMixedPositionalAndNamed)
self.flakes('''
'%(foo)s %s' % {'foo': 'bar'}
''', m.PercentFormatMixedPositionalAndNamed)
self.flakes('''
'%j' % (1,)
''', m.PercentFormatUnsupportedFormatCharacter)
self.flakes('''
'%s %s' % (1,)
''', m.PercentFormatPositionalCountMismatch)
self.flakes('''
'%s %s' % (1, 2, 3)
''', m.PercentFormatPositionalCountMismatch)
self.flakes('''
'%(bar)s' % {}
''', m.PercentFormatMissingArgument,)
self.flakes('''
'%(bar)s' % {'bar': 1, 'baz': 2}
''', m.PercentFormatExtraNamedArguments)
self.flakes('''
'%(bar)s' % (1, 2, 3)
''', m.PercentFormatExpectedMapping)
self.flakes('''
'%s %s' % {'k': 'v'}
''', m.PercentFormatExpectedSequence)
self.flakes('''
'%(bar)*s' % {'bar': 'baz'}
''', m.PercentFormatStarRequiresSequence)
# ok: single %s with mapping
self.flakes('''
'%s' % {'foo': 'bar', 'baz': 'womp'}
''')
# ok: does not cause a MemoryError (the strings aren't evaluated)
self.flakes('''
"%1000000000000f" % 1
''')
# ok: %% should not count towards placeholder count
self.flakes('''
'%% %s %% %s' % (1, 2)
''')
# ok: * consumes one positional argument
self.flakes('''
'%.*f' % (2, 1.1234)
'%*.*f' % (5, 2, 3.1234)
''')
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_ok_percent_format_cannot_determine_element_count(self):
self.flakes('''
a = []
'%s %s' % [*a]
'%s %s' % (*a,)
''')
self.flakes('''
k = {}
'%(k)s' % {**k}
''')
class TestAsyncStatements(TestCase):
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_asyncDef(self):
self.flakes('''
async def bar():
return 42
''')
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_asyncDefAwait(self):
self.flakes('''
async def read_data(db):
await db.fetch('SELECT ...')
''')
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_asyncDefUndefined(self):
self.flakes('''
async def bar():
return foo()
''', m.UndefinedName)
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_asyncFor(self):
self.flakes('''
async def read_data(db):
output = []
async for row in db.cursor():
output.append(row)
return output
''')
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_asyncForUnderscoreLoopVar(self):
self.flakes('''
async def coro(it):
async for _ in it:
pass
''')
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_loopControlInAsyncFor(self):
self.flakes('''
async def read_data(db):
output = []
async for row in db.cursor():
if row[0] == 'skip':
continue
output.append(row)
return output
''')
self.flakes('''
async def read_data(db):
output = []
async for row in db.cursor():
if row[0] == 'stop':
break
output.append(row)
return output
''')
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_loopControlInAsyncForElse(self):
self.flakes('''
async def read_data(db):
output = []
async for row in db.cursor():
output.append(row)
else:
continue
return output
''', m.ContinueOutsideLoop)
self.flakes('''
async def read_data(db):
output = []
async for row in db.cursor():
output.append(row)
else:
break
return output
''', m.BreakOutsideLoop)
@skipIf(version_info < (3, 5), 'new in Python 3.5')
@skipIf(version_info > (3, 8), "Python <= 3.8 only")
def test_continueInAsyncForFinally(self):
self.flakes('''
async def read_data(db):
output = []
async for row in db.cursor():
try:
output.append(row)
finally:
continue
return output
''', m.ContinueInFinally)
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_asyncWith(self):
self.flakes('''
async def commit(session, data):
async with session.transaction():
await session.update(data)
''')
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_asyncWithItem(self):
self.flakes('''
async def commit(session, data):
async with session.transaction() as trans:
await trans.begin()
...
await trans.end()
''')
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_matmul(self):
self.flakes('''
def foo(a, b):
return a @ b
''')
@skipIf(version_info < (3, 6), 'new in Python 3.6')
def test_formatstring(self):
self.flakes('''
hi = 'hi'
mom = 'mom'
f'{hi} {mom}'
''')
def test_raise_notimplemented(self):
self.flakes('''
raise NotImplementedError("This is fine")
''')
self.flakes('''
raise NotImplementedError
''')
self.flakes('''
raise NotImplemented("This isn't gonna work")
''', m.RaiseNotImplemented)
self.flakes('''
raise NotImplemented
''', m.RaiseNotImplemented)
class TestIncompatiblePrintOperator(TestCase):
"""
Tests for warning about invalid use of print function.
"""
def test_valid_print(self):
self.flakes('''
print("Hello")
''')
def test_invalid_print_when_imported_from_future(self):
exc = self.flakes('''
from __future__ import print_function
import sys
print >>sys.stderr, "Hello"
''', m.InvalidPrintSyntax).messages[0]
self.assertEqual(exc.lineno, 4)
self.assertEqual(exc.col, 0)
def test_print_function_assignment(self):
"""
A valid assignment, tested for catching false positives.
"""
self.flakes('''
from __future__ import print_function
log = print
log("Hello")
''')
def test_print_in_lambda(self):
self.flakes('''
from __future__ import print_function
a = lambda: print
''')
def test_print_returned_in_function(self):
self.flakes('''
from __future__ import print_function
def a():
return print
''')
def test_print_as_condition_test(self):
self.flakes('''
from __future__ import print_function
if print: pass
''')
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1609792587.0
pyflakes-2.4.0/pyflakes/test/test_return_with_arguments_inside_generator.py 0000644 0001750 0001750 00000001603 00000000000 031274 0 ustar 00asottile asottile 0000000 0000000
from sys import version_info
from pyflakes import messages as m
from pyflakes.test.harness import TestCase, skipIf
class Test(TestCase):
@skipIf(version_info >= (3, 3), 'new in Python 3.3')
def test_return(self):
self.flakes('''
class a:
def b():
for x in a.c:
if x:
yield x
return a
''', m.ReturnWithArgsInsideGenerator)
@skipIf(version_info >= (3, 3), 'new in Python 3.3')
def test_returnNone(self):
self.flakes('''
def a():
yield 12
return None
''', m.ReturnWithArgsInsideGenerator)
@skipIf(version_info >= (3, 3), 'new in Python 3.3')
def test_returnYieldExpression(self):
self.flakes('''
def a():
b = yield a
return b
''', m.ReturnWithArgsInsideGenerator)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633476446.0
pyflakes-2.4.0/pyflakes/test/test_type_annotations.py 0000644 0001750 0001750 00000047202 00000000000 024637 0 ustar 00asottile asottile 0000000 0000000 """
Tests for behaviour related to type annotations.
"""
from sys import version_info
from pyflakes import messages as m
from pyflakes.test.harness import TestCase, skipIf
class TestTypeAnnotations(TestCase):
def test_typingOverload(self):
"""Allow intentional redefinitions via @typing.overload"""
self.flakes("""
import typing
from typing import overload
@overload
def f(s): # type: (None) -> None
pass
@overload
def f(s): # type: (int) -> int
pass
def f(s):
return s
@typing.overload
def g(s): # type: (None) -> None
pass
@typing.overload
def g(s): # type: (int) -> int
pass
def g(s):
return s
""")
def test_typingExtensionsOverload(self):
"""Allow intentional redefinitions via @typing_extensions.overload"""
self.flakes("""
import typing_extensions
from typing_extensions import overload
@overload
def f(s): # type: (None) -> None
pass
@overload
def f(s): # type: (int) -> int
pass
def f(s):
return s
@typing_extensions.overload
def g(s): # type: (None) -> None
pass
@typing_extensions.overload
def g(s): # type: (int) -> int
pass
def g(s):
return s
""")
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_typingOverloadAsync(self):
"""Allow intentional redefinitions via @typing.overload (async)"""
self.flakes("""
from typing import overload
@overload
async def f(s): # type: (None) -> None
pass
@overload
async def f(s): # type: (int) -> int
pass
async def f(s):
return s
""")
def test_overload_with_multiple_decorators(self):
self.flakes("""
from typing import overload
dec = lambda f: f
@dec
@overload
def f(x): # type: (int) -> int
pass
@dec
@overload
def f(x): # type: (str) -> str
pass
@dec
def f(x): return x
""")
def test_overload_in_class(self):
self.flakes("""
from typing import overload
class C:
@overload
def f(self, x): # type: (int) -> int
pass
@overload
def f(self, x): # type: (str) -> str
pass
def f(self, x): return x
""")
def test_aliased_import(self):
"""Detect when typing is imported as another name"""
self.flakes("""
import typing as t
@t.overload
def f(s): # type: (None) -> None
pass
@t.overload
def f(s): # type: (int) -> int
pass
def f(s):
return s
""")
def test_not_a_typing_overload(self):
"""regression test for @typing.overload detection bug in 2.1.0"""
self.flakes("""
def foo(x):
return x
@foo
def bar():
pass
def bar():
pass
""", m.RedefinedWhileUnused)
@skipIf(version_info < (3, 6), 'new in Python 3.6')
def test_variable_annotations(self):
self.flakes('''
name: str
age: int
''')
self.flakes('''
name: str = 'Bob'
age: int = 18
''')
self.flakes('''
class C:
name: str
age: int
''')
self.flakes('''
class C:
name: str = 'Bob'
age: int = 18
''')
self.flakes('''
def f():
name: str
age: int
''')
self.flakes('''
def f():
name: str = 'Bob'
age: int = 18
foo: not_a_real_type = None
''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable, m.UndefinedName)
self.flakes('''
def f():
name: str
print(name)
''', m.UndefinedName)
self.flakes('''
from typing import Any
def f():
a: Any
''')
self.flakes('''
foo: not_a_real_type
''', m.UndefinedName)
self.flakes('''
foo: not_a_real_type = None
''', m.UndefinedName)
self.flakes('''
class C:
foo: not_a_real_type
''', m.UndefinedName)
self.flakes('''
class C:
foo: not_a_real_type = None
''', m.UndefinedName)
self.flakes('''
def f():
class C:
foo: not_a_real_type
''', m.UndefinedName)
self.flakes('''
def f():
class C:
foo: not_a_real_type = None
''', m.UndefinedName)
self.flakes('''
from foo import Bar
bar: Bar
''')
self.flakes('''
from foo import Bar
bar: 'Bar'
''')
self.flakes('''
import foo
bar: foo.Bar
''')
self.flakes('''
import foo
bar: 'foo.Bar'
''')
self.flakes('''
from foo import Bar
def f(bar: Bar): pass
''')
self.flakes('''
from foo import Bar
def f(bar: 'Bar'): pass
''')
self.flakes('''
from foo import Bar
def f(bar) -> Bar: return bar
''')
self.flakes('''
from foo import Bar
def f(bar) -> 'Bar': return bar
''')
self.flakes('''
bar: 'Bar'
''', m.UndefinedName)
self.flakes('''
bar: 'foo.Bar'
''', m.UndefinedName)
self.flakes('''
from foo import Bar
bar: str
''', m.UnusedImport)
self.flakes('''
from foo import Bar
def f(bar: str): pass
''', m.UnusedImport)
self.flakes('''
def f(a: A) -> A: pass
class A: pass
''', m.UndefinedName, m.UndefinedName)
self.flakes('''
def f(a: 'A') -> 'A': return a
class A: pass
''')
self.flakes('''
a: A
class A: pass
''', m.UndefinedName)
self.flakes('''
a: 'A'
class A: pass
''')
self.flakes('''
T: object
def f(t: T): pass
''', m.UndefinedName)
self.flakes('''
T: object
def g(t: 'T'): pass
''')
self.flakes('''
a: 'A B'
''', m.ForwardAnnotationSyntaxError)
self.flakes('''
a: 'A; B'
''', m.ForwardAnnotationSyntaxError)
self.flakes('''
a: '1 + 2'
''')
self.flakes('''
a: 'a: "A"'
''', m.ForwardAnnotationSyntaxError)
@skipIf(version_info < (3, 6), 'new in Python 3.6')
def test_annotating_an_import(self):
self.flakes('''
from a import b, c
b: c
print(b)
''')
@skipIf(version_info < (3, 6), 'new in Python 3.6')
def test_unused_annotation(self):
# Unused annotations are fine in module and class scope
self.flakes('''
x: int
class Cls:
y: int
''')
# TODO: this should print a UnusedVariable message
self.flakes('''
def f():
x: int
''')
# This should only print one UnusedVariable message
self.flakes('''
def f():
x: int
x = 3
''', m.UnusedVariable)
@skipIf(version_info < (3, 5), 'new in Python 3.5')
def test_annotated_async_def(self):
self.flakes('''
class c: pass
async def func(c: c) -> None: pass
''')
@skipIf(version_info < (3, 7), 'new in Python 3.7')
def test_postponed_annotations(self):
self.flakes('''
from __future__ import annotations
def f(a: A) -> A: pass
class A:
b: B
class B: pass
''')
self.flakes('''
from __future__ import annotations
def f(a: A) -> A: pass
class A:
b: Undefined
class B: pass
''', m.UndefinedName)
self.flakes('''
from __future__ import annotations
T: object
def f(t: T): pass
def g(t: 'T'): pass
''')
@skipIf(version_info < (3, 6), 'new in Python 3.6')
def test_type_annotation_clobbers_all(self):
self.flakes('''\
from typing import TYPE_CHECKING, List
from y import z
if not TYPE_CHECKING:
__all__ = ("z",)
else:
__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):
error = (m.CommentAnnotationSyntaxError if version_info < (3,)
else m.UndefinedName)
self.flakes("""
x = 2 # type: ignore\xc3
""", error)
@skipIf(version_info < (3,), 'new in Python 3')
def test_return_annotation_is_class_scope_variable(self):
self.flakes("""
from typing import TypeVar
class Test:
Y = TypeVar('Y')
def t(self, x: Y) -> Y:
return x
""")
@skipIf(version_info < (3,), 'new in Python 3')
def test_return_annotation_is_function_body_variable(self):
self.flakes("""
class Test:
def t(self) -> Y:
Y = 2
return Y
""", m.UndefinedName)
@skipIf(version_info < (3, 8), 'new in Python 3.8')
def test_positional_only_argument_annotations(self):
self.flakes("""
from x import C
def f(c: C, /): ...
""")
@skipIf(version_info < (3,), 'new in Python 3')
def test_partially_quoted_type_annotation(self):
self.flakes("""
from queue import Queue
from typing import Optional
def f() -> Optional['Queue[str]']:
return None
""")
def test_partially_quoted_type_assignment(self):
self.flakes("""
from queue import Queue
from typing import Optional
MaybeQueue = Optional['Queue[str]']
""")
def test_nested_partially_quoted_type_assignment(self):
self.flakes("""
from queue import Queue
from typing import Callable
Func = Callable[['Queue[str]'], None]
""")
def test_quoted_type_cast(self):
self.flakes("""
from typing import cast, Optional
maybe_int = cast('Optional[int]', 42)
""")
def test_type_cast_literal_str_to_str(self):
# Checks that our handling of quoted type annotations in the first
# argument to `cast` doesn't cause issues when (only) the _second_
# argument is a literal str which looks a bit like a type annotation.
self.flakes("""
from typing import cast
a_string = cast(str, 'Optional[int]')
""")
def test_quoted_type_cast_renamed_import(self):
self.flakes("""
from typing import cast as tsac, Optional as Maybe
maybe_int = tsac('Maybe[int]', 42)
""")
def test_quoted_TypeVar_constraints(self):
self.flakes("""
from typing import TypeVar, Optional
T = TypeVar('T', 'str', 'Optional[int]', bytes)
""")
def test_quoted_TypeVar_bound(self):
self.flakes("""
from typing import TypeVar, Optional, List
T = TypeVar('T', bound='Optional[int]')
S = TypeVar('S', int, bound='List[int]')
""")
@skipIf(version_info < (3,), 'new in Python 3')
def test_literal_type_typing(self):
self.flakes("""
from typing import Literal
def f(x: Literal['some string']) -> None:
return None
""")
@skipIf(version_info < (3,), 'new in Python 3')
def test_literal_type_typing_extensions(self):
self.flakes("""
from typing_extensions import Literal
def f(x: Literal['some string']) -> None:
return None
""")
@skipIf(version_info < (3,), 'new in Python 3')
def test_annotated_type_typing_missing_forward_type(self):
self.flakes("""
from typing import Annotated
def f(x: Annotated['integer']) -> None:
return None
""", m.UndefinedName)
@skipIf(version_info < (3,), 'new in Python 3')
def test_annotated_type_typing_missing_forward_type_multiple_args(self):
self.flakes("""
from typing import Annotated
def f(x: Annotated['integer', 1]) -> None:
return None
""", m.UndefinedName)
@skipIf(version_info < (3,), 'new in Python 3')
def test_annotated_type_typing_with_string_args(self):
self.flakes("""
from typing import Annotated
def f(x: Annotated[int, '> 0']) -> None:
return None
""")
@skipIf(version_info < (3,), 'new in Python 3')
def test_annotated_type_typing_with_string_args_in_union(self):
self.flakes("""
from typing import Annotated, Union
def f(x: Union[Annotated['int', '>0'], 'integer']) -> None:
return None
""", m.UndefinedName)
@skipIf(version_info < (3,), 'new in Python 3')
def test_literal_type_some_other_module(self):
"""err on the side of false-negatives for types named Literal"""
self.flakes("""
from my_module import compat
from my_module.compat import Literal
def f(x: compat.Literal['some string']) -> None:
return None
def g(x: Literal['some string']) -> None:
return None
""")
@skipIf(version_info < (3,), 'new in Python 3')
def test_literal_union_type_typing(self):
self.flakes("""
from typing import Literal
def f(x: Literal['some string', 'foo bar']) -> None:
return None
""")
@skipIf(version_info < (3,), 'new in Python 3')
def test_deferred_twice_annotation(self):
self.flakes("""
from queue import Queue
from typing import Optional
def f() -> "Optional['Queue[str]']":
return None
""")
@skipIf(version_info < (3, 7), 'new in Python 3.7')
def test_partial_string_annotations_with_future_annotations(self):
self.flakes("""
from __future__ import annotations
from queue import Queue
from typing import Optional
def f() -> Optional['Queue[str]']:
return None
""")
def test_idomiatic_typing_guards(self):
# typing.TYPE_CHECKING: python3.5.3+
self.flakes("""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from t import T
def f(): # type: () -> T
pass
""")
# False: the old, more-compatible approach
self.flakes("""
if False:
from t import T
def f(): # type: () -> T
pass
""")
# some choose to assign a constant and do it that way
self.flakes("""
MYPY = False
if MYPY:
from t import T
def f(): # type: () -> T
pass
""")
def test_typing_guard_for_protocol(self):
self.flakes("""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Protocol
else:
Protocol = object
class C(Protocol):
def f(): # type: () -> int
pass
""")
def test_typednames_correct_forward_ref(self):
self.flakes("""
from typing import TypedDict, List, NamedTuple
List[TypedDict("x", {})]
List[TypedDict("x", x=int)]
List[NamedTuple("a", a=int)]
List[NamedTuple("a", [("a", int)])]
""")
self.flakes("""
from typing import TypedDict, List, NamedTuple, TypeVar
List[TypedDict("x", {"x": "Y"})]
List[TypedDict("x", x="Y")]
List[NamedTuple("a", [("a", "Y")])]
List[NamedTuple("a", a="Y")]
List[TypedDict("x", {"x": List["a"]})]
List[TypeVar("A", bound="C")]
List[TypeVar("A", List["C"])]
""", *[m.UndefinedName]*7)
self.flakes("""
from typing import NamedTuple, TypeVar, cast
from t import A, B, C, D, E
NamedTuple("A", [("a", A["C"])])
TypeVar("A", bound=A["B"])
TypeVar("A", A["D"])
cast(A["E"], [])
""")
@skipIf(version_info < (3, 6), 'new in Python 3.6')
def test_namedtypes_classes(self):
self.flakes("""
from typing import TypedDict, NamedTuple
class X(TypedDict):
y: TypedDict("z", {"zz":int})
class Y(NamedTuple):
y: NamedTuple("v", [("vv", int)])
""")
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633476446.0
pyflakes-2.4.0/pyflakes/test/test_undefined_names.py 0000644 0001750 0001750 00000062315 00000000000 024367 0 ustar 00asottile asottile 0000000 0000000 import ast
from sys import version_info
from pyflakes import messages as m, checker
from pyflakes.test.harness import TestCase, skipIf, skip
class Test(TestCase):
def test_undefined(self):
self.flakes('bar', m.UndefinedName)
def test_definedInListComp(self):
self.flakes('[a for a in range(10) if a]')
@skipIf(version_info < (3,),
'in Python 2 list comprehensions execute in the same scope')
def test_undefinedInListComp(self):
self.flakes('''
[a for a in range(10)]
a
''',
m.UndefinedName)
@skipIf(version_info < (3,),
'in Python 2 exception names stay bound after the except: block')
def test_undefinedExceptionName(self):
"""Exception names can't be used after the except: block.
The exc variable is unused inside the exception handler."""
self.flakes('''
try:
raise ValueError('ve')
except ValueError as exc:
pass
exc
''', m.UndefinedName, m.UnusedVariable)
def test_namesDeclaredInExceptBlocks(self):
"""Locals declared in except: blocks can be used after the block.
This shows the example in test_undefinedExceptionName is
different."""
self.flakes('''
try:
raise ValueError('ve')
except ValueError as exc:
e = exc
e
''')
@skip('error reporting disabled due to false positives below')
def test_undefinedExceptionNameObscuringLocalVariable(self):
"""Exception names obscure locals, can't be used after.
Last line will raise UnboundLocalError on Python 3 after exiting
the except: block. Note next two examples for false positives to
watch out for."""
self.flakes('''
exc = 'Original value'
try:
raise ValueError('ve')
except ValueError as exc:
pass
exc
''',
m.UndefinedName)
@skipIf(version_info < (3,),
'in Python 2 exception names stay bound after the except: block')
def test_undefinedExceptionNameObscuringLocalVariable2(self):
"""Exception names are unbound after the `except:` block.
Last line will raise UnboundLocalError on Python 3 but would print out
've' on Python 2. The exc variable is unused inside the exception
handler."""
self.flakes('''
try:
raise ValueError('ve')
except ValueError as exc:
pass
print(exc)
exc = 'Original value'
''', m.UndefinedName, m.UnusedVariable)
def test_undefinedExceptionNameObscuringLocalVariableFalsePositive1(self):
"""Exception names obscure locals, can't be used after. Unless.
Last line will never raise UnboundLocalError because it's only
entered if no exception was raised."""
# The exc variable is unused inside the exception handler.
expected = [] if version_info < (3,) else [m.UnusedVariable]
self.flakes('''
exc = 'Original value'
try:
raise ValueError('ve')
except ValueError as exc:
print('exception logged')
raise
exc
''', *expected)
def test_delExceptionInExcept(self):
"""The exception name can be deleted in the except: block."""
self.flakes('''
try:
pass
except Exception as exc:
del exc
''')
def test_undefinedExceptionNameObscuringLocalVariableFalsePositive2(self):
"""Exception names obscure locals, can't be used after. Unless.
Last line will never raise UnboundLocalError because `error` is
only falsy if the `except:` block has not been entered."""
# The exc variable is unused inside the exception handler.
expected = [] if version_info < (3,) else [m.UnusedVariable]
self.flakes('''
exc = 'Original value'
error = None
try:
raise ValueError('ve')
except ValueError as exc:
error = 'exception logged'
if error:
print(error)
else:
exc
''', *expected)
@skip('error reporting disabled due to false positives below')
def test_undefinedExceptionNameObscuringGlobalVariable(self):
"""Exception names obscure globals, can't be used after.
Last line will raise UnboundLocalError on both Python 2 and
Python 3 because the existence of that exception name creates
a local scope placeholder for it, obscuring any globals, etc."""
self.flakes('''
exc = 'Original value'
def func():
try:
pass # nothing is raised
except ValueError as exc:
pass # block never entered, exc stays unbound
exc
''',
m.UndefinedLocal)
@skip('error reporting disabled due to false positives below')
def test_undefinedExceptionNameObscuringGlobalVariable2(self):
"""Exception names obscure globals, can't be used after.
Last line will raise NameError on Python 3 because the name is
locally unbound after the `except:` block, even if it's
nonlocal. We should issue an error in this case because code
only working correctly if an exception isn't raised, is invalid.
Unless it's explicitly silenced, see false positives below."""
self.flakes('''
exc = 'Original value'
def func():
global exc
try:
raise ValueError('ve')
except ValueError as exc:
pass # block never entered, exc stays unbound
exc
''',
m.UndefinedLocal)
def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1(self):
"""Exception names obscure globals, can't be used after. Unless.
Last line will never raise NameError because it's only entered
if no exception was raised."""
# The exc variable is unused inside the exception handler.
expected = [] if version_info < (3,) else [m.UnusedVariable]
self.flakes('''
exc = 'Original value'
def func():
global exc
try:
raise ValueError('ve')
except ValueError as exc:
print('exception logged')
raise
exc
''', *expected)
def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2(self):
"""Exception names obscure globals, can't be used after. Unless.
Last line will never raise NameError because `error` is only
falsy if the `except:` block has not been entered."""
# The exc variable is unused inside the exception handler.
expected = [] if version_info < (3,) else [m.UnusedVariable]
self.flakes('''
exc = 'Original value'
def func():
global exc
error = None
try:
raise ValueError('ve')
except ValueError as exc:
error = 'exception logged'
if error:
print(error)
else:
exc
''', *expected)
def test_functionsNeedGlobalScope(self):
self.flakes('''
class a:
def b():
fu
fu = 1
''')
def test_builtins(self):
self.flakes('range(10)')
def test_builtinWindowsError(self):
"""
C{WindowsError} is sometimes a builtin name, so no warning is emitted
for using it.
"""
self.flakes('WindowsError')
@skipIf(version_info < (3, 6), 'new feature in 3.6')
def test_moduleAnnotations(self):
"""
Use of the C{__annotations__} in module scope should not emit
an undefined name warning when version is greater than or equal to 3.6.
"""
self.flakes('__annotations__')
def test_magicGlobalsFile(self):
"""
Use of the C{__file__} magic global should not emit an undefined name
warning.
"""
self.flakes('__file__')
def test_magicGlobalsBuiltins(self):
"""
Use of the C{__builtins__} magic global should not emit an undefined
name warning.
"""
self.flakes('__builtins__')
def test_magicGlobalsName(self):
"""
Use of the C{__name__} magic global should not emit an undefined name
warning.
"""
self.flakes('__name__')
def test_magicGlobalsPath(self):
"""
Use of the C{__path__} magic global should not emit an undefined name
warning, if you refer to it from a file called __init__.py.
"""
self.flakes('__path__', m.UndefinedName)
self.flakes('__path__', filename='package/__init__.py')
def test_magicModuleInClassScope(self):
"""
Use of the C{__module__} magic builtin should not emit an undefined
name warning if used in class scope.
"""
self.flakes('__module__', m.UndefinedName)
self.flakes('''
class Foo:
__module__
''')
self.flakes('''
class Foo:
def bar(self):
__module__
''', m.UndefinedName)
@skipIf(version_info < (3, 3), "Python >= 3.3 only")
def test_magicQualnameInClassScope(self):
"""
Use of the C{__qualname__} magic builtin should not emit an undefined
name warning if used in class scope.
"""
self.flakes('__qualname__', m.UndefinedName)
self.flakes('''
class Foo:
__qualname__
''')
self.flakes('''
class Foo:
def bar(self):
__qualname__
''', m.UndefinedName)
def test_globalImportStar(self):
"""Can't find undefined names with import *."""
self.flakes('from fu import *; bar',
m.ImportStarUsed, m.ImportStarUsage)
@skipIf(version_info >= (3,), 'obsolete syntax')
def test_localImportStar(self):
"""
A local import * still allows undefined names to be found
in upper scopes.
"""
self.flakes('''
def a():
from fu import *
bar
''', m.ImportStarUsed, m.UndefinedName, m.UnusedImport)
@skipIf(version_info >= (3,), 'obsolete syntax')
def test_unpackedParameter(self):
"""Unpacked function parameters create bindings."""
self.flakes('''
def a((bar, baz)):
bar; baz
''')
def test_definedByGlobal(self):
"""
"global" can make an otherwise undefined name in another function
defined.
"""
self.flakes('''
def a(): global fu; fu = 1
def b(): fu
''')
self.flakes('''
def c(): bar
def b(): global bar; bar = 1
''')
def test_definedByGlobalMultipleNames(self):
"""
"global" can accept multiple names.
"""
self.flakes('''
def a(): global fu, bar; fu = 1; bar = 2
def b(): fu; bar
''')
def test_globalInGlobalScope(self):
"""
A global statement in the global scope is ignored.
"""
self.flakes('''
global x
def foo():
print(x)
''', m.UndefinedName)
def test_global_reset_name_only(self):
"""A global statement does not prevent other names being undefined."""
# Only different undefined names are reported.
# See following test that fails where the same name is used.
self.flakes('''
def f1():
s
def f2():
global m
''', m.UndefinedName)
@skip("todo")
def test_unused_global(self):
"""An unused global statement does not define the name."""
self.flakes('''
def f1():
m
def f2():
global m
''', m.UndefinedName)
def test_del(self):
"""Del deletes bindings."""
self.flakes('a = 1; del a; a', m.UndefinedName)
def test_delGlobal(self):
"""Del a global binding from a function."""
self.flakes('''
a = 1
def f():
global a
del a
a
''')
def test_delUndefined(self):
"""Del an undefined name."""
self.flakes('del a', m.UndefinedName)
def test_delConditional(self):
"""
Ignores conditional bindings deletion.
"""
self.flakes('''
context = None
test = True
if False:
del(test)
assert(test)
''')
def test_delConditionalNested(self):
"""
Ignored conditional bindings deletion even if they are nested in other
blocks.
"""
self.flakes('''
context = None
test = True
if False:
with context():
del(test)
assert(test)
''')
def test_delWhile(self):
"""
Ignore bindings deletion if called inside the body of a while
statement.
"""
self.flakes('''
def test():
foo = 'bar'
while False:
del foo
assert(foo)
''')
def test_delWhileTestUsage(self):
"""
Ignore bindings deletion if called inside the body of a while
statement and name is used inside while's test part.
"""
self.flakes('''
def _worker():
o = True
while o is not True:
del o
o = False
''')
def test_delWhileNested(self):
"""
Ignore bindings deletions if node is part of while's test, even when
del is in a nested block.
"""
self.flakes('''
context = None
def _worker():
o = True
while o is not True:
while True:
with context():
del o
o = False
''')
def test_globalFromNestedScope(self):
"""Global names are available from nested scopes."""
self.flakes('''
a = 1
def b():
def c():
a
''')
def test_laterRedefinedGlobalFromNestedScope(self):
"""
Test that referencing a local name that shadows a global, before it is
defined, generates a warning.
"""
self.flakes('''
a = 1
def fun():
a
a = 2
return a
''', m.UndefinedLocal)
def test_laterRedefinedGlobalFromNestedScope2(self):
"""
Test that referencing a local name in a nested scope that shadows a
global declared in an enclosing scope, before it is defined, generates
a warning.
"""
self.flakes('''
a = 1
def fun():
global a
def fun2():
a
a = 2
return a
''', m.UndefinedLocal)
def test_intermediateClassScopeIgnored(self):
"""
If a name defined in an enclosing scope is shadowed by a local variable
and the name is used locally before it is bound, an unbound local
warning is emitted, even if there is a class scope between the enclosing
scope and the local scope.
"""
self.flakes('''
def f():
x = 1
class g:
def h(self):
a = x
x = None
print(x, a)
print(x)
''', m.UndefinedLocal)
def test_doubleNestingReportsClosestName(self):
"""
Test that referencing a local name in a nested scope that shadows a
variable declared in two different outer scopes before it is defined
in the innermost scope generates an UnboundLocal warning which
refers to the nearest shadowed name.
"""
exc = self.flakes('''
def a():
x = 1
def b():
x = 2 # line 5
def c():
x
x = 3
return x
return x
return x
''', m.UndefinedLocal).messages[0]
# _DoctestMixin.flakes adds two lines preceding the code above.
expected_line_num = 7 if self.withDoctest else 5
self.assertEqual(exc.message_args, ('x', expected_line_num))
def test_laterRedefinedGlobalFromNestedScope3(self):
"""
Test that referencing a local name in a nested scope that shadows a
global, before it is defined, generates a warning.
"""
self.flakes('''
def fun():
a = 1
def fun2():
a
a = 1
return a
return a
''', m.UndefinedLocal)
def test_undefinedAugmentedAssignment(self):
self.flakes(
'''
def f(seq):
a = 0
seq[a] += 1
seq[b] /= 2
c[0] *= 2
a -= 3
d += 4
e[any] = 5
''',
m.UndefinedName, # b
m.UndefinedName, # c
m.UndefinedName, m.UnusedVariable, # d
m.UndefinedName, # e
)
def test_nestedClass(self):
"""Nested classes can access enclosing scope."""
self.flakes('''
def f(foo):
class C:
bar = foo
def f(self):
return foo
return C()
f(123).f()
''')
def test_badNestedClass(self):
"""Free variables in nested classes must bind at class creation."""
self.flakes('''
def f():
class C:
bar = foo
foo = 456
return foo
f()
''', m.UndefinedName)
def test_definedAsStarArgs(self):
"""Star and double-star arg names are defined."""
self.flakes('''
def f(a, *b, **c):
print(a, b, c)
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_definedAsStarUnpack(self):
"""Star names in unpack are defined."""
self.flakes('''
a, *b = range(10)
print(a, b)
''')
self.flakes('''
*a, b = range(10)
print(a, b)
''')
self.flakes('''
a, *b, c = range(10)
print(a, b, c)
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_usedAsStarUnpack(self):
"""
Star names in unpack are used if RHS is not a tuple/list literal.
"""
self.flakes('''
def f():
a, *b = range(10)
''')
self.flakes('''
def f():
(*a, b) = range(10)
''')
self.flakes('''
def f():
[a, *b, c] = range(10)
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_unusedAsStarUnpack(self):
"""
Star names in unpack are unused if RHS is a tuple/list literal.
"""
self.flakes('''
def f():
a, *b = any, all, 4, 2, 'un'
''', m.UnusedVariable, m.UnusedVariable)
self.flakes('''
def f():
(*a, b) = [bool, int, float, complex]
''', m.UnusedVariable, m.UnusedVariable)
self.flakes('''
def f():
[a, *b, c] = 9, 8, 7, 6, 5, 4
''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable)
@skipIf(version_info < (3,), 'new in Python 3')
def test_keywordOnlyArgs(self):
"""Keyword-only arg names are defined."""
self.flakes('''
def f(*, a, b=None):
print(a, b)
''')
self.flakes('''
import default_b
def f(*, a, b=default_b):
print(a, b)
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_keywordOnlyArgsUndefined(self):
"""Typo in kwonly name."""
self.flakes('''
def f(*, a, b=default_c):
print(a, b)
''', m.UndefinedName)
@skipIf(version_info < (3,), 'new in Python 3')
def test_annotationUndefined(self):
"""Undefined annotations."""
self.flakes('''
from abc import note1, note2, note3, note4, note5
def func(a: note1, *args: note2,
b: note3=12, **kw: note4) -> note5: pass
''')
self.flakes('''
def func():
d = e = 42
def func(a: {1, d}) -> (lambda c: e): pass
''')
@skipIf(version_info < (3,), 'new in Python 3')
def test_metaClassUndefined(self):
self.flakes('''
from abc import ABCMeta
class A(metaclass=ABCMeta): pass
''')
def test_definedInGenExp(self):
"""
Using the loop variable of a generator expression results in no
warnings.
"""
self.flakes('(a for a in [1, 2, 3] if a)')
self.flakes('(b for b in (a for a in [1, 2, 3] if a) if b)')
def test_undefinedInGenExpNested(self):
"""
The loop variables of generator expressions nested together are
not defined in the other generator.
"""
self.flakes('(b for b in (a for a in [1, 2, 3] if b) if b)',
m.UndefinedName)
self.flakes('(b for b in (a for a in [1, 2, 3] if a) if a)',
m.UndefinedName)
def test_undefinedWithErrorHandler(self):
"""
Some compatibility code checks explicitly for NameError.
It should not trigger warnings.
"""
self.flakes('''
try:
socket_map
except NameError:
socket_map = {}
''')
self.flakes('''
try:
_memoryview.contiguous
except (NameError, AttributeError):
raise RuntimeError("Python >= 3.3 is required")
''')
# If NameError is not explicitly handled, generate a warning
self.flakes('''
try:
socket_map
except:
socket_map = {}
''', m.UndefinedName)
self.flakes('''
try:
socket_map
except Exception:
socket_map = {}
''', m.UndefinedName)
def test_definedInClass(self):
"""
Defined name for generator expressions and dict/set comprehension.
"""
self.flakes('''
class A:
T = range(10)
Z = (x for x in T)
L = [x for x in T]
B = dict((i, str(i)) for i in T)
''')
self.flakes('''
class A:
T = range(10)
X = {x for x in T}
Y = {x:x for x in T}
''')
def test_definedInClassNested(self):
"""Defined name for nested generator expressions in a class."""
self.flakes('''
class A:
T = range(10)
Z = (x for x in (a for a in T))
''')
def test_undefinedInLoop(self):
"""
The loop variable is defined after the expression is computed.
"""
self.flakes('''
for i in range(i):
print(i)
''', m.UndefinedName)
self.flakes('''
[42 for i in range(i)]
''', m.UndefinedName)
self.flakes('''
(42 for i in range(i))
''', m.UndefinedName)
def test_definedFromLambdaInDictionaryComprehension(self):
"""
Defined name referenced from a lambda function within a dict/set
comprehension.
"""
self.flakes('''
{lambda: id(x) for x in range(10)}
''')
def test_definedFromLambdaInGenerator(self):
"""
Defined name referenced from a lambda function within a generator
expression.
"""
self.flakes('''
any(lambda: id(x) for x in range(10))
''')
def test_undefinedFromLambdaInDictionaryComprehension(self):
"""
Undefined name referenced from a lambda function within a dict/set
comprehension.
"""
self.flakes('''
{lambda: id(y) for x in range(10)}
''', m.UndefinedName)
def test_undefinedFromLambdaInComprehension(self):
"""
Undefined name referenced from a lambda function within a generator
expression.
"""
self.flakes('''
any(lambda: id(y) for x in range(10))
''', m.UndefinedName)
def test_dunderClass(self):
"""
`__class__` is defined in class scope under Python 3, but is not
in Python 2.
"""
code = '''
class Test(object):
def __init__(self):
print(__class__.__name__)
self.x = 1
t = Test()
'''
if version_info < (3,):
self.flakes(code, m.UndefinedName)
else:
self.flakes(code)
class NameTests(TestCase):
"""
Tests for some extra cases of name handling.
"""
def test_impossibleContext(self):
"""
A Name node with an unrecognized context results in a RuntimeError being
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, file_tokens=file_tokens)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633552776.3134809
pyflakes-2.4.0/pyflakes.egg-info/ 0000755 0001750 0001750 00000000000 00000000000 020336 5 ustar 00asottile asottile 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633552776.0
pyflakes-2.4.0/pyflakes.egg-info/PKG-INFO 0000644 0001750 0001750 00000007472 00000000000 021445 0 ustar 00asottile asottile 0000000 0000000 Metadata-Version: 2.1
Name: pyflakes
Version: 2.4.0
Summary: passive checker of Python programs
Home-page: https://github.com/PyCQA/pyflakes
Author: A lot of people
Author-email: code-quality@python.org
License: MIT
Platform: UNKNOWN
Classifier: Development Status :: 6 - Mature
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development
Classifier: Topic :: Utilities
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
License-File: LICENSE
========
Pyflakes
========
A simple program which checks Python source files for errors.
Pyflakes analyzes programs and detects various errors. It works by
parsing the source file, not importing it, so it is safe to use on
modules with side effects. It's also much faster.
It is `available on PyPI `_
and it supports all active versions of Python: 2.7 and 3.4 to 3.8.
Installation
------------
It can be installed with::
$ pip install --upgrade pyflakes
Useful tips:
* Be sure to install it for a version of Python which is compatible
with your codebase: for Python 2, ``pip2 install pyflakes`` and for
Python3, ``pip3 install pyflakes``.
* 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.
Design Principles
-----------------
Pyflakes makes a simple promise: it will never complain about style,
and it will try very, very hard to never emit false positives.
Pyflakes is also faster than Pylint_. This is
largely because Pyflakes only examines the syntax tree of each file
individually. As a consequence, Pyflakes is more limited in the
types of things it can check.
If you like Pyflakes but also want stylistic checks, you want
flake8_, which combines
Pyflakes with style checks against
`PEP 8`_ and adds
per-project configuration ability.
Mailing-list
------------
Share your feedback and ideas: `subscribe to the mailing-list
`_
Contributing
------------
Issues are tracked on `GitHub `_.
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 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.
All changes should include tests and pass flake8_.
.. image:: https://github.com/PyCQA/pyflakes/workflows/Test/badge.svg
:target: https://github.com/PyCQA/pyflakes/actions
:alt: GitHub Actions build status
.. _Pylint: https://www.pylint.org/
.. _flake8: https://pypi.org/project/flake8/
.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/
.. _`rebase your changes`: https://git-scm.com/book/en/v2/Git-Branching-Rebasing
.. _`GitHub pull request`: https://github.com/PyCQA/pyflakes/pulls
Changelog
---------
Please see `NEWS.rst `_.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633552776.0
pyflakes-2.4.0/pyflakes.egg-info/SOURCES.txt 0000644 0001750 0001750 00000001613 00000000000 022223 0 ustar 00asottile asottile 0000000 0000000 AUTHORS
LICENSE
MANIFEST.in
NEWS.rst
README.rst
setup.cfg
setup.py
bin/pyflakes
pyflakes/__init__.py
pyflakes/__main__.py
pyflakes/api.py
pyflakes/checker.py
pyflakes/messages.py
pyflakes/reporter.py
pyflakes.egg-info/PKG-INFO
pyflakes.egg-info/SOURCES.txt
pyflakes.egg-info/dependency_links.txt
pyflakes.egg-info/entry_points.txt
pyflakes.egg-info/top_level.txt
pyflakes/scripts/__init__.py
pyflakes/scripts/pyflakes.py
pyflakes/test/__init__.py
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
pyflakes/test/test_imports.py
pyflakes/test/test_is_literal.py
pyflakes/test/test_match.py
pyflakes/test/test_other.py
pyflakes/test/test_return_with_arguments_inside_generator.py
pyflakes/test/test_type_annotations.py
pyflakes/test/test_undefined_names.py ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633552776.0
pyflakes-2.4.0/pyflakes.egg-info/dependency_links.txt 0000644 0001750 0001750 00000000001 00000000000 024404 0 ustar 00asottile asottile 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633552776.0
pyflakes-2.4.0/pyflakes.egg-info/entry_points.txt 0000644 0001750 0001750 00000000060 00000000000 023630 0 ustar 00asottile asottile 0000000 0000000 [console_scripts]
pyflakes = pyflakes.api:main
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633552776.0
pyflakes-2.4.0/pyflakes.egg-info/top_level.txt 0000644 0001750 0001750 00000000011 00000000000 023060 0 ustar 00asottile asottile 0000000 0000000 pyflakes
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633552776.3214808
pyflakes-2.4.0/setup.cfg 0000644 0001750 0001750 00000000146 00000000000 016650 0 ustar 00asottile asottile 0000000 0000000 [bdist_wheel]
universal = 1
[metadata]
license_file = LICENSE
[egg_info]
tag_build =
tag_date = 0
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633476446.0
pyflakes-2.4.0/setup.py 0000755 0001750 0001750 00000004133 00000000000 016544 0 ustar 00asottile asottile 0000000 0000000 #!/usr/bin/env python
# Copyright 2005-2011 Divmod, Inc.
# Copyright 2013 Florent Xicluna. See LICENSE file for details
from __future__ import with_statement
import os.path
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
extra = {'scripts': ["bin/pyflakes"]}
else:
extra = {
'test_suite': 'pyflakes.test',
'entry_points': {
'console_scripts': ['pyflakes = pyflakes.api:main'],
},
}
def get_version(fname=os.path.join('pyflakes', '__init__.py')):
with open(fname) as f:
for line in f:
if line.startswith('__version__'):
return eval(line.split('=')[-1])
def get_long_description():
descr = []
for fname in ('README.rst',):
with open(fname) as f:
descr.append(f.read())
return '\n\n'.join(descr)
setup(
name="pyflakes",
license="MIT",
version=get_version(),
description="passive checker of Python programs",
long_description=get_long_description(),
author="A lot of people",
author_email="code-quality@python.org",
url="https://github.com/PyCQA/pyflakes",
packages=["pyflakes", "pyflakes.scripts", "pyflakes.test"],
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
classifiers=[
"Development Status :: 6 - Mature",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development",
"Topic :: Utilities",
],
**extra)