Compare commits

...

171 commits
6.0.0 ... 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
Anthony Sottile
cf1542cefa Release 7.1.1 2024-08-04 16:31:56 -04:00
Anthony Sottile
939ea3d8d9
Merge pull request #1949 from stephenfin/issue-1948
Handle escaped braces in f-strings
2024-08-04 15:56:53 -04:00
Stephen Finucane
bdcd5c2c0a Handle escaped braces in f-strings
To use a curly brace in an f-string, you must escape it. For example:

  >>> k = 1
  >>> f'{{{k}'
  '{1'

Saving this as a script and running the 'tokenize' module highlights
something odd around the counting of tokens:

  ❯ python -m tokenize wow.py
  0,0-0,0:            ENCODING       'utf-8'
  1,0-1,1:            NAME           'k'
  1,2-1,3:            OP             '='
  1,4-1,5:            NUMBER         '1'
  1,5-1,6:            NEWLINE        '\n'
  2,0-2,2:            FSTRING_START  "f'"
  2,2-2,3:            FSTRING_MIDDLE '{'     # <-- here...
  2,4-2,5:            OP             '{'     # <-- and here
  2,5-2,6:            NAME           'k'
  2,6-2,7:            OP             '}'
  2,7-2,8:            FSTRING_END    "'"
  2,8-2,9:            NEWLINE        '\n'
  3,0-3,0:            ENDMARKER      ''

The FSTRING_MIDDLE character we have is the escaped/post-parse single
curly brace rather than the raw double curly brace, however, while our
end index of this token accounts for the parsed form, the start index of
the next token does not (put another way, it jumps from 3 -> 4). This
triggers some existing, unrelated code that we need to bypass. Do just
that.

