Compare commits

...

45 commits
7.1.1 ... main

Author SHA1 Message Date
anthony sottile
9d75094913
Merge pull request #2010 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-12-22 16:54:35 -05:00
pre-commit-ci[bot]
941f908d6c
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/setup-cfg-fmt: v3.1.0 → v3.2.0](https://github.com/asottile/setup-cfg-fmt/compare/v3.1.0...v3.2.0)
2025-12-22 21:52:21 +00:00
Ian Stapleton Cordasco
7abc22bd96
Merge pull request #2009 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-12-15 20:59:37 -06:00
pre-commit-ci[bot]
45c1af5e24
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.19.0 → v1.19.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.19.0...v1.19.1)
2025-12-15 22:21:22 +00:00
anthony sottile
a358fc3f6b
Merge pull request #2007 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-12-01 19:12:24 -05:00
pre-commit-ci[bot]
72c267d2e5
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.18.2 → v1.19.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.18.2...v1.19.0)
2025-12-01 22:40:09 +00:00
Ian Stapleton Cordasco
b84b2bd4a1
Merge pull request #2006 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-11-24 16:29:35 -06:00
pre-commit-ci[bot]
01af84d980
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.21.1 → v3.21.2](https://github.com/asottile/pyupgrade/compare/v3.21.1...v3.21.2)
2025-11-24 22:27:23 +00:00
anthony sottile
5fd56a3013
Merge pull request #2005 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-11-10 17:56:36 -05:00
pre-commit-ci[bot]
e7682d020c
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.21.0 → v3.21.1](https://github.com/asottile/pyupgrade/compare/v3.21.0...v3.21.1)
2025-11-10 22:38:48 +00:00
anthony sottile
fcfa2cd490
Merge pull request #2001 from PyCQA/py310
py310+
2025-10-16 10:06:12 -04:00
Anthony Sottile
567cafc15a py310+ 2025-10-16 10:01:02 -04:00
Ian Stapleton Cordasco
d45bdc05ce
Merge pull request #1998 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-09-22 17:26:41 -05:00
pre-commit-ci[bot]
e9f1cf3f48
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.18.1 → v1.18.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.18.1...v1.18.2)
2025-09-22 22:17:05 +00:00
Ian Stapleton Cordasco
ed8ac7bdc2
Merge pull request #1997 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-09-15 17:14:07 -05:00
pre-commit-ci[bot]
4b13c2cc19
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.17.1 → v1.18.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.17.1...v1.18.1)
2025-09-15 21:47:50 +00:00
Ian Stapleton Cordasco
2b3d18f0e9
Merge pull request #1992 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-08-11 21:07:21 -05:00
pre-commit-ci[bot]
3a2eff0868
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0)
2025-08-11 22:22:04 +00:00
Anthony Sottile
fa4493de4b
Merge pull request #1991 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-08-04 18:07:56 -04:00
pre-commit-ci[bot]
0f1af50108
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.17.0 → v1.17.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.17.0...v1.17.1)
2025-08-04 21:56:25 +00:00
Anthony Sottile
8fdc755d6b
Merge pull request #1990 from mxr/patch-1
Update hooks and use `autopep8` + `add-trailing-comma` instead of `black`
2025-07-24 11:23:43 +02:00
Max R
5fab0d1887 Update hooks and use autopep8 + add-trailing-comma instead of black 2025-07-20 19:13:24 -04:00
Anthony Sottile
23d2a8517e
Merge pull request #1987 from PyCQA/dogfood
adjust global variable definition for new pyflakes
2025-06-20 15:43:22 -04:00
anthony sottile
628aece714 adjust global variable definition for new pyflakes
the original code was only passing pyflakes by accident due to __future__.annotations
2025-06-20 15:40:43 -04:00
anthony sottile
c48217e1fc Release 7.3.0 2025-06-20 15:30:26 -04:00
Anthony Sottile
f9e0f33281
Merge pull request #1986 from PyCQA/document-f542
document F542
2025-06-20 15:23:36 -04:00
anthony sottile
6bcdb62859 document F542 2025-06-20 15:21:27 -04:00
Anthony Sottile
70a15b8890
Merge pull request #1985 from PyCQA/upgrade-deps
upgrade pyflakes / pycodestyle
2025-06-20 15:20:10 -04:00
anthony sottile
4941a3e32e upgrade pyflakes / pycodestyle 2025-06-20 15:15:53 -04:00
Anthony Sottile
23e4005c55
Merge pull request #1983 from PyCQA/py314
add support for t-strings
2025-05-23 18:45:49 -04:00
anthony sottile
019424b80d add support for t-strings 2025-05-23 16:25:06 -04:00
Anthony Sottile
6b6f3d5fef
Merge pull request #1980 from PyCQA/asottile-patch-1
add rtd sphinx config
2025-04-11 17:42:13 -04:00
Anthony Sottile
8dfa6695b4
add rtd sphinx config 2025-04-11 17:39:39 -04:00
Anthony Sottile
ce34111183
Merge pull request #1976 from PyCQA/document-f824
document F824
2025-03-31 10:08:31 -04:00
Anthony Sottile
3613896bd9 document F824 2025-03-31 10:05:31 -04:00
Anthony Sottile
16f5f28a38 Release 7.2.0 2025-03-29 16:17:35 -04:00
Anthony Sottile
ebad305769
Merge pull request #1974 from PyCQA/update-plugins
update versions of pycodestyle / pyflakes
2025-03-29 15:46:35 -04:00
Anthony Sottile
d56d569ce4 update versions of pycodestyle / pyflakes 2025-03-29 15:53:41 -04:00
Anthony Sottile
a7e8f6250c
Merge pull request #1973 from PyCQA/py39-plus
py39+
2025-03-29 15:38:33 -04:00
Anthony Sottile
9d55ccdb72 py39+ 2025-03-29 15:42:19 -04:00
Anthony Sottile
e492aeb385
Merge pull request #1967 from PyCQA/unnecessary-mocks
remove a few unnecessary mocks in test_checker_manager
2025-02-16 15:15:09 -05:00
Anthony Sottile
fa2ed7145c remove a few unnecessary mocks in test_checker_manager
noticed while implementing the --jobs limiter
2025-02-16 15:21:48 -05:00
Anthony Sottile
fffee8ba9d Release 7.1.2 2025-02-16 13:48:15 -05:00
Anthony Sottile
19001f77f3
Merge pull request #1966 from PyCQA/limit-procs-to-file-count
avoid starting unnecessary processes when file count is limited
2025-02-16 13:35:04 -05:00
Anthony Sottile
f35737a32d avoid starting unnecessary processes when file count is limited 2025-02-16 13:29:05 -05:00
51 changed files with 337 additions and 269 deletions

View file

@ -13,13 +13,7 @@ jobs:
include:
# linux
- os: ubuntu-latest
python: pypy-3.9
toxenv: py
- os: ubuntu-latest
python: 3.8
toxenv: py
- os: ubuntu-latest
python: 3.9
python: pypy-3.11
toxenv: py
- os: ubuntu-latest
python: '3.10'
@ -28,11 +22,17 @@ jobs:
python: '3.11'
toxenv: py
- os: ubuntu-latest
python: '3.12-dev'
python: '3.12'
toxenv: py
- os: ubuntu-latest
python: '3.13'
toxenv: py
- os: ubuntu-latest
python: '3.14'
toxenv: py
# windows
- os: windows-latest
python: 3.8
python: '3.10'
toxenv: py
# misc
- os: ubuntu-latest
@ -46,8 +46,8 @@ jobs:
toxenv: dogfood
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: python -mpip install --upgrade setuptools pip tox virtualenv

View file

@ -1,6 +1,10 @@
repos:
- repo: https://github.com/asottile/add-trailing-comma
rev: v4.0.0
hooks:
- id: add-trailing-comma
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v6.0.0
hooks:
- id: check-yaml
- id: debug-statements
@ -8,34 +12,33 @@ repos:
- id: trailing-whitespace
exclude: ^tests/fixtures/
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.5.0
rev: v3.2.0
hooks:
- id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder-python-imports
rev: v3.12.0
rev: v3.16.0
hooks:
- id: reorder-python-imports
args: [
--application-directories, '.:src',
--py38-plus,
--py310-plus,
--add-import, 'from __future__ import annotations',
]
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
rev: v3.21.2
hooks:
- id: pyupgrade
args: [--py38-plus]
- repo: https://github.com/psf/black
rev: 23.12.1
args: [--py310-plus]
- repo: https://github.com/hhatto/autopep8
rev: v2.3.2
hooks:
- id: black
args: [--line-length=79]
- id: autopep8
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
rev: 7.3.0
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
rev: v1.19.1
hooks:
- id: mypy
exclude: ^(docs/|example-plugin/)

View file

@ -8,3 +8,5 @@ python:
install:
- path: .
- requirements: docs/source/requirements.txt
sphinx:
configuration: docs/source/conf.py

View file

@ -3,9 +3,9 @@ from __future__ import annotations
import inspect
import os.path
from collections.abc import Callable
from collections.abc import Generator
from typing import Any
from typing import Callable
from typing import Generator
from typing import NamedTuple
import pycodestyle
@ -42,7 +42,7 @@ class Call(NamedTuple):
return cls(func.__name__, inspect.isgeneratorfunction(func), params)
def lines() -> Generator[str, None, None]:
def lines() -> Generator[str]:
logical = []
physical = []
@ -58,8 +58,8 @@ def lines() -> Generator[str, None, None]:
yield "# fmt: off"
yield "from __future__ import annotations"
yield ""
yield "from collections.abc import Generator"
yield "from typing import Any"
yield "from typing import Generator"
yield ""
imports = sorted(call.name for call in logical + physical)
for name in imports:
@ -71,7 +71,7 @@ def lines() -> Generator[str, None, None]:
logical_params = {param for call in logical for param in call.params}
for param in sorted(logical_params):
yield f" {param}: Any,"
yield ") -> Generator[tuple[int, str], None, None]:"
yield ") -> Generator[tuple[int, str]]:"
yield ' """Run pycodestyle logical checks."""'
for call in sorted(logical):
yield call.to_src()
@ -82,7 +82,7 @@ def lines() -> Generator[str, None, None]:
physical_params = {param for call in physical for param in call.params}
for param in sorted(physical_params):
yield f" {param}: Any,"
yield ") -> Generator[tuple[int, str], None, None]:"
yield ") -> Generator[tuple[int, str]]:"
yield ' """Run pycodestyle physical checks."""'
for call in sorted(physical):
yield call.to_src()

View file

@ -81,9 +81,9 @@ for users.
Before releasing, the following tox test environments must pass:
- Python 3.8 (a.k.a., ``tox -e py38``)
- Python 3.9 (a.k.a., ``tox -e py39``)
- Python 3.12 (a.k.a., ``tox -e py312``)
- Python 3.13 (a.k.a., ``tox -e py313``)
- PyPy 3 (a.k.a., ``tox -e pypy3``)

View file

@ -0,0 +1,15 @@
7.1.2 -- 2025-02-16
-------------------
You can view the `7.1.2 milestone`_ on GitHub for more details.
Bugs Fixed
~~~~~~~~~~
- Avoid starting unnecessary processes when "# files" < "jobs".
(See also :pull:`1966`).
.. all links
.. _7.1.2 milestone:
https://github.com/PyCQA/flake8/milestone/52

View file

@ -0,0 +1,19 @@
7.2.0 -- 2025-03-29
-------------------
You can view the `7.2.0 milestone`_ on GitHub for more details.
New Dependency Information
~~~~~~~~~~~~~~~~~~~~~~~~~~
- pycodestyle has been updated to >= 2.13.0, < 2.14.0 (See also :pull:`1974`).
- pyflakes has been updated to >= 3.3.0, < 3.4.0 (See also :pull:`1974`).
Features
~~~~~~~~
- Require python >= 3.9 (See also :pull:`1973`).
.. all links
.. _7.2.0 milestone:
https://github.com/PyCQA/flake8/milestone/53

View file

@ -0,0 +1,15 @@
7.3.0 -- 2025-06-20
-------------------
You can view the `7.3.0 milestone`_ on GitHub for more details.
New Dependency Information
~~~~~~~~~~~~~~~~~~~~~~~~~~
- Added support for python 3.14 (See also :pull:`1983`).
- pycodestyle has been updated to >= 2.14.0, < 2.15.0 (See also :pull:`1985`).
- Pyflakes has been updated to >= 3.4.0, < 3.5.0 (See also :pull:`1985`).
.. all links
.. _7.3.0 milestone:
https://github.com/PyCQA/flake8/milestone/54

View file

@ -9,16 +9,19 @@ with the newest releases first.
==================
.. toctree::
7.0.0
7.1.0
7.3.0
7.2.0
7.1.2
7.1.1
7.1.0
7.0.0
6.x Release Series
==================
.. toctree::
6.0.0
6.1.0
6.0.0
5.x Release Series
==================

View file

@ -59,6 +59,8 @@ generates its own :term:`error code`\ s for ``pyflakes``:
+------+---------------------------------------------------------------------+
| F541 | f-string without any placeholders |
+------+---------------------------------------------------------------------+
| F542 | t-string without any placeholders |
+------+---------------------------------------------------------------------+
+------+---------------------------------------------------------------------+
| F601 | dictionary key ``name`` repeated with different values |
+------+---------------------------------------------------------------------+
@ -102,6 +104,9 @@ generates its own :term:`error code`\ s for ``pyflakes``:
+------+---------------------------------------------------------------------+
| F823 | local variable ``name`` ... referenced before assignment |
+------+---------------------------------------------------------------------+
| F824 | ``global name`` / ``nonlocal name`` is unused: name is never |
| | assigned in scope |
+------+---------------------------------------------------------------------+
| F831 | duplicate argument ``name`` in function definition |
+------+---------------------------------------------------------------------+
| F841 | local variable ``name`` is assigned to but never used |

View file

@ -14,25 +14,25 @@ like so:
Where you simply allow the shell running in your terminal to locate |Flake8|.
In some cases, though, you may have installed |Flake8| for multiple versions
of Python (e.g., Python 3.8 and Python 3.9) and you need to call a specific
of Python (e.g., Python 3.13 and Python 3.14) and you need to call a specific
version. In that case, you will have much better results using:
.. prompt:: bash
python3.8 -m flake8
python3.13 -m flake8
Or
.. prompt:: bash
python3.9 -m flake8
python3.14 -m flake8
Since that will tell the correct version of Python to run |Flake8|.
.. note::
Installing |Flake8| once will not install it on both Python 3.8 and
Python 3.9. It will only install it for the version of Python that
Installing |Flake8| once will not install it on both Python 3.13 and
Python 3.14. It will only install it for the version of Python that
is running pip.
It is also possible to specify command-line options directly to |Flake8|:

View file

@ -23,8 +23,6 @@ setuptools.setup(
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
],

View file

@ -16,7 +16,6 @@ classifiers =
Environment :: Console
Framework :: Flake8
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
@ -29,9 +28,9 @@ classifiers =
packages = find:
install_requires =
mccabe>=0.7.0,<0.8.0
pycodestyle>=2.12.0,<2.13.0
pyflakes>=3.2.0,<3.3.0
python_requires = >=3.8.1
pycodestyle>=2.14.0,<2.15.0
pyflakes>=3.4.0,<3.5.0
python_requires = >=3.10
package_dir =
=src

View file