Signed-off-by: Stephen Finucane <stephen@that.guru>
Closes: #1948
2024-08-04 15:54:22 -04:00
Ian Stapleton Cordasco
2a811cc4d2
Merge pull request #1946 from Viicos/patch-1
Fix wording of plugins documentation
2024-07-23 14:53:49 -05:00
Victorien
10314ad9e5
Fix wording of plugins documentation 2024-07-23 13:39:49 +02:00
Anthony Sottile
65a38c42a7 Release 7.1.0 2024-06-15 17:36:14 -04:00
Anthony Sottile
34c97e046a
Merge pull request #1939 from PyCQA/new-pycodestyle
latest pycodestyle
2024-06-15 17:32:56 -04:00
Anthony Sottile
defd315175 latest pycodestyle 2024-06-15 17:30:39 -04:00
Ian Stapleton Cordasco
408d4d695c
Merge pull request #1930 from mzagol/patch-1
Add --extend-exclude to the TOC
2024-04-15 16:41:33 -05:00
mzagol
866ad729c6
Add --extend-exclude to the TOC 2024-04-15 15:52:25 -05:00
Ian Stapleton Cordasco
33e508307a
Merge pull request #1923 from Viicos/entry-points-docs
Update documentation regarding entry points
2024-03-19 06:19:50 -05:00
Ian Stapleton Cordasco
6659b213c9
Fix toctree ordering in index
Also fix line-length issue in PR
2024-03-18 11:01:42 -05:00
Viicos
ba0f56610a
Use explicit external references 2024-03-18 11:01:35 -05:00
Viicos
350f2545fd Use explicit external references 2024-03-17 20:03:19 +01:00
Viicos
49f52a8598 Update documentation regarding entry points
Refer to the PyPA packaging guide
Replace references to the deprecated `pkg_resources` docs
2024-03-14 21:06:23 +01:00
Anthony Sottile
5c52d752e6
Merge pull request #1910 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2024-01-08 17:40:02 -05:00
pre-commit-ci[bot]
a2b68c84e7
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 6.1.0 → 7.0.0](https://github.com/PyCQA/flake8/compare/6.1.0...7.0.0)
2024-01-08 21:35:25 +00:00
Ian Stapleton Cordasco
fb9a02aaf7
Merge pull request #1907 from mgorny/sphinx-prompt-1.8.0
update plugins for sphinx-prompt-1.8.0
2024-01-05 08:01:23 -06:00
Michał Górny
26d3184ae2 update plugins for sphinx-prompt-1.8.0
The sphinx-prompt plugin has renamed its package in 1.8.0 from erraneous
`sphinx-prompt` name to `sphinx_prompt`.  Adjust the conf accordingly.
2024-01-05 12:15:40 +01:00
Anthony Sottile
88a4f9b2f4 Release 7.0.0 2024-01-04 19:41:07 -05:00
Anthony Sottile
6f3a60dd46
Merge pull request #1906 from PyCQA/upgrade-pyflakes
upgrade pyflakes to 3.2.x
2024-01-04 19:38:07 -05:00
Anthony Sottile
cde8570df3 upgrade pyflakes to 3.2.x 2024-01-04 19:36:48 -05:00
Ian Stapleton Cordasco
2ab9d76639
Merge pull request #1903 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-12-25 19:52:32 -06:00
pre-commit-ci[bot]
e27611f1ea
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 23.12.0 → 23.12.1](https://github.com/psf/black/compare/23.12.0...23.12.1)
- [github.com/pre-commit/mirrors-mypy: v1.7.1 → v1.8.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.1...v1.8.0)
2023-12-25 21:35:51 +00:00
Ian Stapleton Cordasco
9d20be1d1f
Merge pull request #1902 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-12-20 07:01:01 -06:00
pre-commit-ci[bot]
06c1503842 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2023-12-20 12:54:27 +00:00
Ian Stapleton Cordasco
b67ce03a4a
Fix bugbear lints 2023-12-20 06:54:05 -06:00
pre-commit-ci[bot]
c8801c129a
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 23.11.0 → 23.12.0](https://github.com/psf/black/compare/23.11.0...23.12.0)
2023-12-18 21:54:28 +00:00
Ian Stapleton Cordasco
045f297f89
Merge pull request #1893 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-11-27 16:46:49 -06:00
pre-commit-ci[bot]
7e1c87554d
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.7.0 → v1.7.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.0...v1.7.1)
2023-11-27 21:09:51 +00:00
Ian Stapleton Cordasco
26e09775a8
Merge pull request #1829 from abdulfataiaka/patch-1
Update invocation.rst file
2023-11-26 14:02:17 -06:00
Ian Stapleton Cordasco
018b0c7f2a
Merge pull request #1888 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-11-13 17:52:14 -06:00
pre-commit-ci[bot]
d1b1ec73be
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 23.10.1 → 23.11.0](https://github.com/psf/black/compare/23.10.1...23.11.0)
- [github.com/pre-commit/mirrors-mypy: v1.6.1 → v1.7.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.1...v1.7.0)
2023-11-13 21:55:32 +00:00
Anthony Sottile
34cbf8ef39
Merge pull request #1883 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-10-23 17:47:31 -04:00
pre-commit-ci[bot]
61d6ca224d
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 23.9.1 → 23.10.1](https://github.com/psf/black/compare/23.9.1...23.10.1)
- [github.com/pre-commit/mirrors-mypy: v1.6.0 → v1.6.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.0...v1.6.1)
2023-10-23 21:45:49 +00:00
Ian Stapleton Cordasco
0474b88cfe
Merge pull request #1880 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-10-16 20:02:22 -05:00
pre-commit-ci[bot]
8bdec0b54e
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.5.1 → v1.6.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.1...v1.6.0)
2023-10-16 21:14:00 +00:00
Anthony Sottile
b15cabc0f5
Merge pull request #1879 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-10-09 18:13:52 -04:00
pre-commit-ci[bot]
67c0ecc6df
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0)
- [github.com/asottile/pyupgrade: v3.14.0 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.14.0...v3.15.0)
2023-10-09 21:55:48 +00:00
Anthony Sottile
faef358748
Merge pull request #1854 from PyCQA/remove-doctest-options
remove --include-in-doctest and --exclude-in-doctest
2023-10-06 11:45:23 -04:00
Anthony Sottile
fbcde84822
Merge pull request #1875 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-10-03 12:55:47 -04:00
pre-commit-ci[bot]
0a67cbc61f
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/setup-cfg-fmt: v2.4.0 → v2.5.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.4.0...v2.5.0)
- [github.com/asottile/reorder-python-imports: v3.11.0 → v3.12.0](https://github.com/asottile/reorder-python-imports/compare/v3.11.0...v3.12.0)
- [github.com/asottile/pyupgrade: v3.13.0 → v3.14.0](https://github.com/asottile/pyupgrade/compare/v3.13.0...v3.14.0)
2023-10-03 14:57:35 +00:00
Anthony Sottile
9fcb6db8ad
Merge pull request #1874 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-09-26 09:01:34 -04:00
pre-commit-ci[bot]
fb5759b37f
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.11.0 → v3.13.0](https://github.com/asottile/pyupgrade/compare/v3.11.0...v3.13.0)
2023-09-26 12:18:31 +00:00
Anthony Sottile
10827ff687
Merge pull request #1873 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-09-19 09:04:50 -04:00
pre-commit-ci[bot]
7899a82c5d
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/reorder-python-imports: v3.10.0 → v3.11.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.11.0)
- [github.com/asottile/pyupgrade: v3.10.1 → v3.11.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.11.0)
2023-09-19 12:01:32 +00:00
Anthony Sottile
2f90d07389
Merge pull request #1870 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-09-12 09:27:47 -04:00
pre-commit-ci[bot]
a0f393ca71
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1)
2023-09-12 13:10:16 +00:00
Anthony Sottile
14c5edf336
Merge pull request #1864 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-08-22 09:07:59 -04:00
pre-commit-ci[bot]
aceddfeabb
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.5.0 → v1.5.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.0...v1.5.1)
2023-08-22 11:03:53 +00:00
Ian Stapleton Cordasco
a2b02b8920
Merge pull request #1861 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-08-15 07:08:38 -05:00
pre-commit-ci[bot]
5a5ebaf10a
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.4.1 → v1.5.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.4.1...v1.5.0)
2023-08-15 10:33:21 +00:00
Ian Stapleton Cordasco
564dc51f72
Merge pull request #1858 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-08-01 08:37:22 -05:00
pre-commit-ci[bot]
5cd0bcb45a
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.9.0 → v3.10.1](https://github.com/asottile/pyupgrade/compare/v3.9.0...v3.10.1)
- [github.com/PyCQA/flake8: 6.0.0 → 6.1.0](https://github.com/PyCQA/flake8/compare/6.0.0...6.1.0)
2023-08-01 12:27:47 +00:00
Anthony Sottile
d20a7e1e4a
Merge pull request #1857 from PyCQA/rtd
add minimal rtd configuration
2023-07-31 19:45:25 -04:00
Anthony Sottile
d734e31689 add minimal rtd configuration 2023-07-31 19:41:56 -04:00
Anthony Sottile
b3e2515122 remove --include-in-doctest and --exclude-in-doctest 2023-07-29 15:55:41 -04:00
Anthony Sottile
15f4569670
Merge pull request #1853 from PyCQA/improve-coverage
improve coverage
2023-07-29 15:50:32 -04:00
Anthony Sottile
4a47bab979 improve coverage 2023-07-29 15:46:40 -04:00
Anthony Sottile
7ef0350a43 Release 6.1.0 2023-07-29 15:04:17 -04:00
Anthony Sottile
354f873f8a
Merge pull request #1852 from PyCQA/setup-cfg-fmt
use setup-cfg-fmt
2023-07-29 14:49:15 -04:00
Anthony Sottile
24be8ad052
Merge pull request #1851 from PyCQA/pylint-warnings
fix pylintrc warnings
2023-07-29 14:39:14 -04:00
Anthony Sottile
5233d88069 use setup-cfg-fmt 2023-07-29 14:39:06 -04:00
Anthony Sottile
d4d1552c5b
Merge pull request #1849 from PyCQA/handle-multiline-fstrings-in-312
handle multiline fstrings in 3.12
2023-07-29 14:37:08 -04:00
Anthony Sottile
cc301ed499 fix pylintrc warnings 2023-07-29 14:36:44 -04:00
Anthony Sottile
1ed78d592a handle multiline fstrings in 3.12 2023-07-29 14:33:10 -04:00
Anthony Sottile
f2641954c4
Merge pull request #1850 from PyCQA/312-test
add 3.12 to ci matrix
2023-07-29 14:32:56 -04:00
Anthony Sottile
acca35b800 add 3.12 to ci matrix 2023-07-29 14:30:55 -04:00
Anthony Sottile
579f9f6f94
Merge pull request #1848 from PyCQA/upgrade-pycodestyle
upgrade to pycodestyle 2.11.x
2023-07-29 13:25:53 -04:00
Anthony Sottile
9786562feb upgrade to pycodestyle 2.11.x 2023-07-29 13:23:01 -04:00
Anthony Sottile
9744e5bdd8
Merge pull request #1847 from PyCQA/pyflakes-3-1
upgrade pyflakes to 3.1.x
2023-07-29 13:05:54 -04:00
Anthony Sottile
5bd63bc552 upgrade pyflakes to 3.1.x 2023-07-29 13:03:38 -04:00
Anthony Sottile
b3cee18653
Merge pull request #1842 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-07-11 06:27:38 -04:00
pre-commit-ci[bot]
73825ae81c
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.8.0 → v3.9.0](https://github.com/asottile/pyupgrade/compare/v3.8.0...v3.9.0)
- [github.com/psf/black: 23.3.0 → 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0)
2023-07-11 10:15:18 +00:00
Anthony Sottile
fb5f507848
Merge pull request #1841 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-07-04 08:05:22 -04:00
pre-commit-ci[bot]
fa42096dfe
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.7.0 → v3.8.0](https://github.com/asottile/pyupgrade/compare/v3.7.0...v3.8.0)
2023-07-04 09:39:09 +00:00
Ian Stapleton Cordasco
4076373a3d
Merge pull request #1838 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-06-27 08:04:42 -05:00
pre-commit-ci[bot]
24999ec87e
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.3.0 → v1.4.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.3.0...v1.4.1)
2023-06-27 07:48:29 +00:00
Ian Stapleton Cordasco
5cbd46251c
Merge pull request #1835 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-06-20 04:49:25 -05:00
pre-commit-ci[bot]
ad03ede045
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/reorder-python-imports: v3.9.0 → v3.10.0](https://github.com/asottile/reorder-python-imports/compare/v3.9.0...v3.10.0)
- [github.com/asottile/pyupgrade: v3.6.0 → v3.7.0](https://github.com/asottile/pyupgrade/compare/v3.6.0...v3.7.0)
2023-06-20 08:45:09 +00:00
Anthony Sottile
54f1b3e912
Merge pull request #1833 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-06-13 04:46:53 -04:00
pre-commit-ci[bot]
4d0c97d7af
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.4.0 → v3.6.0](https://github.com/asottile/pyupgrade/compare/v3.4.0...v3.6.0)
2023-06-13 08:00:37 +00:00
Anthony Sottile
1f8437433e
Merge pull request #1832 from PyCQA/fstring-middle
mute FSTRING_MIDDLE tokens
2023-06-12 22:07:34 -04:00
Anthony Sottile
43266a2e26 mute FSTRING_MIDDLE tokens 2023-06-12 22:02:26 -04:00
Abdulfatai Aka
9b6887762b
Update invocation.rst file 2023-05-16 14:44:14 +01:00
Anthony Sottile
c8d75d9966
Merge pull request #1828 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-05-16 06:36:39 -07:00
pre-commit-ci[bot]
4b7ede13fb
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.2.0 → v1.3.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.2.0...v1.3.0)
2023-05-16 07:06:03 +00:00
Anthony Sottile
a40cee5c2f
Merge pull request #1826 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-05-09 08:33:31 -04:00
pre-commit-ci[bot]
a466e461e5
[pre-commit.ci] pre-commit autoupdate
updates:
- https://github.com/asottile/reorder_python_importshttps://github.com/asottile/reorder-python-imports
- [github.com/asottile/pyupgrade: v3.3.2 → v3.4.0](https://github.com/asottile/pyupgrade/compare/v3.3.2...v3.4.0)
2023-05-09 08:00:29 +00:00
Anthony Sottile
6491971bed
Merge pull request #1824 from PyCQA/asottile-patch-1
remove regex link
2023-04-30 15:05:28 -04:00
Anthony Sottile
0c3cf066d3
remove regex link 2023-04-30 15:03:01 -04:00
Ian Stapleton Cordasco
d016204366
Merge pull request #1821 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-04-25 05:54:32 -05:00
pre-commit-ci[bot]
cabc45abab
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.3.1 → v3.3.2](https://github.com/asottile/pyupgrade/compare/v3.3.1...v3.3.2)
2023-04-25 07:12:41 +00:00
Ian Stapleton Cordasco
4b011224b2
Merge pull request #1820 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-04-11 07:20:24 -05:00
pre-commit-ci[bot]
426f952fc8
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.1.1 → v1.2.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.1.1...v1.2.0)
2023-04-11 06:25:16 +00:00
Ian Stapleton Cordasco
29e9e5df65
Merge pull request #1817 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-04-04 05:38:35 -05:00
pre-commit-ci[bot]
050d0dcf4d
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 23.1.0 → 23.3.0](https://github.com/psf/black/compare/23.1.0...23.3.0)
2023-04-04 09:38:50 +00:00
Ian Stapleton Cordasco
45699b61ae
Merge pull request #1810 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-03-14 07:29:09 -05:00
pre-commit-ci[bot]
f096f853c2
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.0.1 → v1.1.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.1...v1.1.1)
2023-03-14 06:44:36 +00:00
Anthony Sottile
cb43b60dcc
Merge pull request #1801 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-02-21 09:04:35 -05:00
pre-commit-ci[bot]
2615d21a5b
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.0.0 → v1.0.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.0...v1.0.1)
2023-02-21 05:18:56 +00:00
Anthony Sottile
7aed16dc09
Merge pull request #1797 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-02-14 00:57:00 -05:00
pre-commit-ci[bot]
77e075c66a
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v0.991 → v1.0.0](https://github.com/pre-commit/mirrors-mypy/compare/v0.991...v1.0.0)
2023-02-14 05:36:21 +00:00
Ian Stapleton Cordasco
0610cd2db8
Merge pull request #1796 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-02-07 01:51:33 -06:00
pre-commit-ci[bot]
b0d7912f7e [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2023-02-07 07:22:50 +00:00
pre-commit-ci[bot]
50755610d2
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.12.0 → 23.1.0](https://github.com/psf/black/compare/22.12.0...23.1.0)
2023-02-07 07:21:58 +00:00
Anthony Sottile
55160008e1
Merge pull request #1790 from bagerard/patch-1
Fix typo in python-api.rst
2023-01-21 15:40:21 -05:00
Bastien Gérard
e6f9db5039
Update python-api.rst
Fix minor typo in doc
2023-01-21 21:37:35 +01:00
Anthony Sottile
5e99de7209
Merge pull request #1785 from PyCQA/asottile-patch-1
add myself to maintenance
2023-01-11 09:22:12 -05:00
Anthony Sottile
0be0ae7842
add myself to maintenance 2023-01-11 09:19:54 -05:00
Anthony Sottile
33e1c55853
Merge pull request #1779 from PyCQA/asottile-patch-1
use pypy3.9 to work around coverage issue
2023-01-02 10:46:26 -05:00
Anthony Sottile
bc6e087235
use pypy3.9 to work around coverage issue 2023-01-02 07:41:47 -08:00
Anthony Sottile
ed3012d29f
Merge pull request #1778 from PyCQA/asottile-patch-1
correct tidelift link
2023-01-02 10:27:05 -05:00
Anthony Sottile
61138f1f4b
correct tidelift link 2023-01-02 10:25:13 -05:00
Anthony Sottile
87198e50f1
Merge pull request #1768 from PyCQA/deprecate-include-exclude-doctest
deprecate --include-in-doctest --exclude-from-doctest
2022-12-14 13:33:05 -05:00
Anthony Sottile
4778fe9643 deprecate --include-in-doctest --exclude-from-doctest 2022-12-14 13:25:35 -05:00
Anthony Sottile
80e6cdf55f
Merge pull request #1767 from PyCQA/update-select-docs
communicate that --select is usually unnecessary
2022-12-14 12:58:30 -05:00
Anthony Sottile
837e81948b communicate that --select is usually unnecessary 2022-12-14 12:55:37 -05:00
Anthony Sottile
780f64fdd2
Merge pull request #1766 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-12-12 23:57:33 -05:00
pre-commit-ci[bot]
00fbfd7774
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.3.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.3.0...v3.3.1)
- [github.com/psf/black: 22.10.0 → 22.12.0](https://github.com/psf/black/compare/22.10.0...22.12.0)
2022-12-13 03:27:50 +00:00
Anthony Sottile
333fd087f9
Merge pull request #1764 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-12-05 20:40:49 -08:00
pre-commit-ci[bot]
c0d0ced5b8
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.2.2 → v3.3.0](https://github.com/asottile/pyupgrade/compare/v3.2.2...v3.3.0)
2022-12-06 02:21:54 +00:00
Anthony Sottile
51f076398e
Merge pull request #1763 from PyCQA/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-11-28 20:15:45 -08:00
pre-commit-ci[bot]
5fde644640
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0)
- [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0)
2022-11-29 04:04:15 +00:00
Anthony Sottile
2111a0aa96
Merge pull request #1761 from PyCQA/remove-manifest
remove MANIFEST.in
2022-11-24 16:31:57 -05:00
Anthony Sottile
cf6d3fe0c6 remove MANIFEST.in
packagers: please package from the github tgz
2022-11-24 16:28:16 -05:00
68 changed files with 685 additions and 586 deletions

View file

@ -1,26 +0,0 @@
[run]
branch = True
source =
flake8
tests
omit =
# Don't complain if non-runnable code isn't run
*/__main__.py
[report]
show_missing = True
skip_covered = True
exclude_lines =
# Have to re-enable the standard pragma
\#\s*pragma: no cover
# Don't complain if tests don't hit defensive assertion code:
^\s*raise AssertionError\b
^\s*raise NotImplementedError\b
^\s*return NotImplemented\b
^\s*raise$
# Don't complain if non-runnable code isn't run:
^if __name__ == ['"]__main__['"]:$
^\s*if False:
^\s*if TYPE_CHECKING:

2
.github/FUNDING.yml vendored
View file

@ -1,2 +1,2 @@
github: asottile github: asottile
tidelift: pypi/pre-commit tidelift: pypi/flake8

View file

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

View file

@ -1,37 +1,44 @@
repos: 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 - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0 rev: v6.0.0
hooks: hooks:
- id: check-yaml - id: check-yaml
- id: debug-statements - id: debug-statements
- id: end-of-file-fixer - id: end-of-file-fixer
- id: trailing-whitespace - id: trailing-whitespace
exclude: ^tests/fixtures/ exclude: ^tests/fixtures/
- repo: https://github.com/asottile/reorder_python_imports - repo: https://github.com/asottile/setup-cfg-fmt
rev: v3.9.0 rev: v3.2.0
hooks:
- id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder-python-imports
rev: v3.16.0
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
args: [ args: [
--application-directories, '.:src', --application-directories, '.:src',
--py38-plus, --py310-plus,
--add-import, 'from __future__ import annotations', --add-import, 'from __future__ import annotations',
] ]
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.2.2 rev: v3.21.2
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py310-plus]
- repo: https://github.com/psf/black - repo: https://github.com/hhatto/autopep8
rev: 22.10.0 rev: v2.3.2
hooks: hooks:
- id: black - id: autopep8
args: [--line-length=79]
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 5.0.4 rev: 7.3.0
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991 rev: v1.19.1
hooks: hooks:
- id: mypy - id: mypy
exclude: ^(docs/|example-plugin/) exclude: ^(docs/|example-plugin/)

View file

@ -365,10 +365,3 @@ ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must # Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled) # not be disabled)
int-import-graph= int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception

12
.readthedocs.yaml Normal file
View file

@ -0,0 +1,12 @@
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
python:
install:
- path: .
- requirements: docs/source/requirements.txt
sphinx:
configuration: docs/source/conf.py

View file

@ -1,9 +0,0 @@
include *.rst
include CONTRIBUTORS.txt
include LICENSE
include *.ini
global-exclude *.pyc
recursive-include docs *.rst *.py
recursive-include tests *.py *.ini *.rst *_diff
recursive-include src *.py
prune docs/build/

View file

@ -85,5 +85,6 @@ Links
Maintenance Maintenance
=========== ===========
Flake8 was created by Tarek Ziadé and is currently maintained by `Ian Cordasco Flake8 was created by Tarek Ziadé and is currently maintained by `anthony sottile
<https://github.com/sponsors/asottile>`_ and `Ian Cordasco
<https://www.coglib.com/~icordasc/>`_ <https://www.coglib.com/~icordasc/>`_

View file

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

View file

@ -34,7 +34,7 @@ extensions = [
"sphinx.ext.todo", "sphinx.ext.todo",
"sphinx.ext.coverage", "sphinx.ext.coverage",
"sphinx.ext.viewcode", "sphinx.ext.viewcode",
"sphinx-prompt", "sphinx_prompt",
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@ -296,7 +296,11 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library. # Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
"packaging": ("https://packaging.python.org/en/latest/", None),
"setuptools": ("https://setuptools.pypa.io/en/latest/", None),
}
extlinks = { extlinks = {
"issue": ("https://github.com/pycqa/flake8/issues/%s", "#%s"), "issue": ("https://github.com/pycqa/flake8/issues/%s", "#%s"),

View file

@ -30,7 +30,7 @@ Historically, |Flake8| has generated major releases for:
- Large scale refactoring (2.0, 3.0, 5.0, 6.0) - Large scale refactoring (2.0, 3.0, 5.0, 6.0)
- Subtly breaking CLI changes (3.0, 4.0, 5.0, 6.0) - Subtly breaking CLI changes (3.0, 4.0, 5.0, 6.0, 7.0)
- Breaking changes to its plugin interface (3.0) - Breaking changes to its plugin interface (3.0)
@ -81,9 +81,9 @@ for users.
Before releasing, the following tox test environments must pass: Before releasing, the following tox test environments must pass:
- Python 3.6 (a.k.a., ``tox -e py36``) - Python 3.9 (a.k.a., ``tox -e py39``)
- Python 3.7 (a.k.a., ``tox -e py37``) - Python 3.13 (a.k.a., ``tox -e py313``)
- PyPy 3 (a.k.a., ``tox -e pypy3``) - PyPy 3 (a.k.a., ``tox -e pypy3``)

View file

@ -30,7 +30,8 @@ To get started writing a |Flake8| :term:`plugin` you first need:
Once you've gathered these things, you can get started. Once you've gathered these things, you can get started.
All plugins for |Flake8| must be registered via `entry points`_. In this All plugins for |Flake8| must be registered via
:external+packaging:doc:`entry points<specifications/entry-points>`. In this
section we cover: section we cover:
- How to register your plugin so |Flake8| can find it - How to register your plugin so |Flake8| can find it
@ -54,6 +55,8 @@ Here's a tutorial which goes over building an ast checking plugin from scratch:
<iframe src="https://www.youtube.com/embed/ot5Z4KQPBL8" frameborder="0" allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe> <iframe src="https://www.youtube.com/embed/ot5Z4KQPBL8" frameborder="0" allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe>
</div> </div>
Detailed Plugin Development Documentation
=========================================
.. toctree:: .. toctree::
:caption: Plugin Developer Documentation :caption: Plugin Developer Documentation
@ -62,7 +65,3 @@ Here's a tutorial which goes over building an ast checking plugin from scratch:
registering-plugins registering-plugins
plugin-parameters plugin-parameters
formatters formatters
.. _entry points:
https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points

View file

@ -12,16 +12,17 @@ To register any kind of plugin with |Flake8|, you need:
#. A name for your plugin that will (ideally) be unique. #. A name for your plugin that will (ideally) be unique.
#. A somewhat recent version of setuptools (newer than 0.7.0 but preferably as |Flake8| relies on functionality provided by build tools called
recent as you can attain). :external+packaging:doc:`entry points<specifications/entry-points>`. These
allow any package to register a plugin with |Flake8| via that package's
|Flake8| relies on functionality provided by setuptools called metadata.
`Entry Points`_. These allow any package to register a plugin with |Flake8|
via that package's ``setup.py`` file.
Let's presume that we already have our plugin written and it's in a module Let's presume that we already have our plugin written and it's in a module
called ``flake8_example``. We might have a ``setup.py`` that looks something called ``flake8_example``. We will also assume ``setuptools`` is used as a
like: :external+packaging:term:`Build Backend`, but be aware that most backends
support entry points.
We might have a ``setup.py`` that looks something like:
.. code-block:: python .. code-block:: python
@ -150,5 +151,7 @@ If your plugin is intended to be opt-in, it can set the attribute
:ref:`enable-extensions<option-enable-extensions>` with your plugin's entry :ref:`enable-extensions<option-enable-extensions>` with your plugin's entry
point. point.
.. _Entry Points: .. seealso::
https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
The :external+setuptools:doc:`setuptools user guide <userguide/entry_point>`
about entry points.

View file

@ -0,0 +1,22 @@
6.1.0 -- 2023-07-29
-------------------
You can view the `6.1.0 milestone`_ on GitHub for more details.
New Dependency Information
~~~~~~~~~~~~~~~~~~~~~~~~~~
- Pyflakes has been updated to >= 3.1.0, < 3.2.0 (See also :pull:`1847`).
- pycodestyle has been updated to >= 2.11.0, < 2.12.0 (See also :pull:`1848`).
Features
~~~~~~~~
- Deprecate ``--include-in-doctest``, ``--exclude-from-doctest`` (See also
:issue:`1747`, :pull:`1768`).
- Add support for python 3.12 (See also :pull:`1832`, :pull:`1849`,
:pull:`1850`).
.. all links
.. _6.1.0 milestone:
https://github.com/PyCQA/flake8/milestone/48

View file

@ -0,0 +1,19 @@
7.0.0 -- 2024-01-04
-------------------
You can view the `7.0.0 milestone`_ on GitHub for more details.
Backwards Incompatible Changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Remove ``--include-in-doctest`` and ``--exclude-from-doctest`` options.
(See also :issue:`1747`, :pull:`1854`)
New Dependency Information
~~~~~~~~~~~~~~~~~~~~~~~~~~
- Pyflakes has been updated to >= 3.2.0, < 3.3.0 (See also :pull:`1906`).
.. all links
.. _7.0.0 milestone:
https://github.com/PyCQA/flake8/milestone/49

View file

@ -0,0 +1,13 @@
7.1.0 -- 2024-06-15
-------------------
You can view the `7.1.0 milestone`_ on GitHub for more details.
New Dependency Information
~~~~~~~~~~~~~~~~~~~~~~~~~~
- pycodestyle has been updated to >= 2.12.0, < 2.13.0 (See also :pull:`1939`).
.. all links
.. _7.1.0 milestone:
https://github.com/PyCQA/flake8/milestone/50

View file

@ -0,0 +1,15 @@
7.1.1 -- 2024-08-04
-------------------
You can view the `7.1.1 milestone`_ on GitHub for more details.
Bugs Fixed
~~~~~~~~~~
- Properly preserve escaped `{` and `}` in fstrings in logical lines in 3.12+.
(See also :issue:`1948`, :pull:`1949`).
.. all links
.. _7.1.1 milestone:
https://github.com/PyCQA/flake8/milestone/51

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

@ -5,10 +5,22 @@
All of the release notes that have been recorded for Flake8 are organized here All of the release notes that have been recorded for Flake8 are organized here
with the newest releases first. with the newest releases first.
7.x Release Series
==================
.. toctree::
7.3.0
7.2.0
7.1.2
7.1.1
7.1.0
7.0.0
6.x Release Series 6.x Release Series
================== ==================
.. toctree:: .. toctree::
6.1.0
6.0.0 6.0.0
5.x Release Series 5.x Release Series

View file

@ -1,4 +1,4 @@
sphinx>=2.1.0,!=3.1.0 sphinx>=2.1.0,!=3.1.0
sphinx_rtd_theme sphinx-rtd-theme>=1.2.2
sphinx-prompt sphinx-prompt>=1.8.0
docutils!=0.18 docutils!=0.18

View file

@ -59,6 +59,8 @@ generates its own :term:`error code`\ s for ``pyflakes``:
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F541 | f-string without any placeholders | | F541 | f-string without any placeholders |
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F542 | t-string without any placeholders |
+------+---------------------------------------------------------------------+
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F601 | dictionary key ``name`` repeated with different values | | F601 | dictionary key ``name`` repeated with different values |
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
@ -81,8 +83,6 @@ generates its own :term:`error code`\ s for ``pyflakes``:
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F702 | a ``continue`` statement outside of a ``while`` or ``for`` loop | | F702 | a ``continue`` statement outside of a ``while`` or ``for`` loop |
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F703 | a ``continue`` statement in a ``finally`` block in a loop |
+------+---------------------------------------------------------------------+
| F704 | a ``yield`` or ``yield from`` statement outside of a function | | F704 | a ``yield`` or ``yield from`` statement outside of a function |
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F706 | a ``return`` statement outside of a function/method | | F706 | a ``return`` statement outside of a function/method |
@ -104,6 +104,9 @@ generates its own :term:`error code`\ s for ``pyflakes``:
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F823 | local variable ``name`` ... referenced before assignment | | 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 | | F831 | duplicate argument ``name`` in function definition |
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F841 | local variable ``name`` is assigned to but never used | | 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|. 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 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: version. In that case, you will have much better results using:
.. prompt:: bash .. prompt:: bash
python3.8 -m flake8 python3.13 -m flake8
Or Or
.. prompt:: bash .. prompt:: bash
python3.9 -m flake8 python3.14 -m flake8
Since that will tell the correct version of Python to run |Flake8|. Since that will tell the correct version of Python to run |Flake8|.
.. note:: .. note::
Installing |Flake8| once will not install it on both Python 3.8 and Installing |Flake8| once will not install it on both Python 3.13 and
Python 3.9. It will only install it for the version of Python that Python 3.14. It will only install it for the version of Python that
is running pip. is running pip.
It is also possible to specify command-line options directly to |Flake8|: It is also possible to specify command-line options directly to |Flake8|:
@ -51,7 +51,7 @@ Or
This is the last time we will show both versions of an invocation. This is the last time we will show both versions of an invocation.
From now on, we'll simply use ``flake8`` and assume that the user From now on, we'll simply use ``flake8`` and assume that the user
knows they can instead use ``python<version> -m flake8`` instead. knows they can instead use ``python<version> -m flake8``.
It's also possible to narrow what |Flake8| will try to check by specifying It's also possible to narrow what |Flake8| will try to check by specifying
exactly the paths and directories you want it to check. Let's assume that exactly the paths and directories you want it to check. Let's assume that

View file

@ -46,6 +46,8 @@ Index of Options
- :option:`flake8 --exclude` - :option:`flake8 --exclude`
- :option:`flake8 --extend-exclude`
- :option:`flake8 --filename` - :option:`flake8 --filename`
- :option:`flake8 --stdin-display-name` - :option:`flake8 --stdin-display-name`
@ -98,10 +100,6 @@ Index of Options
- :option:`flake8 --doctests` - :option:`flake8 --doctests`
- :option:`flake8 --include-in-doctest`
- :option:`flake8 --exclude-from-doctest`
- :option:`flake8 --benchmark` - :option:`flake8 --benchmark`
- :option:`flake8 --bug-report` - :option:`flake8 --bug-report`
@ -607,13 +605,14 @@ Options and their Descriptions
:ref:`Go back to index <top>` :ref:`Go back to index <top>`
**You usually do not need to specify this option as the default includes
all installed plugin codes.**
Specify the list of error codes you wish |Flake8| to report. Similarly to Specify the list of error codes you wish |Flake8| to report. Similarly to
:option:`--ignore`. You can specify a portion of an error code to get all :option:`--ignore`. You can specify a portion of an error code to get all
that start with that string. For example, you can use ``E``, ``E4``, that start with that string. For example, you can use ``E``, ``E4``,
``E43``, and ``E431``. ``E43``, and ``E431``.
This defaults to: ``E,F,W,C90``
Command-line example: Command-line example:
.. prompt:: bash .. prompt:: bash
@ -649,6 +648,9 @@ Options and their Descriptions
.. versionadded:: 4.0.0 .. versionadded:: 4.0.0
**You usually do not need to specify this option as the default includes
all installed plugin codes.**
Specify a list of codes to add to the list of selected ones. Similar Specify a list of codes to add to the list of selected ones. Similar
considerations as in :option:`--select` apply here with regard to the considerations as in :option:`--select` apply here with regard to the
value. value.
@ -993,62 +995,6 @@ Options and their Descriptions
doctests = True doctests = True
.. option:: --include-in-doctest=<paths>
:ref:`Go back to index <top>`
Specify which files are checked by PyFlakes for doctest syntax.
This is registered by the default PyFlakes plugin.
Command-line example:
.. prompt:: bash
flake8 --include-in-doctest=dir/subdir/file.py,dir/other/file.py dir/
This **can** be specified in config files.
Example config file usage:
.. code-block:: ini
include-in-doctest =
dir/subdir/file.py,
dir/other/file.py
include_in_doctest =
dir/subdir/file.py,
dir/other/file.py
.. option:: --exclude-from-doctest=<paths>
:ref:`Go back to index <top>`
Specify which files are not to be checked by PyFlakes for doctest syntax.
This is registered by the default PyFlakes plugin.
Command-line example:
.. prompt:: bash
flake8 --exclude-from-doctest=dir/subdir/file.py,dir/other/file.py dir/
This **can** be specified in config files.
Example config file usage:
.. code-block:: ini
exclude-from-doctest =
dir/subdir/file.py,
dir/other/file.py
exclude_from_doctest =
dir/subdir/file.py,
dir/other/file.py
.. option:: --benchmark .. option:: --benchmark
:ref:`Go back to index <top>` :ref:`Go back to index <top>`

View file

@ -70,7 +70,7 @@ namely
.. warning:: .. warning::
These are not *perfectly* backwards compatible. Not all arguments are These are not *perfectly* backwards compatible. Not all arguments are
respsected, and some of the types necessary for something to work have respected, and some of the types necessary for something to work have
changed. changed.
Most people, we observed, were using Most people, we observed, were using

View file

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

View file

@ -10,13 +10,12 @@ author_email = tarek@ziade.org
maintainer = Ian Stapleton Cordasco maintainer = Ian Stapleton Cordasco
maintainer_email = graffatcolmingov@gmail.com maintainer_email = graffatcolmingov@gmail.com
license = MIT license = MIT
license_file = LICENSE license_files = LICENSE
classifiers = classifiers =
Development Status :: 5 - Production/Stable Development Status :: 5 - Production/Stable
Environment :: Console Environment :: Console
Framework :: Flake8 Framework :: Flake8
Intended Audience :: Developers Intended Audience :: Developers
License :: OSI Approved :: MIT License
Programming Language :: Python Programming Language :: Python
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 :: Only
@ -27,18 +26,13 @@ classifiers =
[options] [options]
packages = find: packages = find:
package_dir =
=src
# We document the reasoning for using ranges here:
# https://flake8.pycqa.org/en/latest/faq.html#why-does-flake8-use-ranges-for-its-dependencies
# And in which releases we will update those ranges here:
# https://flake8.pycqa.org/en/latest/internal/releases.html#releasing-flake8
install_requires = install_requires =
mccabe>=0.7.0,<0.8.0 mccabe>=0.7.0,<0.8.0
pycodestyle>=2.10.0,<2.11.0 pycodestyle>=2.14.0,<2.15.0
pyflakes>=3.0.0,<3.1.0 pyflakes>=3.4.0,<3.5.0
# 3.8.0's importlib.metadata is broken python_requires = >=3.10
python_requires = >=3.8.1 package_dir =
=src
[options.packages.find] [options.packages.find]
where = src where = src
@ -59,6 +53,15 @@ flake8.report =
[bdist_wheel] [bdist_wheel]
universal = 1 universal = 1
[coverage:run]
source =
flake8
tests
plugins = covdefaults
[coverage:report]
fail_under = 97
[mypy] [mypy]
check_untyped_defs = true check_untyped_defs = true
disallow_any_generics = true disallow_any_generics = true

View file

@ -17,7 +17,7 @@ import sys
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
LOG.addHandler(logging.NullHandler()) LOG.addHandler(logging.NullHandler())
__version__ = "6.0.0" __version__ = "7.3.0"
__version_info__ = tuple(int(i) for i in __version__.split(".") if i.isdigit()) __version_info__ = tuple(int(i) for i in __version__.split(".") if i.isdigit())
_VERBOSITY_TO_LOG_LEVEL = { _VERBOSITY_TO_LOG_LEVEL = {
@ -66,5 +66,5 @@ def configure_logging(
LOG.addHandler(handler) LOG.addHandler(handler)
LOG.setLevel(log_level) LOG.setLevel(log_level)
LOG.debug( 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__,
) )

18
src/flake8/_compat.py Normal file
View file

@ -0,0 +1,18 @@
from __future__ import annotations
import sys
import tokenize
if sys.version_info >= (3, 12): # pragma: >=3.12 cover
FSTRING_START = tokenize.FSTRING_START
FSTRING_MIDDLE = tokenize.FSTRING_MIDDLE
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, stdin_display_name=self.options.stdin_display_name,
filename_patterns=self.options.filename, filename_patterns=self.options.filename,
exclude=self.options.exclude, exclude=self.options.exclude,
) ),
) )
return not paths return not paths
@ -153,7 +153,7 @@ class StyleGuide:
if not issubclass(reporter, formatter.BaseFormatter): if not issubclass(reporter, formatter.BaseFormatter):
raise ValueError( raise ValueError(
"Report should be subclass of " "Report should be subclass of "
"flake8.formatter.BaseFormatter." "flake8.formatter.BaseFormatter.",
) )
self._application.formatter = reporter(self.options) self._application.formatter = reporter(self.options)
self._application.guide = None self._application.guide = None