@ -17,7 +17,7 @@ import sys
LOG = logging.getLogger(__name__)
LOG.addHandler(logging.NullHandler())
__version__ = "7.1.1"
__version__ = "7.3.0"
__version_info__ = tuple(int(i) for i in __version__.split(".") if i.isdigit())
_VERBOSITY_TO_LOG_LEVEL = {
@ -66,5 +66,5 @@ def configure_logging(
LOG.addHandler(handler)
LOG.setLevel(log_level)
LOG.debug(
"Added a %s logging handler to logger root at %s", filename, __name__
"Added a %s logging handler to logger root at %s", filename, __name__,
)

View file

@ -9,3 +9,10 @@ if sys.version_info >= (3, 12): # pragma: >=3.12 cover
FSTRING_END = tokenize.FSTRING_END
else: # pragma: <3.12 cover
FSTRING_START = FSTRING_MIDDLE = FSTRING_END = -1
if sys.version_info >= (3, 14): # pragma: >=3.14 cover
TSTRING_START = tokenize.TSTRING_START
TSTRING_MIDDLE = tokenize.TSTRING_MIDDLE
TSTRING_END = tokenize.TSTRING_END
else: # pragma: <3.14 cover
TSTRING_START = TSTRING_MIDDLE = TSTRING_END = -1

View file

@ -135,7 +135,7 @@ class StyleGuide:
stdin_display_name=self.options.stdin_display_name,
filename_patterns=self.options.filename,
exclude=self.options.exclude,
)
),
)
return not paths
@ -153,7 +153,7 @@ class StyleGuide:
if not issubclass(reporter, formatter.BaseFormatter):
raise ValueError(
"Report should be subclass of "
"flake8.formatter.BaseFormatter."
"flake8.formatter.BaseFormatter.",
)
self._application.formatter = reporter(self.options)
self._application.guide = None

View file

@ -9,25 +9,24 @@ import multiprocessing.pool
import operator
import signal
import tokenize
from collections.abc import Generator
from collections.abc import Sequence
from typing import Any
from typing import Generator
from typing import List
from typing import Optional
from typing import Sequence
from typing import Tuple
from flake8 import defaults
from flake8 import exceptions
from flake8 import processor
from flake8 import utils
from flake8._compat import FSTRING_START
from flake8._compat import TSTRING_START
from flake8.discover_files import expand_paths
from flake8.options.parse_args import parse_args
from flake8.plugins.finder import Checkers
from flake8.plugins.finder import LoadedPlugin
from flake8.style_guide import StyleGuideManager
Results = List[Tuple[str, int, int, str, Optional[str]]]
Results = list[tuple[str, int, int, str, Optional[str]]]
LOG = logging.getLogger(__name__)
@ -46,40 +45,39 @@ SERIAL_RETRY_ERRNOS = {
# noise in diffs.
}
_mp_plugins: Checkers
_mp_options: argparse.Namespace
_mp: tuple[Checkers, argparse.Namespace] | None = None
@contextlib.contextmanager
def _mp_prefork(
plugins: Checkers, options: argparse.Namespace
) -> Generator[None, None, None]:
plugins: Checkers, options: argparse.Namespace,
) -> Generator[None]:
# we can save significant startup work w/ `fork` multiprocessing
global _mp_plugins, _mp_options
_mp_plugins, _mp_options = plugins, options
global _mp
_mp = plugins, options
try:
yield
finally:
del _mp_plugins, _mp_options
_mp = None
def _mp_init(argv: Sequence[str]) -> None:
global _mp_plugins, _mp_options
global _mp
# Ensure correct signaling of ^C using multiprocessing.Pool.
signal.signal(signal.SIGINT, signal.SIG_IGN)
try:
# for `fork` this'll already be set
_mp_plugins, _mp_options # noqa: B018
except NameError:
# for `fork` this'll already be set
if _mp is None:
plugins, options = parse_args(argv)
_mp_plugins, _mp_options = plugins.checkers, options
_mp = plugins.checkers, options
def _mp_run(filename: str) -> tuple[str, Results, dict[str, int]]:
assert _mp is not None, _mp
plugins, options = _mp
return FileChecker(
filename=filename, plugins=_mp_plugins, options=_mp_options
filename=filename, plugins=plugins, options=options,
).run_checks()
@ -139,7 +137,7 @@ class Manager:
if utils.is_using_stdin(self.options.filenames):
LOG.warning(
"The --jobs option is not compatible with supplying "
"input using - . Ignoring --jobs arguments."
"input using - . Ignoring --jobs arguments.",
)
return 0
@ -254,8 +252,9 @@ class Manager:
stdin_display_name=self.options.stdin_display_name,
filename_patterns=self.options.filename,
exclude=self.exclude,
)
),
)
self.jobs = min(len(self.filenames), self.jobs)
def stop(self) -> None:
"""Stop checking files."""
@ -333,11 +332,11 @@ class FileChecker:
assert self.processor is not None, self.filename
try:
params = self.processor.keyword_arguments_for(
plugin.parameters, arguments
plugin.parameters, arguments,
)
except AttributeError as ae:
raise exceptions.PluginRequestedUnknownParameters(
plugin_name=plugin.display_name, exception=ae
plugin_name=plugin.display_name, exception=ae,
)
try:
return plugin.obj(**arguments, **params)
@ -373,43 +372,6 @@ class FileChecker:
token = ()
row, column = (1, 0)
if (
column > 0
and token
and isinstance(exception, SyntaxError)
and len(token) == 4 # Python 3.9 or earlier
):
# NOTE(sigmavirus24): SyntaxErrors report 1-indexed column
# numbers. We need to decrement the column number by 1 at
# least.
column_offset = 1
row_offset = 0
# See also: https://github.com/pycqa/flake8/issues/169,
# https://github.com/PyCQA/flake8/issues/1372
# On Python 3.9 and earlier, token will be a 4-item tuple with the
# last item being the string. Starting with 3.10, they added to
# the tuple so now instead of it ending with the code that failed
# to parse, it ends with the end of the section of code that
# failed to parse. Luckily the absolute position in the tuple is
# stable across versions so we can use that here
physical_line = token[3]
# NOTE(sigmavirus24): Not all "tokens" have a string as the last
# argument. In this event, let's skip trying to find the correct
# column and row values.
if physical_line is not None:
# NOTE(sigmavirus24): SyntaxErrors also don't exactly have a
# "physical" line so much as what was accumulated by the point
# tokenizing failed.
# See also: https://github.com/pycqa/flake8/issues/169
lines = physical_line.rstrip("\n").split("\n")
row_offset = len(lines) - 1
logical_line = lines[0]
logical_line_length = len(logical_line)
if column > logical_line_length:
column = logical_line_length
row -= row_offset
column -= column_offset
return row, column
def run_ast_checks(self) -> None:
@ -549,12 +511,14 @@ class FileChecker:
self.run_logical_checks()
def check_physical_eol(
self, token: tokenize.TokenInfo, prev_physical: str
self, token: tokenize.TokenInfo, prev_physical: str,
) -> None:
"""Run physical checks if and only if it is at the end of the line."""
assert self.processor is not None
if token.type == FSTRING_START: # pragma: >=3.12 cover
self.processor.fstring_start(token.start[0])
elif token.type == TSTRING_START: # pragma: >=3.14 cover
self.processor.tstring_start(token.start[0])
# a newline token ends a single physical line.
elif processor.is_eol_token(token):
# if the file does not end with a newline, the NEWLINE
@ -597,7 +561,7 @@ def _try_initialize_processpool(
def find_offset(
offset: int, mapping: processor._LogicalMapping
offset: int, mapping: processor._LogicalMapping,
) -> tuple[int, int]:
"""Find the offset tuple for a single offset."""
if isinstance(offset, tuple):

View file

@ -3,9 +3,9 @@ from __future__ import annotations
import logging
import os.path
from typing import Callable
from typing import Generator
from typing import Sequence
from collections.abc import Callable
from collections.abc import Generator
from collections.abc import Sequence
from flake8 import utils
@ -16,7 +16,7 @@ def _filenames_from(
arg: str,
*,
predicate: Callable[[str], bool],
) -> Generator[str, None, None]:
) -> Generator[str]:
"""Generate filenames from an argument.
:param arg:
@ -55,7 +55,7 @@ def expand_paths(
stdin_display_name: str,
filename_patterns: Sequence[str],
exclude: Sequence[str],
) -> Generator[str, None, None]:
) -> Generator[str]:
"""Expand out ``paths`` from commandline to the lintable files."""
if not paths:
paths = ["."]

View file

@ -110,7 +110,7 @@ class BaseFormatter:
The formatted error string.
"""
raise NotImplementedError(
"Subclass of BaseFormatter did not implement" " format."
"Subclass of BaseFormatter did not implement" " format.",
)
def show_statistics(self, statistics: Statistics) -> None:

View file

@ -5,7 +5,7 @@ import argparse
import json
import logging
import time
from typing import Sequence
from collections.abc import Sequence
import flake8
from flake8 import checker
@ -76,7 +76,7 @@ class Application:
assert self.formatter is not None
assert self.options is not None
self.guide = style_guide.StyleGuideManager(
self.options, self.formatter
self.options, self.formatter,
)
def make_file_checker_manager(self, argv: Sequence[str]) -> None:

View file

@ -2,7 +2,7 @@
from __future__ import annotations
import sys
from typing import Sequence
from collections.abc import Sequence
from flake8.main import application

View file

@ -14,7 +14,7 @@ def information(version: str, plugins: Plugins) -> dict[str, Any]:
(loaded.plugin.package, loaded.plugin.version)
for loaded in plugins.all_plugins()
if loaded.plugin.package not in {"flake8", "local"}
}
},
)
return {
"version": version,

View file

@ -32,7 +32,7 @@ def stage1_arg_parser() -> argparse.ArgumentParser:
)
parser.add_argument(
"--output-file", default=None, help="Redirect report to a file."
"--output-file", default=None, help="Redirect report to a file.",
)
# Config file options

View file

@ -8,7 +8,7 @@ from __future__ import annotations
import argparse
import configparser
import logging
from typing import Sequence
from collections.abc import Sequence
from flake8.options import config
from flake8.options.manager import OptionManager

View file

@ -78,7 +78,7 @@ def load_config(
if config is not None:
if not cfg.read(config, encoding="UTF-8"):
raise exceptions.ExecutionError(
f"The specified config file does not exist: {config}"
f"The specified config file does not exist: {config}",
)
cfg_dir = os.path.dirname(config)
else:
@ -89,7 +89,7 @@ def load_config(
for filename in extra:
if not cfg.read(filename, encoding="UTF-8"):
raise exceptions.ExecutionError(
f"The specified config file does not exist: {filename}"
f"The specified config file does not exist: {filename}",
)
return cfg, cfg_dir
@ -131,7 +131,7 @@ def parse_config(
raise ValueError(
f"Error code {error_code!r} "
f"supplied to {option_name!r} option "
f"does not match {VALID_CODE_PREFIX.pattern!r}"
f"does not match {VALID_CODE_PREFIX.pattern!r}",
)
assert option.config_name is not None

View file

@ -5,9 +5,9 @@ import argparse
import enum
import functools
import logging
from collections.abc import Callable
from collections.abc import Sequence
from typing import Any
from typing import Callable
from typing import Sequence
from flake8 import utils
from flake8.plugins.finder import Plugins
@ -165,7 +165,7 @@ class Option:
if long_option_name is _ARG.NO:
raise ValueError(
"When specifying parse_from_config=True, "
"a long_option_name must also be specified."
"a long_option_name must also be specified.",
)
self.config_name = long_option_name[2:].replace("-", "_")

View file

@ -2,7 +2,7 @@
from __future__ import annotations
import argparse
from typing import Sequence
from collections.abc import Sequence
import flake8
from flake8.main import options

View file

@ -7,9 +7,9 @@ import inspect
import itertools
import logging
import sys
from collections.abc import Generator
from collections.abc import Iterable
from typing import Any
from typing import Generator
from typing import Iterable
from typing import NamedTuple
from flake8 import utils
@ -68,7 +68,7 @@ class Plugins(NamedTuple):
reporters: dict[str, LoadedPlugin]
disabled: list[LoadedPlugin]
def all_plugins(self) -> Generator[LoadedPlugin, None, None]:
def all_plugins(self) -> Generator[LoadedPlugin]:
"""Return an iterator over all :class:`LoadedPlugin`s."""
yield from self.checkers.tree
yield from self.checkers.logical_line
@ -83,8 +83,8 @@ class Plugins(NamedTuple):
f"{loaded.plugin.package}: {loaded.plugin.version}"
for loaded in self.all_plugins()
if loaded.plugin.package not in {"flake8", "local"}
}
)
},
),
)
@ -151,7 +151,7 @@ def _flake8_plugins(
eps: Iterable[importlib.metadata.EntryPoint],
name: str,
version: str,
) -> Generator[Plugin, None, None]:
) -> Generator[Plugin]:
pyflakes_meta = importlib.metadata.distribution("pyflakes").metadata
pycodestyle_meta = importlib.metadata.distribution("pycodestyle").metadata
@ -167,13 +167,13 @@ def _flake8_plugins(
# ideally pycodestyle's plugin entrypoints would exactly represent
# the codes they produce...
yield Plugin(
pycodestyle_meta["name"], pycodestyle_meta["version"], ep
pycodestyle_meta["name"], pycodestyle_meta["version"], ep,
)
else:
yield Plugin(name, version, ep)
def _find_importlib_plugins() -> Generator[Plugin, None, None]:
def _find_importlib_plugins() -> Generator[Plugin]:
# some misconfigured pythons (RHEL) have things on `sys.path` twice
seen = set()
for dist in importlib.metadata.distributions():
@ -212,7 +212,7 @@ def _find_importlib_plugins() -> Generator[Plugin, None, None]:
def _find_local_plugins(
cfg: configparser.RawConfigParser,
) -> Generator[Plugin, None, None]:
) -> Generator[Plugin]:
for plugin_type in ("extension", "report"):
group = f"flake8.{plugin_type}"
for plugin_s in utils.parse_comma_separated_list(
@ -240,7 +240,7 @@ def _check_required_plugins(
f"required plugins were not installed!\n"
f"- installed: {', '.join(sorted(plugin_names))}\n"
f"- expected: {', '.join(sorted(expected_names))}\n"
f"- missing: {', '.join(sorted(missing_plugins))}"
f"- missing: {', '.join(sorted(missing_plugins))}",
)
@ -338,7 +338,7 @@ def _classify_plugins(
if not VALID_CODE_PREFIX.match(loaded.entry_name):
raise ExecutionError(
f"plugin code for `{loaded.display_name}` does not match "
f"{VALID_CODE_PREFIX.pattern}"
f"{VALID_CODE_PREFIX.pattern}",
)
return Plugins(

View file

@ -2,8 +2,8 @@
# fmt: off
from __future__ import annotations
from collections.abc import Generator
from typing import Any
from typing import Generator
from pycodestyle import ambiguous_identifier as _ambiguous_identifier
from pycodestyle import bare_except as _bare_except
@ -55,7 +55,7 @@ def pycodestyle_logical(
previous_unindented_logical_line: Any,
tokens: Any,
verbose: Any,
) -> Generator[tuple[int, str], None, None]:
) -> Generator[tuple[int, str]]:
"""Run pycodestyle logical checks."""
yield from _ambiguous_identifier(logical_line, tokens)
yield from _bare_except(logical_line, noqa)
@ -93,7 +93,7 @@ def pycodestyle_physical(
noqa: Any,
physical_line: Any,
total_lines: Any,
) -> Generator[tuple[int, str], None, None]:
) -> Generator[tuple[int, str]]:
"""Run pycodestyle physical checks."""
ret = _maximum_line_length(physical_line, max_line_length, multiline, line_number, noqa) # noqa: E501
if ret is not None:

View file

@ -4,8 +4,8 @@ from __future__ import annotations
import argparse
import ast
import logging
from collections.abc import Generator
from typing import Any
from typing import Generator
import pyflakes.checker
@ -36,6 +36,7 @@ FLAKE8_PYFLAKES_CODES = {
"StringDotFormatMissingArgument": "F524",
"StringDotFormatMixingAutomatic": "F525",
"FStringMissingPlaceholders": "F541",
"TStringMissingPlaceholders": "F542",
"MultiValueRepeatedKeyLiteral": "F601",
"MultiValueRepeatedKeyVariable": "F602",
"TooManyExpressionsInStarredAssignment": "F621",
@ -55,6 +56,7 @@ FLAKE8_PYFLAKES_CODES = {
"UndefinedName": "F821",
"UndefinedExport": "F822",
"UndefinedLocal": "F823",
"UnusedIndirectAssignment": "F824",
"DuplicateArgument": "F831",
"UnusedVariable": "F841",
"UnusedAnnotation": "F842",
@ -70,7 +72,7 @@ class FlakesChecker(pyflakes.checker.Checker):
def __init__(self, tree: ast.AST, filename: str) -> None:
"""Initialize the PyFlakes plugin with an AST tree and filename."""
super().__init__(
tree, filename=filename, withDoctest=self.with_doctest
tree, filename=filename, withDoctest=self.with_doctest,
)
@classmethod
@ -97,7 +99,7 @@ class FlakesChecker(pyflakes.checker.Checker):
cls.builtIns = cls.builtIns.union(options.builtins)
cls.with_doctest = options.doctests
def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]:
def run(self) -> Generator[tuple[int, int, str, type[Any]]]:
"""Run the plugin."""
for message in self.messages:
col = getattr(message, "col", 0)

View file

@ -6,26 +6,26 @@ import ast
import functools
import logging
import tokenize
from collections.abc import Generator
from typing import Any
from typing import Generator
from typing import List
from typing import Tuple
from flake8 import defaults
from flake8 import utils
from flake8._compat import FSTRING_END
from flake8._compat import FSTRING_MIDDLE
from flake8._compat import TSTRING_END
from flake8._compat import TSTRING_MIDDLE
from flake8.plugins.finder import LoadedPlugin
LOG = logging.getLogger(__name__)
NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE])
SKIP_TOKENS = frozenset(
[tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT]
[tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT],
)
_LogicalMapping = List[Tuple[int, Tuple[int, int]]]
_Logical = Tuple[List[str], List[str], _LogicalMapping]
_LogicalMapping = list[tuple[int, tuple[int, int]]]
_Logical = tuple[list[str], list[str], _LogicalMapping]
class FileProcessor:
@ -115,7 +115,7 @@ class FileProcessor:
self.verbose = options.verbose
#: Statistics dictionary
self.statistics = {"logical lines": 0}
self._fstring_start = -1
self._fstring_start = self._tstring_start = -1
@functools.cached_property
def file_tokens(self) -> list[tokenize.TokenInfo]:
@ -127,12 +127,16 @@ class FileProcessor:
"""Signal the beginning of an fstring."""
self._fstring_start = lineno
def multiline_string(
self, token: tokenize.TokenInfo
) -> Generator[str, None, None]:
def tstring_start(self, lineno: int) -> None: # pragma: >=3.14 cover
"""Signal the beginning of an tstring."""
self._tstring_start = lineno
def multiline_string(self, token: tokenize.TokenInfo) -> Generator[str]:
"""Iterate through the lines of a multiline string."""
if token.type == FSTRING_END: # pragma: >=3.12 cover
start = self._fstring_start
elif token.type == TSTRING_END: # pragma: >=3.14 cover
start = self._tstring_start
else:
start = token.start[0]
@ -169,7 +173,7 @@ class FileProcessor:
"""Update the checker_state attribute for the plugin."""
if "checker_state" in plugin.parameters:
self.checker_state = self._checker_states.setdefault(
plugin.entry_name, {}
plugin.entry_name, {},
)
def next_logical_line(self) -> None:
@ -202,7 +206,10 @@ class FileProcessor:
continue
if token_type == tokenize.STRING:
text = mutate_string(text)
elif token_type == FSTRING_MIDDLE: # pragma: >=3.12 cover
elif token_type in {
FSTRING_MIDDLE,
TSTRING_MIDDLE,
}: # pragma: >=3.12 cover # noqa: E501
# A curly brace in an FSTRING_MIDDLE token must be an escaped
# curly brace. Both 'text' and 'end' will account for the
# escaped version of the token (i.e. a single brace) rather
@ -210,7 +217,7 @@ class FileProcessor:
brace_offset = text.count("{") + text.count("}")
text = "x" * (len(text) + brace_offset)
end = (end[0], end[1] + brace_offset)
if previous_row:
if previous_row is not None and previous_column is not None:
(start_row, start_column) = start
if previous_row != start_row:
row_index = previous_row - 1
@ -263,7 +270,7 @@ class FileProcessor:
)
return ret
def generate_tokens(self) -> Generator[tokenize.TokenInfo, None, None]:
def generate_tokens(self) -> Generator[tokenize.TokenInfo]:
"""Tokenize the file and yield the tokens."""
for token in tokenize.generate_tokens(self.next_line):
if token[2][0] > self.total_lines:
@ -273,7 +280,7 @@ class FileProcessor:
def _noqa_line_range(self, min_line: int, max_line: int) -> dict[int, str]:
line_range = range(min_line, max_line + 1)
joined = "".join(self.lines[min_line - 1 : max_line])
joined = "".join(self.lines[min_line - 1: max_line])
return dict.fromkeys(line_range, joined)
@functools.cached_property
@ -360,7 +367,7 @@ class FileProcessor:
elif any(defaults.NOQA_FILE.search(line) for line in self.lines):
LOG.warning(
"Detected `flake8: noqa` on line with code. To ignore an "
"error on a line use `noqa` instead."
"error on a line use `noqa` instead.",
)
return False
else:
@ -381,12 +388,12 @@ class FileProcessor:
def is_eol_token(token: tokenize.TokenInfo) -> bool:
"""Check if the token is an end-of-line token."""
return token[0] in NEWLINE or token[4][token[3][1] :].lstrip() == "\\\n"
return token[0] in NEWLINE or token[4][token[3][1]:].lstrip() == "\\\n"
def is_multiline_string(token: tokenize.TokenInfo) -> bool:
"""Check if this is a multiline string."""
return token.type == FSTRING_END or (
return token.type in {FSTRING_END, TSTRING_END} or (
token.type == tokenize.STRING and "\n" in token.string
)

View file

@ -1,7 +1,7 @@
"""Statistic collection logic for Flake8."""
from __future__ import annotations
from typing import Generator
from collections.abc import Generator
from typing import NamedTuple
from flake8.violation import Violation
@ -35,8 +35,8 @@ class Statistics:
self._store[key].increment()
def statistics_for(
self, prefix: str, filename: str | None = None
) -> Generator[Statistic, None, None]:
self, prefix: str, filename: str | None = None,
) -> Generator[Statistic]:
"""Generate statistics for the prefix and filename.
If you have a :class:`Statistics` object that has recorded errors,
@ -108,7 +108,7 @@ class Statistic:
"""
def __init__(
self, error_code: str, filename: str, message: str, count: int
self, error_code: str, filename: str, message: str, count: int,
) -> None:
"""Initialize our Statistic."""
self.error_code = error_code

View file