View file

@ -9,24 +9,24 @@ import multiprocessing.pool
import operator import operator
import signal import signal
import tokenize import tokenize
from collections.abc import Generator
from collections.abc import Sequence
from typing import Any from typing import Any
from typing import Generator
from typing import List
from typing import Optional from typing import Optional
from typing import Sequence
from typing import Tuple
from flake8 import defaults from flake8 import defaults
from flake8 import exceptions from flake8 import exceptions
from flake8 import processor from flake8 import processor
from flake8 import utils 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.discover_files import expand_paths
from flake8.options.parse_args import parse_args from flake8.options.parse_args import parse_args
from flake8.plugins.finder import Checkers from flake8.plugins.finder import Checkers
from flake8.plugins.finder import LoadedPlugin from flake8.plugins.finder import LoadedPlugin
from flake8.style_guide import StyleGuideManager 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__) LOG = logging.getLogger(__name__)
@ -45,39 +45,39 @@ SERIAL_RETRY_ERRNOS = {
# noise in diffs. # noise in diffs.
} }
_mp_plugins: Checkers _mp: tuple[Checkers, argparse.Namespace] | None = None
_mp_options: argparse.Namespace
@contextlib.contextmanager @contextlib.contextmanager
def _mp_prefork( def _mp_prefork(
plugins: Checkers, options: argparse.Namespace plugins: Checkers, options: argparse.Namespace,
) -> Generator[None, None, None]: ) -> Generator[None]:
# we can save significant startup work w/ `fork` multiprocessing # we can save significant startup work w/ `fork` multiprocessing
global _mp_plugins, _mp_options global _mp
_mp_plugins, _mp_options = plugins, options _mp = plugins, options
try: try:
yield yield
finally: finally:
del _mp_plugins, _mp_options _mp = None
def _mp_init(argv: Sequence[str]) -> None: def _mp_init(argv: Sequence[str]) -> None:
global _mp_plugins, _mp_options global _mp
# Ensure correct signaling of ^C using multiprocessing.Pool. # Ensure correct signaling of ^C using multiprocessing.Pool.
signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN)
try: # for `fork` this'll already be set
_mp_plugins, _mp_options # for `fork` this'll already be set if _mp is None:
except NameError:
plugins, options = parse_args(argv) 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]]: def _mp_run(filename: str) -> tuple[str, Results, dict[str, int]]:
assert _mp is not None, _mp
plugins, options = _mp
return FileChecker( return FileChecker(
filename=filename, plugins=_mp_plugins, options=_mp_options filename=filename, plugins=plugins, options=options,
).run_checks() ).run_checks()
@ -137,7 +137,7 @@ class Manager:
if utils.is_using_stdin(self.options.filenames): if utils.is_using_stdin(self.options.filenames):
LOG.warning( LOG.warning(
"The --jobs option is not compatible with supplying " "The --jobs option is not compatible with supplying "
"input using - . Ignoring --jobs arguments." "input using - . Ignoring --jobs arguments.",
) )
return 0 return 0
@ -160,7 +160,7 @@ class Manager:
def _handle_results(self, filename: str, results: Results) -> int: def _handle_results(self, filename: str, results: Results) -> int:
style_guide = self.style_guide style_guide = self.style_guide
reported_results_count = 0 reported_results_count = 0
for (error_code, line_number, column, text, physical_line) in results: for error_code, line_number, column, text, physical_line in results:
reported_results_count += style_guide.handle_error( reported_results_count += style_guide.handle_error(
code=error_code, code=error_code,
filename=filename, filename=filename,
@ -252,8 +252,9 @@ class Manager:
stdin_display_name=self.options.stdin_display_name, stdin_display_name=self.options.stdin_display_name,
filename_patterns=self.options.filename, filename_patterns=self.options.filename,
exclude=self.exclude, exclude=self.exclude,
),
) )
) self.jobs = min(len(self.filenames), self.jobs)
def stop(self) -> None: def stop(self) -> None:
"""Stop checking files.""" """Stop checking files."""
@ -331,11 +332,11 @@ class FileChecker:
assert self.processor is not None, self.filename assert self.processor is not None, self.filename
try: try:
params = self.processor.keyword_arguments_for( params = self.processor.keyword_arguments_for(
plugin.parameters, arguments plugin.parameters, arguments,
) )
except AttributeError as ae: except AttributeError as ae:
raise exceptions.PluginRequestedUnknownParameters( raise exceptions.PluginRequestedUnknownParameters(
plugin_name=plugin.display_name, exception=ae plugin_name=plugin.display_name, exception=ae,
) )
try: try:
return plugin.obj(**arguments, **params) return plugin.obj(**arguments, **params)
@ -371,43 +372,6 @@ class FileChecker:
token = () token = ()
row, column = (1, 0) 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 return row, column
def run_ast_checks(self) -> None: def run_ast_checks(self) -> None:
@ -423,7 +387,7 @@ class FileChecker:
runner = checker.run() runner = checker.run()
except AttributeError: except AttributeError:
runner = checker runner = checker
for (line_number, offset, text, _) in runner: for line_number, offset, text, _ in runner:
self.report( self.report(
error_code=None, error_code=None,
line_number=line_number, line_number=line_number,
@ -547,19 +511,23 @@ class FileChecker:
self.run_logical_checks() self.run_logical_checks()
def check_physical_eol( def check_physical_eol(
self, token: tokenize.TokenInfo, prev_physical: str self, token: tokenize.TokenInfo, prev_physical: str,
) -> None: ) -> None:
"""Run physical checks if and only if it is at the end of the line.""" """Run physical checks if and only if it is at the end of the line."""
assert self.processor is not None 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. # a newline token ends a single physical line.
if processor.is_eol_token(token): elif processor.is_eol_token(token):
# if the file does not end with a newline, the NEWLINE # if the file does not end with a newline, the NEWLINE
# token is inserted by the parser, but it does not contain # token is inserted by the parser, but it does not contain
# the previous physical line in `token[4]` # the previous physical line in `token[4]`
if token[4] == "": if token.line == "":
self.run_physical_checks(prev_physical) self.run_physical_checks(prev_physical)
else: else:
self.run_physical_checks(token[4]) self.run_physical_checks(token.line)
elif processor.is_multiline_string(token): elif processor.is_multiline_string(token):
# Less obviously, a string that contains newlines is a # Less obviously, a string that contains newlines is a
# multiline string, either triple-quoted or with internal # multiline string, either triple-quoted or with internal
@ -572,9 +540,7 @@ class FileChecker:
# - have to wind self.line_number back because initially it # - have to wind self.line_number back because initially it
# points to the last line of the string, and we want # points to the last line of the string, and we want
# check_physical() to give accurate feedback # check_physical() to give accurate feedback
line_no = token[2][0] for line in self.processor.multiline_string(token):
with self.processor.inside_multiline(line_number=line_no):
for line in self.processor.split_line(token):
self.run_physical_checks(line) self.run_physical_checks(line)
@ -595,7 +561,7 @@ def _try_initialize_processpool(
def find_offset( def find_offset(
offset: int, mapping: processor._LogicalMapping offset: int, mapping: processor._LogicalMapping,
) -> tuple[int, int]: ) -> tuple[int, int]:
"""Find the offset tuple for a single offset.""" """Find the offset tuple for a single offset."""
if isinstance(offset, tuple): if isinstance(offset, tuple):

View file

@ -16,7 +16,6 @@ EXCLUDE = (
"*.egg", "*.egg",
) )
IGNORE = ("E121", "E123", "E126", "E226", "E24", "E704", "W503", "W504") IGNORE = ("E121", "E123", "E126", "E226", "E24", "E704", "W503", "W504")
SELECT = ("E", "F", "W", "C90")
MAX_LINE_LENGTH = 79 MAX_LINE_LENGTH = 79
INDENT_SIZE = 4 INDENT_SIZE = 4
@ -37,7 +36,6 @@ NOQA_INLINE_REGEXP = re.compile(
# We do not want to capture the ``: `` that follows ``noqa`` # We do not want to capture the ``: `` that follows ``noqa``
# We do not care about the casing of ``noqa`` # We do not care about the casing of ``noqa``
# We want a comma-separated list of errors # We want a comma-separated list of errors
# https://regex101.com/r/4XUuax/2 full explanation of the regex
r"# noqa(?::[\s]?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?", r"# noqa(?::[\s]?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?",
re.IGNORECASE, re.IGNORECASE,
) )

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
from typing import Sequence from collections.abc import Sequence
from flake8.main import application 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) (loaded.plugin.package, loaded.plugin.version)
for loaded in plugins.all_plugins() for loaded in plugins.all_plugins()
if loaded.plugin.package not in {"flake8", "local"} if loaded.plugin.package not in {"flake8", "local"}
} },
) )
return { return {
"version": version, "version": version,

View file

@ -32,7 +32,7 @@ def stage1_arg_parser() -> argparse.ArgumentParser:
) )
parser.add_argument( 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 # Config file options
@ -297,9 +297,11 @@ def register_default_options(option_manager: OptionManager) -> None:
parse_from_config=True, parse_from_config=True,
comma_separated_list=True, comma_separated_list=True,
help=( help=(
f"Comma-separated list of error codes to enable. " "Limit the reported error codes to codes prefix-matched by this "
f"For example, ``--select=E4,E51,W234``. " "list. "
f"(Default: {','.join(defaults.SELECT)})" "You usually do not need to specify this option as the default "
"includes all installed plugin codes. "
"For example, ``--select=E4,E51,W234``."
), ),
) )
@ -309,8 +311,10 @@ def register_default_options(option_manager: OptionManager) -> None:
parse_from_config=True, parse_from_config=True,
comma_separated_list=True, comma_separated_list=True,
help=( help=(
"Comma-separated list of error codes to add to the list " "Add additional error codes to the default ``--select``. "
"of selected ones. For example, ``--extend-select=E4,E51,W234``." "You usually do not need to specify this option as the default "
"includes all installed plugin codes. "
"For example, ``--extend-select=E4,E51,W234``."
), ),
) )