@ -7,8 +7,8 @@ import copy
import enum
import functools
import logging
from typing import Generator
from typing import Sequence
from collections.abc import Generator
from collections.abc import Sequence
from flake8 import defaults
from flake8 import statistics
@ -218,20 +218,18 @@ class StyleGuideManager:
self.decider = decider or DecisionEngine(options)
self.style_guides: list[StyleGuide] = []
self.default_style_guide = StyleGuide(
options, formatter, self.stats, decider=decider
options, formatter, self.stats, decider=decider,
)
self.style_guides = [
self.default_style_guide,
*self.populate_style_guides_with(options),
]
self.style_guide_for = functools.lru_cache(maxsize=None)(
self._style_guide_for
)
self.style_guide_for = functools.cache(self._style_guide_for)
def populate_style_guides_with(
self, options: argparse.Namespace
) -> Generator[StyleGuide, None, None]:
self, options: argparse.Namespace,
) -> Generator[StyleGuide]:
"""Generate style guides from the per-file-ignores option.
:param options:
@ -242,7 +240,7 @@ class StyleGuideManager:
per_file = utils.parse_files_to_codes_mapping(options.per_file_ignores)
for filename, violations in per_file:
yield self.default_style_guide.copy(
filename=filename, extend_ignore_with=violations
filename=filename, extend_ignore_with=violations,
)
def _style_guide_for(self, filename: str) -> StyleGuide:
@ -253,9 +251,7 @@ class StyleGuideManager:
)
@contextlib.contextmanager
def processing_file(
self, filename: str
) -> Generator[StyleGuide, None, None]:
def processing_file(self, filename: str) -> Generator[StyleGuide]:
"""Record the fact that we're processing the file's results."""
guide = self.style_guide_for(filename)
with guide.processing_file(filename):
@ -292,7 +288,7 @@ class StyleGuideManager:
"""
guide = self.style_guide_for(filename)
return guide.handle_error(
code, filename, line_number, column_number, text, physical_line
code, filename, line_number, column_number, text, physical_line,
)
@ -334,13 +330,11 @@ class StyleGuide:
options.extend_ignore = options.extend_ignore or []
options.extend_ignore.extend(extend_ignore_with or [])
return StyleGuide(
options, self.formatter, self.stats, filename=filename
options, self.formatter, self.stats, filename=filename,
)
@contextlib.contextmanager
def processing_file(
self, filename: str
) -> Generator[StyleGuide, None, None]:
def processing_file(self, filename: str) -> Generator[StyleGuide]:
"""Record the fact that we're processing the file's results."""
self.formatter.beginning(filename)
yield self

View file

@ -11,9 +11,9 @@ import re
import sys
import textwrap
import tokenize
from collections.abc import Sequence
from re import Pattern
from typing import NamedTuple
from typing import Pattern
from typing import Sequence
from flake8 import exceptions
@ -23,7 +23,7 @@ NORMALIZE_PACKAGE_NAME_RE = re.compile(r"[-_.]+")
def parse_comma_separated_list(
value: str, regexp: Pattern[str] = COMMA_SEPARATED_LIST_RE
value: str, regexp: Pattern[str] = COMMA_SEPARATED_LIST_RE,
) -> list[str]:
"""Parse a comma-separated list.
@ -115,7 +115,7 @@ def parse_files_to_codes_mapping( # noqa: C901
f"Expected `per-file-ignores` to be a mapping from file exclude "
f"patterns to ignore codes.\n\n"
f"Configured `per-file-ignores` setting:\n\n"
f"{textwrap.indent(value.strip(), ' ')}"
f"{textwrap.indent(value.strip(), ' ')}",
)
for token in _tokenize_files_to_codes_mapping(value):
@ -150,7 +150,7 @@ def parse_files_to_codes_mapping( # noqa: C901
def normalize_paths(
paths: Sequence[str], parent: str = os.curdir
paths: Sequence[str], parent: str = os.curdir,
) -> list[str]:
"""Normalize a list of paths relative to a parent directory.

View file

@ -4,7 +4,7 @@ from __future__ import annotations
import functools
import linecache
import logging
from typing import Match
from re import Match
from typing import NamedTuple
from flake8 import defaults
@ -64,6 +64,6 @@ class Violation(NamedTuple):
return True
LOG.debug(
"%r is not ignored inline with ``# noqa: %s``", self, codes_str
"%r is not ignored inline with ``# noqa: %s``", self, codes_str,
)
return False

View file

@ -2,7 +2,6 @@
from __future__ import annotations
import importlib.metadata
import sys
from unittest import mock
import pytest
@ -97,7 +96,7 @@ def mock_file_checker_with_plugin(plugin_target):
# Prevent it from reading lines from stdin or somewhere else
with mock.patch(
"flake8.processor.FileProcessor.read_lines", return_value=["Line 1"]
"flake8.processor.FileProcessor.read_lines", return_value=["Line 1"],
):
file_checker = checker.FileChecker(
filename="-",
@ -322,17 +321,10 @@ def test_handling_syntaxerrors_across_pythons():
We need to handle that correctly to avoid crashing.
https://github.com/PyCQA/flake8/issues/1372
"""
if sys.version_info < (3, 10): # pragma: no cover (<3.10)
# Python 3.9 or older
err = SyntaxError(
"invalid syntax", ("<unknown>", 2, 5, "bad python:\n")
)
expected = (2, 4)
else: # pragma: no cover (3.10+)
err = SyntaxError(
"invalid syntax", ("<unknown>", 2, 1, "bad python:\n", 2, 11)
)
expected = (2, 1)
err = SyntaxError(
"invalid syntax", ("<unknown>", 2, 1, "bad python:\n", 2, 11),
)
expected = (2, 1)
file_checker = checker.FileChecker(
filename="-",
plugins=finder.Checkers([], [], []),

View file

@ -168,10 +168,8 @@ def test_tokenization_error_but_not_syntax_error(tmpdir, capsys):
tmpdir.join("t.py").write("b'foo' \\\n")
assert cli.main(["t.py"]) == 1
if hasattr(sys, "pypy_version_info"): # pragma: no cover (pypy)
expected = "t.py:2:1: E999 SyntaxError: end of file (EOF) in multi-line statement\n" # noqa: E501
elif sys.version_info < (3, 10): # pragma: no cover (cp38+)
expected = "t.py:1:8: E999 SyntaxError: unexpected EOF while parsing\n"
if sys.implementation.name == "pypy": # pragma: no cover (pypy)
expected = "t.py:1:9: E999 SyntaxError: unexpected end of file (EOF) in multi-line statement\n" # noqa: E501
else: # pragma: no cover (cp310+)
expected = "t.py:1:10: E999 SyntaxError: unexpected EOF while parsing\n" # noqa: E501
@ -186,10 +184,8 @@ def test_tokenization_error_is_a_syntax_error(tmpdir, capsys):
tmpdir.join("t.py").write("if True:\n pass\n pass\n")
assert cli.main(["t.py"]) == 1
if hasattr(sys, "pypy_version_info"): # pragma: no cover (pypy)
expected = "t.py:3:2: E999 IndentationError: unindent does not match any outer indentation level\n" # noqa: E501
elif sys.version_info < (3, 10): # pragma: no cover (<cp310)
expected = "t.py:3:5: E999 IndentationError: unindent does not match any outer indentation level\n" # noqa: E501
if sys.implementation.name == "pypy": # pragma: no cover (pypy)
expected = "t.py:3:3: E999 IndentationError: unindent does not match any outer indentation level\n" # noqa: E501
else: # pragma: no cover (cp310+)
expected = "t.py:3:7: E999 IndentationError: unindent does not match any outer indentation level\n" # noqa: E501
@ -314,7 +310,7 @@ def test_cli_config_option_respected(tmp_path):
"""\
[flake8]
ignore = F401
"""
""",
)
py_file = tmp_path / "t.py"
@ -330,7 +326,7 @@ def test_cli_isolated_overrides_config_option(tmp_path):
"""\
[flake8]
ignore = F401
"""
""",
)
py_file = tmp_path / "t.py"
@ -364,7 +360,7 @@ def test_output_file(tmpdir, capsys):
def test_early_keyboard_interrupt_does_not_crash(capsys):
with mock.patch.object(
config, "load_config", side_effect=KeyboardInterrupt
config, "load_config", side_effect=KeyboardInterrupt,
):
assert cli.main(["does-not-exist"]) == 1
out, err = capsys.readouterr()

View file

@ -86,7 +86,7 @@ def test_local_plugin_can_add_option(local_config):
stage1_args, rest = stage1_parser.parse_known_args(argv)
cfg, cfg_dir = config.load_config(
config=stage1_args.config, extra=[], isolated=False
config=stage1_args.config, extra=[], isolated=False,
)
opts = finder.parse_plugin_options(
@ -296,3 +296,36 @@ t.py:1:1: T001 "f'xxxxxxxxxxxxxxxxxxxxxxxx'"
"""
out, err = capsys.readouterr()
assert out == expected
@pytest.mark.xfail(sys.version_info < (3, 14), reason="3.14+")
def test_tstring_logical_line(tmpdir, capsys): # pragma: >=3.14 cover
cfg_s = f"""\
[flake8]
extend-ignore = F
[flake8:local-plugins]
extension =
T = {yields_logical_line.__module__}:{yields_logical_line.__name__}
"""
cfg = tmpdir.join("tox.ini")
cfg.write(cfg_s)
src = """\
t'''
hello {world}
'''
t'{{"{hello}": "{world}"}}'
"""
t_py = tmpdir.join("t.py")
t_py.write_binary(src.encode())
with tmpdir.as_cwd():
assert main(("t.py", "--config", str(cfg))) == 1
expected = """\
t.py:1:1: T001 "t'''xxxxxxx{world}x'''"
t.py:4:1: T001 "t'xxx{hello}xxxx{world}xxx'"
"""
out, err = capsys.readouterr()
assert out == expected

View file

@ -42,7 +42,7 @@ def test_plugins_all_plugins():
logical_line_plugin = _loaded(parameters={"logical_line": True})
physical_line_plugin = _loaded(parameters={"physical_line": True})
report_plugin = _loaded(
plugin=_plugin(ep=_ep(name="R", group="flake8.report"))
plugin=_plugin(ep=_ep(name="R", group="flake8.report")),
)
plugins = finder.Plugins(
@ -200,14 +200,16 @@ def test_flake8_plugins(flake8_dist, mock_distribution):
"flake8",
"9001",
importlib.metadata.EntryPoint(
"default", "flake8.formatting.default:Default", "flake8.report"
"default",
"flake8.formatting.default:Default",
"flake8.report",
),
),
finder.Plugin(
"flake8",
"9001",
importlib.metadata.EntryPoint(
"pylint", "flake8.formatting.default:Pylint", "flake8.report"
"pylint", "flake8.formatting.default:Pylint", "flake8.report",
),
),
}
@ -270,7 +272,7 @@ unrelated = unrelated:main
"flake8-foo",
"1.2.3",
importlib.metadata.EntryPoint(
"Q", "flake8_foo:Plugin", "flake8.extension"
"Q", "flake8_foo:Plugin", "flake8.extension",
),
),
finder.Plugin(
@ -304,21 +306,23 @@ unrelated = unrelated:main
"flake8",
"9001",
importlib.metadata.EntryPoint(
"default", "flake8.formatting.default:Default", "flake8.report"
"default",
"flake8.formatting.default:Default",
"flake8.report",
),
),
finder.Plugin(
"flake8",
"9001",
importlib.metadata.EntryPoint(
"pylint", "flake8.formatting.default:Pylint", "flake8.report"
"pylint", "flake8.formatting.default:Pylint", "flake8.report",
),
),
finder.Plugin(
"flake8-foo",
"1.2.3",
importlib.metadata.EntryPoint(
"foo", "flake8_foo:Formatter", "flake8.report"
"foo", "flake8_foo:Formatter", "flake8.report",
),
),
}
@ -485,28 +489,30 @@ def test_find_plugins(
"flake8",
"9001",
importlib.metadata.EntryPoint(
"default", "flake8.formatting.default:Default", "flake8.report"
"default",
"flake8.formatting.default:Default",
"flake8.report",
),
),
finder.Plugin(
"flake8",
"9001",
importlib.metadata.EntryPoint(
"pylint", "flake8.formatting.default:Pylint", "flake8.report"
"pylint", "flake8.formatting.default:Pylint", "flake8.report",
),
),
finder.Plugin(
"flake8-foo",
"1.2.3",
importlib.metadata.EntryPoint(
"Q", "flake8_foo:Plugin", "flake8.extension"
"Q", "flake8_foo:Plugin", "flake8.extension",
),
),
finder.Plugin(
"flake8-foo",
"1.2.3",
importlib.metadata.EntryPoint(
"foo", "flake8_foo:Formatter", "flake8.report"
"foo", "flake8_foo:Formatter", "flake8.report",
),
),
finder.Plugin(
@ -518,7 +524,7 @@ def test_find_plugins(
"local",
"local",
importlib.metadata.EntryPoint(
"Y", "mod2:attr", "flake8.extension"
"Y", "mod2:attr", "flake8.extension",
),
),
finder.Plugin(
@ -723,7 +729,7 @@ def test_import_plugins_extends_sys_path():
def test_classify_plugins():
report_plugin = _loaded(
plugin=_plugin(ep=_ep(name="R", group="flake8.report"))
plugin=_plugin(ep=_ep(name="R", group="flake8.report")),
)
tree_plugin = _loaded(parameters={"tree": True})
logical_line_plugin = _loaded(parameters={"logical_line": True})

View file

@ -25,7 +25,7 @@ def reporters():
"flake8",
"123",
importlib.metadata.EntryPoint(
name, f"{cls.__module__}:{cls.__name__}", "flake8.report"
name, f"{cls.__module__}:{cls.__name__}", "flake8.report",
),
),
cls,
@ -72,5 +72,5 @@ def test_make_formatter_format_string(reporters, caplog):
"flake8.plugins.reporter",
30,
"'hi %(code)s' is an unknown formatter. Falling back to default.",
)
),
]

View file

@ -36,7 +36,7 @@ def application():
],
)
def test_application_exit_code(
result_count, catastrophic, exit_zero, value, application
result_count, catastrophic, exit_zero, value, application,
):
"""Verify Application.exit_code returns the correct value."""
application.result_count = result_count

View file

@ -50,7 +50,7 @@ def test_format_needs_to_be_implemented():
formatter = base.BaseFormatter(options())
with pytest.raises(NotImplementedError):
formatter.format(
Violation("A000", "file.py", 1, 1, "error text", None)
Violation("A000", "file.py", 1, 1, "error text", None),
)
@ -59,7 +59,7 @@ def test_show_source_returns_nothing_when_not_showing_source():
formatter = base.BaseFormatter(options(show_source=False))
assert (
formatter.show_source(
Violation("A000", "file.py", 1, 1, "error text", "line")
Violation("A000", "file.py", 1, 1, "error text", "line"),
)
== ""
)
@ -70,7 +70,7 @@ def test_show_source_returns_nothing_when_there_is_source():
formatter = base.BaseFormatter(options(show_source=True))
assert (
formatter.show_source(
Violation("A000", "file.py", 1, 1, "error text", None)
Violation("A000", "file.py", 1, 1, "error text", None),
)
== ""
)

View file

@ -41,9 +41,11 @@ def test_oserrors_are_reraised():
err = OSError(errno.EAGAIN, "Ominous message")
with mock.patch("_multiprocessing.SemLock", side_effect=err):
manager = _parallel_checker_manager()
with mock.patch.object(manager, "run_serial") as serial:
with pytest.raises(OSError):
manager.run()
with (
mock.patch.object(manager, "run_serial") as serial,
pytest.raises(OSError),
):
manager.run()
assert serial.call_count == 0
@ -61,14 +63,20 @@ def test_multiprocessing_cpu_count_not_implemented():
assert manager.jobs == 0
def test_jobs_count_limited_to_file_count():
style_guide = style_guide_mock()
style_guide.options.jobs = JobsArgument("4")
style_guide.options.filenames = ["file1", "file2"]
manager = checker.Manager(style_guide, finder.Checkers([], [], []), [])
assert manager.jobs == 4
manager.start()
assert manager.jobs == 2
def test_make_checkers():
"""Verify that we create a list of FileChecker instances."""
style_guide = style_guide_mock()
style_guide.options.filenames = ["file1", "file2"]
manager = checker.Manager(style_guide, finder.Checkers([], [], []), [])
with mock.patch("flake8.utils.fnmatch", return_value=True):
with mock.patch("flake8.processor.FileProcessor"):
manager.start()
manager.start()
assert manager.filenames == ("file1", "file2")

View file

@ -14,7 +14,7 @@ def test_debug_information():
pkg,
version,
importlib.metadata.EntryPoint(
ep_name, "dne:dne", "flake8.extension"
ep_name, "dne:dne", "flake8.extension",
),
),
None,