View file

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

View file

@ -78,7 +78,7 @@ def load_config(
if config is not None: if config is not None:
if not cfg.read(config, encoding="UTF-8"): if not cfg.read(config, encoding="UTF-8"):
raise exceptions.ExecutionError( 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) cfg_dir = os.path.dirname(config)
else: else:
@ -89,7 +89,7 @@ def load_config(
for filename in extra: for filename in extra:
if not cfg.read(filename, encoding="UTF-8"): if not cfg.read(filename, encoding="UTF-8"):
raise exceptions.ExecutionError( 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 return cfg, cfg_dir
@ -131,7 +131,7 @@ def parse_config(
raise ValueError( raise ValueError(
f"Error code {error_code!r} " f"Error code {error_code!r} "
f"supplied to {option_name!r} option " 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 assert option.config_name is not None

View file

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

View file

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

View file

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

View file

@ -2,8 +2,8 @@
# fmt: off # fmt: off
from __future__ import annotations from __future__ import annotations
from collections.abc import Generator
from typing import Any from typing import Any
from typing import Generator
from pycodestyle import ambiguous_identifier as _ambiguous_identifier from pycodestyle import ambiguous_identifier as _ambiguous_identifier
from pycodestyle import bare_except as _bare_except from pycodestyle import bare_except as _bare_except
@ -23,9 +23,7 @@ from pycodestyle import maximum_doc_length as _maximum_doc_length
from pycodestyle import maximum_line_length as _maximum_line_length from pycodestyle import maximum_line_length as _maximum_line_length
from pycodestyle import missing_whitespace as _missing_whitespace from pycodestyle import missing_whitespace as _missing_whitespace
from pycodestyle import missing_whitespace_after_keyword as _missing_whitespace_after_keyword # noqa: E501 from pycodestyle import missing_whitespace_after_keyword as _missing_whitespace_after_keyword # noqa: E501
from pycodestyle import missing_whitespace_around_operator as _missing_whitespace_around_operator # noqa: E501
from pycodestyle import module_imports_on_top_of_file as _module_imports_on_top_of_file # noqa: E501 from pycodestyle import module_imports_on_top_of_file as _module_imports_on_top_of_file # noqa: E501
from pycodestyle import python_3000_async_await_keywords as _python_3000_async_await_keywords # noqa: E501
from pycodestyle import python_3000_invalid_escape_sequence as _python_3000_invalid_escape_sequence # noqa: E501 from pycodestyle import python_3000_invalid_escape_sequence as _python_3000_invalid_escape_sequence # noqa: E501
from pycodestyle import tabs_obsolete as _tabs_obsolete from pycodestyle import tabs_obsolete as _tabs_obsolete
from pycodestyle import tabs_or_spaces as _tabs_or_spaces from pycodestyle import tabs_or_spaces as _tabs_or_spaces
@ -57,7 +55,7 @@ def pycodestyle_logical(
previous_unindented_logical_line: Any, previous_unindented_logical_line: Any,
tokens: Any, tokens: Any,
verbose: Any, verbose: Any,
) -> Generator[tuple[int, str], None, None]: ) -> Generator[tuple[int, str]]:
"""Run pycodestyle logical checks.""" """Run pycodestyle logical checks."""
yield from _ambiguous_identifier(logical_line, tokens) yield from _ambiguous_identifier(logical_line, tokens)
yield from _bare_except(logical_line, noqa) yield from _bare_except(logical_line, noqa)
@ -74,11 +72,9 @@ def pycodestyle_logical(
yield from _imports_on_separate_lines(logical_line) yield from _imports_on_separate_lines(logical_line)
yield from _indentation(logical_line, previous_logical, indent_char, indent_level, previous_indent_level, indent_size) # noqa: E501 yield from _indentation(logical_line, previous_logical, indent_char, indent_level, previous_indent_level, indent_size) # noqa: E501
yield from _maximum_doc_length(logical_line, max_doc_length, noqa, tokens) yield from _maximum_doc_length(logical_line, max_doc_length, noqa, tokens)
yield from _missing_whitespace(logical_line) yield from _missing_whitespace(logical_line, tokens)
yield from _missing_whitespace_after_keyword(logical_line, tokens) yield from _missing_whitespace_after_keyword(logical_line, tokens)
yield from _missing_whitespace_around_operator(logical_line, tokens)
yield from _module_imports_on_top_of_file(logical_line, indent_level, checker_state, noqa) # noqa: E501 yield from _module_imports_on_top_of_file(logical_line, indent_level, checker_state, noqa) # noqa: E501
yield from _python_3000_async_await_keywords(logical_line, tokens)
yield from _python_3000_invalid_escape_sequence(logical_line, tokens, noqa) yield from _python_3000_invalid_escape_sequence(logical_line, tokens, noqa)
yield from _whitespace_around_comma(logical_line) yield from _whitespace_around_comma(logical_line)
yield from _whitespace_around_keywords(logical_line) yield from _whitespace_around_keywords(logical_line)
@ -97,7 +93,7 @@ def pycodestyle_physical(
noqa: Any, noqa: Any,
physical_line: Any, physical_line: Any,
total_lines: Any, total_lines: Any,
) -> Generator[tuple[int, str], None, None]: ) -> Generator[tuple[int, str]]:
"""Run pycodestyle physical checks.""" """Run pycodestyle physical checks."""
ret = _maximum_line_length(physical_line, max_line_length, multiline, line_number, noqa) # noqa: E501 ret = _maximum_line_length(physical_line, max_line_length, multiline, line_number, noqa) # noqa: E501
if ret is not None: if ret is not None:

View file

@ -3,15 +3,16 @@ from __future__ import annotations
import argparse import argparse
import ast import ast
import os import logging
from collections.abc import Generator
from typing import Any from typing import Any
from typing import Generator
import pyflakes.checker import pyflakes.checker
from flake8 import utils
from flake8.options.manager import OptionManager from flake8.options.manager import OptionManager
LOG = logging.getLogger(__name__)
FLAKE8_PYFLAKES_CODES = { FLAKE8_PYFLAKES_CODES = {
"UnusedImport": "F401", "UnusedImport": "F401",
"ImportShadowedByLoopVar": "F402", "ImportShadowedByLoopVar": "F402",
@ -35,6 +36,7 @@ FLAKE8_PYFLAKES_CODES = {
"StringDotFormatMissingArgument": "F524", "StringDotFormatMissingArgument": "F524",
"StringDotFormatMixingAutomatic": "F525", "StringDotFormatMixingAutomatic": "F525",
"FStringMissingPlaceholders": "F541", "FStringMissingPlaceholders": "F541",
"TStringMissingPlaceholders": "F542",
"MultiValueRepeatedKeyLiteral": "F601", "MultiValueRepeatedKeyLiteral": "F601",
"MultiValueRepeatedKeyVariable": "F602", "MultiValueRepeatedKeyVariable": "F602",
"TooManyExpressionsInStarredAssignment": "F621", "TooManyExpressionsInStarredAssignment": "F621",
@ -45,7 +47,6 @@ FLAKE8_PYFLAKES_CODES = {
"IfTuple": "F634", "IfTuple": "F634",
"BreakOutsideLoop": "F701", "BreakOutsideLoop": "F701",
"ContinueOutsideLoop": "F702", "ContinueOutsideLoop": "F702",
"ContinueInFinally": "F703",
"YieldOutsideFunction": "F704", "YieldOutsideFunction": "F704",
"ReturnOutsideFunction": "F706", "ReturnOutsideFunction": "F706",
"DefaultExceptNotLast": "F707", "DefaultExceptNotLast": "F707",
@ -55,6 +56,7 @@ FLAKE8_PYFLAKES_CODES = {
"UndefinedName": "F821", "UndefinedName": "F821",
"UndefinedExport": "F822", "UndefinedExport": "F822",
"UndefinedLocal": "F823", "UndefinedLocal": "F823",
"UnusedIndirectAssignment": "F824",
"DuplicateArgument": "F831", "DuplicateArgument": "F831",
"UnusedVariable": "F841", "UnusedVariable": "F841",
"UnusedAnnotation": "F842", "UnusedAnnotation": "F842",
@ -66,34 +68,12 @@ class FlakesChecker(pyflakes.checker.Checker):
"""Subclass the Pyflakes checker to conform with the flake8 API.""" """Subclass the Pyflakes checker to conform with the flake8 API."""
with_doctest = False with_doctest = False
include_in_doctest: list[str] = []
exclude_from_doctest: list[str] = []
def __init__(self, tree: ast.AST, filename: str) -> None: def __init__(self, tree: ast.AST, filename: str) -> None:
"""Initialize the PyFlakes plugin with an AST tree and filename.""" """Initialize the PyFlakes plugin with an AST tree and filename."""
filename = utils.normalize_path(filename) super().__init__(
with_doctest = self.with_doctest tree, filename=filename, withDoctest=self.with_doctest,
included_by = [ )
include
for include in self.include_in_doctest
if include != "" and filename.startswith(include)
]
if included_by:
with_doctest = True
for exclude in self.exclude_from_doctest:
if exclude != "" and filename.startswith(exclude):
with_doctest = False
overlapped_by = [
include
for include in included_by
if include.startswith(exclude)
]
if overlapped_by:
with_doctest = True
super().__init__(tree, filename=filename, withDoctest=with_doctest)
@classmethod @classmethod
def add_options(cls, parser: OptionManager) -> None: def add_options(cls, parser: OptionManager) -> None:
@ -111,24 +91,6 @@ class FlakesChecker(pyflakes.checker.Checker):
parse_from_config=True, parse_from_config=True,
help="also check syntax of the doctests", help="also check syntax of the doctests",
) )
parser.add_option(
"--include-in-doctest",
default="",
dest="include_in_doctest",
parse_from_config=True,
comma_separated_list=True,
normalize_paths=True,
help="Run doctests only on these files",
)
parser.add_option(
"--exclude-from-doctest",
default="",
dest="exclude_from_doctest",
parse_from_config=True,
comma_separated_list=True,
normalize_paths=True,
help="Skip these files when running doctests",
)
@classmethod @classmethod
def parse_options(cls, options: argparse.Namespace) -> None: def parse_options(cls, options: argparse.Namespace) -> None:
@ -137,38 +99,7 @@ class FlakesChecker(pyflakes.checker.Checker):
cls.builtIns = cls.builtIns.union(options.builtins) cls.builtIns = cls.builtIns.union(options.builtins)
cls.with_doctest = options.doctests cls.with_doctest = options.doctests
included_files = [] def run(self) -> Generator[tuple[int, int, str, type[Any]]]:
for included_file in options.include_in_doctest:
if included_file == "":
continue
if not included_file.startswith((os.sep, "./", "~/")):
included_files.append(f"./{included_file}")
else:
included_files.append(included_file)
cls.include_in_doctest = utils.normalize_paths(included_files)
excluded_files = []
for excluded_file in options.exclude_from_doctest:
if excluded_file == "":
continue
if not excluded_file.startswith((os.sep, "./", "~/")):
excluded_files.append(f"./{excluded_file}")
else:
excluded_files.append(excluded_file)
cls.exclude_from_doctest = utils.normalize_paths(excluded_files)
inc_exc = set(cls.include_in_doctest).intersection(
cls.exclude_from_doctest
)
if inc_exc:
raise ValueError(
f"{inc_exc!r} was specified in both the "
f"include-in-doctest and exclude-from-doctest "
f"options. You are not allowed to specify it in "
f"both for doctesting."
)
def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]:
"""Run the plugin.""" """Run the plugin."""
for message in self.messages: for message in self.messages:
col = getattr(message, "col", 0) col = getattr(message, "col", 0)

View file

@ -3,27 +3,29 @@ from __future__ import annotations
import argparse import argparse
import ast import ast
import contextlib import functools
import logging import logging
import tokenize import tokenize
from collections.abc import Generator
from typing import Any from typing import Any
from typing import Generator
from typing import List
from typing import Tuple
from flake8 import defaults from flake8 import defaults
from flake8 import utils 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 from flake8.plugins.finder import LoadedPlugin
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE])
SKIP_TOKENS = frozenset( 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]]] _LogicalMapping = list[tuple[int, tuple[int, int]]]
_Logical = Tuple[List[str], List[str], _LogicalMapping] _Logical = tuple[list[str], list[str], _LogicalMapping]
class FileProcessor: class FileProcessor:
@ -113,29 +115,38 @@ class FileProcessor:
self.verbose = options.verbose self.verbose = options.verbose
#: Statistics dictionary #: Statistics dictionary
self.statistics = {"logical lines": 0} self.statistics = {"logical lines": 0}
self._file_tokens: list[tokenize.TokenInfo] | None = None self._fstring_start = self._tstring_start = -1
# map from line number to the line we'll search for `noqa` in
self._noqa_line_mapping: dict[int, str] | None = None
@property @functools.cached_property
def file_tokens(self) -> list[tokenize.TokenInfo]: def file_tokens(self) -> list[tokenize.TokenInfo]:
"""Return the complete set of tokens for a file.""" """Return the complete set of tokens for a file."""
if self._file_tokens is None:
line_iter = iter(self.lines) line_iter = iter(self.lines)
self._file_tokens = list( return list(tokenize.generate_tokens(lambda: next(line_iter)))
tokenize.generate_tokens(lambda: next(line_iter))
)
return self._file_tokens def fstring_start(self, lineno: int) -> None: # pragma: >=3.12 cover
"""Signal the beginning of an fstring."""
self._fstring_start = lineno
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]
@contextlib.contextmanager
def inside_multiline(
self, line_number: int
) -> Generator[None, None, None]:
"""Context-manager to toggle the multiline attribute."""
self.line_number = line_number
self.multiline = True self.multiline = True
yield self.line_number = start
# intentionally don't include the last line, that line will be
# terminated later by a future end-of-line
for _ in range(start, token.end[0]):
yield self.lines[self.line_number - 1]
self.line_number += 1
self.multiline = False self.multiline = False
def reset_blank_before(self) -> None: def reset_blank_before(self) -> None:
@ -162,7 +173,7 @@ class FileProcessor:
"""Update the checker_state attribute for the plugin.""" """Update the checker_state attribute for the plugin."""
if "checker_state" in plugin.parameters: if "checker_state" in plugin.parameters:
self.checker_state = self._checker_states.setdefault( self.checker_state = self._checker_states.setdefault(
plugin.entry_name, {} plugin.entry_name, {},
) )
def next_logical_line(self) -> None: def next_logical_line(self) -> None:
@ -178,7 +189,7 @@ class FileProcessor:
self.blank_lines = 0 self.blank_lines = 0
self.tokens = [] self.tokens = []
def build_logical_line_tokens(self) -> _Logical: def build_logical_line_tokens(self) -> _Logical: # noqa: C901
"""Build the mapping, comments, and logical line lists.""" """Build the mapping, comments, and logical line lists."""
logical = [] logical = []
comments = [] comments = []
@ -195,7 +206,18 @@ class FileProcessor:
continue continue
if token_type == tokenize.STRING: if token_type == tokenize.STRING:
text = mutate_string(text) text = mutate_string(text)
if previous_row: 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
# than the raw double brace version, so we must counteract this
brace_offset = text.count("{") + text.count("}")
text = "x" * (len(text) + brace_offset)
end = (end[0], end[1] + brace_offset)
if previous_row is not None and previous_column is not None:
(start_row, start_column) = start (start_row, start_column) = start
if previous_row != start_row: if previous_row != start_row:
row_index = previous_row - 1 row_index = previous_row - 1
@ -225,19 +247,6 @@ class FileProcessor:
self.statistics["logical lines"] += 1 self.statistics["logical lines"] += 1
return joined_comments, self.logical_line, mapping_list return joined_comments, self.logical_line, mapping_list
def split_line(
self, token: tokenize.TokenInfo
) -> Generator[str, None, None]:
"""Split a physical line's line based on new-lines.
This also auto-increments the line number for the caller.
"""
# intentionally don't include the last line, that line will be
# terminated later by a future end-of-line
for line_no in range(token.start[0], token.end[0]):
yield self.lines[line_no - 1]
self.line_number += 1
def keyword_arguments_for( def keyword_arguments_for(
self, self,
parameters: dict[str, bool], parameters: dict[str, bool],
@ -261,7 +270,7 @@ class FileProcessor:
) )
return ret 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.""" """Tokenize the file and yield the tokens."""
for token in tokenize.generate_tokens(self.next_line): for token in tokenize.generate_tokens(self.next_line):
if token[2][0] > self.total_lines: if token[2][0] > self.total_lines:
@ -274,23 +283,23 @@ class FileProcessor:
joined = "".join(self.lines[min_line - 1: max_line]) joined = "".join(self.lines[min_line - 1: max_line])
return dict.fromkeys(line_range, joined) return dict.fromkeys(line_range, joined)
def noqa_line_for(self, line_number: int) -> str | None: @functools.cached_property
"""Retrieve the line which will be used to determine noqa.""" def _noqa_line_mapping(self) -> dict[int, str]:
if self._noqa_line_mapping is None: """Map from line number to the line we'll search for `noqa` in."""
try: try:
file_tokens = self.file_tokens file_tokens = self.file_tokens
except (tokenize.TokenError, SyntaxError): except (tokenize.TokenError, SyntaxError):
# if we failed to parse the file tokens, we'll always fail in # if we failed to parse the file tokens, we'll always fail in
# the future, so set this so the code does not try again # the future, so set this so the code does not try again
self._noqa_line_mapping = {} return {}
else: else:
ret = {} ret = {}
min_line = len(self.lines) + 2 min_line = len(self.lines) + 2
max_line = -1 max_line = -1
for tp, _, (s_line, _), (e_line, _), _ in file_tokens: for tp, _, (s_line, _), (e_line, _), _ in file_tokens:
if tp == tokenize.ENDMARKER: if tp == tokenize.ENDMARKER or tp == tokenize.DEDENT:
break continue
min_line = min(min_line, s_line) min_line = min(min_line, s_line)
max_line = max(max_line, e_line) max_line = max(max_line, e_line)
@ -301,14 +310,10 @@ class FileProcessor:
min_line = len(self.lines) + 2 min_line = len(self.lines) + 2
max_line = -1 max_line = -1
# in newer versions of python, a `NEWLINE` token is inserted return ret
# at the end of the file even if it doesn't have one.
# on old pythons, they will not have hit a `NEWLINE`
if max_line != -1:
ret.update(self._noqa_line_range(min_line, max_line))
self._noqa_line_mapping = ret
def noqa_line_for(self, line_number: int) -> str | None:
"""Retrieve the line which will be used to determine noqa."""
# NOTE(sigmavirus24): Some plugins choose to report errors for empty # NOTE(sigmavirus24): Some plugins choose to report errors for empty
# files on Line 1. In those cases, we shouldn't bother trying to # files on Line 1. In those cases, we shouldn't bother trying to
# retrieve a physical line (since none exist). # retrieve a physical line (since none exist).
@ -362,7 +367,7 @@ class FileProcessor:
elif any(defaults.NOQA_FILE.search(line) for line in self.lines): elif any(defaults.NOQA_FILE.search(line) for line in self.lines):
LOG.warning( LOG.warning(
"Detected `flake8: noqa` on line with code. To ignore an " "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 return False
else: else:
@ -374,12 +379,8 @@ class FileProcessor:
# If we have nothing to analyze quit early # If we have nothing to analyze quit early
return return
first_byte = ord(self.lines[0][0])
if first_byte not in (0xEF, 0xFEFF):
return
# If the first byte of the file is a UTF-8 BOM, strip it # If the first byte of the file is a UTF-8 BOM, strip it
if first_byte == 0xFEFF: if self.lines[0][:1] == "\uFEFF":
self.lines[0] = self.lines[0][1:] self.lines[0] = self.lines[0][1:]
elif self.lines[0][:3] == "\xEF\xBB\xBF": elif self.lines[0][:3] == "\xEF\xBB\xBF":
self.lines[0] = self.lines[0][3:] self.lines[0] = self.lines[0][3:]
@ -392,7 +393,9 @@ def is_eol_token(token: tokenize.TokenInfo) -> bool:
def is_multiline_string(token: tokenize.TokenInfo) -> bool: def is_multiline_string(token: tokenize.TokenInfo) -> bool:
"""Check if this is a multiline string.""" """Check if this is a multiline string."""
return token[0] == tokenize.STRING and "\n" in token[1] return token.type in {FSTRING_END, TSTRING_END} or (
token.type == tokenize.STRING and "\n" in token.string
)
def token_is_newline(token: tokenize.TokenInfo) -> bool: def token_is_newline(token: tokenize.TokenInfo) -> bool:

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ from __future__ import annotations
import functools import functools
import linecache import linecache
import logging import logging
from typing import Match from re import Match
from typing import NamedTuple from typing import NamedTuple
from flake8 import defaults from flake8 import defaults
@ -64,6 +64,6 @@ class Violation(NamedTuple):
return True return True
LOG.debug( 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 return False

View file

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
import importlib.metadata import importlib.metadata
import sys
from unittest import mock from unittest import mock
import pytest import pytest
@ -97,7 +96,7 @@ def mock_file_checker_with_plugin(plugin_target):
# Prevent it from reading lines from stdin or somewhere else # Prevent it from reading lines from stdin or somewhere else
with mock.patch( 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( file_checker = checker.FileChecker(
filename="-", filename="-",
@ -322,15 +321,8 @@ def test_handling_syntaxerrors_across_pythons():
We need to handle that correctly to avoid crashing. We need to handle that correctly to avoid crashing.
https://github.com/PyCQA/flake8/issues/1372 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( err = SyntaxError(
"invalid syntax", ("<unknown>", 2, 5, "bad python:\n") "invalid syntax", ("<unknown>", 2, 1, "bad python:\n", 2, 11),
)
expected = (2, 4)
else: # pragma: no cover (3.10+)
err = SyntaxError(
"invalid syntax", ("<unknown>", 2, 1, "bad python:\n", 2, 11)
) )
expected = (2, 1) expected = (2, 1)
file_checker = checker.FileChecker( file_checker = checker.FileChecker(

View file

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

View file

@ -1,6 +1,8 @@
"""Integration tests for plugin loading.""" """Integration tests for plugin loading."""
from __future__ import annotations from __future__ import annotations
import sys
import pytest import pytest
from flake8.main.cli import main from flake8.main.cli import main
@ -84,7 +86,7 @@ def test_local_plugin_can_add_option(local_config):
stage1_args, rest = stage1_parser.parse_known_args(argv) stage1_args, rest = stage1_parser.parse_known_args(argv)
cfg, cfg_dir = config.load_config( 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( opts = finder.parse_plugin_options(
@ -197,3 +199,133 @@ t.py:3:1: T001 '"""\\n'
''' '''
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert out == expected assert out == expected
def test_physical_line_plugin_multiline_fstring(tmpdir, capsys):
cfg_s = f"""\
[flake8:local-plugins]
extension =
T = {yields_physical_line.__module__}:{yields_physical_line.__name__}
"""
cfg = tmpdir.join("tox.ini")
cfg.write(cfg_s)
src = '''\
y = 1
x = f"""
hello {y}
"""
'''
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 'y = 1\\n'
t.py:2:1: T001 'x = f"""\\n'
t.py:3:1: T001 'hello {y}\\n'
t.py:4:1: T001 '"""\\n'
'''
out, err = capsys.readouterr()
assert out == expected
def yields_logical_line(logical_line):
yield 0, f"T001 {logical_line!r}"
def test_logical_line_plugin(tmpdir, capsys):
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 = """\
f'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 "f'xxxxxxxxxxx'"
"""
out, err = capsys.readouterr()
assert out == expected
def test_escaping_of_fstrings_in_string_redacter(tmpdir, capsys):
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 = """\
f'{{"{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
if sys.version_info >= (3, 12): # pragma: >=3.12 cover
expected = """\
t.py:1:1: T001 "f'xxx{hello}xxxx{world}xxx'"
"""
else: # pragma: <3.12 cover
expected = """\
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}) logical_line_plugin = _loaded(parameters={"logical_line": True})
physical_line_plugin = _loaded(parameters={"physical_line": True}) physical_line_plugin = _loaded(parameters={"physical_line": True})
report_plugin = _loaded( report_plugin = _loaded(
plugin=_plugin(ep=_ep(name="R", group="flake8.report")) plugin=_plugin(ep=_ep(name="R", group="flake8.report")),
) )
plugins = finder.Plugins( plugins = finder.Plugins(
@ -200,14 +200,16 @@ def test_flake8_plugins(flake8_dist, mock_distribution):
"flake8", "flake8",
"9001", "9001",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
"default", "flake8.formatting.default:Default", "flake8.report" "default",
"flake8.formatting.default:Default",
"flake8.report",
), ),
), ),
finder.Plugin( finder.Plugin(
"flake8", "flake8",
"9001", "9001",
importlib.metadata.EntryPoint( 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", "flake8-foo",
"1.2.3", "1.2.3",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
"Q", "flake8_foo:Plugin", "flake8.extension" "Q", "flake8_foo:Plugin", "flake8.extension",
), ),
), ),
finder.Plugin( finder.Plugin(
@ -304,21 +306,23 @@ unrelated = unrelated:main
"flake8", "flake8",
"9001", "9001",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
"default", "flake8.formatting.default:Default", "flake8.report" "default",
"flake8.formatting.default:Default",
"flake8.report",
), ),
), ),
finder.Plugin( finder.Plugin(
"flake8", "flake8",
"9001", "9001",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
"pylint", "flake8.formatting.default:Pylint", "flake8.report" "pylint", "flake8.formatting.default:Pylint", "flake8.report",
), ),
), ),
finder.Plugin( finder.Plugin(
"flake8-foo", "flake8-foo",
"1.2.3", "1.2.3",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
"foo", "flake8_foo:Formatter", "flake8.report" "foo", "flake8_foo:Formatter", "flake8.report",
), ),
), ),
} }
@ -485,28 +489,30 @@ def test_find_plugins(
"flake8", "flake8",
"9001", "9001",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
"default", "flake8.formatting.default:Default", "flake8.report" "default",
"flake8.formatting.default:Default",
"flake8.report",
), ),
), ),
finder.Plugin( finder.Plugin(
"flake8", "flake8",
"9001", "9001",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
"pylint", "flake8.formatting.default:Pylint", "flake8.report" "pylint", "flake8.formatting.default:Pylint", "flake8.report",
), ),
), ),
finder.Plugin( finder.Plugin(
"flake8-foo", "flake8-foo",
"1.2.3", "1.2.3",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
"Q", "flake8_foo:Plugin", "flake8.extension" "Q", "flake8_foo:Plugin", "flake8.extension",
), ),
), ),
finder.Plugin( finder.Plugin(
"flake8-foo", "flake8-foo",
"1.2.3", "1.2.3",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
"foo", "flake8_foo:Formatter", "flake8.report" "foo", "flake8_foo:Formatter", "flake8.report",
), ),
), ),
finder.Plugin( finder.Plugin(
@ -518,7 +524,7 @@ def test_find_plugins(
"local", "local",
"local", "local",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
"Y", "mod2:attr", "flake8.extension" "Y", "mod2:attr", "flake8.extension",
), ),
), ),
finder.Plugin( finder.Plugin(
@ -723,7 +729,7 @@ def test_import_plugins_extends_sys_path():
def test_classify_plugins(): def test_classify_plugins():
report_plugin = _loaded( 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}) tree_plugin = _loaded(parameters={"tree": True})
logical_line_plugin = _loaded(parameters={"logical_line": True}) logical_line_plugin = _loaded(parameters={"logical_line": True})

View file

@ -11,7 +11,7 @@ from flake8.plugins import reporter
def _opts(**kwargs): def _opts(**kwargs):
kwargs.setdefault("quiet", 0), kwargs.setdefault("quiet", 0)
kwargs.setdefault("color", "never") kwargs.setdefault("color", "never")
kwargs.setdefault("output_file", None) kwargs.setdefault("output_file", None)
return argparse.Namespace(**kwargs) return argparse.Namespace(**kwargs)
@ -25,7 +25,7 @@ def reporters():
"flake8", "flake8",
"123", "123",
importlib.metadata.EntryPoint( importlib.metadata.EntryPoint(
name, f"{cls.__module__}:{cls.__name__}", "flake8.report" name, f"{cls.__module__}:{cls.__name__}", "flake8.report",
), ),
), ),
cls, cls,
@ -72,5 +72,5 @@ def test_make_formatter_format_string(reporters, caplog):
"flake8.plugins.reporter", "flake8.plugins.reporter",
30, 30,
"'hi %(code)s' is an unknown formatter. Falling back to default.", "'hi %(code)s' is an unknown formatter. Falling back to default.",
) ),
] ]

View file

@ -36,7 +36,7 @@ def application():
], ],
) )
def test_application_exit_code( 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.""" """Verify Application.exit_code returns the correct value."""
application.result_count = result_count application.result_count = result_count

View file

@ -50,7 +50,7 @@ def test_format_needs_to_be_implemented():
formatter = base.BaseFormatter(options()) formatter = base.BaseFormatter(options())
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
formatter.format( 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)) formatter = base.BaseFormatter(options(show_source=False))
assert ( assert (
formatter.show_source( 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)) formatter = base.BaseFormatter(options(show_source=True))
assert ( assert (
formatter.show_source( formatter.show_source(
Violation("A000", "file.py", 1, 1, "error text", None) Violation("A000", "file.py", 1, 1, "error text", None),
) )
== "" == ""
) )

View file

@ -41,8 +41,10 @@ def test_oserrors_are_reraised():
err = OSError(errno.EAGAIN, "Ominous message") err = OSError(errno.EAGAIN, "Ominous message")
with mock.patch("_multiprocessing.SemLock", side_effect=err): with mock.patch("_multiprocessing.SemLock", side_effect=err):
manager = _parallel_checker_manager() manager = _parallel_checker_manager()
with mock.patch.object(manager, "run_serial") as serial: with (
with pytest.raises(OSError): mock.patch.object(manager, "run_serial") as serial,
pytest.raises(OSError),
):
manager.run() manager.run()
assert serial.call_count == 0 assert serial.call_count == 0
@ -61,14 +63,20 @@ def test_multiprocessing_cpu_count_not_implemented():
assert manager.jobs == 0 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(): def test_make_checkers():
"""Verify that we create a list of FileChecker instances.""" """Verify that we create a list of FileChecker instances."""
style_guide = style_guide_mock() style_guide = style_guide_mock()
style_guide.options.filenames = ["file1", "file2"] style_guide.options.filenames = ["file1", "file2"]
manager = checker.Manager(style_guide, finder.Checkers([], [], []), []) 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") assert manager.filenames == ("file1", "file2")

View file

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

View file

@ -14,7 +14,7 @@ def create_options(**kwargs):
kwargs.setdefault("ignore", None) kwargs.setdefault("ignore", None)
kwargs.setdefault("extend_select", None) kwargs.setdefault("extend_select", None)
kwargs.setdefault("extend_ignore", None) kwargs.setdefault("extend_ignore", None)
kwargs.setdefault("extended_default_select", []) kwargs.setdefault("extended_default_select", ["C90", "F", "E", "W"])
kwargs.setdefault("extended_default_ignore", []) kwargs.setdefault("extended_default_ignore", [])
kwargs.setdefault("disable_noqa", False) kwargs.setdefault("disable_noqa", False)
return argparse.Namespace(**kwargs) return argparse.Namespace(**kwargs)
@ -35,7 +35,7 @@ def create_options(**kwargs):
def test_was_ignored_ignores_errors(ignore_list, extend_ignore, error_code): def test_was_ignored_ignores_errors(ignore_list, extend_ignore, error_code):
"""Verify we detect users explicitly ignoring an error.""" """Verify we detect users explicitly ignoring an error."""
decider = style_guide.DecisionEngine( 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 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( 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.""" """Verify we detect users does not explicitly ignore an error."""
decider = style_guide.DecisionEngine( 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 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( 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.""" """Verify we decide when to report an error."""
decider = style_guide.DecisionEngine( decider = style_guide.DecisionEngine(
@ -187,7 +187,7 @@ def test_decision_for(
select=select_list, select=select_list,
ignore=ignore_list, ignore=ignore_list,
extend_ignore=extend_ignore, extend_ignore=extend_ignore,
) ),
) )
assert decider.decision_for(error_code) is expected 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( _filenames_from(
arg=_normpath("a/b/"), arg=_normpath("a/b/"),
predicate=lambda path: path.endswith(_normpath("b/c.py")), predicate=lambda path: path.endswith(_normpath("b/c.py")),
) ),
) )
# should not include c.py # should not include c.py
expected = _normpaths(("a/b/d.py", "a/b/e/f.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( _filenames_from(
arg=_normpath("./a/b"), arg=_normpath("./a/b"),
predicate=lambda path: path == "c.py", predicate=lambda path: path == "c.py",
) ),
) )
# none should have matched the predicate so all returned # none should have matched the predicate so all returned
expected = _normpaths(("./a/b/c.py", "./a/b/d.py", "./a/b/e/f.py")) 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, stdin_display_name=stdin_display_name,
filename_patterns=filename_patterns, filename_patterns=filename_patterns,
exclude=exclude, exclude=exclude,
) ),
) )

View file

@ -28,7 +28,7 @@ def _lines_from_file(tmpdir, contents, options):
def test_read_lines_universal_newlines(tmpdir, default_options): def test_read_lines_universal_newlines(tmpdir, default_options):
r"""Verify that line endings are translated to \n.""" r"""Verify that line endings are translated to \n."""
lines = _lines_from_file( 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"] 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): def test_read_lines_incorrect_utf_16(tmpdir, default_options):
"""Verify that an incorrectly encoded file is read as latin-1.""" """Verify that an incorrectly encoded file is read as latin-1."""
lines = _lines_from_file( 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"] 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): def test_read_lines_unknown_encoding(tmpdir, default_options):
"""Verify that an unknown encoding is still read as latin-1.""" """Verify that an unknown encoding is still read as latin-1."""
lines = _lines_from_file( 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"] assert lines == ["# coding: fake-encoding\n", "x = 1\n"]
@ -275,19 +275,21 @@ def test_processor_split_line(default_options):
(3, 3), (3, 3),
'x = """\ncontents\n"""\n', 'x = """\ncontents\n"""\n',
) )
expected = [('x = """\n', 0), ("contents\n", 1)] expected = [('x = """\n', 1, True), ("contents\n", 2, True)]
assert file_processor.multiline is False
actual = [ actual = [
(line, file_processor.line_number) (line, file_processor.line_number, file_processor.multiline)
for line in file_processor.split_line(token) for line in file_processor.multiline_string(token)
] ]
assert file_processor.multiline is False
assert expected == actual assert expected == actual
assert file_processor.line_number == 2 assert file_processor.line_number == 3
def test_build_ast(default_options): def test_build_ast(default_options):
"""Verify the logic for how we build an AST for plugins.""" """Verify the logic for how we build an AST for plugins."""
file_processor = processor.FileProcessor( file_processor = processor.FileProcessor(
"-", default_options, lines=["a = 1\n"] "-", default_options, lines=["a = 1\n"],
) )
module = file_processor.build_ast() module = file_processor.build_ast()
@ -297,7 +299,7 @@ def test_build_ast(default_options):
def test_next_logical_line_updates_the_previous_logical_line(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.""" """Verify that we update our tracking of the previous logical line."""
file_processor = processor.FileProcessor( file_processor = processor.FileProcessor(
"-", default_options, lines=["a = 1\n"] "-", default_options, lines=["a = 1\n"],
) )
file_processor.indent_level = 1 file_processor.indent_level = 1
@ -313,7 +315,7 @@ def test_next_logical_line_updates_the_previous_logical_line(default_options):
def test_visited_new_blank_line(default_options): def test_visited_new_blank_line(default_options):
"""Verify we update the number of blank lines seen.""" """Verify we update the number of blank lines seen."""
file_processor = processor.FileProcessor( file_processor = processor.FileProcessor(
"-", default_options, lines=["a = 1\n"] "-", default_options, lines=["a = 1\n"],
) )
assert file_processor.blank_lines == 0 assert file_processor.blank_lines == 0
@ -321,21 +323,6 @@ def test_visited_new_blank_line(default_options):
assert file_processor.blank_lines == 1 assert file_processor.blank_lines == 1
def test_inside_multiline(default_options):
"""Verify we update the line number and reset multiline."""
file_processor = processor.FileProcessor(
"-", default_options, lines=["a = 1\n"]
)
assert file_processor.multiline is False
assert file_processor.line_number == 0
with file_processor.inside_multiline(10):
assert file_processor.multiline is True
assert file_processor.line_number == 10
assert file_processor.multiline is False
@pytest.mark.parametrize( @pytest.mark.parametrize(
"string, expected", "string, expected",
[ [

View file

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

View file

@ -122,7 +122,7 @@ def test_parse_args_handles_comma_separated_defaults(optmanager):
assert optmanager.config_options_dict == {} assert optmanager.config_options_dict == {}
optmanager.add_option( optmanager.add_option(
"--exclude", default="E123,W234", comma_separated_list=True "--exclude", default="E123,W234", comma_separated_list=True,
) )
options = optmanager.parse_args([]) options = optmanager.parse_args([])
@ -135,7 +135,7 @@ def test_parse_args_handles_comma_separated_lists(optmanager):
assert optmanager.config_options_dict == {} assert optmanager.config_options_dict == {}
optmanager.add_option( 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"]) options = optmanager.parse_args(["--exclude", "E201,W111,F280"])
@ -148,11 +148,11 @@ def test_parse_args_normalize_paths(optmanager):
assert optmanager.config_options_dict == {} assert optmanager.config_options_dict == {}
optmanager.add_option( 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( 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 == [ assert options.extra_config == [
os.path.abspath("../config.ini"), os.path.abspath("../config.ini"),

View file

@ -169,7 +169,7 @@ def test_load_extra_config_utf8(tmpdir):
@pytest.fixture @pytest.fixture
def opt_manager(): def opt_manager():
ret = OptionManager( ret = OptionManager(
version="123", plugin_versions="", parents=[], formatter_names=[] version="123", plugin_versions="", parents=[], formatter_names=[],
) )
register_default_options(ret) register_default_options(ret)
return ret return ret
@ -213,7 +213,7 @@ def test_parse_config_ignores_unknowns(tmp_path, opt_manager, caplog):
"flake8.options.config", "flake8.options.config",
10, 10,
'Option "wat" is not registered. Ignoring.', '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( 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( 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.""" """Verify how the StyleGuideManager creates a default style guide."""
formatter = mock.create_autospec(base.BaseFormatter, instance=True) formatter = mock.create_autospec(base.BaseFormatter, instance=True)

View file

@ -6,6 +6,7 @@ envlist = py,flake8,linters,docs
deps = deps =
pytest!=3.0.5,!=5.2.3 pytest!=3.0.5,!=5.2.3
coverage>=6 coverage>=6
covdefaults
commands = commands =
coverage run -m pytest {posargs} coverage run -m pytest {posargs}
coverage report coverage report