View file

@ -35,7 +35,7 @@ def create_options(**kwargs):
def test_was_ignored_ignores_errors(ignore_list, extend_ignore, error_code):
"""Verify we detect users explicitly ignoring an error."""
decider = style_guide.DecisionEngine(
create_options(ignore=ignore_list, extend_ignore=extend_ignore)
create_options(ignore=ignore_list, extend_ignore=extend_ignore),
)
assert decider.was_ignored(error_code) is style_guide.Ignored.Explicitly
@ -53,11 +53,11 @@ def test_was_ignored_ignores_errors(ignore_list, extend_ignore, error_code):
],
)
def test_was_ignored_implicitly_selects_errors(
ignore_list, extend_ignore, error_code
ignore_list, extend_ignore, error_code,
):
"""Verify we detect users does not explicitly ignore an error."""
decider = style_guide.DecisionEngine(
create_options(ignore=ignore_list, extend_ignore=extend_ignore)
create_options(ignore=ignore_list, extend_ignore=extend_ignore),
)
assert decider.was_ignored(error_code) is style_guide.Selected.Implicitly
@ -179,7 +179,7 @@ def test_was_selected_excludes_errors(select_list, error_code):
],
)
def test_decision_for(
select_list, ignore_list, extend_ignore, error_code, expected
select_list, ignore_list, extend_ignore, error_code, expected,
):
"""Verify we decide when to report an error."""
decider = style_guide.DecisionEngine(
@ -187,7 +187,7 @@ def test_decision_for(
select=select_list,
ignore=ignore_list,
extend_ignore=extend_ignore,
)
),
)
assert decider.decision_for(error_code) is expected

View file

@ -47,7 +47,7 @@ def test_filenames_from_a_directory_with_a_predicate():
_filenames_from(
arg=_normpath("a/b/"),
predicate=lambda path: path.endswith(_normpath("b/c.py")),
)
),
)
# should not include c.py
expected = _normpaths(("a/b/d.py", "a/b/e/f.py"))
@ -61,7 +61,7 @@ def test_filenames_from_a_directory_with_a_predicate_from_the_current_dir():
_filenames_from(
arg=_normpath("./a/b"),
predicate=lambda path: path == "c.py",
)
),
)
# none should have matched the predicate so all returned
expected = _normpaths(("./a/b/c.py", "./a/b/d.py", "./a/b/e/f.py"))
@ -132,7 +132,7 @@ def _expand_paths(
stdin_display_name=stdin_display_name,
filename_patterns=filename_patterns,
exclude=exclude,
)
),
)

View file

@ -28,7 +28,7 @@ def _lines_from_file(tmpdir, contents, options):
def test_read_lines_universal_newlines(tmpdir, default_options):
r"""Verify that line endings are translated to \n."""
lines = _lines_from_file(
tmpdir, b"# coding: utf-8\r\nx = 1\r\n", default_options
tmpdir, b"# coding: utf-8\r\nx = 1\r\n", default_options,
)
assert lines == ["# coding: utf-8\n", "x = 1\n"]
@ -36,7 +36,7 @@ def test_read_lines_universal_newlines(tmpdir, default_options):
def test_read_lines_incorrect_utf_16(tmpdir, default_options):
"""Verify that an incorrectly encoded file is read as latin-1."""
lines = _lines_from_file(
tmpdir, b"# coding: utf16\nx = 1\n", default_options
tmpdir, b"# coding: utf16\nx = 1\n", default_options,
)
assert lines == ["# coding: utf16\n", "x = 1\n"]
@ -44,7 +44,7 @@ def test_read_lines_incorrect_utf_16(tmpdir, default_options):
def test_read_lines_unknown_encoding(tmpdir, default_options):
"""Verify that an unknown encoding is still read as latin-1."""
lines = _lines_from_file(
tmpdir, b"# coding: fake-encoding\nx = 1\n", default_options
tmpdir, b"# coding: fake-encoding\nx = 1\n", default_options,
)
assert lines == ["# coding: fake-encoding\n", "x = 1\n"]
@ -289,7 +289,7 @@ def test_processor_split_line(default_options):
def test_build_ast(default_options):
"""Verify the logic for how we build an AST for plugins."""
file_processor = processor.FileProcessor(
"-", default_options, lines=["a = 1\n"]
"-", default_options, lines=["a = 1\n"],
)
module = file_processor.build_ast()
@ -299,7 +299,7 @@ def test_build_ast(default_options):
def test_next_logical_line_updates_the_previous_logical_line(default_options):
"""Verify that we update our tracking of the previous logical line."""
file_processor = processor.FileProcessor(
"-", default_options, lines=["a = 1\n"]
"-", default_options, lines=["a = 1\n"],
)
file_processor.indent_level = 1
@ -315,7 +315,7 @@ def test_next_logical_line_updates_the_previous_logical_line(default_options):
def test_visited_new_blank_line(default_options):
"""Verify we update the number of blank lines seen."""
file_processor = processor.FileProcessor(
"-", default_options, lines=["a = 1\n"]
"-", default_options, lines=["a = 1\n"],
)
assert file_processor.blank_lines == 0

View file

@ -6,7 +6,7 @@ from flake8.main import options
def test_stage1_arg_parser():
stage1_parser = options.stage1_arg_parser()
opts, args = stage1_parser.parse_known_args(
["--foo", "--verbose", "src", "setup.py", "--statistics", "--version"]
["--foo", "--verbose", "src", "setup.py", "--statistics", "--version"],
)
assert opts.verbose

View file

@ -122,7 +122,7 @@ def test_parse_args_handles_comma_separated_defaults(optmanager):
assert optmanager.config_options_dict == {}
optmanager.add_option(
"--exclude", default="E123,W234", comma_separated_list=True
"--exclude", default="E123,W234", comma_separated_list=True,
)
options = optmanager.parse_args([])
@ -135,7 +135,7 @@ def test_parse_args_handles_comma_separated_lists(optmanager):
assert optmanager.config_options_dict == {}
optmanager.add_option(
"--exclude", default="E123,W234", comma_separated_list=True
"--exclude", default="E123,W234", comma_separated_list=True,
)
options = optmanager.parse_args(["--exclude", "E201,W111,F280"])
@ -148,11 +148,11 @@ def test_parse_args_normalize_paths(optmanager):
assert optmanager.config_options_dict == {}
optmanager.add_option(
"--extra-config", normalize_paths=True, comma_separated_list=True
"--extra-config", normalize_paths=True, comma_separated_list=True,
)
options = optmanager.parse_args(
["--extra-config", "../config.ini,tox.ini,flake8/some-other.cfg"]
["--extra-config", "../config.ini,tox.ini,flake8/some-other.cfg"],
)
assert options.extra_config == [
os.path.abspath("../config.ini"),

View file

@ -169,7 +169,7 @@ def test_load_extra_config_utf8(tmpdir):
@pytest.fixture
def opt_manager():
ret = OptionManager(
version="123", plugin_versions="", parents=[], formatter_names=[]
version="123", plugin_versions="", parents=[], formatter_names=[],
)
register_default_options(ret)
return ret
@ -213,7 +213,7 @@ def test_parse_config_ignores_unknowns(tmp_path, opt_manager, caplog):
"flake8.options.config",
10,
'Option "wat" is not registered. Ignoring.',
)
),
]

View file

@ -36,7 +36,7 @@ def test_handle_error_does_not_raise_type_errors():
)
assert 1 == guide.handle_error(
"T111", "file.py", 1, 1, "error found", "a = 1"
"T111", "file.py", 1, 1, "error found", "a = 1",
)
@ -110,7 +110,7 @@ def test_style_guide_manager_pre_file_ignores_parsing():
],
)
def test_style_guide_manager_pre_file_ignores(
ignores, violation, filename, handle_error_return
ignores, violation, filename, handle_error_return,
):
"""Verify how the StyleGuideManager creates a default style guide."""
formatter = mock.create_autospec(base.BaseFormatter, instance=True)