From a1dfd984380b0afcc0521f6869a4b3103b18058c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 22:00:50 +0000 Subject: [PATCH 001/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.0.1 → v3.1.0](https://github.com/asottile/reorder_python_imports/compare/v3.0.1...v3.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fea42165..e932206d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.0.1 + rev: v3.1.0 hooks: - id: reorder-python-imports args: [--py36-plus] From 3de9e91bfba14fa74a4ff78ee7e2d90faeab2878 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 22:08:07 +0000 Subject: [PATCH 002/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.32.0 → v2.32.1](https://github.com/asottile/pyupgrade/compare/v2.32.0...v2.32.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e932206d..383f1b9a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.32.0 + rev: v2.32.1 hooks: - id: pyupgrade args: [--py36-plus] From e72afc551f1fa178cb753e1ec164803655ba5692 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 16 May 2022 20:42:29 -0400 Subject: [PATCH 003/125] add python3.11 support (except* and a[*b]) --- .github/workflows/main.yml | 5 ++++- pycodestyle.py | 1 + testsuite/python311.py | 23 +++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 testsuite/python311.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0d26707d..00f5e75c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,10 @@ jobs: py: 3.9 toxenv: py - os: ubuntu-latest - py: 3.10-dev + py: '3.10' + toxenv: py + - os: ubuntu-latest + py: '3.11-dev' toxenv: py - os: ubuntu-latest py: 3.9 diff --git a/pycodestyle.py b/pycodestyle.py index e2e4b96b..550770b2 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -496,6 +496,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): keyword.iskeyword(tok0.string) and tok0.string not in SINGLETONS and tok0.string not in ('async', 'await') and + not (tok0.string == 'except' and tok1.string == '*') and tok1.string not in ':\n'): line, pos = tok0.end yield pos, "E275 missing whitespace after keyword" diff --git a/testsuite/python311.py b/testsuite/python311.py new file mode 100644 index 00000000..a405125a --- /dev/null +++ b/testsuite/python311.py @@ -0,0 +1,23 @@ +#: Okay +try: + ... +except* OSError as e: + pass +#: Okay +from typing import Generic +from typing import TypeVarTuple + + +Ts = TypeVarTuple('Ts') + + +class Shape(Generic[*Ts]): + pass + + +def f(*args: *Ts) -> None: + ... + + +def g(x: Shape[*Ts]) -> Shape[*Ts]: + ... From c1c9a4be729e63a2b72495f8b8d43f3a7580c4a9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 30 May 2022 14:25:21 -0400 Subject: [PATCH 004/125] fix IndexError regression with E275 --- pycodestyle.py | 3 +-- testsuite/E27.py | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index e2e4b96b..adf743ca 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -497,8 +497,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): tok0.string not in SINGLETONS and tok0.string not in ('async', 'await') and tok1.string not in ':\n'): - line, pos = tok0.end - yield pos, "E275 missing whitespace after keyword" + yield tok0.end, "E275 missing whitespace after keyword" @register_check diff --git a/testsuite/E27.py b/testsuite/E27.py index 91aa0790..5b476577 100644 --- a/testsuite/E27.py +++ b/testsuite/E27.py @@ -49,3 +49,6 @@ pass #: Okay matched = {"true": True, "false": False} +#: E275:2:11 +if True: + assert(1) From 6d9d1248117c0ab14e02163002fb39ddc736761c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jun 2022 21:59:34 +0000 Subject: [PATCH 005/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.32.1 → v2.33.0](https://github.com/asottile/pyupgrade/compare/v2.32.1...v2.33.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 383f1b9a..0135a7a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.32.1 + rev: v2.33.0 hooks: - id: pyupgrade args: [--py36-plus] From a0c4d7ded79cadf353c3391c1117c5dea2387910 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Jun 2022 00:06:33 +0000 Subject: [PATCH 006/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.2.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.2.0...v4.3.0) - [github.com/asottile/pyupgrade: v2.33.0 → v2.34.0](https://github.com/asottile/pyupgrade/compare/v2.33.0...v2.34.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0135a7a9..3d92a7d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testsuite/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: check-yaml - id: debug-statements @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.33.0 + rev: v2.34.0 hooks: - id: pyupgrade args: [--py36-plus] From 856115397248db361a430ae3d6065342288248d1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 00:17:46 +0000 Subject: [PATCH 007/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.1.0 → v3.3.0](https://github.com/asottile/reorder_python_imports/compare/v3.1.0...v3.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d92a7d5..7f1e5aeb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.1.0 + rev: v3.3.0 hooks: - id: reorder-python-imports args: [--py36-plus] From b80783672014f7de213b5dec44f2d6f1be5cc482 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Jul 2022 23:19:26 +0000 Subject: [PATCH 008/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.3.0 → v3.8.1](https://github.com/asottile/reorder_python_imports/compare/v3.3.0...v3.8.1) - [github.com/asottile/pyupgrade: v2.34.0 → v2.37.1](https://github.com/asottile/pyupgrade/compare/v2.34.0...v2.37.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7f1e5aeb..a0541126 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.3.0 + rev: v3.8.1 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v2.37.1 hooks: - id: pyupgrade args: [--py36-plus] From 3af77d1b1030fae288899ce0c6f0c192844b94b4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Jul 2022 23:43:36 +0000 Subject: [PATCH 009/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.1 → v3.8.2](https://github.com/asottile/reorder_python_imports/compare/v3.8.1...v3.8.2) - [github.com/asottile/pyupgrade: v2.37.1 → v2.37.2](https://github.com/asottile/pyupgrade/compare/v2.37.1...v2.37.2) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0541126..9b2ef3ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.1 + rev: v3.8.2 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.37.1 + rev: v2.37.2 hooks: - id: pyupgrade args: [--py36-plus] From c14bd2aac8e370bc84048a97f17a1ed906523bf9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 30 Jul 2022 15:01:02 -0400 Subject: [PATCH 010/125] Revert "Merge pull request #1041 from asfaltboy/issue-830-e721-types-regex-incorrect" This reverts commit 8b5c964321776b2fe8dfd25f4f18db0ffbdbd281, reversing changes made to 9777ac5a8ea1ae14e70bfb27063e2e7c0daa06e3. --- pycodestyle.py | 13 +++++++++---- testsuite/E72.py | 9 +++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index f866dd4a..ee0a58f2 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -134,10 +134,8 @@ r'\s*(?(1)|(None|False|True))\b') COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?%&^]+|:=)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') @@ -1446,6 +1444,13 @@ def comparison_type(logical_line, noqa): Okay: if isinstance(obj, int): E721: if type(obj) is type(1): + + When checking if an object is a string, keep in mind that it might + be a unicode string too! In Python 2.3, str and unicode have a + common base class, basestring, so you can do: + + Okay: if isinstance(obj, basestring): + Okay: if type(a1) is type(b1): """ match = COMPARE_TYPE_REGEX.search(logical_line) if match and not noqa: diff --git a/testsuite/E72.py b/testsuite/E72.py index 61e17eb2..d127ff77 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -4,7 +4,7 @@ #: E721 if type(res) != type(""): pass -#: Okay +#: E721 import types if res == types.IntType: @@ -47,6 +47,8 @@ pass if isinstance(res, types.MethodType): pass +if type(a) != type(b) or type(a) == type(ccc): + pass #: Okay def func_histype(a, b, c): pass @@ -79,11 +81,6 @@ def func_histype(a, b, c): except Exception: pass #: Okay -from . import custom_types as types - -red = types.ColorTypeRED -red is types.ColorType.RED -#: Okay from . import compute_type if compute_type(foo) == 5: From 57e39fa4f66707c305ead679e62d7ce1b7af9362 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 30 Jul 2022 15:26:26 -0400 Subject: [PATCH 011/125] Release 2.9.0 --- CHANGES.txt | 12 ++++++++++++ pycodestyle.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 49db29b2..f24ee348 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,18 @@ Changelog ========= +2.9.0 (2022-07-30) +------------------ + +Changes: + +* E221, E222, E223, E224: add support for ``:=`` operator. PR #1032. +* Drop python 2.7 / 3.5. +* E262: consider non-breaking spaces (``\xa0``) as whitespace. PR #1035. +* Improve performance of ``_is_binary_operator``. PR #1052. +* E275: requires whitespace around keywords. PR #1063. +* Add support for python 3.11. PR #1070. + 2.8.0 (2021-10-10) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index ee0a58f2..5c4d5f99 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -72,7 +72,7 @@ ): # pragma: no cover ( Date: Tue, 2 Aug 2022 01:02:55 +0000 Subject: [PATCH 012/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.37.2 → v2.37.3](https://github.com/asottile/pyupgrade/compare/v2.37.2...v2.37.3) - [github.com/pycqa/flake8: 4.0.1 → 5.0.3](https://github.com/pycqa/flake8/compare/4.0.1...5.0.3) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9b2ef3ff..4a25298a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,11 +13,11 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.37.2 + rev: v2.37.3 hooks: - id: pyupgrade args: [--py36-plus] - repo: https://github.com/pycqa/flake8 - rev: 4.0.1 + rev: 5.0.3 hooks: - id: flake8 From 43c5afaeef44a01b512ade340030ff4d7b0ba78e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 3 Aug 2022 18:26:51 -0400 Subject: [PATCH 013/125] allow parenthesized yield (generator-coroutines) --- pycodestyle.py | 1 + testsuite/E27.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index 5c4d5f99..ad3030af 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -495,6 +495,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): tok0.string not in SINGLETONS and tok0.string not in ('async', 'await') and not (tok0.string == 'except' and tok1.string == '*') and + not (tok0.string == 'yield' and tok1.string == ')') and tok1.string not in ':\n'): yield tok0.end, "E275 missing whitespace after keyword" diff --git a/testsuite/E27.py b/testsuite/E27.py index 5b476577..ca069306 100644 --- a/testsuite/E27.py +++ b/testsuite/E27.py @@ -52,3 +52,7 @@ #: E275:2:11 if True: assert(1) +#: Okay +def f(): + print((yield)) + x = (yield) From c33e852a5938b823b04dd981260bd1664c643385 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 3 Aug 2022 19:09:49 -0400 Subject: [PATCH 014/125] Release 2.9.1 --- CHANGES.txt | 7 +++++++ pycodestyle.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index f24ee348..f7fddf7f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Changelog ========= +2.9.1 (2022-08-03) +------------------ + +Changes: + +* E275: fix false positive for yield expressions. + 2.9.0 (2022-07-30) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index ad3030af..542272c3 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -72,7 +72,7 @@ ): # pragma: no cover ( Date: Tue, 9 Aug 2022 00:39:28 +0000 Subject: [PATCH 015/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 5.0.3 → 5.0.4](https://github.com/pycqa/flake8/compare/5.0.3...5.0.4) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a25298a..d0871973 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,6 @@ repos: - id: pyupgrade args: [--py36-plus] - repo: https://github.com/pycqa/flake8 - rev: 5.0.3 + rev: 5.0.4 hooks: - id: flake8 From b88a4c348ea88fd6489cbdc2f68ca05fcd644004 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 00:55:15 +0000 Subject: [PATCH 016/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.37.3 → v2.38.0](https://github.com/asottile/pyupgrade/compare/v2.37.3...v2.38.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d0871973..e3a9341b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.37.3 + rev: v2.38.0 hooks: - id: pyupgrade args: [--py36-plus] From a5768ea128eba3badc46a8e52a4f46fae78b3017 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 01:09:46 +0000 Subject: [PATCH 017/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.2 → v3.8.3](https://github.com/asottile/reorder_python_imports/compare/v3.8.2...v3.8.3) - [github.com/asottile/pyupgrade: v2.38.0 → v2.38.2](https://github.com/asottile/pyupgrade/compare/v2.38.0...v2.38.2) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e3a9341b..e0e09fd5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.2 + rev: v3.8.3 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.38.0 + rev: v2.38.2 hooks: - id: pyupgrade args: [--py36-plus] From 6d136b80963ccfe9c89e971eed98e8c5d109b594 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 28 Sep 2022 20:26:11 -0400 Subject: [PATCH 018/125] make the example ignore a valid prefix resolves #1104 --- docs/intro.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.rst b/docs/intro.rst index 2f108138..3be9d8ed 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -200,7 +200,7 @@ Example:: [pycodestyle] count = False - ignore = E226,E302,E41 + ignore = E226,E302,E71 max-line-length = 160 statistics = True From b74e8b91e11360782223fd0da79941fc4efaec6b Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Sat, 8 Oct 2022 23:50:50 -0400 Subject: [PATCH 019/125] E231 should work with tuples in brackets --- pycodestyle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 542272c3..2119d06c 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -506,6 +506,7 @@ def missing_whitespace(logical_line): Okay: [a, b] Okay: (3,) + Okay: a[3,] = 1 Okay: a[1:4] Okay: a[:4] Okay: a[1:] @@ -523,7 +524,7 @@ def missing_whitespace(logical_line): if char == ':' and before.count('[') > before.count(']') and \ before.rfind('{') < before.rfind('['): continue # Slice syntax, no space required - if char == ',' and next_char == ')': + if char == ',' and next_char in ')]': continue # Allow tuple with only one element: (3,) if char == ':' and next_char == '=' and sys.version_info >= (3, 8): continue # Allow assignment expression From 15f6d7bdf57b908cc6e544816206e8b15aad307f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 02:28:05 +0000 Subject: [PATCH 020/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.3 → v3.8.4](https://github.com/asottile/reorder_python_imports/compare/v3.8.3...v3.8.4) - [github.com/asottile/pyupgrade: v2.38.2 → v3.1.0](https://github.com/asottile/pyupgrade/compare/v2.38.2...v3.1.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e0e09fd5..32f46853 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.3 + rev: v3.8.4 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.38.2 + rev: v3.1.0 hooks: - id: pyupgrade args: [--py36-plus] From b2cd3bcc71b2f4b043129e6be5242d14d29e10db Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 01:25:58 +0000 Subject: [PATCH 021/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.4 → v3.8.5](https://github.com/asottile/reorder_python_imports/compare/v3.8.4...v3.8.5) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 32f46853..cd9567a4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.4 + rev: v3.8.5 hooks: - id: reorder-python-imports args: [--py36-plus] From 80c5a50ccfb2fba29594901c02b5a9c4689f7f92 Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Tue, 18 Oct 2022 00:21:49 -0400 Subject: [PATCH 022/125] Delete W601 -> W604 warnings --- README.rst | 4 ---- docs/intro.rst | 12 ----------- pycodestyle.py | 53 ------------------------------------------------ testsuite/W60.py | 15 -------------- 4 files changed, 84 deletions(-) diff --git a/README.rst b/README.rst index c71b933e..2f6ddb87 100644 --- a/README.rst +++ b/README.rst @@ -65,11 +65,9 @@ Example usage and output optparse.py:69:11: E401 multiple imports on one line optparse.py:77:1: E302 expected 2 blank lines, found 1 optparse.py:88:5: E301 expected 1 blank line, found 0 - optparse.py:222:34: W602 deprecated form of raising exception optparse.py:347:31: E211 whitespace before '(' optparse.py:357:17: E201 whitespace after '{' optparse.py:472:29: E221 multiple spaces before operator - optparse.py:544:21: W601 .has_key() is deprecated, use 'in' You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: @@ -97,8 +95,6 @@ Or you can display how often each error was found:: 165 E303 too many blank lines (4) 325 E401 multiple imports on one line 3615 E501 line too long (82 characters) - 612 W601 .has_key() is deprecated, use 'in' - 1188 W602 deprecated form of raising exception Links ----- diff --git a/docs/intro.rst b/docs/intro.rst index 3be9d8ed..aba8a151 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -71,11 +71,9 @@ Example usage and output optparse.py:69:11: E401 multiple imports on one line optparse.py:77:1: E302 expected 2 blank lines, found 1 optparse.py:88:5: E301 expected 1 blank line, found 0 - optparse.py:222:34: W602 deprecated form of raising exception optparse.py:347:31: E211 whitespace before '(' optparse.py:357:17: E201 whitespace after '{' optparse.py:472:29: E221 multiple spaces before operator - optparse.py:544:21: W601 .has_key() is deprecated, use 'in' You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: @@ -103,8 +101,6 @@ Or you can display how often each error was found:: 165 E303 too many blank lines (4) 325 E401 multiple imports on one line 3615 E501 line too long (82 characters) - 612 W601 .has_key() is deprecated, use 'in' - 1188 W602 deprecated form of raising exception You can also make ``pycodestyle.py`` show the error text in different formats by using ``--format`` having options default/pylint/custom:: @@ -415,14 +411,6 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | **W6** | *Deprecation warning* | +------------+----------------------------------------------------------------------+ -| W601 | .has_key() is deprecated, use 'in' | -+------------+----------------------------------------------------------------------+ -| W602 | deprecated form of raising exception | -+------------+----------------------------------------------------------------------+ -| W603 | '<>' is deprecated, use '!=' | -+------------+----------------------------------------------------------------------+ -| W604 | backticks are deprecated, use 'repr()' | -+------------+----------------------------------------------------------------------+ | W605 | invalid escape sequence '\x' | +------------+----------------------------------------------------------------------+ | W606 | 'async' and 'await' are reserved keywords starting with Python 3.7 | diff --git a/pycodestyle.py b/pycodestyle.py index 2119d06c..4a02ff26 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1563,59 +1563,6 @@ def ambiguous_identifier(logical_line, tokens): prev_start = start -@register_check -def python_3000_has_key(logical_line, noqa): - r"""The {}.has_key() method is removed in Python 3: use the 'in' - operator. - - Okay: if "alph" in d:\n print d["alph"] - W601: assert d.has_key('alph') - """ - pos = logical_line.find('.has_key(') - if pos > -1 and not noqa: - yield pos, "W601 .has_key() is deprecated, use 'in'" - - -@register_check -def python_3000_raise_comma(logical_line): - r"""When raising an exception, use "raise ValueError('message')". - - The older form is removed in Python 3. - - Okay: raise DummyError("Message") - W602: raise DummyError, "Message" - """ - match = RAISE_COMMA_REGEX.match(logical_line) - if match and not RERAISE_COMMA_REGEX.match(logical_line): - yield match.end() - 1, "W602 deprecated form of raising exception" - - -@register_check -def python_3000_not_equal(logical_line): - r"""New code should always use != instead of <>. - - The older syntax is removed in Python 3. - - Okay: if a != 'no': - W603: if a <> 'no': - """ - pos = logical_line.find('<>') - if pos > -1: - yield pos, "W603 '<>' is deprecated, use '!='" - - -@register_check -def python_3000_backticks(logical_line): - r"""Use repr() instead of backticks in Python 3. - - Okay: val = repr(1 + 2) - W604: val = `1 + 2` - """ - pos = logical_line.find('`') - if pos > -1: - yield pos, "W604 backticks are deprecated, use 'repr()'" - - @register_check def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): r"""Invalid escape sequences are deprecated in Python 3.6. diff --git a/testsuite/W60.py b/testsuite/W60.py index 5003677d..f44552d9 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -1,18 +1,3 @@ -#: W601 -if a.has_key("b"): - print a -#: W602 -raise DummyError, "Message" -#: W602 -raise ValueError, "hello %s %s" % (1, 2) -#: Okay -raise type_, val, tb -raise Exception, Exception("f"), t -#: W603 -if x <> 0: - x = 0 -#: W604 -val = `1 + 2` #: W605:1:10 regex = '\.png$' #: W605:2:1 From 9a0bc6eecccdc50e725c4ca9f866d6e13fa9e298 Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Fri, 7 Oct 2022 00:21:38 -0400 Subject: [PATCH 023/125] E741 should work with lambdas --- pycodestyle.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 4a02ff26..0e5694be 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1494,14 +1494,18 @@ def ambiguous_identifier(logical_line, tokens): E741: I = 42 Variables can be bound in several other contexts, including class - and function definitions, 'global' and 'nonlocal' statements, - exception handlers, and 'with' and 'for' statements. + and function definitions, lambda functions, 'global' and 'nonlocal' + statements, exception handlers, and 'with' and 'for' statements. In addition, we have a special handling for function parameters. Okay: except AttributeError as o: Okay: with lock as L: Okay: foo(l=12) + Okay: foo(l=I) Okay: for a in foo(l=12): + Okay: lambda arg: arg * l + Okay: lambda a=l[I:5]: None + Okay: lambda x=a.I: None E741: except AttributeError as O: E741: with lock as l: E741: global I @@ -1510,17 +1514,23 @@ def ambiguous_identifier(logical_line, tokens): E741: def foo(l=12): E741: l = foo(l=12) E741: for l in range(10): + E741: [l for l in lines if l] + E741: lambda l: None + E741: lambda a=x[1:5], l: None + E741: lambda **l: + E741: def f(**l): E742: class I(object): E743: def l(x): """ - is_func_def = False # Set to true if 'def' is found + is_func_def = False # Set to true if 'def' or 'lambda' is found parameter_parentheses_level = 0 idents_to_avoid = ('l', 'O', 'I') prev_type, prev_text, prev_start, prev_end, __ = tokens[0] - for token_type, text, start, end, line in tokens[1:]: + for index in range(1, len(tokens)): + token_type, text, start, end, line = tokens[index] ident = pos = None # find function definitions - if prev_text == 'def': + if prev_text in {'def', 'lambda'}: is_func_def = True # update parameter parentheses level if parameter_parentheses_level == 0 and \ @@ -1545,11 +1555,15 @@ def ambiguous_identifier(logical_line, tokens): if text in idents_to_avoid: ident = text pos = start - # function parameter definitions - if is_func_def: - if text in idents_to_avoid: - ident = text - pos = start + # function / lambda parameter definitions + if ( + is_func_def and + index < len(tokens) - 1 and tokens[index + 1][1] in ':,=)' and + prev_text in {'lambda', ',', '*', '**', '('} and + text in idents_to_avoid + ): + ident = text + pos = start if prev_text == 'class': if text in idents_to_avoid: yield start, "E742 ambiguous class definition '%s'" % text From cc42c160abbd9d7452973d43537cf690863711b6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 30 Oct 2022 14:49:37 -0400 Subject: [PATCH 024/125] remove some unused regexes --- pycodestyle.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 0e5694be..29f93ea8 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -124,8 +124,6 @@ BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines'] INDENT_REGEX = re.compile(r'([ \t]*)') -RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,') -RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,.*,\s*\w+\s*$') ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b') DOCSTRING_REGEX = re.compile(r'u?r?["\']') EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[\[({][ \t]|[ \t][\]}),;:](?!=)') From c09994f46a62b3f3e036bfe99ee95d38484463b6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 01:55:45 +0000 Subject: [PATCH 025/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.5 → v3.9.0](https://github.com/asottile/reorder_python_imports/compare/v3.8.5...v3.9.0) - [github.com/asottile/pyupgrade: v3.1.0 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.2.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cd9567a4..39acafe1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.5 + rev: v3.9.0 hooks: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.1.0 + rev: v3.2.0 hooks: - id: pyupgrade args: [--py36-plus] From a13131627c67db9e422c203f391a74b3ca7315a8 Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Mon, 31 Oct 2022 22:44:15 -0400 Subject: [PATCH 026/125] Fix false positive with E741 --- pycodestyle.py | 3 ++- testsuite/python38.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 29f93ea8..a105a0f3 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1504,6 +1504,7 @@ def ambiguous_identifier(logical_line, tokens): Okay: lambda arg: arg * l Okay: lambda a=l[I:5]: None Okay: lambda x=a.I: None + Okay: if l >= 12: E741: except AttributeError as O: E741: with lock as l: E741: global I @@ -1542,7 +1543,7 @@ def ambiguous_identifier(logical_line, tokens): elif text == ')': parameter_parentheses_level -= 1 # identifiers on the lhs of an assignment operator - if token_type == tokenize.OP and '=' in text and \ + if token_type == tokenize.OP and text in {'=', ':='} and \ parameter_parentheses_level == 0: if prev_text in idents_to_avoid: ident = prev_text diff --git a/testsuite/python38.py b/testsuite/python38.py index 8bf0d4d0..faf9aa7a 100644 --- a/testsuite/python38.py +++ b/testsuite/python38.py @@ -53,3 +53,6 @@ def f3( #: E221:1:6 E221:1:19 if (x := 1) == (y := 2): pass +#: E741 +while l := 1: + pass From 10946760efb082c6616d09abfe1d6ea553704434 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 01:56:28 +0000 Subject: [PATCH 027/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.2.0 → v3.2.2](https://github.com/asottile/pyupgrade/compare/v3.2.0...v3.2.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39acafe1..7c927561 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.2.2 hooks: - id: pyupgrade args: [--py36-plus] From 56dac1348f44d2e1f0bca085d415658cd3ee9874 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Nov 2022 13:51:42 -0500 Subject: [PATCH 028/125] fix reporting of ambiguous identifier after parameter list --- pycodestyle.py | 24 ++++++++++-------------- testsuite/E74.py | 4 ++++ testsuite/python38.py | 3 +++ 3 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 testsuite/E74.py diff --git a/pycodestyle.py b/pycodestyle.py index a105a0f3..7ee2375a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1522,7 +1522,8 @@ def ambiguous_identifier(logical_line, tokens): E743: def l(x): """ is_func_def = False # Set to true if 'def' or 'lambda' is found - parameter_parentheses_level = 0 + seen_colon = False # set to true if we're done with function parameters + brace_depth = 0 idents_to_avoid = ('l', 'O', 'I') prev_type, prev_text, prev_start, prev_end, __ = tokens[0] for index in range(1, len(tokens)): @@ -1531,20 +1532,15 @@ def ambiguous_identifier(logical_line, tokens): # find function definitions if prev_text in {'def', 'lambda'}: is_func_def = True + elif is_func_def and text == ':' and brace_depth == 0: + seen_colon = True # update parameter parentheses level - if parameter_parentheses_level == 0 and \ - prev_type == tokenize.NAME and \ - token_type == tokenize.OP and text == '(': - parameter_parentheses_level = 1 - elif parameter_parentheses_level > 0 and \ - token_type == tokenize.OP: - if text == '(': - parameter_parentheses_level += 1 - elif text == ')': - parameter_parentheses_level -= 1 + if text in '([{': + brace_depth += 1 + elif text in ')]}': + brace_depth -= 1 # identifiers on the lhs of an assignment operator - if token_type == tokenize.OP and text in {'=', ':='} and \ - parameter_parentheses_level == 0: + if text == ':=' or (text == '=' and brace_depth == 0): if prev_text in idents_to_avoid: ident = prev_text pos = prev_start @@ -1557,6 +1553,7 @@ def ambiguous_identifier(logical_line, tokens): # function / lambda parameter definitions if ( is_func_def and + not seen_colon and index < len(tokens) - 1 and tokens[index + 1][1] in ':,=)' and prev_text in {'lambda', ',', '*', '**', '('} and text in idents_to_avoid @@ -1571,7 +1568,6 @@ def ambiguous_identifier(logical_line, tokens): yield start, "E743 ambiguous function definition '%s'" % text if ident: yield pos, "E741 ambiguous variable name '%s'" % ident - prev_type = token_type prev_text = text prev_start = start diff --git a/testsuite/E74.py b/testsuite/E74.py new file mode 100644 index 00000000..93d6c131 --- /dev/null +++ b/testsuite/E74.py @@ -0,0 +1,4 @@ +#: E741:1:8 +lambda l: dict(zip(l, range(len(l)))) +#: E741:1:7 E704:1:1 +def f(l): print(l, l, l) diff --git a/testsuite/python38.py b/testsuite/python38.py index faf9aa7a..536448d8 100644 --- a/testsuite/python38.py +++ b/testsuite/python38.py @@ -56,3 +56,6 @@ def f3( #: E741 while l := 1: pass +#: E741 +if (l := 1): + pass From 956ab1fa7aeffdca4eb1c7b625f8921883e1bc0d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Nov 2022 14:06:14 -0500 Subject: [PATCH 029/125] remove some leftover python 2 compat --- pycodestyle.py | 12 ++++-------- testsuite/test_shell.py | 7 ++++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 7ee2375a..b75eed00 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -47,7 +47,9 @@ 900 syntax error """ import bisect +import configparser import inspect +import io import keyword import os import re @@ -59,12 +61,6 @@ from functools import lru_cache from optparse import OptionParser -try: - from configparser import RawConfigParser - from io import TextIOWrapper -except ImportError: - from ConfigParser import RawConfigParser - # this is a performance hack. see https://bugs.python.org/issue43014 if ( sys.version_info < (3, 10) and @@ -1769,7 +1765,7 @@ def readlines(filename): def stdin_get_value(): """Read the value from stdin.""" - return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() + return io.TextIOWrapper(sys.stdin.buffer, errors='ignore').read() noqa = lru_cache(512)(re.compile(r'# no(?:qa|pep8)\b', re.I).search) @@ -2558,7 +2554,7 @@ def read_config(options, args, arglist, parser): merged together (in that order) using the read method of ConfigParser. """ - config = RawConfigParser() + config = configparser.RawConfigParser() cli_conf = options.config diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index 059d8823..a2fa35ea 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import configparser import os.path import sys import unittest @@ -15,7 +16,7 @@ def setUp(self): self._saved_stdout = sys.stdout self._saved_stderr = sys.stderr self._saved_pconfig = pycodestyle.PROJECT_CONFIG - self._saved_cpread = pycodestyle.RawConfigParser._read + self._saved_cpread = configparser.RawConfigParser._read self._saved_stdin_get_value = pycodestyle.stdin_get_value self._config_filenames = [] self.stdin = '' @@ -25,7 +26,7 @@ def setUp(self): def fake_config_parser_read(cp, fp, filename): self._config_filenames.append(filename) - pycodestyle.RawConfigParser._read = fake_config_parser_read + configparser.RawConfigParser._read = fake_config_parser_read pycodestyle.stdin_get_value = self.stdin_get_value def tearDown(self): @@ -33,7 +34,7 @@ def tearDown(self): sys.stdout = self._saved_stdout sys.stderr = self._saved_stderr pycodestyle.PROJECT_CONFIG = self._saved_pconfig - pycodestyle.RawConfigParser._read = self._saved_cpread + configparser.RawConfigParser._read = self._saved_cpread pycodestyle.stdin_get_value = self._saved_stdin_get_value def stdin_get_value(self): From 798d620cd461a1e7abac1cbba5aab66274aa3229 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Nov 2022 14:11:37 -0500 Subject: [PATCH 030/125] fix ambiguous identifiers in lambda bodies inside braces --- pycodestyle.py | 13 +++++++++---- testsuite/E74.py | 9 +++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 7ee2375a..6dc9142b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1521,7 +1521,7 @@ def ambiguous_identifier(logical_line, tokens): E742: class I(object): E743: def l(x): """ - is_func_def = False # Set to true if 'def' or 'lambda' is found + func_depth = None # set to brace depth if 'def' or 'lambda' is found seen_colon = False # set to true if we're done with function parameters brace_depth = 0 idents_to_avoid = ('l', 'O', 'I') @@ -1531,8 +1531,13 @@ def ambiguous_identifier(logical_line, tokens): ident = pos = None # find function definitions if prev_text in {'def', 'lambda'}: - is_func_def = True - elif is_func_def and text == ':' and brace_depth == 0: + func_depth = brace_depth + seen_colon = False + elif ( + func_depth is not None and + text == ':' and + brace_depth == func_depth + ): seen_colon = True # update parameter parentheses level if text in '([{': @@ -1552,7 +1557,7 @@ def ambiguous_identifier(logical_line, tokens): pos = start # function / lambda parameter definitions if ( - is_func_def and + func_depth is not None and not seen_colon and index < len(tokens) - 1 and tokens[index + 1][1] in ':,=)' and prev_text in {'lambda', ',', '*', '**', '('} and diff --git a/testsuite/E74.py b/testsuite/E74.py index 93d6c131..9bb4c581 100644 --- a/testsuite/E74.py +++ b/testsuite/E74.py @@ -2,3 +2,12 @@ lambda l: dict(zip(l, range(len(l)))) #: E741:1:7 E704:1:1 def f(l): print(l, l, l) +#: E741:2:12 +x = ( + lambda l: dict(zip(l, range(len(l)))), +) +#: E741:2:12 E741:3:12 +x = ( + lambda l: dict(zip(l, range(len(l)))), + lambda l: dict(zip(l, range(len(l)))), +) From 806bf5c7b556017fbc759f7576d2fb19aae8c464 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 23 Nov 2022 13:15:34 -0500 Subject: [PATCH 031/125] Release 2.10.0 --- CHANGES.txt | 12 +++++++++++- pycodestyle.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f7fddf7f..9570eecf 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,12 +1,22 @@ Changelog ========= +2.10.0 (2022-11-23) +------------------- + +Changes: + +* E231: allow trailing comma inside 1-tuples in `[]`. PR #1108. +* W601, W602, W603, W604: removed (no longer relevant in python 3). PR #1111. +* E741: also apply to lambdas. PR #1106. +* E741: fix false positive for comparison operators. PR #1118. + 2.9.1 (2022-08-03) ------------------ Changes: -* E275: fix false positive for yield expressions. +* E275: fix false positive for yield expressions. PR #1091. 2.9.0 (2022-07-30) ------------------ diff --git a/pycodestyle.py b/pycodestyle.py index f06f9231..f5e05cfa 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Tue, 29 Nov 2022 04:04:49 +0000 Subject: [PATCH 032/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c927561..39b7bfdd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testsuite/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-yaml - id: debug-statements @@ -18,6 +18,6 @@ repos: - id: pyupgrade args: [--py36-plus] - repo: https://github.com/pycqa/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 From 9c7d93f7cf9445ed0d8f62e29989b60fed408bec Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 02:22:42 +0000 Subject: [PATCH 033/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.2.2 → v3.3.0](https://github.com/asottile/pyupgrade/compare/v3.2.2...v3.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39b7bfdd..061253f7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.3.0 hooks: - id: pyupgrade args: [--py36-plus] From e20394e5cbd8d440c9c1868ddc25d036afcbfe45 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 13 Dec 2022 01:06:43 -0500 Subject: [PATCH 034/125] drop python3.6 github actions no longer supports it --- .github/workflows/main.yml | 14 +++++++------- .pre-commit-config.yaml | 4 ++-- setup.py | 5 +---- testsuite/test_api.py | 5 ++++- tox.ini | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 00f5e75c..b2eba4c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,10 +14,7 @@ jobs: py: 3.9 toxenv: py - os: ubuntu-latest - py: pypy3 - toxenv: py - - os: ubuntu-latest - py: 3.6 + py: pypy3.9 toxenv: py - os: ubuntu-latest py: 3.7 @@ -32,15 +29,18 @@ jobs: py: '3.10' toxenv: py - os: ubuntu-latest - py: '3.11-dev' + py: '3.11' + toxenv: py + - os: ubuntu-latest + py: '3.12-dev' toxenv: py - os: ubuntu-latest py: 3.9 toxenv: flake8 runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.py }} - run: pip install tox diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 061253f7..c477ffe3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,12 +11,12 @@ repos: rev: v3.9.0 hooks: - id: reorder-python-imports - args: [--py36-plus] + args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade rev: v3.3.0 hooks: - id: pyupgrade - args: [--py36-plus] + args: [--py37-plus] - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: diff --git a/setup.py b/setup.py index fe96737b..2fe5a11b 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ def get_long_description(): py_modules=['pycodestyle'], include_package_data=True, zip_safe=False, - python_requires='>=3.6', + python_requires='>=3.7', entry_points={ 'console_scripts': [ 'pycodestyle = pycodestyle:_main', @@ -45,9 +45,6 @@ def get_long_description(): 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 8dde32ff..38e34acf 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -329,7 +329,10 @@ def test_check_nullbytes(self): count_errors = pep8style.input_file('stdin', lines=['\x00\n']) stdout = sys.stdout.getvalue() - expected = "stdin:1:1: E901 ValueError" + if sys.version_info < (3, 12): + expected = "stdin:1:1: E901 ValueError" + else: + expected = "stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes" # noqa: E501 self.assertTrue(stdout.startswith(expected), msg='Output %r does not start with %r' % (stdout, expected)) diff --git a/tox.ini b/tox.ini index 18f1a4ff..f4a32d71 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py36, py37, py38, py39, py310, pypy3 +envlist = py, pypy3 skip_missing_interpreters = True [testenv] From dc23a7bfceead62b92f9da3f6436662e5391c0c9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 03:28:41 +0000 Subject: [PATCH 035/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.3.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.3.0...v3.3.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c477ffe3..d2aa5d19 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.0 + rev: v3.3.1 hooks: - id: pyupgrade args: [--py37-plus] From f43be155bda895d2cc0a9369c0b8c816ba123c9c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 07:14:02 +0000 Subject: [PATCH 036/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.3.1 → v3.3.2](https://github.com/asottile/pyupgrade/compare/v3.3.1...v3.3.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d2aa5d19..1a7b2b44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.3.2 hooks: - id: pyupgrade args: [--py37-plus] From 28de31778c2cdda96fdf2f0a590e302ef24c8995 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 08:01:52 +0000 Subject: [PATCH 037/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - https://github.com/asottile/reorder_python_imports → https://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) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a7b2b44..0a29d813 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,13 +7,13 @@ repos: - id: debug-statements - id: end-of-file-fixer - id: trailing-whitespace -- repo: https://github.com/asottile/reorder_python_imports +- repo: https://github.com/asottile/reorder-python-imports rev: v3.9.0 hooks: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.2 + rev: v3.4.0 hooks: - id: pyupgrade args: [--py37-plus] From 18cf7a27bca5af03bff67f22759867dd6f1372c8 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Jun 2023 20:16:42 -0400 Subject: [PATCH 038/125] remove 3.12 for now: it is not passing --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b2eba4c7..505a1d04 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,9 +31,6 @@ jobs: - os: ubuntu-latest py: '3.11' toxenv: py - - os: ubuntu-latest - py: '3.12-dev' - toxenv: py - os: ubuntu-latest py: 3.9 toxenv: flake8 From 84839aa65cdd652aa7af6b74ac24fdae3d173fd9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Jun 2023 20:12:45 -0400 Subject: [PATCH 039/125] make testsuite helper for generating errors --- testsuite/support.py | 13 ++++++++ testsuite/test_blank_lines.py | 60 ++++++++++++++--------------------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/testsuite/support.py b/testsuite/support.py index eb8b4436..a116c697 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import annotations + import os.path import re import sys from pycodestyle import Checker, BaseReport, StandardReport, readlines +from pycodestyle import StyleGuide SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -222,3 +225,13 @@ def run_tests(style): if options.testsuite: init_tests(style) return style.check_files() + + +def errors_from_src(src: str) -> list[str]: + guide = StyleGuide() + reporter = guide.init_report(InMemoryReport) + guide.input_file( + filename='in-memory-test-file.py', + lines=src.splitlines(True), + ) + return reporter.in_memory_errors diff --git a/testsuite/test_blank_lines.py b/testsuite/test_blank_lines.py index e239f8b7..fa36f236 100644 --- a/testsuite/test_blank_lines.py +++ b/testsuite/test_blank_lines.py @@ -6,7 +6,7 @@ import unittest import pycodestyle -from testsuite.support import InMemoryReport +from testsuite.support import errors_from_src class BlankLinesTestCase(unittest.TestCase): @@ -14,18 +14,6 @@ class BlankLinesTestCase(unittest.TestCase): Common code for running blank_lines tests. """ - def check(self, content): - """ - Run checks on `content` and return the the list of errors. - """ - sut = pycodestyle.StyleGuide() - reporter = sut.init_report(InMemoryReport) - sut.input_file( - filename='in-memory-test-file.py', - lines=content.splitlines(True), - ) - return reporter.in_memory_errors - def assertNoErrors(self, actual): """ Check that the actual result from the checker has no errors. @@ -43,7 +31,7 @@ def test_initial_no_blank(self): """ It will accept no blank lines at the start of the file. """ - result = self.check("""def some_function(): + result = errors_from_src("""def some_function(): pass """) @@ -54,7 +42,7 @@ def test_initial_lines_one_blank(self): It will accept 1 blank lines before the first line of actual code, even if in other places it asks for 2 """ - result = self.check(""" + result = errors_from_src(""" def some_function(): pass """) @@ -66,7 +54,7 @@ def test_initial_lines_two_blanks(self): It will accept 2 blank lines before the first line of actual code, as normal. """ - result = self.check(""" + result = errors_from_src(""" def some_function(): pass @@ -79,7 +67,7 @@ def test_method_less_blank_lines(self): It will trigger an error when less than 1 blank lin is found before method definitions. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. class X: def a(): @@ -96,7 +84,7 @@ def test_method_less_blank_lines_comment(self): It will trigger an error when less than 1 blank lin is found before method definition, ignoring comments. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. class X: def a(): @@ -114,7 +102,7 @@ def test_top_level_fewer_blank_lines(self): It will trigger an error when less 2 blank lines are found before top level definitions. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. # Second line of comment. def some_function(): @@ -148,7 +136,7 @@ def test_top_level_more_blank_lines(self): It will trigger an error when more 2 blank lines are found before top level definitions. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. # Second line of comment. @@ -179,7 +167,7 @@ def test_method_more_blank_lines(self): It will trigger an error when more than 1 blank line is found before method definition """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. class SomeCloseClass(object): @@ -211,7 +199,7 @@ def test_initial_lines_more_blank(self): It will trigger an error for more than 2 blank lines before the first line of actual code. """ - result = self.check(""" + result = errors_from_src(""" def some_function(): @@ -224,7 +212,7 @@ def test_blank_line_between_decorator(self): It will trigger an error when the decorator is followed by a blank line. """ - result = self.check("""# First line. + result = errors_from_src("""# First line. @some_decorator @@ -247,7 +235,7 @@ def test_blank_line_decorator(self): It will accept the decorators which are adjacent to the function and method definition. """ - result = self.check("""# First line. + result = errors_from_src("""# First line. @another_decorator @@ -269,7 +257,7 @@ def test_top_level_fewer_follow_lines(self): It will trigger an error when less than 2 blank lines are found between a top level definitions and other top level code. """ - result = self.check(""" + result = errors_from_src(""" def a(): print('Something') @@ -285,7 +273,7 @@ def test_top_level_fewer_follow_lines_comments(self): found between a top level definitions and other top level code, even if we have comments before """ - result = self.check(""" + result = errors_from_src(""" def a(): print('Something') @@ -306,7 +294,7 @@ def test_top_level_good_follow_lines(self): It not trigger an error when 2 blank lines are found between a top level definitions and other top level code. """ - result = self.check(""" + result = errors_from_src(""" def a(): print('Something') @@ -326,7 +314,7 @@ def test_method_fewer_follow_lines(self): It will trigger an error when less than 1 blank line is found between a method and previous definitions. """ - result = self.check(""" + result = errors_from_src(""" def a(): x = 1 def b(): @@ -342,7 +330,7 @@ def test_method_nested_fewer_follow_lines(self): found between a method and previous definitions, even when nested. """ - result = self.check(""" + result = errors_from_src(""" def a(): x = 2 @@ -361,7 +349,7 @@ def test_method_nested_less_class(self): between a method and previous definitions, even when used to define a class. """ - result = self.check(""" + result = errors_from_src(""" def a(): x = 1 class C: @@ -377,7 +365,7 @@ def test_method_nested_ok(self): found between a method and previous definitions, even when nested. """ - result = self.check(""" + result = errors_from_src(""" def a(): x = 2 @@ -412,7 +400,7 @@ def test_initial_lines_one_blanks(self): It will accept less than 3 blank lines before the first line of actual code. """ - result = self.check(""" + result = errors_from_src(""" def some_function(): @@ -426,7 +414,7 @@ def test_initial_lines_tree_blanks(self): It will accept 3 blank lines before the first line of actual code, as normal. """ - result = self.check(""" + result = errors_from_src(""" def some_function(): @@ -440,7 +428,7 @@ def test_top_level_fewer_blank_lines(self): It will trigger an error when less 3 blank lines are found before top level definitions. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. # Second line of comment. @@ -479,7 +467,7 @@ def test_top_level_more_blank_lines(self): It will trigger an error when more 2 blank lines are found before top level definitions. """ - result = self.check("""# First comment line. + result = errors_from_src("""# First comment line. # Second line of comment. @@ -513,7 +501,7 @@ def test_the_right_blanks(self): """ It will accept 3 blank for top level and 2 for nested. """ - result = self.check(""" + result = errors_from_src(""" def some_function(): From 84937582b81d570b95354e33a746947f84462c55 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Jun 2023 21:28:57 -0400 Subject: [PATCH 040/125] add fix for muting FSTRING_MIDDLE in 3.12+ --- pycodestyle.py | 5 +++++ testsuite/python36.py | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 testsuite/python36.py diff --git a/pycodestyle.py b/pycodestyle.py index f5e05cfa..4a973724 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -2010,6 +2010,11 @@ def build_tokens_line(self): continue if token_type == tokenize.STRING: text = mute_string(text) + elif ( + sys.version_info >= (3, 12) and + token_type == tokenize.FSTRING_MIDDLE + ): + text = 'x' * len(text) if prev_row: (start_row, start_col) = start if prev_row != start_row: # different row diff --git a/testsuite/python36.py b/testsuite/python36.py new file mode 100644 index 00000000..94ec2dc5 --- /dev/null +++ b/testsuite/python36.py @@ -0,0 +1,2 @@ +#: Okay +f'{hello}:{world}' From 0b13fddbe352901070ed06906e5879ee22fdeee7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 08:01:43 +0000 Subject: [PATCH 041/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.4.0 → v3.6.0](https://github.com/asottile/pyupgrade/compare/v3.4.0...v3.6.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0a29d813..ec7ddaa9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.4.0 + rev: v3.6.0 hooks: - id: pyupgrade args: [--py37-plus] From e8d84098da10d013ee686027e174814dbe4dd908 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Jun 2023 20:11:49 -0400 Subject: [PATCH 042/125] get testsuite passing on 3.12 --- .github/workflows/main.yml | 10 +++++- pycodestyle.py | 4 +-- testsuite/E10.py | 5 --- testsuite/E90.py | 10 ------ testsuite/python312.py | 9 ++++++ testsuite/test_E101.py | 18 +++++++++++ testsuite/test_E901.py | 29 ++++++++++++++++++ testsuite/test_api.py | 62 ++++++++++++++++---------------------- 8 files changed, 93 insertions(+), 54 deletions(-) create mode 100644 testsuite/python312.py create mode 100644 testsuite/test_E101.py create mode 100644 testsuite/test_E901.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 505a1d04..61c41971 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: py: 3.9 toxenv: py - os: ubuntu-latest - py: pypy3.9 + py: pypy3.10 toxenv: py - os: ubuntu-latest py: 3.7 @@ -31,6 +31,9 @@ jobs: - os: ubuntu-latest py: '3.11' toxenv: py + - os: ubuntu-latest + py: '3.12-dev' + toxenv: py - os: ubuntu-latest py: 3.9 toxenv: flake8 @@ -40,5 +43,10 @@ jobs: - uses: actions/setup-python@v4 with: python-version: ${{ matrix.py }} + if: matrix.py != '3.12-dev' + - uses: deadsnakes/action@v3.0.1 + with: + python-version: ${{ matrix.py }} + if: matrix.py == '3.12-dev' - run: pip install tox - run: tox -e ${{ matrix.toxenv }} diff --git a/pycodestyle.py b/pycodestyle.py index 4a973724..2ee3dac5 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -196,7 +196,6 @@ def tabs_or_spaces(physical_line, indent_char): These options are highly recommended! Okay: if a == 0:\n a = 1\n b = 1 - E101: if a == 0:\n a = 1\n\tb = 1 """ indent = INDENT_REGEX.match(physical_line).group(1) for offset, char in enumerate(indent): @@ -802,9 +801,10 @@ def whitespace_before_parameters(logical_line, tokens): (index < 2 or tokens[index - 2][1] != 'class') and # Allow "return (a.foo for a in range(5))" not keyword.iskeyword(prev_text) and - # 'match' and 'case' are only soft keywords ( sys.version_info < (3, 9) or + # 3.12+: type is a soft keyword but no braces after + prev_text == 'type' or not keyword.issoftkeyword(prev_text) ) ): diff --git a/testsuite/E10.py b/testsuite/E10.py index 7b425945..1a901124 100644 --- a/testsuite/E10.py +++ b/testsuite/E10.py @@ -1,8 +1,3 @@ -#: E101 W191 -for a in 'abc': - for b in 'xyz': - print a # indented with 8 spaces - print b # indented with 1 tab #: E101 E122 W191 W191 if True: pass diff --git a/testsuite/E90.py b/testsuite/E90.py index 2c18e9af..e0a10d04 100644 --- a/testsuite/E90.py +++ b/testsuite/E90.py @@ -1,6 +1,4 @@ #: E901 -} -#: E901 = [x #: E901 E101 W191 while True: @@ -8,14 +6,6 @@ pass except: print 'Whoops' -#: E122 E225 E251 E251 - -# Do not crash if code is invalid -if msg: - errmsg = msg % progress.get(cr_dbname)) - -def lasting(self, duration=300): - progress = self._progress.setdefault('foo', {} #: Okay # Issue #119 diff --git a/testsuite/python312.py b/testsuite/python312.py new file mode 100644 index 00000000..2b42833f --- /dev/null +++ b/testsuite/python312.py @@ -0,0 +1,9 @@ +#: Okay +# https://github.com/python/cpython/issues/90432: fixed in 3.12 +def foo(): + pas + +\ + +def bar(): + pass diff --git a/testsuite/test_E101.py b/testsuite/test_E101.py new file mode 100644 index 00000000..ba7d707a --- /dev/null +++ b/testsuite/test_E101.py @@ -0,0 +1,18 @@ +"""moved from testsuite files due to 3.12 making this a TokenError""" +import unittest +import sys + +from testsuite.support import errors_from_src + + +class E101Test(unittest.TestCase): + def test_E101(self): + errors = errors_from_src( + 'if True:\n' + '\tprint(1) # tabs\n' + ' print(2) # spaces\n' + ) + if sys.version_info >= (3, 12): + self.assertEqual(errors, ['W191:2:1', 'E901:3:28']) + else: + self.assertEqual(errors, ['W191:2:1', 'E101:3:1']) diff --git a/testsuite/test_E901.py b/testsuite/test_E901.py new file mode 100644 index 00000000..3633822b --- /dev/null +++ b/testsuite/test_E901.py @@ -0,0 +1,29 @@ +"""moved from testsuite files due to 3.12 changing syntax errors""" +import unittest +import sys + +from testsuite.support import errors_from_src + + +class E901Test(unittest.TestCase): + def test_closing_brace(self): + errors = errors_from_src('}\n') + if sys.version_info < (3, 12): + self.assertEqual(errors, ['E901:2:1']) + else: + self.assertEqual(errors, []) + + def test_unclosed_brace(self): + src = '''\ +if msg: + errmsg = msg % progress.get(cr_dbname)) + +def lasting(self, duration=300): + progress = self._progress.setdefault('foo', {} +''' + errors = errors_from_src(src) + if sys.version_info < (3, 12): + expected = ['E122:4:1', 'E225:4:27', 'E251:5:13', 'E251:5:15'] + else: + expected = ['E122:4:1', 'E225:4:27', 'E251:5:13', 'E251:5:15', 'E901:5:1'] # noqa: E501 + self.assertEqual(errors, expected) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 38e34acf..fa4d8a03 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -329,15 +329,18 @@ def test_check_nullbytes(self): count_errors = pep8style.input_file('stdin', lines=['\x00\n']) stdout = sys.stdout.getvalue() - if sys.version_info < (3, 12): - expected = "stdin:1:1: E901 ValueError" + if sys.version_info < (3, 11, 4): + expected = ["stdin:1:1: E901 ValueError: source code string cannot contain null bytes"] # noqa: E501 + elif sys.version_info < (3, 12): + expected = ["stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes"] # noqa: E501 else: - expected = "stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes" # noqa: E501 - self.assertTrue(stdout.startswith(expected), - msg='Output %r does not start with %r' % - (stdout, expected)) + expected = [ + "stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes", # noqa: E501 + "stdin:1:1: E901 TokenError: source code cannot contain null bytes", # noqa: E501 + ] + self.assertEqual(stdout.splitlines(), expected) self.assertFalse(sys.stderr) - self.assertEqual(count_errors, 1) + self.assertEqual(count_errors, len(expected)) def test_styleguide_unmatched_triple_quotes(self): pycodestyle.register_check(DummyChecker, ['Z701']) @@ -350,35 +353,22 @@ def test_styleguide_unmatched_triple_quotes(self): pep8style.input_file('stdin', lines=lines) stdout = sys.stdout.getvalue() - expected = 'stdin:2:5: E901 TokenError: EOF in multi-line string' - self.assertTrue(expected in stdout) - - def test_styleguide_continuation_line_outdented(self): - pycodestyle.register_check(DummyChecker, ['Z701']) - lines = [ - 'def foo():\n', - ' pass\n', - '\n', - '\\\n', - '\n', - 'def bar():\n', - ' pass\n', - ] - - pep8style = pycodestyle.StyleGuide() - count_errors = pep8style.input_file('stdin', lines=lines) - self.assertEqual(count_errors, 2) - stdout = sys.stdout.getvalue() - expected = ( - 'stdin:6:1: ' - 'E122 continuation line missing indentation or outdented' - ) - self.assertTrue(expected in stdout) - expected = 'stdin:6:1: E302 expected 2 blank lines, found 1' - self.assertTrue(expected in stdout) - - # TODO: runner - # TODO: input_file + if sys.version_info < (3, 10): + expected = [ + 'stdin:2:5: E901 TokenError: EOF in multi-line string', + 'stdin:2:26: E901 SyntaxError: EOF while scanning triple-quoted string literal', # noqa: E501 + ] + elif sys.version_info < (3, 12): + expected = [ + 'stdin:2:5: E901 TokenError: EOF in multi-line string', + 'stdin:2:6: E901 SyntaxError: unterminated triple-quoted string literal (detected at line 2)', # noqa: E501 + ] + else: + expected = [ + 'stdin:2:6: E901 SyntaxError: unterminated triple-quoted string literal (detected at line 2)', # noqa: E501 + 'stdin:2:6: E901 TokenError: EOF in multi-line string', + ] + self.assertEqual(stdout.splitlines(), expected) def test_styleguides_other_indent_size(self): pycodestyle.register_check(DummyChecker, ['Z701']) From 0d786b43a09243d7655831d0b6c4a94a8d7c7581 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 17 Jun 2023 09:22:31 -0400 Subject: [PATCH 043/125] add test file for new 3.12 syntax --- testsuite/python312.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/testsuite/python312.py b/testsuite/python312.py index 2b42833f..d2e73bc3 100644 --- a/testsuite/python312.py +++ b/testsuite/python312.py @@ -7,3 +7,23 @@ def foo(): def bar(): pass +#: Okay +# new type aliases +type X = int | str +type Y[T] = list[T] +type Z[T: str] = list[T] +#: Okay +# new generics +def f[T](x: T) -> T: + pass + + +def g[T: str, U: int](x: T, y: U) -> dict[T, U]: + pass +#: Okay +# new nested f-strings +f'{ + thing +} {f'{other} {thing}'}' +#: E201:1:4 E202:1:17 +f'{ an_error_now }' From 6fddf7399d70627c46d1cc82bb3c02da2d708ec4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 17 Jun 2023 09:58:23 -0400 Subject: [PATCH 044/125] 3.12: format specs are not an error --- pycodestyle.py | 53 +++++++++++++++++++++++++++--------------- testsuite/E12.py | 2 +- testsuite/python312.py | 2 ++ 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 2ee3dac5..f9c236a1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -150,6 +150,13 @@ DUNDER_REGEX = re.compile(r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = ") BLANK_EXCEPT_REGEX = re.compile(r"except\s*:") +if sys.version_info >= (3, 12): + FSTRING_START = tokenize.FSTRING_START + FSTRING_MIDDLE = tokenize.FSTRING_MIDDLE + FSTRING_END = tokenize.FSTRING_END +else: + FSTRING_START = FSTRING_MIDDLE = FSTRING_END = -1 + _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} @@ -494,7 +501,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): @register_check -def missing_whitespace(logical_line): +def missing_whitespace(logical_line, tokens): r"""Each comma, semicolon or colon should be followed by whitespace. Okay: [a, b] @@ -508,20 +515,31 @@ def missing_whitespace(logical_line): E231: foo(bar,baz) E231: [{'a':'b'}] """ - line = logical_line - for index in range(len(line) - 1): - char = line[index] - next_char = line[index + 1] - if char in ',;:' and next_char not in WHITESPACE: - before = line[:index] - if char == ':' and before.count('[') > before.count(']') and \ - before.rfind('{') < before.rfind('['): - continue # Slice syntax, no space required - if char == ',' and next_char in ')]': - continue # Allow tuple with only one element: (3,) - if char == ':' and next_char == '=' and sys.version_info >= (3, 8): - continue # Allow assignment expression - yield index, "E231 missing whitespace after '%s'" % char + brace_stack = [] + for tok in tokens: + if tok.type == tokenize.OP and tok.string in {'[', '(', '{'}: + brace_stack.append(tok.string) + elif tok.type == FSTRING_START: + brace_stack.append('f') + elif brace_stack: + if tok.type == tokenize.OP and tok.string in {']', ')', '}'}: + brace_stack.pop() + elif tok.type == FSTRING_END: + brace_stack.pop() + + if tok.type == tokenize.OP and tok.string in {',', ';', ':'}: + next_char = tok.line[tok.end[1]:tok.end[1] + 1] + if next_char not in WHITESPACE and next_char not in '\r\n': + # slice + if tok.string == ':' and brace_stack[-1:] == ['[']: + continue + # 3.12+ fstring format specifier + elif tok.string == ':' and brace_stack[-2:] == ['f', '{']: + continue + # tuple (and list for some reason?) + elif tok.string == ',' and next_char in ')]': + continue + yield tok.end, f'E231 missing whitespace after {tok.string!r}' @register_check @@ -2010,10 +2028,7 @@ def build_tokens_line(self): continue if token_type == tokenize.STRING: text = mute_string(text) - elif ( - sys.version_info >= (3, 12) and - token_type == tokenize.FSTRING_MIDDLE - ): + elif token_type == FSTRING_MIDDLE: text = 'x' * len(text) if prev_row: (start_row, start_col) = start diff --git a/testsuite/E12.py b/testsuite/E12.py index 968382c3..241bad0d 100644 --- a/testsuite/E12.py +++ b/testsuite/E12.py @@ -366,7 +366,7 @@ def example_issue254(): # more stuff ) ) -#: E701:1:8 E122:2:1 E203:4:8 E128:5:1 +#: E701:1:8 E231:1:9 E122:2:1 E203:4:8 E128:5:1 if True:\ print(True) diff --git a/testsuite/python312.py b/testsuite/python312.py index d2e73bc3..aabb6a42 100644 --- a/testsuite/python312.py +++ b/testsuite/python312.py @@ -27,3 +27,5 @@ def g[T: str, U: int](x: T, y: U) -> dict[T, U]: } {f'{other} {thing}'}' #: E201:1:4 E202:1:17 f'{ an_error_now }' +#: Okay +f'{x:02x}' From 2f040e85af2338afc875682ca16c68174fd405b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 08:45:55 +0000 Subject: [PATCH 045/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ec7ddaa9..90256d6c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.9.0 + rev: v3.10.0 hooks: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.6.0 + rev: v3.7.0 hooks: - id: pyupgrade args: [--py37-plus] From f661e95ee8a0507e2575b6bb4f731f8b4f24c3b8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jul 2023 09:40:10 +0000 Subject: [PATCH 046/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.7.0 → v3.8.0](https://github.com/asottile/pyupgrade/compare/v3.7.0...v3.8.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 90256d6c..757b2c07 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.7.0 + rev: v3.8.0 hooks: - id: pyupgrade args: [--py37-plus] From 233b9002a7d27a957c0c7eaf9022d884c8a87d0a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 10:16:05 +0000 Subject: [PATCH 047/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.8.0 → v3.9.0](https://github.com/asottile/pyupgrade/compare/v3.8.0...v3.9.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 757b2c07..f54a2d22 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.8.0 + rev: v3.9.0 hooks: - id: pyupgrade args: [--py37-plus] From 9f6a18169bb38f33a6893a32033db5ffb61c85f7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 30 Jul 2022 15:13:05 -0400 Subject: [PATCH 048/125] Revert "Merge pull request #1085 from PyCQA/revert-1041" This reverts commit ab806f3f9133ca24366b6254e499f0363f6bf5ec, reversing changes made to d3566623bb451f6c7f1b65cc4f8538d2540da9e6. --- pycodestyle.py | 13 ++++--------- testsuite/E72.py | 9 ++++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index f9c236a1..93638aa8 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -128,8 +128,10 @@ r'\s*(?(1)|(None|False|True))\b') COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?%&^]+|:=)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') @@ -1458,13 +1460,6 @@ def comparison_type(logical_line, noqa): Okay: if isinstance(obj, int): E721: if type(obj) is type(1): - - When checking if an object is a string, keep in mind that it might - be a unicode string too! In Python 2.3, str and unicode have a - common base class, basestring, so you can do: - - Okay: if isinstance(obj, basestring): - Okay: if type(a1) is type(b1): """ match = COMPARE_TYPE_REGEX.search(logical_line) if match and not noqa: diff --git a/testsuite/E72.py b/testsuite/E72.py index d127ff77..61e17eb2 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -4,7 +4,7 @@ #: E721 if type(res) != type(""): pass -#: E721 +#: Okay import types if res == types.IntType: @@ -47,8 +47,6 @@ pass if isinstance(res, types.MethodType): pass -if type(a) != type(b) or type(a) == type(ccc): - pass #: Okay def func_histype(a, b, c): pass @@ -81,6 +79,11 @@ def func_histype(a, b, c): except Exception: pass #: Okay +from . import custom_types as types + +red = types.ColorTypeRED +red is types.ColorType.RED +#: Okay from . import compute_type if compute_type(foo) == 5: From ac223bde3970c50d679ee4dae6e2b3b8f5fad3c2 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 30 Jul 2022 14:48:12 -0400 Subject: [PATCH 049/125] allow `is` and `is not` for type compares --- pycodestyle.py | 6 +++--- testsuite/E72.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 93638aa8..876fc0db 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -129,8 +129,8 @@ COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?%&^]+|:=)(\s*)') @@ -1459,7 +1459,7 @@ def comparison_type(logical_line, noqa): Do not compare types directly. Okay: if isinstance(obj, int): - E721: if type(obj) is type(1): + E721: if type(obj) == type(1): """ match = COMPARE_TYPE_REGEX.search(logical_line) if match and not noqa: diff --git a/testsuite/E72.py b/testsuite/E72.py index 61e17eb2..ac55a958 100644 --- a/testsuite/E72.py +++ b/testsuite/E72.py @@ -9,7 +9,7 @@ if res == types.IntType: pass -#: E721 +#: Okay import types if type(res) is not types.ListType: @@ -26,9 +26,9 @@ assert type(res) == type((0)) #: E721 assert type(res) != type((1, )) -#: E721 +#: Okay assert type(res) is type((1, )) -#: E721 +#: Okay assert type(res) is not type((1, )) #: E211 E721 assert type(res) == type ([2, ]) From 5f24f6dafb805e7ebb19abd9f1887d30d2711aac Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 13:11:50 -0400 Subject: [PATCH 050/125] 3.12: handle multiline FSTRING_MIDDLE --- pycodestyle.py | 5 ++++- testsuite/E50.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 876fc0db..9c71c573 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -2119,7 +2119,10 @@ def maybe_check_physical(self, token, prev_physical): self.check_physical(prev_physical) else: self.check_physical(token[4]) - elif token[0] == tokenize.STRING and '\n' in token[1]: + elif ( + token[0] in {tokenize.STRING, FSTRING_MIDDLE} and + '\n' in token[1] + ): # Less obviously, a string that contains newlines is a # multiline string, either triple-quoted or with internal # newlines backslash-escaped. Check every physical line in diff --git a/testsuite/E50.py b/testsuite/E50.py index bcf3bdce..ab7ddd45 100644 --- a/testsuite/E50.py +++ b/testsuite/E50.py @@ -69,6 +69,11 @@ #: E501 W505 '''same thing, but this time without a terminal newline in the string long long long long long long long long long long long long long long long long line''' +#: E501 +if True: + x = f""" + covdefaults>=1.2; python_version == '2.7' or python_version == '{py_ver}' + """ # # issue 224 (unavoidable long lines in docstrings) #: Okay From 933eed867163b3e4da2b99b6473751cc25eaba06 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 13:38:26 -0400 Subject: [PATCH 051/125] drop python 3.7 support --- .github/workflows/main.yml | 3 --- .pre-commit-config.yaml | 4 ++-- pycodestyle.py | 17 ++++++++--------- setup.py | 2 +- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 61c41971..393067b1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,6 @@ jobs: - os: ubuntu-latest py: pypy3.10 toxenv: py - - os: ubuntu-latest - py: 3.7 - toxenv: py - os: ubuntu-latest py: 3.8 toxenv: py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f54a2d22..4a68e31a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,12 +11,12 @@ repos: rev: v3.10.0 hooks: - id: reorder-python-imports - args: [--py37-plus] + args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade rev: v3.9.0 hooks: - id: pyupgrade - args: [--py37-plus] + args: [--py38-plus] - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: diff --git a/pycodestyle.py b/pycodestyle.py index 9c71c573..1f2f2344 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -66,7 +66,7 @@ sys.version_info < (3, 10) and callable(getattr(tokenize, '_compile', None)) ): # pragma: no cover (>', '**', '*', '+', '-']) ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-', '@']) WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) -ASSIGNMENT_EXPRESSION_OP = [':='] if sys.version_info >= (3, 8) else [] WS_NEEDED_OPERATORS = frozenset([ '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=', - 'and', 'in', 'is', 'or', '->'] + - ASSIGNMENT_EXPRESSION_OP) + 'and', 'in', 'is', 'or', '->', ':=']) WHITESPACE = frozenset(' \t\xa0') NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT]) @@ -1219,11 +1217,12 @@ def compound_statements(logical_line): counts = {char: 0 for char in '{}[]()'} while -1 < found < last_char: update_counts(line[prev_found:found], counts) - if ((counts['{'] <= counts['}'] and # {'a': 1} (dict) - counts['['] <= counts[']'] and # [1:2] (slice) - counts['('] <= counts[')']) and # (annotation) - not (sys.version_info >= (3, 8) and - line[found + 1] == '=')): # assignment expression + if ( + counts['{'] <= counts['}'] and # {'a': 1} (dict) + counts['['] <= counts[']'] and # [1:2] (slice) + counts['('] <= counts[')'] and # (annotation) + line[found + 1] != '=' # assignment expression + ): lambda_kw = LAMBDA_REGEX.search(line, 0, found) if lambda_kw: before = line[:lambda_kw.start()].rstrip() diff --git a/setup.py b/setup.py index 2fe5a11b..ab735f9f 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ def get_long_description(): py_modules=['pycodestyle'], include_package_data=True, zip_safe=False, - python_requires='>=3.7', + python_requires='>=3.8', entry_points={ 'console_scripts': [ 'pycodestyle = pycodestyle:_main', From 19b55a9d2b2fe02121e0daead468954cdbfacaee Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 14:10:16 -0400 Subject: [PATCH 052/125] remove python 2.x handling of <> and -> operators --- pycodestyle.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 1f2f2344..65c59b9e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -107,7 +107,7 @@ ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-', '@']) WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) WS_NEEDED_OPERATORS = frozenset([ - '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', + '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<', '>', '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=', 'and', 'in', 'is', 'or', '->', ':=']) WHITESPACE = frozenset(' \t\xa0') @@ -905,10 +905,6 @@ def missing_whitespace_around_operator(logical_line, tokens): yield (need_space[0], "E225 missing whitespace around operator") need_space = False - elif text == '>' and prev_text in ('<', '-'): - # Tolerate the "<>" operator, even if running Python 3 - # Deal with Python 3's annotated return value "->" - pass elif ( # def f(a, /, b): # ^ From 2c14709e7daa2633c6512901b9aeda4624fd6966 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 14:18:07 -0400 Subject: [PATCH 053/125] async/await are now always keywords pycodestyle no longer supports python 3.6 --- docs/intro.rst | 2 -- pycodestyle.py | 73 +----------------------------------------------- testsuite/W60.py | 52 ---------------------------------- 3 files changed, 1 insertion(+), 126 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index aba8a151..cdf5312b 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -413,8 +413,6 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | W605 | invalid escape sequence '\x' | +------------+----------------------------------------------------------------------+ -| W606 | 'async' and 'await' are reserved keywords starting with Python 3.7 | -+------------+----------------------------------------------------------------------+ **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, **E133**, diff --git a/pycodestyle.py b/pycodestyle.py index 1f2f2344..c8d2aa34 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -102,7 +102,7 @@ PyCF_ONLY_AST = 1024 SINGLETONS = frozenset(['False', 'None', 'True']) -KEYWORDS = frozenset(keyword.kwlist + ['print', 'async']) - SINGLETONS +KEYWORDS = frozenset(keyword.kwlist + ['print']) - SINGLETONS UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-', '@']) WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) @@ -493,7 +493,6 @@ def missing_whitespace_after_keyword(logical_line, tokens): if (tok0.end == tok1.start and keyword.iskeyword(tok0.string) and tok0.string not in SINGLETONS and - tok0.string not in ('async', 'await') and not (tok0.string == 'except' and tok1.string == '*') and not (tok0.string == 'yield' and tok1.string == ')') and tok1.string not in ':\n'): @@ -1645,76 +1644,6 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): pos = string.find('\\', pos + 1) -@register_check -def python_3000_async_await_keywords(logical_line, tokens): - """'async' and 'await' are reserved keywords starting at Python 3.7. - - W606: async = 42 - W606: await = 42 - Okay: async def read(db):\n data = await db.fetch('SELECT ...') - """ - # The Python tokenize library before Python 3.5 recognizes - # async/await as a NAME token. Therefore, use a state machine to - # look for the possible async/await constructs as defined by the - # Python grammar: - # https://docs.python.org/3/reference/grammar.html - - state = None - for token_type, text, start, end, line in tokens: - error = False - - if token_type == tokenize.NL: - continue - - if state is None: - if token_type == tokenize.NAME: - if text == 'async': - state = ('async_stmt', start) - elif text == 'await': - state = ('await', start) - elif (token_type == tokenize.NAME and - text in ('def', 'for')): - state = ('define', start) - - elif state[0] == 'async_stmt': - if token_type == tokenize.NAME and text in ('def', 'with', 'for'): - # One of funcdef, with_stmt, or for_stmt. Return to - # looking for async/await names. - state = None - else: - error = True - elif state[0] == 'await': - if token_type == tokenize.NAME: - # An await expression. Return to looking for async/await - # names. - state = None - elif token_type == tokenize.OP and text == '(': - state = None - else: - error = True - elif state[0] == 'define': - if token_type == tokenize.NAME and text in ('async', 'await'): - error = True - else: - state = None - - if error: - yield ( - state[1], - "W606 'async' and 'await' are reserved keywords starting with " - "Python 3.7", - ) - state = None - - # Last token - if state is not None: - yield ( - state[1], - "W606 'async' and 'await' are reserved keywords starting with " - "Python 3.7", - ) - - ######################################################################## @register_check def maximum_doc_length(logical_line, max_doc_length, noqa, tokens): diff --git a/testsuite/W60.py b/testsuite/W60.py index f44552d9..71263f94 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -29,55 +29,3 @@ regex = ''' \w ''' # noqa -#: W606 -async = 42 -#: W606 -await = 42 -#: W606 -await 42 -#: W606 -await 'test' -#: W606 -def async(): - pass -#: W606 -def await(): - pass -#: W606 -class async: - pass -#: W606 -class await: - pass -#: Okay -async def read_data(db): - data = await db.fetch('SELECT ...') -#: Okay -if await fut: - pass -if (await fut): - pass -if await fut + 1: - pass -if (await fut) + 1: - pass -pair = await fut, 'spam' -pair = (await fut), 'spam' -with await fut, open(): - pass -with (await fut), open(): - pass -await foo()['spam'].baz()() -return await coro() -return (await coro()) -res = await coro() ** 2 -res = (await coro()) ** 2 -func(a1=await coro(), a2=0) -func(a1=(await coro()), a2=0) -await foo() + await bar() -(await foo()) + (await bar()) --await foo() --(await foo()) -(await - foo()) -await(await foo()) From f04e2a1f5d37f8668519f123b97bf38165994b72 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 15:03:50 -0400 Subject: [PATCH 054/125] combine whitespace around operator checks this slightly simplifies some checking --- pycodestyle.py | 100 ++++++++++++++++++++--------------------- testsuite/E22.py | 2 + testsuite/test_E901.py | 4 +- 3 files changed, 54 insertions(+), 52 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 35258160..36acd8b0 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -499,48 +499,6 @@ def missing_whitespace_after_keyword(logical_line, tokens): yield tok0.end, "E275 missing whitespace after keyword" -@register_check -def missing_whitespace(logical_line, tokens): - r"""Each comma, semicolon or colon should be followed by whitespace. - - Okay: [a, b] - Okay: (3,) - Okay: a[3,] = 1 - Okay: a[1:4] - Okay: a[:4] - Okay: a[1:] - Okay: a[1:4:2] - E231: ['a','b'] - E231: foo(bar,baz) - E231: [{'a':'b'}] - """ - brace_stack = [] - for tok in tokens: - if tok.type == tokenize.OP and tok.string in {'[', '(', '{'}: - brace_stack.append(tok.string) - elif tok.type == FSTRING_START: - brace_stack.append('f') - elif brace_stack: - if tok.type == tokenize.OP and tok.string in {']', ')', '}'}: - brace_stack.pop() - elif tok.type == FSTRING_END: - brace_stack.pop() - - if tok.type == tokenize.OP and tok.string in {',', ';', ':'}: - next_char = tok.line[tok.end[1]:tok.end[1] + 1] - if next_char not in WHITESPACE and next_char not in '\r\n': - # slice - if tok.string == ':' and brace_stack[-1:] == ['[']: - continue - # 3.12+ fstring format specifier - elif tok.string == ':' and brace_stack[-2:] == ['f', '{']: - continue - # tuple (and list for some reason?) - elif tok.string == ',' and next_char in ')]': - continue - yield tok.end, f'E231 missing whitespace after {tok.string!r}' - - @register_check def indentation(logical_line, previous_logical, indent_char, indent_level, previous_indent_level, @@ -856,14 +814,16 @@ def whitespace_around_operator(logical_line): @register_check -def missing_whitespace_around_operator(logical_line, tokens): - r"""Surround operators with a single space on either side. +def missing_whitespace(logical_line, tokens): + r"""Surround operators with the correct amount of whitespace. - Always surround these binary operators with a single space on either side: assignment (=), augmented assignment (+=, -= etc.), comparisons (==, <, >, !=, <=, >=, in, not in, is, is not), Booleans (and, or, not). + - Each comma, semicolon or colon should be followed by whitespace. + - If operators with different priorities are used, consider adding whitespace around the operators with the lowest priorities. @@ -874,6 +834,13 @@ def missing_whitespace_around_operator(logical_line, tokens): Okay: c = (a + b) * (a - b) Okay: foo(bar, key='word', *args, **kwargs) Okay: alpha[:-i] + Okay: [a, b] + Okay: (3,) + Okay: a[3,] = 1 + Okay: a[1:4] + Okay: a[:4] + Okay: a[1:] + Okay: a[1:4:2] E225: i=i+1 E225: submitted +=1 @@ -884,19 +851,52 @@ def missing_whitespace_around_operator(logical_line, tokens): E226: hypot2 = x*x + y*y E227: c = a|b E228: msg = fmt%(errno, errmsg) + E231: ['a','b'] + E231: foo(bar,baz) + E231: [{'a':'b'}] """ - parens = 0 need_space = False prev_type = tokenize.OP prev_text = prev_end = None operator_types = (tokenize.OP, tokenize.NAME) + brace_stack = [] for token_type, text, start, end, line in tokens: + if token_type == tokenize.OP and text in {'[', '(', '{'}: + brace_stack.append(text) + elif token_type == FSTRING_START: + brace_stack.append('f') + elif token_type == tokenize.NAME and text == 'lambda': + brace_stack.append('l') + elif brace_stack: + if token_type == tokenize.OP and text in {']', ')', '}'}: + brace_stack.pop() + elif token_type == FSTRING_END: + brace_stack.pop() + elif ( + brace_stack[-1] == 'l' and + token_type == tokenize.OP and + text == ':' + ): + brace_stack.pop() + if token_type in SKIP_COMMENTS: continue - if text in ('(', 'lambda'): - parens += 1 - elif text == ')': - parens -= 1 + + if token_type == tokenize.OP and text in {',', ';', ':'}: + next_char = line[end[1]:end[1] + 1] + if next_char not in WHITESPACE and next_char not in '\r\n': + # slice + if text == ':' and brace_stack[-1:] == ['[']: + pass + # 3.12+ fstring format specifier + elif text == ':' and brace_stack[-2:] == ['f', '{']: + pass + # tuple (and list for some reason?) + elif text == ',' and next_char in ')]': + pass + else: + yield end, f'E231 missing whitespace after {text!r}' + if need_space: if start != prev_end: # Found a (probably) needed space @@ -933,7 +933,7 @@ def missing_whitespace_around_operator(logical_line, tokens): "around %s operator" % (code, optype)) need_space = False elif token_type in operator_types and prev_end is not None: - if text == '=' and parens: + if text == '=' and brace_stack and brace_stack[-1] in {'l', '('}: # Allow keyword args or defaults: foo(bar=None). pass elif text in WS_NEEDED_OPERATORS: diff --git a/testsuite/E22.py b/testsuite/E22.py index 7ea27927..ba3f3960 100644 --- a/testsuite/E22.py +++ b/testsuite/E22.py @@ -98,6 +98,8 @@ c = (a +b)*(a - b) #: E225 E226 c = (a+ b)*(a - b) +#: E225 +x[lambda: None]=1 #: #: E226 diff --git a/testsuite/test_E901.py b/testsuite/test_E901.py index 3633822b..d94e9233 100644 --- a/testsuite/test_E901.py +++ b/testsuite/test_E901.py @@ -23,7 +23,7 @@ def lasting(self, duration=300): ''' errors = errors_from_src(src) if sys.version_info < (3, 12): - expected = ['E122:4:1', 'E225:4:27', 'E251:5:13', 'E251:5:15'] + expected = ['E122:4:1', 'E251:5:13', 'E251:5:15'] else: - expected = ['E122:4:1', 'E225:4:27', 'E251:5:13', 'E251:5:15', 'E901:5:1'] # noqa: E501 + expected = ['E122:4:1', 'E251:5:13', 'E251:5:15', 'E901:5:1'] # noqa: E501 self.assertEqual(errors, expected) From eeeca60ace678f093f3bfeda0cd4ae243a5346ae Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 15:09:34 -0400 Subject: [PATCH 055/125] E225: fix false-positive in 3.12 --- pycodestyle.py | 12 ++++++++++-- testsuite/python38.py | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 36acd8b0..24421d05 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -933,8 +933,16 @@ def missing_whitespace(logical_line, tokens): "around %s operator" % (code, optype)) need_space = False elif token_type in operator_types and prev_end is not None: - if text == '=' and brace_stack and brace_stack[-1] in {'l', '('}: - # Allow keyword args or defaults: foo(bar=None). + if ( + text == '=' and ( + # allow lambda default args: lambda x=None: None + brace_stack[-1:] == ['l'] or + # allow keyword args or defaults: foo(bar=None). + brace_stack[-1:] == ['('] or + # allow python 3.8 fstring repr specifier + brace_stack[-2:] == ['f', '{'] + ) + ): pass elif text in WS_NEEDED_OPERATORS: need_space = True diff --git a/testsuite/python38.py b/testsuite/python38.py index 536448d8..44737fed 100644 --- a/testsuite/python38.py +++ b/testsuite/python38.py @@ -59,3 +59,5 @@ def f3( #: E741 if (l := 1): pass +#: Okay +f'{x=}' From bb8ed07c4d3f4b21a4673d4b60fc6d0d81a1e4fe Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jul 2023 16:03:28 -0400 Subject: [PATCH 056/125] 3.12+ handle W605 for fstrings --- pycodestyle.py | 34 +++++++++++++++++----------------- testsuite/W60.py | 2 ++ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 24421d05..3224b62b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1620,32 +1620,32 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): 'U', ] - for token_type, text, start, end, line in tokens: - if token_type == tokenize.STRING: - start_line, start_col = start - quote = text[-3:] if text[-3:] in ('"""', "'''") else text[-1] + prefixes = [] + for token_type, text, start, _, _ in tokens: + if token_type in {tokenize.STRING, FSTRING_START}: # Extract string modifiers (e.g. u or r) - quote_pos = text.index(quote) - prefix = text[:quote_pos].lower() - start = quote_pos + len(quote) - string = text[start:-len(quote)] + prefixes.append(text[:text.index(text[-1])].lower()) - if 'r' not in prefix: - pos = string.find('\\') + if token_type in {tokenize.STRING, FSTRING_MIDDLE}: + if 'r' not in prefixes[-1]: + start_line, start_col = start + pos = text.find('\\') while pos >= 0: pos += 1 - if string[pos] not in valid: - line = start_line + string.count('\n', 0, pos) + if text[pos] not in valid: + line = start_line + text.count('\n', 0, pos) if line == start_line: - col = start_col + len(prefix) + len(quote) + pos + col = start_col + pos else: - col = pos - string.rfind('\n', 0, pos) - 1 + col = pos - text.rfind('\n', 0, pos) - 1 yield ( (line, col - 1), - "W605 invalid escape sequence '\\%s'" % - string[pos], + f"W605 invalid escape sequence '\\{text[pos]}'" ) - pos = string.find('\\', pos + 1) + pos = text.find('\\', pos + 1) + + if token_type in {tokenize.STRING, FSTRING_END}: + prefixes.pop() ######################################################################## diff --git a/testsuite/W60.py b/testsuite/W60.py index 71263f94..cf719b4d 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -15,6 +15,8 @@ with \_ somewhere in the middle """ +#: W605:1:3 +f"\d" #: Okay regex = r'\.png$' regex = '\\.png$' From 5f2dc8655c9a0d6092d13e64cdbd2e8013458d72 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 18:03:48 -0400 Subject: [PATCH 057/125] make E231 offsets match pycodestyle 2.10 --- pycodestyle.py | 2 +- testsuite/E12.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3224b62b..3a2e1656 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -895,7 +895,7 @@ def missing_whitespace(logical_line, tokens): elif text == ',' and next_char in ')]': pass else: - yield end, f'E231 missing whitespace after {text!r}' + yield start, f'E231 missing whitespace after {text!r}' if need_space: if start != prev_end: diff --git a/testsuite/E12.py b/testsuite/E12.py index 241bad0d..dabac0d7 100644 --- a/testsuite/E12.py +++ b/testsuite/E12.py @@ -366,7 +366,7 @@ def example_issue254(): # more stuff ) ) -#: E701:1:8 E231:1:9 E122:2:1 E203:4:8 E128:5:1 +#: E701:1:8 E231:1:8 E122:2:1 E203:4:8 E128:5:1 if True:\ print(True) From 2f9a0b5c3a0fdb7fd3f5c6ace95a18dabeb0b51b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 18:12:46 -0400 Subject: [PATCH 058/125] E721: have the message better match the check --- pycodestyle.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3a2e1656..bcd857a5 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1456,11 +1456,12 @@ def comparison_negative(logical_line): @register_check def comparison_type(logical_line, noqa): - r"""Object type comparisons should always use isinstance(). + r"""Object type comparisons should `is` / `is not` / `isinstance()`. Do not compare types directly. Okay: if isinstance(obj, int): + Okay: if type(obj) is int: E721: if type(obj) == type(1): """ match = COMPARE_TYPE_REGEX.search(logical_line) @@ -1468,7 +1469,11 @@ def comparison_type(logical_line, noqa): inst = match.group(1) if inst and inst.isidentifier() and inst not in SINGLETONS: return # Allow comparison for types which are not obvious - yield match.start(), "E721 do not compare types, use 'isinstance()'" + yield ( + match.start(), + "E721 do not compare types, for exact checks use `is` / `is not`, " + "for instance checks use `isinstance()`", + ) @register_check From dc633e222dca50762500a3c91fdb282d40749223 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 18:31:47 -0400 Subject: [PATCH 059/125] improve coverage --- .coveragerc | 40 ---------------------------------------- pycodestyle.py | 16 ++++++++-------- setup.cfg | 15 +++++++++++++++ testsuite/support.py | 35 +++++++++++++++++------------------ testsuite/test_E101.py | 4 ++-- testsuite/test_E901.py | 8 ++++---- testsuite/test_api.py | 12 ++++++------ tox.ini | 4 +++- 8 files changed, 55 insertions(+), 79 deletions(-) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 57fc88ec..00000000 --- a/.coveragerc +++ /dev/null @@ -1,40 +0,0 @@ -[run] -source = . -branch = true -parallel = true -omit = - */.tox/* - */__main__.py - */setup.py - */venv*/* - # TODO: separate the tests from the test data - testsuite/E*.py - testsuite/W*.py - testsuite/latin-1.py - testsuite/noqa.py - testsuite/python*.py - testsuite/utf-8-bom.py - -[report] -show_missing = True -skip_covered = True -# TODO: increase this -fail_under = 90 -exclude_lines = - # a more strict default pragma - \# pragma: no cover\b - - # allow defensive code - ^\s*raise AssertionError\b - ^\s*raise NotImplementedError\b - ^\s*return NotImplemented\b - ^\s*raise$ - - # typing-related code - ^if (False|TYPE_CHECKING): - : \.\.\.$ - ^ +\.\.\.$ - -> ['"]?NoReturn['"]?: - - # non-runnable code - if __name__ == ['"]__main__['"]:$ diff --git a/pycodestyle.py b/pycodestyle.py index bcd857a5..6836ac1e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -73,9 +73,9 @@ DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504' try: - if sys.platform == 'win32': + if sys.platform == 'win32': # pragma: win32 cover USER_CONFIG = os.path.expanduser(r'~\.pycodestyle') - else: + else: # pragma: win32 no cover USER_CONFIG = os.path.join( os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), 'pycodestyle' @@ -150,11 +150,11 @@ DUNDER_REGEX = re.compile(r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = ") BLANK_EXCEPT_REGEX = re.compile(r"except\s*:") -if sys.version_info >= (3, 12): +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: +else: # pragma: <3.12 cover FSTRING_START = FSTRING_MIDDLE = FSTRING_END = -1 _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} @@ -863,14 +863,14 @@ def missing_whitespace(logical_line, tokens): for token_type, text, start, end, line in tokens: if token_type == tokenize.OP and text in {'[', '(', '{'}: brace_stack.append(text) - elif token_type == FSTRING_START: + elif token_type == FSTRING_START: # pragma: >=3.12 cover brace_stack.append('f') elif token_type == tokenize.NAME and text == 'lambda': brace_stack.append('l') elif brace_stack: if token_type == tokenize.OP and text in {']', ')', '}'}: brace_stack.pop() - elif token_type == FSTRING_END: + elif token_type == FSTRING_END: # pragma: >=3.12 cover brace_stack.pop() elif ( brace_stack[-1] == 'l' and @@ -889,7 +889,7 @@ def missing_whitespace(logical_line, tokens): if text == ':' and brace_stack[-1:] == ['[']: pass # 3.12+ fstring format specifier - elif text == ':' and brace_stack[-2:] == ['f', '{']: + elif text == ':' and brace_stack[-2:] == ['f', '{']: # pragma: >=3.12 cover # noqa: E501 pass # tuple (and list for some reason?) elif text == ',' and next_char in ')]': @@ -1960,7 +1960,7 @@ def build_tokens_line(self): continue if token_type == tokenize.STRING: text = mute_string(text) - elif token_type == FSTRING_MIDDLE: + elif token_type == FSTRING_MIDDLE: # pragma: >=3.12 cover text = 'x' * len(text) if prev_row: (start_row, start_col) = start diff --git a/setup.cfg b/setup.cfg index 73ae4e79..6ea2d8ce 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,3 +9,18 @@ select = ignore = E226,E24,W504 max_line_length = 79 max_doc_length = 72 + +[coverage:run] +plugins = covdefaults +omit = + # TODO: separate the tests from the test data + testsuite/E*.py + testsuite/W*.py + testsuite/latin-1.py + testsuite/noqa.py + testsuite/python*.py + testsuite/utf-8-bom.py +parallel = true + +[coverage:report] +fail_under = 93 diff --git a/testsuite/support.py b/testsuite/support.py index a116c697..39a3380a 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -40,20 +40,19 @@ def error(self, line_number, offset, text, check): self.counters[code] = 1 detailed_code = '%s:%s:%s' % (code, line_number, offset + 1) # Don't care about expected errors or warnings - if code in self.expected or detailed_code in self.expected: - return - self._deferred_print.append( - (line_number, offset, detailed_code, text[5:], check.__doc__)) - self.file_errors += 1 - self.total_errors += 1 - return code + if code not in self.expected and detailed_code not in self.expected: # pragma: no cover # noqa: E501 + err = (line_number, offset, detailed_code, text[5:], check.__doc__) + self._deferred_print.append(err) + self.file_errors += 1 + self.total_errors += 1 + return code def get_file_results(self): # Check if the expected errors were found label = '%s:%s:1' % (self.filename, self.line_offset) for extended_code in self.expected: code = extended_code.split(':')[0] - if not self.counters.get(code): + if not self.counters.get(code): # pragma: no cover self.file_errors += 1 self.total_errors += 1 print('%s: error %s not found' % (label, extended_code)) @@ -61,25 +60,25 @@ def get_file_results(self): self.counters[code] -= 1 for code, extra in sorted(self.counters.items()): if code not in self._benchmark_keys: - if extra and code in self.expected: + if extra and code in self.expected: # pragma: no cover self.file_errors += 1 self.total_errors += 1 print('%s: error %s found too many times (+%d)' % (label, code, extra)) # Reset counters del self.counters[code] - if self._verbose and not self.file_errors: + if self._verbose and not self.file_errors: # pragma: no cover print('%s: passed (%s)' % (label, ' '.join(self.expected) or 'Okay')) self.counters['test cases'] += 1 - if self.file_errors: + if self.file_errors: # pragma: no cover self.counters['failed tests'] += 1 return super(TestReport, self).get_file_results() def print_results(self): results = ("%(physical lines)d lines tested: %(files)d files, " "%(test cases)d test cases%%s." % self.counters) - if self.total_errors: + if self.total_errors: # pragma: no cover print(results % ", %s failures" % self.total_errors) else: print(results % "") @@ -127,20 +126,20 @@ def selftest(options): checker.check_all() error = None if code == 'Okay': - if len(counters) > len(options.benchmark_keys): + if len(counters) > len(options.benchmark_keys): # pragma: no cover # noqa: E501 codes = [key for key in counters if key not in options.benchmark_keys] error = "incorrectly found %s" % ', '.join(codes) - elif not counters.get(code): + elif not counters.get(code): # pragma: no cover error = "failed to find %s" % code # Keep showing errors for multiple tests for key in set(counters) - set(options.benchmark_keys): del counters[key] count_all += 1 if not error: - if options.verbose: + if options.verbose: # pragma: no cover print("%s: %s" % (code, source)) - else: + else: # pragma: no cover count_failed += 1 print("pycodestyle.py: %s:" % error) for line in checker.lines: @@ -176,7 +175,7 @@ def run_tests(filename): if ver_match: test_against_version = tuple(int(val or 0) for val in ver_match.groups()) - if sys.version_info < test_against_version: + if sys.version_info < test_against_version: # pragma: no cover return lines = readlines(filename) + ['#:\n'] line_offset = 0 @@ -220,7 +219,7 @@ def run_tests(style): count_passed = done_d + done_s - count_failed print("%d passed and %d failed." % (count_passed, count_failed)) print("Test failed." if count_failed else "Test passed.") - if count_failed: + if count_failed: # pragma: no cover sys.exit(1) if options.testsuite: init_tests(style) diff --git a/testsuite/test_E101.py b/testsuite/test_E101.py index ba7d707a..10c43bfa 100644 --- a/testsuite/test_E101.py +++ b/testsuite/test_E101.py @@ -12,7 +12,7 @@ def test_E101(self): '\tprint(1) # tabs\n' ' print(2) # spaces\n' ) - if sys.version_info >= (3, 12): + if sys.version_info >= (3, 12): # pragma: >=3.12 cover self.assertEqual(errors, ['W191:2:1', 'E901:3:28']) - else: + else: # pragma: <3.12 cover self.assertEqual(errors, ['W191:2:1', 'E101:3:1']) diff --git a/testsuite/test_E901.py b/testsuite/test_E901.py index d94e9233..706db8ca 100644 --- a/testsuite/test_E901.py +++ b/testsuite/test_E901.py @@ -8,9 +8,9 @@ class E901Test(unittest.TestCase): def test_closing_brace(self): errors = errors_from_src('}\n') - if sys.version_info < (3, 12): + if sys.version_info < (3, 12): # pragma: <3.12 cover self.assertEqual(errors, ['E901:2:1']) - else: + else: # pragma: >=3.12 cover self.assertEqual(errors, []) def test_unclosed_brace(self): @@ -22,8 +22,8 @@ def lasting(self, duration=300): progress = self._progress.setdefault('foo', {} ''' errors = errors_from_src(src) - if sys.version_info < (3, 12): + if sys.version_info < (3, 12): # pragma: <3.12 cover expected = ['E122:4:1', 'E251:5:13', 'E251:5:15'] - else: + else: # pragma: >=3.12 cover expected = ['E122:4:1', 'E251:5:13', 'E251:5:15', 'E901:5:1'] # noqa: E501 self.assertEqual(errors, expected) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index fa4d8a03..20450324 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -329,11 +329,11 @@ def test_check_nullbytes(self): count_errors = pep8style.input_file('stdin', lines=['\x00\n']) stdout = sys.stdout.getvalue() - if sys.version_info < (3, 11, 4): + if sys.version_info < (3, 11, 4): # pragma: <3.11 cover expected = ["stdin:1:1: E901 ValueError: source code string cannot contain null bytes"] # noqa: E501 - elif sys.version_info < (3, 12): + elif sys.version_info < (3, 12): # pragma: <3.12 cover # pragma: >=3.11 cover # noqa: E501 expected = ["stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes"] # noqa: E501 - else: + else: # pragma: >=3.12 cover expected = [ "stdin:1:1: E901 SyntaxError: source code string cannot contain null bytes", # noqa: E501 "stdin:1:1: E901 TokenError: source code cannot contain null bytes", # noqa: E501 @@ -353,17 +353,17 @@ def test_styleguide_unmatched_triple_quotes(self): pep8style.input_file('stdin', lines=lines) stdout = sys.stdout.getvalue() - if sys.version_info < (3, 10): + if sys.version_info < (3, 10): # pragma: <3.10 cover expected = [ 'stdin:2:5: E901 TokenError: EOF in multi-line string', 'stdin:2:26: E901 SyntaxError: EOF while scanning triple-quoted string literal', # noqa: E501 ] - elif sys.version_info < (3, 12): + elif sys.version_info < (3, 12): # pragma: >=3.10 cover # pragma: <3.12 cover # noqa: E501 expected = [ 'stdin:2:5: E901 TokenError: EOF in multi-line string', 'stdin:2:6: E901 SyntaxError: unterminated triple-quoted string literal (detected at line 2)', # noqa: E501 ] - else: + else: # pragma: >=3.12 cover expected = [ 'stdin:2:6: E901 SyntaxError: unterminated triple-quoted string literal (detected at line 2)', # noqa: E501 'stdin:2:6: E901 TokenError: EOF in multi-line string', diff --git a/tox.ini b/tox.ini index f4a32d71..9a004b19 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,9 @@ envlist = py, pypy3 skip_missing_interpreters = True [testenv] -deps = coverage +deps = + covdefaults + coverage commands = python -m pycodestyle --statistics pycodestyle.py coverage run -m pycodestyle --max-doc-length=72 --testsuite testsuite From 7e94466850b3602256cf600ba30a59d1c1a78a23 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 19:34:30 -0400 Subject: [PATCH 060/125] move test data to ./testing/data --- .pre-commit-config.yaml | 2 +- setup.cfg | 9 +------ {testsuite => testing/data}/E10.py | 0 {testsuite => testing/data}/E11.py | 0 {testsuite => testing/data}/E12.py | 0 {testsuite => testing/data}/E12not.py | 0 {testsuite => testing/data}/E20.py | 0 {testsuite => testing/data}/E21.py | 0 {testsuite => testing/data}/E22.py | 0 {testsuite => testing/data}/E23.py | 0 {testsuite => testing/data}/E24.py | 0 {testsuite => testing/data}/E25.py | 0 {testsuite => testing/data}/E26.py | 0 {testsuite => testing/data}/E27.py | 0 {testsuite => testing/data}/E30.py | 0 {testsuite => testing/data}/E30not.py | 0 {testsuite => testing/data}/E40.py | 0 {testsuite => testing/data}/E50.py | 0 {testsuite => testing/data}/E70.py | 0 {testsuite => testing/data}/E71.py | 0 {testsuite => testing/data}/E72.py | 0 {testsuite => testing/data}/E73.py | 0 {testsuite => testing/data}/E74.py | 0 {testsuite => testing/data}/E90.py | 0 {testsuite => testing/data}/W19.py | 0 {testsuite => testing/data}/W29.py | 0 {testsuite => testing/data}/W39.py | 0 {testsuite => testing/data}/W60.py | 0 {testsuite => testing/data}/crlf.py | 0 {testsuite => testing/data}/latin-1.py | 0 {testsuite => testing/data}/noqa.py | 0 {testsuite => testing/data}/python3.py | 0 {testsuite => testing/data}/python310.py | 0 {testsuite => testing/data}/python311.py | 0 {testsuite => testing/data}/python312.py | 0 {testsuite => testing/data}/python35.py | 0 {testsuite => testing/data}/python36.py | 0 {testsuite => testing/data}/python38.py | 0 {testsuite => testing/data}/utf-8-bom.py | 0 {testsuite => testing/data}/utf-8.py | 0 testsuite/support.py | 24 +++++++++-------- testsuite/test_E101.py | 2 +- testsuite/test_E901.py | 2 +- testsuite/test_all.py | 6 ++--- testsuite/test_api.py | 10 +++---- testsuite/test_shell.py | 34 +++++++++++++----------- testsuite/test_util.py | 1 - tox.ini | 2 +- 48 files changed, 45 insertions(+), 47 deletions(-) rename {testsuite => testing/data}/E10.py (100%) rename {testsuite => testing/data}/E11.py (100%) rename {testsuite => testing/data}/E12.py (100%) rename {testsuite => testing/data}/E12not.py (100%) rename {testsuite => testing/data}/E20.py (100%) rename {testsuite => testing/data}/E21.py (100%) rename {testsuite => testing/data}/E22.py (100%) rename {testsuite => testing/data}/E23.py (100%) rename {testsuite => testing/data}/E24.py (100%) rename {testsuite => testing/data}/E25.py (100%) rename {testsuite => testing/data}/E26.py (100%) rename {testsuite => testing/data}/E27.py (100%) rename {testsuite => testing/data}/E30.py (100%) rename {testsuite => testing/data}/E30not.py (100%) rename {testsuite => testing/data}/E40.py (100%) rename {testsuite => testing/data}/E50.py (100%) rename {testsuite => testing/data}/E70.py (100%) rename {testsuite => testing/data}/E71.py (100%) rename {testsuite => testing/data}/E72.py (100%) rename {testsuite => testing/data}/E73.py (100%) rename {testsuite => testing/data}/E74.py (100%) rename {testsuite => testing/data}/E90.py (100%) rename {testsuite => testing/data}/W19.py (100%) rename {testsuite => testing/data}/W29.py (100%) rename {testsuite => testing/data}/W39.py (100%) rename {testsuite => testing/data}/W60.py (100%) rename {testsuite => testing/data}/crlf.py (100%) rename {testsuite => testing/data}/latin-1.py (100%) rename {testsuite => testing/data}/noqa.py (100%) rename {testsuite => testing/data}/python3.py (100%) rename {testsuite => testing/data}/python310.py (100%) rename {testsuite => testing/data}/python311.py (100%) rename {testsuite => testing/data}/python312.py (100%) rename {testsuite => testing/data}/python35.py (100%) rename {testsuite => testing/data}/python36.py (100%) rename {testsuite => testing/data}/python38.py (100%) rename {testsuite => testing/data}/utf-8-bom.py (100%) rename {testsuite => testing/data}/utf-8.py (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a68e31a..aff95394 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: ^testsuite/ +exclude: ^testing/data/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 diff --git a/setup.cfg b/setup.cfg index 6ea2d8ce..2116dfb6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,14 +12,7 @@ max_doc_length = 72 [coverage:run] plugins = covdefaults -omit = - # TODO: separate the tests from the test data - testsuite/E*.py - testsuite/W*.py - testsuite/latin-1.py - testsuite/noqa.py - testsuite/python*.py - testsuite/utf-8-bom.py +omit = testsuite/data parallel = true [coverage:report] diff --git a/testsuite/E10.py b/testing/data/E10.py similarity index 100% rename from testsuite/E10.py rename to testing/data/E10.py diff --git a/testsuite/E11.py b/testing/data/E11.py similarity index 100% rename from testsuite/E11.py rename to testing/data/E11.py diff --git a/testsuite/E12.py b/testing/data/E12.py similarity index 100% rename from testsuite/E12.py rename to testing/data/E12.py diff --git a/testsuite/E12not.py b/testing/data/E12not.py similarity index 100% rename from testsuite/E12not.py rename to testing/data/E12not.py diff --git a/testsuite/E20.py b/testing/data/E20.py similarity index 100% rename from testsuite/E20.py rename to testing/data/E20.py diff --git a/testsuite/E21.py b/testing/data/E21.py similarity index 100% rename from testsuite/E21.py rename to testing/data/E21.py diff --git a/testsuite/E22.py b/testing/data/E22.py similarity index 100% rename from testsuite/E22.py rename to testing/data/E22.py diff --git a/testsuite/E23.py b/testing/data/E23.py similarity index 100% rename from testsuite/E23.py rename to testing/data/E23.py diff --git a/testsuite/E24.py b/testing/data/E24.py similarity index 100% rename from testsuite/E24.py rename to testing/data/E24.py diff --git a/testsuite/E25.py b/testing/data/E25.py similarity index 100% rename from testsuite/E25.py rename to testing/data/E25.py diff --git a/testsuite/E26.py b/testing/data/E26.py similarity index 100% rename from testsuite/E26.py rename to testing/data/E26.py diff --git a/testsuite/E27.py b/testing/data/E27.py similarity index 100% rename from testsuite/E27.py rename to testing/data/E27.py diff --git a/testsuite/E30.py b/testing/data/E30.py similarity index 100% rename from testsuite/E30.py rename to testing/data/E30.py diff --git a/testsuite/E30not.py b/testing/data/E30not.py similarity index 100% rename from testsuite/E30not.py rename to testing/data/E30not.py diff --git a/testsuite/E40.py b/testing/data/E40.py similarity index 100% rename from testsuite/E40.py rename to testing/data/E40.py diff --git a/testsuite/E50.py b/testing/data/E50.py similarity index 100% rename from testsuite/E50.py rename to testing/data/E50.py diff --git a/testsuite/E70.py b/testing/data/E70.py similarity index 100% rename from testsuite/E70.py rename to testing/data/E70.py diff --git a/testsuite/E71.py b/testing/data/E71.py similarity index 100% rename from testsuite/E71.py rename to testing/data/E71.py diff --git a/testsuite/E72.py b/testing/data/E72.py similarity index 100% rename from testsuite/E72.py rename to testing/data/E72.py diff --git a/testsuite/E73.py b/testing/data/E73.py similarity index 100% rename from testsuite/E73.py rename to testing/data/E73.py diff --git a/testsuite/E74.py b/testing/data/E74.py similarity index 100% rename from testsuite/E74.py rename to testing/data/E74.py diff --git a/testsuite/E90.py b/testing/data/E90.py similarity index 100% rename from testsuite/E90.py rename to testing/data/E90.py diff --git a/testsuite/W19.py b/testing/data/W19.py similarity index 100% rename from testsuite/W19.py rename to testing/data/W19.py diff --git a/testsuite/W29.py b/testing/data/W29.py similarity index 100% rename from testsuite/W29.py rename to testing/data/W29.py diff --git a/testsuite/W39.py b/testing/data/W39.py similarity index 100% rename from testsuite/W39.py rename to testing/data/W39.py diff --git a/testsuite/W60.py b/testing/data/W60.py similarity index 100% rename from testsuite/W60.py rename to testing/data/W60.py diff --git a/testsuite/crlf.py b/testing/data/crlf.py similarity index 100% rename from testsuite/crlf.py rename to testing/data/crlf.py diff --git a/testsuite/latin-1.py b/testing/data/latin-1.py similarity index 100% rename from testsuite/latin-1.py rename to testing/data/latin-1.py diff --git a/testsuite/noqa.py b/testing/data/noqa.py similarity index 100% rename from testsuite/noqa.py rename to testing/data/noqa.py diff --git a/testsuite/python3.py b/testing/data/python3.py similarity index 100% rename from testsuite/python3.py rename to testing/data/python3.py diff --git a/testsuite/python310.py b/testing/data/python310.py similarity index 100% rename from testsuite/python310.py rename to testing/data/python310.py diff --git a/testsuite/python311.py b/testing/data/python311.py similarity index 100% rename from testsuite/python311.py rename to testing/data/python311.py diff --git a/testsuite/python312.py b/testing/data/python312.py similarity index 100% rename from testsuite/python312.py rename to testing/data/python312.py diff --git a/testsuite/python35.py b/testing/data/python35.py similarity index 100% rename from testsuite/python35.py rename to testing/data/python35.py diff --git a/testsuite/python36.py b/testing/data/python36.py similarity index 100% rename from testsuite/python36.py rename to testing/data/python36.py diff --git a/testsuite/python38.py b/testing/data/python38.py similarity index 100% rename from testsuite/python38.py rename to testing/data/python38.py diff --git a/testsuite/utf-8-bom.py b/testing/data/utf-8-bom.py similarity index 100% rename from testsuite/utf-8-bom.py rename to testing/data/utf-8-bom.py diff --git a/testsuite/utf-8.py b/testing/data/utf-8.py similarity index 100% rename from testsuite/utf-8.py rename to testing/data/utf-8.py diff --git a/testsuite/support.py b/testsuite/support.py index 39a3380a..f5399f40 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -1,11 +1,13 @@ -# -*- coding: utf-8 -*- from __future__ import annotations import os.path import re import sys -from pycodestyle import Checker, BaseReport, StandardReport, readlines +from pycodestyle import BaseReport +from pycodestyle import Checker +from pycodestyle import readlines +from pycodestyle import StandardReport from pycodestyle import StyleGuide SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') @@ -28,7 +30,7 @@ class TestReport(StandardReport): def __init__(self, options): options.benchmark_keys += ['test cases', 'failed tests'] - super(TestReport, self).__init__(options) + super().__init__(options) self._verbose = options.verbose def error(self, line_number, offset, text, check): @@ -38,7 +40,7 @@ def error(self, line_number, offset, text, check): self.counters[code] += 1 else: self.counters[code] = 1 - detailed_code = '%s:%s:%s' % (code, line_number, offset + 1) + detailed_code = f'{code}:{line_number}:{offset + 1}' # Don't care about expected errors or warnings if code not in self.expected and detailed_code not in self.expected: # pragma: no cover # noqa: E501 err = (line_number, offset, detailed_code, text[5:], check.__doc__) @@ -49,13 +51,13 @@ def error(self, line_number, offset, text, check): def get_file_results(self): # Check if the expected errors were found - label = '%s:%s:1' % (self.filename, self.line_offset) + label = f'{self.filename}:{self.line_offset}:1' for extended_code in self.expected: code = extended_code.split(':')[0] if not self.counters.get(code): # pragma: no cover self.file_errors += 1 self.total_errors += 1 - print('%s: error %s not found' % (label, extended_code)) + print(f'{label}: error {extended_code} not found') else: self.counters[code] -= 1 for code, extra in sorted(self.counters.items()): @@ -73,7 +75,7 @@ def get_file_results(self): self.counters['test cases'] += 1 if self.file_errors: # pragma: no cover self.counters['failed tests'] += 1 - return super(TestReport, self).get_file_results() + return super().get_file_results() def print_results(self): results = ("%(physical lines)d lines tested: %(files)d files, " @@ -91,7 +93,7 @@ class InMemoryReport(BaseReport): """ def __init__(self, options): - super(InMemoryReport, self).__init__(options) + super().__init__(options) self.in_memory_errors = [] def error(self, line_number, offset, text, check): @@ -99,9 +101,9 @@ def error(self, line_number, offset, text, check): Report an error, according to options. """ code = text[:4] - self.in_memory_errors.append('%s:%s:%s' % ( + self.in_memory_errors.append('{}:{}:{}'.format( code, line_number, offset + 1)) - return super(InMemoryReport, self).error( + return super().error( line_number, offset, text, check) @@ -138,7 +140,7 @@ def selftest(options): count_all += 1 if not error: if options.verbose: # pragma: no cover - print("%s: %s" % (code, source)) + print(f"{code}: {source}") else: # pragma: no cover count_failed += 1 print("pycodestyle.py: %s:" % error) diff --git a/testsuite/test_E101.py b/testsuite/test_E101.py index 10c43bfa..0f1915ff 100644 --- a/testsuite/test_E101.py +++ b/testsuite/test_E101.py @@ -1,6 +1,6 @@ """moved from testsuite files due to 3.12 making this a TokenError""" -import unittest import sys +import unittest from testsuite.support import errors_from_src diff --git a/testsuite/test_E901.py b/testsuite/test_E901.py index 706db8ca..423c2f39 100644 --- a/testsuite/test_E901.py +++ b/testsuite/test_E901.py @@ -1,6 +1,6 @@ """moved from testsuite files due to 3.12 changing syntax errors""" -import unittest import sys +import unittest from testsuite.support import errors_from_src diff --git a/testsuite/test_all.py b/testsuite/test_all.py index 38b3c452..122966b6 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -1,11 +1,11 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import os.path -import sys import unittest import pycodestyle -from testsuite.support import init_tests, selftest, ROOT_DIR +from testsuite.support import init_tests +from testsuite.support import ROOT_DIR +from testsuite.support import selftest class PycodestyleTestCase(unittest.TestCase): diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 20450324..a9f23568 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -1,16 +1,16 @@ -# -*- coding: utf-8 -*- import os.path import shlex import sys import unittest import pycodestyle -from testsuite.support import ROOT_DIR, PseudoFile +from testsuite.support import PseudoFile +from testsuite.support import ROOT_DIR -E11 = os.path.join(ROOT_DIR, 'testsuite', 'E11.py') +E11 = os.path.join(ROOT_DIR, 'testing', 'data', 'E11.py') -class DummyChecker(object): +class DummyChecker: def __init__(self, tree, filename): pass @@ -151,7 +151,7 @@ def test_styleguide_options(self): 'doctest', 'quiet', 'show_pep8', 'show_source', 'statistics', 'testsuite', 'verbose'): oval = getattr(pep8style.options, o) - self.assertTrue(oval in (None, False), msg='%s = %r' % (o, oval)) + self.assertTrue(oval in (None, False), msg=f'{o} = {oval!r}') # Check default options self.assertTrue(pep8style.options.repeat) diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index a2fa35ea..f25864e6 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- import configparser import os.path import sys import unittest import pycodestyle -from testsuite.support import ROOT_DIR, PseudoFile +from testsuite.support import PseudoFile +from testsuite.support import ROOT_DIR class ShellTestCase(unittest.TestCase): @@ -73,7 +73,7 @@ def test_print_usage(self): self.assertFalse(self._config_filenames) def test_check_simple(self): - E11 = os.path.join(ROOT_DIR, 'testsuite', 'E11.py') + E11 = os.path.join(ROOT_DIR, 'testing', 'data', 'E11.py') stdout, stderr, errcode = self.pycodestyle(E11) stdout = stdout.splitlines() self.assertEqual(errcode, 1) @@ -125,8 +125,8 @@ def test_check_noarg(self): def test_check_diff(self): pycodestyle.PROJECT_CONFIG = () diff_lines = [ - "--- testsuite/E11.py 2006-06-01 08:49:50 +0500", - "+++ testsuite/E11.py 2008-04-06 17:36:29 +0500", + "--- testing/data/E11.py 2006-06-01 08:49:50 +0500", + "+++ testing/data/E11.py 2008-04-06 17:36:29 +0500", "@@ -2,4 +2,7 @@", " if x > 2:", " print x", @@ -149,8 +149,10 @@ def test_check_diff(self): self.assertEqual(y, str(col)) self.assertTrue(msg.startswith(' E11')) - diff_lines[:2] = ["--- a/testsuite/E11.py 2006-06-01 08:49 +0400", - "+++ b/testsuite/E11.py 2008-04-06 17:36 +0400"] + diff_lines[:2] = [ + "--- a/testing/data/E11.py 2006-06-01 08:49 +0400", + "+++ b/testing/data/E11.py 2008-04-06 17:36 +0400", + ] self.stdin = '\n'.join(diff_lines) stdout, stderr, errcode = self.pycodestyle('--diff') stdout = stdout.splitlines() @@ -163,19 +165,21 @@ def test_check_diff(self): self.assertTrue(msg.startswith(' E11')) # issue #127, #137: one-line chunks - diff_lines[:-1] = ["diff --git a/testsuite/E11.py b/testsuite/E11.py", - "index 8735e25..2ecb529 100644", - "--- a/testsuite/E11.py", - "+++ b/testsuite/E11.py", - "@@ -5,0 +6 @@ if True:", - "+ print"] + diff_lines[:-1] = [ + "diff --git a/testing/data/E11.py b/testing/data/E11.py", + "index 8735e25..2ecb529 100644", + "--- a/testing/data/E11.py", + "+++ b/testing/data/E11.py", + "@@ -5,0 +6 @@ if True:", + "+ print", + ] self.stdin = '\n'.join(diff_lines) stdout, stderr, errcode = self.pycodestyle('--diff') stdout = stdout.splitlines() self.assertEqual(errcode, 1) self.assertFalse(stderr) - self.assertTrue('testsuite/E11.py:6:6: E111 ' in stdout[0]) - self.assertTrue('testsuite/E11.py:6:6: E117 ' in stdout[1]) + self.assertTrue('testing/data/E11.py:6:6: E111 ' in stdout[0]) + self.assertTrue('testing/data/E11.py:6:6: E117 ' in stdout[1]) # missing '--diff' self.stdin = '\n'.join(diff_lines) diff --git a/testsuite/test_util.py b/testsuite/test_util.py index 075b163c..ec4b9d4d 100644 --- a/testsuite/test_util.py +++ b/testsuite/test_util.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import os import unittest diff --git a/tox.ini b/tox.ini index 9a004b19..a8ed0dab 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ deps = coverage commands = python -m pycodestyle --statistics pycodestyle.py - coverage run -m pycodestyle --max-doc-length=72 --testsuite testsuite + coverage run -m pycodestyle --max-doc-length=72 --testsuite testing/data coverage run -m pycodestyle --max-doc-length=72 --doctest coverage run -m unittest discover testsuite -vv coverage combine From a15e618f31c753cc03a0e3dcf9da4d7c2b31b547 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 19:48:16 -0400 Subject: [PATCH 061/125] remove some unnecessary shebangs --- testsuite/test_all.py | 1 - testsuite/test_util.py | 1 - 2 files changed, 2 deletions(-) diff --git a/testsuite/test_all.py b/testsuite/test_all.py index 122966b6..fd404c54 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python import os.path import unittest diff --git a/testsuite/test_util.py b/testsuite/test_util.py index ec4b9d4d..ce3058a9 100644 --- a/testsuite/test_util.py +++ b/testsuite/test_util.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python import os import unittest From 7f6e3688cfd822866900b523a5a7aa77747bbbd1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 20:09:45 -0400 Subject: [PATCH 062/125] run testsuite with pytest --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a8ed0dab..fb8e3903 100644 --- a/tox.ini +++ b/tox.ini @@ -11,11 +11,12 @@ skip_missing_interpreters = True deps = covdefaults coverage + pytest commands = python -m pycodestyle --statistics pycodestyle.py coverage run -m pycodestyle --max-doc-length=72 --testsuite testing/data coverage run -m pycodestyle --max-doc-length=72 --doctest - coverage run -m unittest discover testsuite -vv + coverage run -m pytest testsuite coverage combine coverage report From 3443725aff46aa02bad7d44cdc5723778cd2e918 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 20:15:14 -0400 Subject: [PATCH 063/125] use setup.cfg to handle setuptools metadata --- .pre-commit-config.yaml | 4 +++ setup.cfg | 43 ++++++++++++++++++++++++++++--- setup.py | 56 +---------------------------------------- 3 files changed, 44 insertions(+), 59 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aff95394..f57e1dd9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,6 +17,10 @@ repos: hooks: - id: pyupgrade args: [--py38-plus] +- repo: https://github.com/asottile/setup-cfg-fmt + rev: v2.4.0 + hooks: + - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: diff --git a/setup.cfg b/setup.cfg index 2116dfb6..9d735614 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,46 @@ +[metadata] +name = pycodestyle +version = attr: pycodestyle.__version__ +description = Python style guide checker +long_description = file: README.rst +long_description_content_type = text/x-rst +url = https://pycodestyle.pycqa.org/ +author = Johann C. Rocholl +author_email = johann@rocholl.net +maintainer = Ian Lee +maintainer_email = IanLee1521@gmail.com +license = MIT +license_files = LICENSE +classifiers = + Development Status :: 5 - Production/Stable + Environment :: Console + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: Implementation :: CPython + Programming Language :: Python :: Implementation :: PyPy + Topic :: Software Development :: Libraries :: Python Modules +keywords = pycodestyle, pep8, PEP 8, PEP-8, PEP8 +project_urls = + Changes=https://pycodestyle.pycqa.org/en/latest/developer.html#changes + +[options] +py_modules = pycodestyle +python_requires = >=3.8 +include_package_data = True +zip_safe = False + +[options.entry_points] +console_scripts = + pycodestyle = pycodestyle:_main + [bdist_wheel] universal = 1 -[metadata] -license_file = LICENSE - [pycodestyle] -select = ignore = E226,E24,W504 max_line_length = 79 max_doc_length = 72 diff --git a/setup.py b/setup.py index ab735f9f..8bf1ba93 100644 --- a/setup.py +++ b/setup.py @@ -1,56 +1,2 @@ from setuptools import setup - - -def get_version(): - with open('pycodestyle.py') as f: - for line in f: - if line.startswith('__version__'): - return eval(line.split('=')[-1]) - - -def get_long_description(): - descr = [] - for fname in 'README.rst', 'CHANGES.txt': - with open(fname) as f: - descr.append(f.read()) - return '\n\n'.join(descr) - - -setup( - name='pycodestyle', - version=get_version(), - description="Python style guide checker", - long_description=get_long_description(), - keywords='pycodestyle, pep8, PEP 8, PEP-8, PEP8', - author='Johann C. Rocholl', - author_email='johann@rocholl.net', - maintainer='Ian Lee', - maintainer_email='IanLee1521@gmail.com', - url='https://pycodestyle.pycqa.org/', - license='Expat license', - py_modules=['pycodestyle'], - include_package_data=True, - zip_safe=False, - python_requires='>=3.8', - entry_points={ - 'console_scripts': [ - 'pycodestyle = pycodestyle:_main', - ], - }, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - project_urls={ - 'Changes': - 'https://pycodestyle.pycqa.org/en/latest/developer.html#changes', - }, -) +setup() From 68f5ee1d69957ed4e38a4c6c6276a95738a79c18 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 16 Jul 2023 21:08:04 -0400 Subject: [PATCH 064/125] convert --doctest to pytest tests --- .gitignore | 2 +- docs/developer.rst | 6 +-- pycodestyle.py | 33 ++---------- testing/__init__.py | 0 testing/support.py | 32 ++++++++++++ testsuite/support.py | 89 +-------------------------------- testsuite/test_E101.py | 2 +- testsuite/test_E901.py | 2 +- testsuite/test_all.py | 14 ------ testsuite/test_api.py | 6 +-- testsuite/test_blank_lines.py | 2 +- testsuite/test_pycodestyle.py | 29 +++++++++++ testsuite/test_self_doctests.py | 38 ++++++++++++++ tox.ini | 1 - 14 files changed, 113 insertions(+), 143 deletions(-) create mode 100644 testing/__init__.py create mode 100644 testing/support.py create mode 100644 testsuite/test_pycodestyle.py create mode 100644 testsuite/test_self_doctests.py diff --git a/.gitignore b/.gitignore index a2848173..ba74c72f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ *.egg *.egg-info *.pyc -/.coverage +/.coverage* /.tox /build/ /dist diff --git a/docs/developer.rst b/docs/developer.rst index 74e3ede7..59800113 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -89,8 +89,8 @@ Several docstrings contain examples directly from the `PEP 8`_ document. Okay: spam(ham[1], {eggs: 2}) E201: spam( ham[1], {eggs: 2}) -These examples are verified automatically when ``pycodestyle.py`` is run with -the ``--doctest`` option. You can add examples for your own check functions. +These examples are verified automatically by ``test_self_doctest.py``. +You can add examples for your own check functions. The format is simple: ``"Okay"`` or error/warning code followed by colon and space, the rest of the line is example source code. If you put ``'r'`` before the docstring, you can use ``\n`` for newline and ``\t`` for tab. @@ -98,7 +98,7 @@ the docstring, you can use ``\n`` for newline and ``\t`` for tab. Then be sure to pass the tests:: $ python pycodestyle.py --testsuite testsuite - $ python pycodestyle.py --doctest + $ pytest testsuite/test_self_doctest.py $ python pycodestyle.py --verbose pycodestyle.py When contributing to pycodestyle, please observe our `Code of Conduct`_. diff --git a/pycodestyle.py b/pycodestyle.py index 6836ac1e..0c7d15d7 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1136,10 +1136,6 @@ def module_imports_on_top_of_file( Okay: # this is a comment\nimport os Okay: '''this is a module docstring'''\nimport os Okay: r'''this is a module docstring'''\nimport os - Okay: - try:\n\timport x\nexcept ImportError:\n\tpass\nelse:\n\tpass\nimport y - Okay: - try:\n\timport x\nexcept ImportError:\n\tpass\nfinally:\n\tpass\nimport y E402: a=1\nimport os E402: 'One string'\n"Two string"\nimport os E402: a=1\nfrom sys import x @@ -1730,15 +1726,6 @@ def expand_indent(line): r"""Return the amount of indentation. Tabs are expanded to the next multiple of 8. - - >>> expand_indent(' ') - 4 - >>> expand_indent('\t') - 8 - >>> expand_indent(' \t') - 8 - >>> expand_indent(' \t') - 16 """ line = line.rstrip('\n\r') if '\t' not in line: @@ -1755,15 +1742,7 @@ def expand_indent(line): def mute_string(text): - """Replace contents with 'xxx' to prevent syntax matching. - - >>> mute_string('"abc"') - '"xxx"' - >>> mute_string("'''abc'''") - "'''xxx'''" - >>> mute_string("r'abc'") - "r'xxx'" - """ + """Replace contents with 'xxx' to prevent syntax matching.""" # String modifiers (e.g. u or r) start = text.index(text[-1]) + 1 end = len(text) - 1 @@ -2323,7 +2302,7 @@ def __init__(self, *args, **kwargs): options.select = tuple(options.select or ()) if not (options.select or options.ignore or - options.testsuite or options.doctest) and DEFAULT_IGNORE: + options.testsuite) and DEFAULT_IGNORE: # The default choice: ignore controversial checks options.ignore = tuple(DEFAULT_IGNORE.split(',')) else: @@ -2496,8 +2475,6 @@ def get_parser(prog='pycodestyle', version=__version__): if os.path.exists(TESTSUITE_PATH): group.add_option('--testsuite', metavar='dir', help="run regression tests from dir") - group.add_option('--doctest', action='store_true', - help="run doctest on myself") group.add_option('--benchmark', action='store_true', help="measure processing speed") return parser @@ -2574,7 +2551,7 @@ def read_config(options, args, arglist, parser): # Third, overwrite with the command-line options (options, __) = parser.parse_args(arglist, values=new_options) - options.doctest = options.testsuite = False + options.testsuite = False return options @@ -2609,7 +2586,7 @@ def process_options(arglist=None, parse_argv=False, config_file=None, if options.ensure_value('testsuite', False): args.append(options.testsuite) - elif not options.ensure_value('doctest', False): + else: if parse_argv and not args: if options.diff or any(os.path.exists(name) for name in PROJECT_CONFIG): @@ -2662,7 +2639,7 @@ def _main(): style_guide = StyleGuide(parse_argv=True) options = style_guide.options - if options.doctest or options.testsuite: + if options.testsuite: from testsuite.support import run_tests report = run_tests(style_guide) else: diff --git a/testing/__init__.py b/testing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testing/support.py b/testing/support.py new file mode 100644 index 00000000..ca4744be --- /dev/null +++ b/testing/support.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from pycodestyle import BaseReport +from pycodestyle import StyleGuide + + +class InMemoryReport(BaseReport): + """ + Collect the results in memory, without printing anything. + """ + + def __init__(self, options): + super().__init__(options) + self.in_memory_errors = [] + + def error(self, line_number, offset, text, check): + """ + Report an error, according to options. + """ + code = text[:4] + self.in_memory_errors.append(f'{code}:{line_number}:{offset + 1}') + return super().error(line_number, offset, text, check) + + +def errors_from_src(src: str) -> list[str]: + guide = StyleGuide(select=('E', 'W')) + reporter = guide.init_report(InMemoryReport) + guide.input_file( + filename='in-memory-test-file.py', + lines=src.splitlines(True), + ) + return reporter.in_memory_errors diff --git a/testsuite/support.py b/testsuite/support.py index f5399f40..8bea0920 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -4,11 +4,8 @@ import re import sys -from pycodestyle import BaseReport -from pycodestyle import Checker from pycodestyle import readlines from pycodestyle import StandardReport -from pycodestyle import StyleGuide SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -87,68 +84,6 @@ def print_results(self): print("Test failed." if self.total_errors else "Test passed.") -class InMemoryReport(BaseReport): - """ - Collect the results in memory, without printing anything. - """ - - def __init__(self, options): - super().__init__(options) - self.in_memory_errors = [] - - def error(self, line_number, offset, text, check): - """ - Report an error, according to options. - """ - code = text[:4] - self.in_memory_errors.append('{}:{}:{}'.format( - code, line_number, offset + 1)) - return super().error( - line_number, offset, text, check) - - -def selftest(options): - """ - Test all check functions with test cases in docstrings. - """ - count_failed = count_all = 0 - report = BaseReport(options) - counters = report.counters - checks = options.physical_checks + options.logical_checks - for name, check, argument_names in checks: - for line in check.__doc__.splitlines(): - line = line.lstrip() - match = SELFTEST_REGEX.match(line) - if match is None: - continue - code, source = match.groups() - lines = [part.replace(r'\t', '\t') + '\n' - for part in source.split(r'\n')] - checker = Checker(lines=lines, options=options, report=report) - checker.check_all() - error = None - if code == 'Okay': - if len(counters) > len(options.benchmark_keys): # pragma: no cover # noqa: E501 - codes = [key for key in counters - if key not in options.benchmark_keys] - error = "incorrectly found %s" % ', '.join(codes) - elif not counters.get(code): # pragma: no cover - error = "failed to find %s" % code - # Keep showing errors for multiple tests - for key in set(counters) - set(options.benchmark_keys): - del counters[key] - count_all += 1 - if not error: - if options.verbose: # pragma: no cover - print(f"{code}: {source}") - else: # pragma: no cover - count_failed += 1 - print("pycodestyle.py: %s:" % error) - for line in checker.lines: - print(line.rstrip()) - return count_failed, count_all - - def init_tests(pep8style): """ Initialize testing framework. @@ -211,28 +146,6 @@ def run_tests(filename): def run_tests(style): - options = style.options - if options.doctest: - import doctest - fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose) - fail_s, done_s = selftest(options) - count_failed = fail_s + fail_d - if not options.quiet: - count_passed = done_d + done_s - count_failed - print("%d passed and %d failed." % (count_passed, count_failed)) - print("Test failed." if count_failed else "Test passed.") - if count_failed: # pragma: no cover - sys.exit(1) - if options.testsuite: + if style.options.testsuite: init_tests(style) return style.check_files() - - -def errors_from_src(src: str) -> list[str]: - guide = StyleGuide() - reporter = guide.init_report(InMemoryReport) - guide.input_file( - filename='in-memory-test-file.py', - lines=src.splitlines(True), - ) - return reporter.in_memory_errors diff --git a/testsuite/test_E101.py b/testsuite/test_E101.py index 0f1915ff..c713370f 100644 --- a/testsuite/test_E101.py +++ b/testsuite/test_E101.py @@ -2,7 +2,7 @@ import sys import unittest -from testsuite.support import errors_from_src +from testing.support import errors_from_src class E101Test(unittest.TestCase): diff --git a/testsuite/test_E901.py b/testsuite/test_E901.py index 423c2f39..402b85a4 100644 --- a/testsuite/test_E901.py +++ b/testsuite/test_E901.py @@ -2,7 +2,7 @@ import sys import unittest -from testsuite.support import errors_from_src +from testing.support import errors_from_src class E901Test(unittest.TestCase): diff --git a/testsuite/test_all.py b/testsuite/test_all.py index fd404c54..aa4a93b4 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -4,7 +4,6 @@ import pycodestyle from testsuite.support import init_tests from testsuite.support import ROOT_DIR -from testsuite.support import selftest class PycodestyleTestCase(unittest.TestCase): @@ -15,19 +14,6 @@ def setUp(self): paths=[os.path.join(ROOT_DIR, 'testsuite')], select='E,W', quiet=True) - def test_doctest(self): - import doctest - fail_d, done_d = doctest.testmod( - pycodestyle, verbose=False, report=False - ) - self.assertTrue(done_d, msg='tests not found') - self.assertFalse(fail_d, msg='%s failure(s)' % fail_d) - - def test_selftest(self): - fail_s, done_s = selftest(self._style.options) - self.assertTrue(done_s, msg='tests not found') - self.assertFalse(fail_s, msg='%s failure(s)' % fail_s) - def test_checkers_testsuite(self): init_tests(self._style) report = self._style.check_files() diff --git a/testsuite/test_api.py b/testsuite/test_api.py index a9f23568..8f467b21 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -148,7 +148,7 @@ def test_styleguide_options(self): # Check unset options for o in ('benchmark', 'config', 'count', 'diff', - 'doctest', 'quiet', 'show_pep8', 'show_source', + 'quiet', 'show_pep8', 'show_source', 'statistics', 'testsuite', 'verbose'): oval = getattr(pep8style.options, o) self.assertTrue(oval in (None, False), msg=f'{o} = {oval!r}') @@ -183,10 +183,6 @@ def parse_argv(argstring): ('E121', 'E123', 'E126', 'E226', 'E24', 'E704', 'W503', 'W504') ) - options = parse_argv('--doctest').options - self.assertEqual(options.select, ()) - self.assertEqual(options.ignore, ()) - options = parse_argv('--ignore E,W').options self.assertEqual(options.select, ()) self.assertEqual(options.ignore, ('E', 'W')) diff --git a/testsuite/test_blank_lines.py b/testsuite/test_blank_lines.py index fa36f236..d1f78eae 100644 --- a/testsuite/test_blank_lines.py +++ b/testsuite/test_blank_lines.py @@ -6,7 +6,7 @@ import unittest import pycodestyle -from testsuite.support import errors_from_src +from testing.support import errors_from_src class BlankLinesTestCase(unittest.TestCase): diff --git a/testsuite/test_pycodestyle.py b/testsuite/test_pycodestyle.py new file mode 100644 index 00000000..8885f0d8 --- /dev/null +++ b/testsuite/test_pycodestyle.py @@ -0,0 +1,29 @@ +import pytest + +from pycodestyle import expand_indent +from pycodestyle import mute_string + + +@pytest.mark.parametrize( + ('s', 'expected'), + ( + (' ', 4), + ('\t', 8), + (' \t', 8), + (' \t', 16), + ), +) +def test_expand_indent(s, expected): + assert expand_indent(s) == expected + + +@pytest.mark.parametrize( + ('s', 'expected'), + ( + ('"abc"', '"xxx"'), + ("'''abc'''", "'''xxx'''"), + ("r'abc'", "r'xxx'"), + ), +) +def test_mute_string(s, expected): + assert mute_string(s) == expected diff --git a/testsuite/test_self_doctests.py b/testsuite/test_self_doctests.py new file mode 100644 index 00000000..e2f10ba6 --- /dev/null +++ b/testsuite/test_self_doctests.py @@ -0,0 +1,38 @@ +import re + +import pytest + +import pycodestyle +from testing.support import errors_from_src + +SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}): (.*)') + + +def get_tests(): + ret = [ + pytest.param( + match[1], + match[2], + id=f'pycodestyle.py:{f.__code__.co_firstlineno}:{f.__name__}@{i}', + ) + for group in pycodestyle._checks.values() + for f in group + if f.__doc__ is not None + for i, match in enumerate(SELFTEST_REGEX.finditer(f.__doc__)) + ] + assert ret + return tuple(ret) + + +@pytest.mark.parametrize(('expected', 's'), get_tests()) +def test(expected, s): + s = '\n'.join((*s.replace(r'\t', '\t').split(r'\n'), '')) + errors = errors_from_src(s) + if expected == 'Okay': + assert errors == [] + else: + for error in errors: + if error.startswith(f'{expected}:'): + break + else: + raise AssertionError(f'expected {expected} from {s!r}') diff --git a/tox.ini b/tox.ini index fb8e3903..064a7f00 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,6 @@ deps = commands = python -m pycodestyle --statistics pycodestyle.py coverage run -m pycodestyle --max-doc-length=72 --testsuite testing/data - coverage run -m pycodestyle --max-doc-length=72 --doctest coverage run -m pytest testsuite coverage combine coverage report From 57dc8def1b5113cb53f21b6ea25e148e1d9ba6fa Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 17 Jul 2023 20:05:42 -0400 Subject: [PATCH 065/125] port --testsuite to pytest --- CONTRIBUTING.rst | 20 +++--- docs/developer.rst | 3 +- pycodestyle.py | 36 +++------- setup.cfg | 1 - testing/data/W29.py | 2 - testing/data/python39.py | 2 + testing/support.py | 6 +- testsuite/support.py | 151 --------------------------------------- testsuite/test_all.py | 31 ++------ testsuite/test_api.py | 31 ++++---- testsuite/test_data.py | 95 ++++++++++++++++++++++++ testsuite/test_shell.py | 15 ++-- tox.ini | 2 - 13 files changed, 154 insertions(+), 241 deletions(-) create mode 100644 testing/data/python39.py delete mode 100644 testsuite/support.py create mode 100644 testsuite/test_data.py diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 5515108e..062b726a 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -70,32 +70,28 @@ GitHub has an excellent `guide`_. The current tests are written in 2 styles: -* standard xUnit based only on stdlib unittest -* functional test using a custom framework and executed by the - pycodestyle itself when installed in dev mode. +* pytest tests +* functional test using a custom framework -Running unittest -~~~~~~~~~~~~~~~~ +Running tests +~~~~~~~~~~~~~ -The tests are written using stdlib ``unittest`` module, the existing tests +The tests are written using ``pytest``, the existing tests include unit, integration and functional tests. To run the tests:: - $ python setup.py test + $ pytest testsuite Running functional ~~~~~~~~~~~~~~~~~~ -When installed in dev mode, pycodestyle will have the ``--testsuite`` option -which can be used to run the tests:: - $ pip install -e . $ # Run all tests. - $ pycodestyle --testsuite testsuite + $ pytest testsuite $ # Run a subset of the tests. - $ pycodestyle --testsuite testsuite/E30.py + $ pytest testsuite -k testing/data/E30.py .. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ diff --git a/docs/developer.rst b/docs/developer.rst index 59800113..5a529e66 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -97,8 +97,7 @@ the docstring, you can use ``\n`` for newline and ``\t`` for tab. Then be sure to pass the tests:: - $ python pycodestyle.py --testsuite testsuite - $ pytest testsuite/test_self_doctest.py + $ pytest testsuite $ python pycodestyle.py --verbose pycodestyle.py When contributing to pycodestyle, please observe our `Code of Conduct`_. diff --git a/pycodestyle.py b/pycodestyle.py index 0c7d15d7..9388ae52 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -84,7 +84,6 @@ USER_CONFIG = None PROJECT_CONFIG = ('setup.cfg', 'tox.ini') -TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') MAX_LINE_LENGTH = 79 # Number of blank lines between various code parts. BLANK_LINES_CONFIG = { @@ -2301,8 +2300,7 @@ def __init__(self, *args, **kwargs): options.reporter = BaseReport if options.quiet else StandardReport options.select = tuple(options.select or ()) - if not (options.select or options.ignore or - options.testsuite) and DEFAULT_IGNORE: + if not (options.select or options.ignore) and DEFAULT_IGNORE: # The default choice: ignore controversial checks options.ignore = tuple(DEFAULT_IGNORE.split(',')) else: @@ -2472,9 +2470,6 @@ def get_parser(prog='pycodestyle', version=__version__): help="report changes only within line number ranges in " "the unified diff received on STDIN") group = parser.add_option_group("Testing Options") - if os.path.exists(TESTSUITE_PATH): - group.add_option('--testsuite', metavar='dir', - help="run regression tests from dir") group.add_option('--benchmark', action='store_true', help="measure processing speed") return parser @@ -2551,7 +2546,6 @@ def read_config(options, args, arglist, parser): # Third, overwrite with the command-line options (options, __) = parser.parse_args(arglist, values=new_options) - options.testsuite = False return options @@ -2584,17 +2578,14 @@ def process_options(arglist=None, parse_argv=False, config_file=None, if verbose is not None: options.verbose = verbose - if options.ensure_value('testsuite', False): - args.append(options.testsuite) - else: - if parse_argv and not args: - if options.diff or any(os.path.exists(name) - for name in PROJECT_CONFIG): - args = ['.'] - else: - parser.error('input not specified') - options = read_config(options, args, arglist, parser) - options.reporter = parse_argv and options.quiet == 1 and FileReport + if parse_argv and not args: + if options.diff or any(os.path.exists(name) + for name in PROJECT_CONFIG): + args = ['.'] + else: + parser.error('input not specified') + options = read_config(options, args, arglist, parser) + options.reporter = parse_argv and options.quiet == 1 and FileReport options.filename = _parse_multi_options(options.filename) options.exclude = normalize_paths(options.exclude) @@ -2639,11 +2630,7 @@ def _main(): style_guide = StyleGuide(parse_argv=True) options = style_guide.options - if options.testsuite: - from testsuite.support import run_tests - report = run_tests(style_guide) - else: - report = style_guide.check_files() + report = style_guide.check_files() if options.statistics: report.print_statistics() @@ -2651,9 +2638,6 @@ def _main(): if options.benchmark: report.print_benchmark() - if options.testsuite and not options.quiet: - report.print_results() - if report.total_errors: if options.count: sys.stderr.write(str(report.total_errors) + '\n') diff --git a/setup.cfg b/setup.cfg index 9d735614..2871b652 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,7 +48,6 @@ max_doc_length = 72 [coverage:run] plugins = covdefaults omit = testsuite/data -parallel = true [coverage:report] fail_under = 93 diff --git a/testing/data/W29.py b/testing/data/W29.py index e9ad5800..f8471976 100644 --- a/testing/data/W29.py +++ b/testing/data/W29.py @@ -14,8 +14,6 @@ class Foo(object): #: W191 W292 noeol if False: pass # indented with tabs -#: W292:1:36 noeol -# This line doesn't have a linefeed #: W292:1:5 E225:1:2 noeol 1+ 1 #: W292:1:27 E261:1:12 noeol diff --git a/testing/data/python39.py b/testing/data/python39.py new file mode 100644 index 00000000..723f0e1c --- /dev/null +++ b/testing/data/python39.py @@ -0,0 +1,2 @@ +#: W292:1:70 noeol +# This line doesn't have a linefeed (in 3.8 this is reported thrice!) diff --git a/testing/support.py b/testing/support.py index ca4744be..20a0eab8 100644 --- a/testing/support.py +++ b/testing/support.py @@ -1,8 +1,12 @@ from __future__ import annotations +import os.path + from pycodestyle import BaseReport from pycodestyle import StyleGuide +ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + class InMemoryReport(BaseReport): """ @@ -23,7 +27,7 @@ def error(self, line_number, offset, text, check): def errors_from_src(src: str) -> list[str]: - guide = StyleGuide(select=('E', 'W')) + guide = StyleGuide(select=('E', 'W'), max_doc_length=72) reporter = guide.init_report(InMemoryReport) guide.input_file( filename='in-memory-test-file.py', diff --git a/testsuite/support.py b/testsuite/support.py deleted file mode 100644 index 8bea0920..00000000 --- a/testsuite/support.py +++ /dev/null @@ -1,151 +0,0 @@ -from __future__ import annotations - -import os.path -import re -import sys - -from pycodestyle import readlines -from pycodestyle import StandardReport - -SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') -ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) - - -class PseudoFile(list): - """Simplified file interface.""" - write = list.append - - def getvalue(self): - return ''.join(self) - - def flush(self): - pass - - -class TestReport(StandardReport): - """Collect the results for the tests.""" - - def __init__(self, options): - options.benchmark_keys += ['test cases', 'failed tests'] - super().__init__(options) - self._verbose = options.verbose - - def error(self, line_number, offset, text, check): - """Report an error, according to options.""" - code = text[:4] - if code in self.counters: - self.counters[code] += 1 - else: - self.counters[code] = 1 - detailed_code = f'{code}:{line_number}:{offset + 1}' - # Don't care about expected errors or warnings - if code not in self.expected and detailed_code not in self.expected: # pragma: no cover # noqa: E501 - err = (line_number, offset, detailed_code, text[5:], check.__doc__) - self._deferred_print.append(err) - self.file_errors += 1 - self.total_errors += 1 - return code - - def get_file_results(self): - # Check if the expected errors were found - label = f'{self.filename}:{self.line_offset}:1' - for extended_code in self.expected: - code = extended_code.split(':')[0] - if not self.counters.get(code): # pragma: no cover - self.file_errors += 1 - self.total_errors += 1 - print(f'{label}: error {extended_code} not found') - else: - self.counters[code] -= 1 - for code, extra in sorted(self.counters.items()): - if code not in self._benchmark_keys: - if extra and code in self.expected: # pragma: no cover - self.file_errors += 1 - self.total_errors += 1 - print('%s: error %s found too many times (+%d)' % - (label, code, extra)) - # Reset counters - del self.counters[code] - if self._verbose and not self.file_errors: # pragma: no cover - print('%s: passed (%s)' % - (label, ' '.join(self.expected) or 'Okay')) - self.counters['test cases'] += 1 - if self.file_errors: # pragma: no cover - self.counters['failed tests'] += 1 - return super().get_file_results() - - def print_results(self): - results = ("%(physical lines)d lines tested: %(files)d files, " - "%(test cases)d test cases%%s." % self.counters) - if self.total_errors: # pragma: no cover - print(results % ", %s failures" % self.total_errors) - else: - print(results % "") - print("Test failed." if self.total_errors else "Test passed.") - - -def init_tests(pep8style): - """ - Initialize testing framework. - - A test file can provide many tests. Each test starts with a - declaration. This declaration is a single line starting with '#:'. - It declares codes of expected failures, separated by spaces or - 'Okay' if no failure is expected. - If the file does not contain such declaration, it should pass all - tests. If the declaration is empty, following lines are not - checked, until next declaration. - - Examples: - - * Only E224 and W701 are expected: #: E224 W701 - * Following example is conform: #: Okay - * Don't check these lines: #: - """ - report = pep8style.init_report(TestReport) - runner = pep8style.input_file - - def run_tests(filename): - """Run all the tests from a file.""" - # Skip tests meant for higher versions of python - ver_match = re.search(r'python(\d)(\d+)?\.py$', filename) - if ver_match: - test_against_version = tuple(int(val or 0) - for val in ver_match.groups()) - if sys.version_info < test_against_version: # pragma: no cover - return - lines = readlines(filename) + ['#:\n'] - line_offset = 0 - codes = ['Okay'] - testcase = [] - count_files = report.counters['files'] - for index, line in enumerate(lines): - if not line.startswith('#:'): - if codes: - # Collect the lines of the test case - testcase.append(line) - continue - if codes and index: - if 'noeol' in codes: - testcase[-1] = testcase[-1].rstrip('\n') - codes = [c for c in codes - if c not in ('Okay', 'noeol')] - # Run the checker - runner(filename, testcase, expected=codes, - line_offset=line_offset) - # output the real line numbers - line_offset = index + 1 - # configure the expected errors - codes = line.split()[1:] - # empty the test case buffer - del testcase[:] - report.counters['files'] = count_files + 1 - return report.counters['failed tests'] - - pep8style.runner = run_tests - - -def run_tests(style): - if style.options.testsuite: - init_tests(style) - return style.check_files() diff --git a/testsuite/test_all.py b/testsuite/test_all.py index aa4a93b4..99a81183 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -1,29 +1,12 @@ import os.path -import unittest import pycodestyle -from testsuite.support import init_tests -from testsuite.support import ROOT_DIR +from testing.support import ROOT -class PycodestyleTestCase(unittest.TestCase): - """Test the standard errors and warnings (E and W).""" - - def setUp(self): - self._style = pycodestyle.StyleGuide( - paths=[os.path.join(ROOT_DIR, 'testsuite')], - select='E,W', quiet=True) - - def test_checkers_testsuite(self): - init_tests(self._style) - report = self._style.check_files() - self.assertFalse(report.total_errors, - msg='%s failure(s)' % report.total_errors) - - def test_own_dog_food(self): - files = [pycodestyle.__file__.rstrip('oc'), __file__.rstrip('oc'), - os.path.join(ROOT_DIR, 'setup.py')] - report = self._style.init_report(pycodestyle.StandardReport) - report = self._style.check_files(files) - self.assertEqual(list(report.messages.keys()), ['W504'], - msg='Failures: %s' % report.messages) +def test_own_dog_food(): + style = pycodestyle.StyleGuide(select='E,W', quiet=True) + files = [pycodestyle.__file__, __file__, os.path.join(ROOT, 'setup.py')] + report = style.init_report(pycodestyle.StandardReport) + report = style.check_files(files) + assert list(report.messages) == ['W504'], f'Failures: {report.messages}' diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 8f467b21..32f6ad2a 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -1,13 +1,13 @@ +import io import os.path import shlex import sys import unittest import pycodestyle -from testsuite.support import PseudoFile -from testsuite.support import ROOT_DIR +from testing.support import ROOT -E11 = os.path.join(ROOT_DIR, 'testing', 'data', 'E11.py') +E11 = os.path.join(ROOT, 'testing', 'data', 'E11.py') class DummyChecker: @@ -26,8 +26,8 @@ def setUp(self): self._saved_stdout = sys.stdout self._saved_stderr = sys.stderr self._saved_checks = pycodestyle._checks - sys.stdout = PseudoFile() - sys.stderr = PseudoFile() + sys.stdout = io.StringIO() + sys.stderr = io.StringIO() pycodestyle._checks = { k: {f: (vals[0][:], vals[1]) for (f, vals) in v.items()} for k, v in self._saved_checks.items() @@ -39,7 +39,10 @@ def tearDown(self): pycodestyle._checks = self._saved_checks def reset(self): - del sys.stdout[:], sys.stderr[:] + sys.stdout.seek(0) + sys.stdout.truncate() + sys.stderr.seek(0) + sys.stderr.truncate() def test_register_physical_check(self): def check_dummy(physical_line, line_number): @@ -107,8 +110,8 @@ def check_dummy(logical, tokens): def test_styleguide(self): report = pycodestyle.StyleGuide().check_files() self.assertEqual(report.total_errors, 0) - self.assertFalse(sys.stdout) - self.assertFalse(sys.stderr) + self.assertFalse(sys.stdout.getvalue()) + self.assertFalse(sys.stderr.getvalue()) self.reset() report = pycodestyle.StyleGuide().check_files(['missing-file']) @@ -116,15 +119,15 @@ def test_styleguide(self): self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 1) # < 3.3 returns IOError; >= 3.3 returns FileNotFoundError - self.assertTrue(stdout[0].startswith("missing-file:1:1: E902 ")) - self.assertFalse(sys.stderr) + assert stdout[0].startswith("missing-file:1:1: E902 ") + self.assertFalse(sys.stderr.getvalue()) self.reset() report = pycodestyle.StyleGuide().check_files([E11]) stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 24) - self.assertFalse(sys.stderr) + self.assertFalse(sys.stderr.getvalue()) self.reset() # Passing the paths in the constructor gives same result @@ -132,7 +135,7 @@ def test_styleguide(self): stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 24) - self.assertFalse(sys.stderr) + self.assertFalse(sys.stderr.getvalue()) self.reset() def test_styleguide_options(self): @@ -149,7 +152,7 @@ def test_styleguide_options(self): # Check unset options for o in ('benchmark', 'config', 'count', 'diff', 'quiet', 'show_pep8', 'show_source', - 'statistics', 'testsuite', 'verbose'): + 'statistics', 'verbose'): oval = getattr(pep8style.options, o) self.assertTrue(oval in (None, False), msg=f'{o} = {oval!r}') @@ -335,7 +338,7 @@ def test_check_nullbytes(self): "stdin:1:1: E901 TokenError: source code cannot contain null bytes", # noqa: E501 ] self.assertEqual(stdout.splitlines(), expected) - self.assertFalse(sys.stderr) + self.assertFalse(sys.stderr.getvalue()) self.assertEqual(count_errors, len(expected)) def test_styleguide_unmatched_triple_quotes(self): diff --git a/testsuite/test_data.py b/testsuite/test_data.py new file mode 100644 index 00000000..80a5529b --- /dev/null +++ b/testsuite/test_data.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +import collections +import os.path +import re +import sys + +import pytest + +import pycodestyle +from testing.support import errors_from_src +from testing.support import ROOT + +PY_RE = re.compile(r'^python(\d)(\d*)\.py$') +CASE_RE = re.compile('^(#:.*\n)', re.MULTILINE) + + +def _nsort(items: list[str]) -> list[str]: + return sorted( + items, + key=lambda s: [ + int(part) if part.isdigit() else part.lower() + for part in re.split(r'(\d+)', s) + ], + ) + + +def get_tests(): + ret = [] + for fname in _nsort(os.listdir(os.path.join(ROOT, 'testing', 'data'))): + match = PY_RE.match(fname) + if match is not None: + major, minor = int(match[1]), int(match[2] or '0') + mark = pytest.mark.skipif( + sys.version_info < (major, minor), + reason=f'requires Python {major}.{minor}', + ) + else: + mark = () + + fname = os.path.join('testing', 'data', fname) + fname_full = os.path.join(ROOT, fname) + src = ''.join(pycodestyle.readlines(fname_full)) + + line = 1 + parts_it = iter(CASE_RE.split(src)) + # the first case will not have a comment for it + s = next(parts_it) + if s.strip(): + id_s = f'{fname}:{line}' + ret.append(pytest.param('#: Okay', s, id=id_s, marks=mark)) + line += s.count('\n') + + for comment, s in zip(parts_it, parts_it): + if s.strip(): + id_s = f'{fname}:{line}' + ret.append(pytest.param(comment, s, id=id_s, marks=mark)) + line += s.count('\n') + 1 + + assert ret + return ret + + +@pytest.mark.parametrize(('case', 's'), get_tests()) +def test(case, s): + codes = collections.Counter() + exact = collections.Counter() + + assert case.startswith('#:') + for code in case[2:].strip().split(): + if code == 'Okay': + continue + elif code == 'noeol': + s = s.rstrip('\n') + elif ':' in code: + exact[code] += 1 + else: + codes[code] += 1 + + unexpected = collections.Counter() + for code in errors_from_src(s): + if exact[code]: + exact[code] -= 1 + elif codes[code[:4]]: + codes[code[:4]] -= 1 + else: # pragma: no cover + unexpected[code] += 1 + + messages = ( + *(f'-{k}\n' for k, v in codes.items() for _ in range(v)), + *(f'-{k}\n' for k, v in exact.items() for _ in range(v)), + *(f'+{k}\n' for k, v in unexpected.items() for _ in range(v)), + ) + if messages: # pragma: no cover + raise AssertionError(f'unexpected codes!\n{"".join(messages)}') diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index f25864e6..8de8886d 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -1,11 +1,11 @@ import configparser +import io import os.path import sys import unittest import pycodestyle -from testsuite.support import PseudoFile -from testsuite.support import ROOT_DIR +from testing.support import ROOT class ShellTestCase(unittest.TestCase): @@ -21,8 +21,8 @@ def setUp(self): self._config_filenames = [] self.stdin = '' sys.argv = ['pycodestyle'] - sys.stdout = PseudoFile() - sys.stderr = PseudoFile() + sys.stdout = io.StringIO() + sys.stderr = io.StringIO() def fake_config_parser_read(cp, fp, filename): self._config_filenames.append(filename) @@ -41,7 +41,10 @@ def stdin_get_value(self): return self.stdin def pycodestyle(self, *args): - del sys.stdout[:], sys.stderr[:] + sys.stdout.seek(0) + sys.stdout.truncate() + sys.stderr.seek(0) + sys.stderr.truncate() sys.argv[1:] = args try: pycodestyle._main() @@ -73,7 +76,7 @@ def test_print_usage(self): self.assertFalse(self._config_filenames) def test_check_simple(self): - E11 = os.path.join(ROOT_DIR, 'testing', 'data', 'E11.py') + E11 = os.path.join(ROOT, 'testing', 'data', 'E11.py') stdout, stderr, errcode = self.pycodestyle(E11) stdout = stdout.splitlines() self.assertEqual(errcode, 1) diff --git a/tox.ini b/tox.ini index 064a7f00..de802015 100644 --- a/tox.ini +++ b/tox.ini @@ -14,9 +14,7 @@ deps = pytest commands = python -m pycodestyle --statistics pycodestyle.py - coverage run -m pycodestyle --max-doc-length=72 --testsuite testing/data coverage run -m pytest testsuite - coverage combine coverage report [testenv:flake8] From c8c388e40ed6b79c931b8f6d10fe19510ee33ad5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 17 Jul 2023 20:16:29 -0400 Subject: [PATCH 066/125] move testsuite -> tests --- .gitattributes | 2 +- CONTRIBUTING.rst | 6 +++--- MANIFEST.in | 10 ---------- README.rst | 4 ++-- docs/advanced.rst | 2 +- docs/developer.rst | 2 +- docs/intro.rst | 18 +++++++++--------- setup.cfg | 2 +- {testsuite => tests}/__init__.py | 0 {testsuite => tests}/test_E101.py | 2 +- {testsuite => tests}/test_E901.py | 2 +- {testsuite => tests}/test_all.py | 0 {testsuite => tests}/test_api.py | 0 {testsuite => tests}/test_blank_lines.py | 0 {testsuite => tests}/test_data.py | 0 {testsuite => tests}/test_parser.py | 0 {testsuite => tests}/test_pycodestyle.py | 0 {testsuite => tests}/test_self_doctests.py | 0 {testsuite => tests}/test_shell.py | 2 +- {testsuite => tests}/test_util.py | 0 tox.ini | 2 +- 21 files changed, 22 insertions(+), 32 deletions(-) delete mode 100644 MANIFEST.in rename {testsuite => tests}/__init__.py (100%) rename {testsuite => tests}/test_E101.py (87%) rename {testsuite => tests}/test_E901.py (92%) rename {testsuite => tests}/test_all.py (100%) rename {testsuite => tests}/test_api.py (100%) rename {testsuite => tests}/test_blank_lines.py (100%) rename {testsuite => tests}/test_data.py (100%) rename {testsuite => tests}/test_parser.py (100%) rename {testsuite => tests}/test_pycodestyle.py (100%) rename {testsuite => tests}/test_self_doctests.py (100%) rename {testsuite => tests}/test_shell.py (99%) rename {testsuite => tests}/test_util.py (100%) diff --git a/.gitattributes b/.gitattributes index 0aadd30b..e1d5e1b9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -testsuite/E90.py -text +testing/data/E90.py -text diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 062b726a..e7a79ae3 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -82,16 +82,16 @@ include unit, integration and functional tests. To run the tests:: - $ pytest testsuite + $ pytest tests Running functional ~~~~~~~~~~~~~~~~~~ $ pip install -e . $ # Run all tests. - $ pytest testsuite + $ pytest tests/test_data.py $ # Run a subset of the tests. - $ pytest testsuite -k testing/data/E30.py + $ pytest tests/tests_data.py -k testing/data/E30.py .. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index fb8bc97d..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,10 +0,0 @@ -include *.txt -include *.rst -include LICENSE -recursive-include docs * -recursive-include testsuite * -recursive-exclude docs *.pyc -recursive-exclude docs *.pyo -recursive-exclude testsuite *.pyc -recursive-exclude testsuite *.pyo -prune docs/_build diff --git a/README.rst b/README.rst index 2f6ddb87..bab8d71c 100644 --- a/README.rst +++ b/README.rst @@ -72,8 +72,8 @@ Example usage and output You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: - $ pycodestyle --show-source --show-pep8 testsuite/E40.py - testsuite/E40.py:2:10: E401 multiple imports on one line + $ pycodestyle --show-source --show-pep8 testing/data/E40.py + testing/data/E40.py:2:10: E401 multiple imports on one line import os, sys ^ Imports should usually be on separate lines. diff --git a/docs/advanced.rst b/docs/advanced.rst index 89700d56..9cdb5000 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -29,7 +29,7 @@ There's also a shortcut for checking a single file:: import pycodestyle - fchecker = pycodestyle.Checker('testsuite/E27.py', show_source=True) + fchecker = pycodestyle.Checker('tests/data/E27.py', show_source=True) file_errors = fchecker.check_all() print("Found %s errors (and warnings)" % file_errors) diff --git a/docs/developer.rst b/docs/developer.rst index 5a529e66..5aaee620 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -97,7 +97,7 @@ the docstring, you can use ``\n`` for newline and ``\t`` for tab. Then be sure to pass the tests:: - $ pytest testsuite + $ pytest tests $ python pycodestyle.py --verbose pycodestyle.py When contributing to pycodestyle, please observe our `Code of Conduct`_. diff --git a/docs/intro.rst b/docs/intro.rst index cdf5312b..8c74c782 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -78,8 +78,8 @@ Example usage and output You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: - $ pycodestyle --show-source --show-pep8 testsuite/E40.py - testsuite/E40.py:2:10: E401 multiple imports on one line + $ pycodestyle --show-source --show-pep8 testing/data/E40.py + testing/data/E40.py:2:10: E401 multiple imports on one line import os, sys ^ Imports should usually be on separate lines. @@ -105,14 +105,14 @@ Or you can display how often each error was found:: You can also make ``pycodestyle.py`` show the error text in different formats by using ``--format`` having options default/pylint/custom:: - $ pycodestyle testsuite/E40.py --format=default - testsuite/E40.py:2:10: E401 multiple imports on one line + $ pycodestyle testing/data/E40.py --format=default + testing/data/E40.py:2:10: E401 multiple imports on one line - $ pycodestyle testsuite/E40.py --format=pylint - testsuite/E40.py:2: [E401] multiple imports on one line + $ pycodestyle testing/data/E40.py --format=pylint + testing/data/E40.py:2: [E401] multiple imports on one line - $ pycodestyle testsuite/E40.py --format='%(path)s|%(row)d|%(col)d| %(code)s %(text)s' - testsuite/E40.py|2|10| E401 multiple imports on one line + $ pycodestyle testing/data/E40.py --format='%(path)s|%(row)d|%(col)d| %(code)s %(text)s' + testing/data/E40.py|2|10| E401 multiple imports on one line Variables in the ``custom`` format option @@ -433,7 +433,7 @@ special comment. This possibility should be reserved for special cases. Note: most errors can be listed with such one-liner:: - $ python pycodestyle.py --first --select E,W testsuite/ --format '%(code)s: %(text)s' + $ python pycodestyle.py --first --select E,W testing/data --format '%(code)s: %(text)s' .. _related-tools: diff --git a/setup.cfg b/setup.cfg index 2871b652..24c006f0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,7 +47,7 @@ max_doc_length = 72 [coverage:run] plugins = covdefaults -omit = testsuite/data +omit = testing/data [coverage:report] fail_under = 93 diff --git a/testsuite/__init__.py b/tests/__init__.py similarity index 100% rename from testsuite/__init__.py rename to tests/__init__.py diff --git a/testsuite/test_E101.py b/tests/test_E101.py similarity index 87% rename from testsuite/test_E101.py rename to tests/test_E101.py index c713370f..e3f2e5d0 100644 --- a/testsuite/test_E101.py +++ b/tests/test_E101.py @@ -1,4 +1,4 @@ -"""moved from testsuite files due to 3.12 making this a TokenError""" +"""moved from data files due to 3.12 making this a TokenError""" import sys import unittest diff --git a/testsuite/test_E901.py b/tests/test_E901.py similarity index 92% rename from testsuite/test_E901.py rename to tests/test_E901.py index 402b85a4..a1dbcfa9 100644 --- a/testsuite/test_E901.py +++ b/tests/test_E901.py @@ -1,4 +1,4 @@ -"""moved from testsuite files due to 3.12 changing syntax errors""" +"""moved from data files due to 3.12 changing syntax errors""" import sys import unittest diff --git a/testsuite/test_all.py b/tests/test_all.py similarity index 100% rename from testsuite/test_all.py rename to tests/test_all.py diff --git a/testsuite/test_api.py b/tests/test_api.py similarity index 100% rename from testsuite/test_api.py rename to tests/test_api.py diff --git a/testsuite/test_blank_lines.py b/tests/test_blank_lines.py similarity index 100% rename from testsuite/test_blank_lines.py rename to tests/test_blank_lines.py diff --git a/testsuite/test_data.py b/tests/test_data.py similarity index 100% rename from testsuite/test_data.py rename to tests/test_data.py diff --git a/testsuite/test_parser.py b/tests/test_parser.py similarity index 100% rename from testsuite/test_parser.py rename to tests/test_parser.py diff --git a/testsuite/test_pycodestyle.py b/tests/test_pycodestyle.py similarity index 100% rename from testsuite/test_pycodestyle.py rename to tests/test_pycodestyle.py diff --git a/testsuite/test_self_doctests.py b/tests/test_self_doctests.py similarity index 100% rename from testsuite/test_self_doctests.py rename to tests/test_self_doctests.py diff --git a/testsuite/test_shell.py b/tests/test_shell.py similarity index 99% rename from testsuite/test_shell.py rename to tests/test_shell.py index 8de8886d..e4a75e1c 100644 --- a/testsuite/test_shell.py +++ b/tests/test_shell.py @@ -194,7 +194,7 @@ def test_check_diff(self): )) # no matching file in the diff - diff_lines[3] = "+++ b/testsuite/lost/E11.py" + diff_lines[3] = "+++ b/testing/lost/E11.py" self.stdin = '\n'.join(diff_lines) stdout, stderr, errcode = self.pycodestyle('--diff') self.assertFalse(errcode) diff --git a/testsuite/test_util.py b/tests/test_util.py similarity index 100% rename from testsuite/test_util.py rename to tests/test_util.py diff --git a/tox.ini b/tox.ini index de802015..c9b9255f 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ deps = pytest commands = python -m pycodestyle --statistics pycodestyle.py - coverage run -m pytest testsuite + coverage run -m pytest tests coverage report [testenv:flake8] From 7701377c0bdb999c380d67f82c3d555fa4c5d69c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 17 Jul 2023 20:58:16 -0400 Subject: [PATCH 067/125] handle noqa for multiline fstrings in 3.12 --- pycodestyle.py | 27 +++++++++++++++++---------- testing/data/noqa.py | 6 ++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 9388ae52..c902ad72 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1842,6 +1842,7 @@ def __init__(self, filename=None, lines=None, self.max_line_length = options.max_line_length self.max_doc_length = options.max_doc_length self.indent_size = options.indent_size + self.fstring_start = 0 self.multiline = False # in a multiline string? self.hang_closing = options.hang_closing self.indent_size = options.indent_size @@ -2030,13 +2031,15 @@ def maybe_check_physical(self, token, prev_physical): # if the file does not end with a newline, the NEWLINE # token is inserted by the parser, but it does not contain # the previous physical line in `token[4]` - if token[4] == '': + if token.line == '': self.check_physical(prev_physical) else: - self.check_physical(token[4]) + self.check_physical(token.line) + elif token.type == FSTRING_START: # pragma: >=3.12 cover + self.fstring_start = token.start[0] elif ( - token[0] in {tokenize.STRING, FSTRING_MIDDLE} and - '\n' in token[1] + token.type == tokenize.STRING and '\n' in token.string or + token.type == FSTRING_END ): # Less obviously, a string that contains newlines is a # multiline string, either triple-quoted or with internal @@ -2053,14 +2056,18 @@ def maybe_check_physical(self, token, prev_physical): # - have to wind self.line_number back because initially it # points to the last line of the string, and we want # check_physical() to give accurate feedback - if noqa(token[4]): + if noqa(token.line): return + if token.type == FSTRING_END: # pragma: >=3.12 cover + start = self.fstring_start + else: + start = token.start[0] + end = token.end[0] + self.multiline = True - self.line_number = token[2][0] - _, src, (_, offset), _, _ = token - src = self.lines[self.line_number - 1][:offset] + src - for line in src.split('\n')[:-1]: - self.check_physical(line + '\n') + self.line_number = start + for line_number in range(start, end): + self.check_physical(self.lines[line_number - 1] + '\n') self.line_number += 1 self.multiline = False diff --git a/testing/data/noqa.py b/testing/data/noqa.py index 02fdd4f8..3d02492e 100644 --- a/testing/data/noqa.py +++ b/testing/data/noqa.py @@ -12,4 +12,10 @@ a = 1 if a == None: # noqa pass + +# should silence E501 +s = f''' +loong {y} looooooooooooooong loooooooooooooong looooooooong loooooooong looooooooong +{x} +''' # noqa #: From 146c422aba6346fbdd17c043b77faa411a7bed8e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 17 Jul 2023 21:24:51 -0400 Subject: [PATCH 068/125] 3.12: fix fstring starting with escape --- pycodestyle.py | 6 +++--- testing/data/E50.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index c902ad72..db0c78ab 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -2026,8 +2026,10 @@ def maybe_check_physical(self, token, prev_physical): """If appropriate for token, check current physical line(s).""" # Called after every token, but act only on end of line. + if token.type == FSTRING_START: # pragma: >=3.12 cover + self.fstring_start = token.start[0] # a newline token ends a single physical line. - if _is_eol_token(token): + elif _is_eol_token(token): # if the file does not end with a newline, the NEWLINE # token is inserted by the parser, but it does not contain # the previous physical line in `token[4]` @@ -2035,8 +2037,6 @@ def maybe_check_physical(self, token, prev_physical): self.check_physical(prev_physical) else: self.check_physical(token.line) - elif token.type == FSTRING_START: # pragma: >=3.12 cover - self.fstring_start = token.start[0] elif ( token.type == tokenize.STRING and '\n' in token.string or token.type == FSTRING_END diff --git a/testing/data/E50.py b/testing/data/E50.py index ab7ddd45..6ab50c88 100644 --- a/testing/data/E50.py +++ b/testing/data/E50.py @@ -92,6 +92,10 @@ def foo(): """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis pulvinar vitae """ +#: E501 +loooooong = 'looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong' +f"""\ +""" #: Okay """ This From a21f8e3d1ebb74070c82c313e02807536976c8bf Mon Sep 17 00:00:00 2001 From: Lev Blit Date: Fri, 21 Jul 2023 15:06:44 +0300 Subject: [PATCH 069/125] fix some wrong renames from testsuite to testing/data vs test --- docs/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 9cdb5000..4769e06f 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -29,7 +29,7 @@ There's also a shortcut for checking a single file:: import pycodestyle - fchecker = pycodestyle.Checker('tests/data/E27.py', show_source=True) + fchecker = pycodestyle.Checker('testing/data/E27.py', show_source=True) file_errors = fchecker.check_all() print("Found %s errors (and warnings)" % file_errors) From e2a366da80cd83af1f9fb95bb28ba0a332e23597 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Jul 2023 12:40:06 -0400 Subject: [PATCH 070/125] remove handling of python 2 ur'' strings --- pycodestyle.py | 3 +-- testing/data/E12not.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index db0c78ab..b10c498d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -686,8 +686,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if verbose >= 4: print(f"bracket depth {depth} indent to {start[1]}") # deal with implicit string concatenation - elif (token_type in (tokenize.STRING, tokenize.COMMENT) or - text in ('u', 'ur', 'b', 'br')): + elif token_type in (tokenize.STRING, tokenize.COMMENT): indent_chances[start[1]] = str # visual indent after assert/raise/with elif not row and not depth and text in ["assert", "raise", "with"]: diff --git a/testing/data/E12not.py b/testing/data/E12not.py index 76776591..86c0fdd7 100644 --- a/testing/data/E12not.py +++ b/testing/data/E12not.py @@ -429,9 +429,6 @@ def unicode2html(s): help = u"print total number of errors " \ u"to standard error" -help = ur"print total number of errors " \ - ur"to standard error" - help = b"print total number of errors " \ b"to standard error" From e0c1069ee97c4f041d69e29141f992416f4f100e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Jul 2023 12:44:30 -0400 Subject: [PATCH 071/125] handle fstring continuation in 3.12 --- pycodestyle.py | 2 +- testing/data/E12not.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index b10c498d..445281c1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -686,7 +686,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if verbose >= 4: print(f"bracket depth {depth} indent to {start[1]}") # deal with implicit string concatenation - elif token_type in (tokenize.STRING, tokenize.COMMENT): + elif token_type in (tokenize.STRING, tokenize.COMMENT, FSTRING_START): indent_chances[start[1]] = str # visual indent after assert/raise/with elif not row and not depth and text in ["assert", "raise", "with"]: diff --git a/testing/data/E12not.py b/testing/data/E12not.py index 86c0fdd7..a92739e5 100644 --- a/testing/data/E12not.py +++ b/testing/data/E12not.py @@ -435,6 +435,9 @@ def unicode2html(s): help = br"print total number of errors " \ br"to standard error" +help = f"print total number of errors " \ + f"to standard error" + d = dict('foo', help="exclude files or directories which match these " "comma separated patterns (default: %s)" % DEFAULT_EXCLUDE) From 21abd9b6dcbfa38635bc85a2c2327ec11ad91ffc Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Jul 2023 13:18:24 -0400 Subject: [PATCH 072/125] Release 2.11.0 --- CHANGES.txt | 18 +++++++++++++++++- pycodestyle.py | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9570eecf..931a6ee3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,12 +1,28 @@ Changelog ========= +2.11.0 (2023-07-29) +------------------- + +Changes: + +* Drop EOL python 3.6 / 3.7. PR #1129, #1160. +* Add support for python 3.12. PR #1147, #1148, #1152, #1153, #1154, #1163, + #1164, #1165, #1166, #1176, #1177, #1182. +* E721: adjust handling of type comparison. Allowed forms are now + ``isinstance(x, t)`` or ``type(x) is t``. PR #1086, #1167. +* Remove handling of python 2 ``<>`` operator. PR #1161. +* W606: removed. ``async`` / ``await`` are always keywords. PR #1162. +* Internal: move tests to pytest. PR #1168, #1169, #1171, #1173, #1174, #1175. +* Remove handling of python 2 ``ur''`` strings. PR #1181. + + 2.10.0 (2022-11-23) ------------------- Changes: -* E231: allow trailing comma inside 1-tuples in `[]`. PR #1108. +* E231: allow trailing comma inside 1-tuples in ``[]``. PR #1108. * W601, W602, W603, W604: removed (no longer relevant in python 3). PR #1111. * E741: also apply to lambdas. PR #1106. * E741: fix false positive for comparison operators. PR #1118. diff --git a/pycodestyle.py b/pycodestyle.py index 445281c1..c492436b 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Mon, 31 Jul 2023 19:20:51 -0400 Subject: [PATCH 073/125] add minimal rtd configuration --- .readthedocs.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..4b74802e --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,6 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" From dcf87677c9b6611ba31e0a3d9b009842071fdc56 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 12:28:38 +0000 Subject: [PATCH 074/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f57e1dd9..98a90b84 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.9.0 + rev: v3.10.1 hooks: - id: pyupgrade args: [--py38-plus] @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 From 6b6c75c073a5cff56d872252b8eb08512162ca57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:02:04 +0000 Subject: [PATCH 075/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 98a90b84..035978a3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.10.0 + rev: v3.11.0 hooks: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + rev: v3.11.0 hooks: - id: pyupgrade args: [--py38-plus] From 0fd02d7dcbb4485b5130c7119893eed94dd58353 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:18:57 +0000 Subject: [PATCH 076/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.11.0 → v3.13.0](https://github.com/asottile/pyupgrade/compare/v3.11.0...v3.13.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 035978a3..a1798df8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.11.0 + rev: v3.13.0 hooks: - id: pyupgrade args: [--py38-plus] From f1b552e44f0e7055fdf34503580c60eebf3deb17 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:58:01 +0000 Subject: [PATCH 077/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [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) - [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) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1798df8..07d60338 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,17 +8,17 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.11.0 + rev: v3.12.0 hooks: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.13.0 + rev: v3.14.0 hooks: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.4.0 + rev: v2.5.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 From 04d9c84b8804e32d50f637cf1f169e8235f1b562 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 21:55:50 +0000 Subject: [PATCH 078/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07d60338..8b1ea6d2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testing/data/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml - id: debug-statements @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.14.0 + rev: v3.15.0 hooks: - id: pyupgrade args: [--py38-plus] From 0c5e01a41d86ca30385622948252f350878921c1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 12 Oct 2023 09:19:56 -0400 Subject: [PATCH 079/125] fix 3.12 false positive when fstring chunk is a keyword --- pycodestyle.py | 1 + testing/data/python36.py | 1 + 2 files changed, 2 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index c492436b..18514f06 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -490,6 +490,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): # appear e.g. as "if x is None:", and async/await, which were # valid identifier names in old Python versions. if (tok0.end == tok1.start and + tok0.type == tokenize.NAME and keyword.iskeyword(tok0.string) and tok0.string not in SINGLETONS and not (tok0.string == 'except' and tok1.string == '*') and diff --git a/testing/data/python36.py b/testing/data/python36.py index 94ec2dc5..aefd6540 100644 --- a/testing/data/python36.py +++ b/testing/data/python36.py @@ -1,2 +1,3 @@ #: Okay f'{hello}:{world}' +f'in{x}' From cefd59cf8e8a199a978da491e75a37fe4d37de82 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 12 Oct 2023 19:38:21 -0400 Subject: [PATCH 080/125] Release 2.11.1 --- CHANGES.txt | 7 +++++++ pycodestyle.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 931a6ee3..55c000aa 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Changelog ========= +2.11.1 (2023-10-12) +------------------- + +Changes: + +* E275: fix false positive with fstrings containing keyword parts in python 3.12 + 2.11.0 (2023-07-29) ------------------- diff --git a/pycodestyle.py b/pycodestyle.py index 18514f06..3354346a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Tue, 21 Nov 2023 11:28:02 +0100 Subject: [PATCH 081/125] testing: add a pair of E303 testcases Add two test cases to verify that pycodestyle correctly flags 2 empty lines in non-toplevel contexts, even outside of functions: one for two empty lines between class methods and one for two empty lines in the indented body of an `if:` statement at toplevel. --- testing/data/E30.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/testing/data/E30.py b/testing/data/E30.py index ebe4e9d2..7ef46890 100644 --- a/testing/data/E30.py +++ b/testing/data/E30.py @@ -75,6 +75,20 @@ def a(): #: +#: E303:6:5 +class xyz: + def a(self): + pass + + + def b(self): + pass +#: E303:5:5 +if True: + a = 1 + + + a = 2 #: E304:3:1 @decorator From 9dc066bc792ef2b58e87ef2fd5b5e0203c520c30 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 27 Dec 2023 00:07:24 -0500 Subject: [PATCH 082/125] improve backtracking of COMPARE_TYPE_REGEX --- pycodestyle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3354346a..2f092c40 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -126,8 +126,8 @@ COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?%&^]+|:=)(\s*)') From 2a9ee347e09ec4f00bc9e085cf4c7110a257595b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 21:35:32 +0000 Subject: [PATCH 083/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 6.1.0 → 7.0.0](https://github.com/pycqa/flake8/compare/6.1.0...7.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8b1ea6d2..abecae48 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 + rev: 7.0.0 hooks: - id: flake8 From 2a612c15c24afaf09fe585fcb5038a3cac54c3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20D=C3=BCmig?= Date: Tue, 6 Feb 2024 22:24:25 +0200 Subject: [PATCH 084/125] prevent false positive for E721 on member function E721 was reported on the result of member functions of name "type" with one parameter, if those occurred on the left side of the comparison. --- pycodestyle.py | 2 +- testing/data/E72.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 2f092c40..9adbb6b1 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -127,7 +127,7 @@ r'(in|is)\s') COMPARE_TYPE_REGEX = re.compile( r'[=!]=\s+type(?:\s*\(\s*([^)]*[^\s)])\s*\))' - r'|\btype(?:\s*\(\s*([^)]*[^\s)])\s*\))\s+[=!]=' + r'|(?%&^]+|:=)(\s*)') diff --git a/testing/data/E72.py b/testing/data/E72.py index ac55a958..5d1046cb 100644 --- a/testing/data/E72.py +++ b/testing/data/E72.py @@ -5,6 +5,8 @@ if type(res) != type(""): pass #: Okay +res.type("") == "" +#: Okay import types if res == types.IntType: From 45e2d9f9726d9682f9baadc11c61cc6c7af56c72 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 01:17:01 +0000 Subject: [PATCH 085/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.15.0 → v3.15.1](https://github.com/asottile/pyupgrade/compare/v3.15.0...v3.15.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index abecae48..5aecb4eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 + rev: v3.15.1 hooks: - id: pyupgrade args: [--py38-plus] From 72668f67e94d58e69914dd4b139907956ef79575 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Fri, 8 Mar 2024 15:42:38 -0500 Subject: [PATCH 086/125] fix E502 being disabled after a comment --- pycodestyle.py | 2 ++ testing/data/E50.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/pycodestyle.py b/pycodestyle.py index 9adbb6b1..6443a42a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1269,6 +1269,8 @@ def explicit_line_join(logical_line, tokens): comment = True if start[0] != prev_start and parens and backslash and not comment: yield backslash, "E502 the backslash is redundant between brackets" + if start[0] != prev_start: + comment = False # Reset comment flag on newline if end[0] != prev_end: if line.rstrip('\r\n').endswith('\\'): backslash = (end[0], len(line.splitlines()[-1]) - 1) diff --git a/testing/data/E50.py b/testing/data/E50.py index 6ab50c88..4870fcc2 100644 --- a/testing/data/E50.py +++ b/testing/data/E50.py @@ -25,6 +25,13 @@ if (foo is None and bar is "e000" and \ blah == 'yeah'): blah = 'yeahnah' +#: E502 W503 W503 +y = ( + 2 + 2 # \ + + 3 # \ + + 4 \ + + 3 +) # #: Okay a = ('AAA' From 6febb0d05ff37a177ddf10190804b0e0076d84d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:47:40 +0000 Subject: [PATCH 087/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.15.1 → v3.15.2](https://github.com/asottile/pyupgrade/compare/v3.15.1...v3.15.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5aecb4eb..0b95e30d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.15.1 + rev: v3.15.2 hooks: - id: pyupgrade args: [--py38-plus] From 04f579dd76aa0294db65c3dbbbd110c80609137e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 23:37:50 +0000 Subject: [PATCH 088/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0b95e30d..541017f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testing/data/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-yaml - id: debug-statements From 53591a50faf21c27d9ffaeda75dd869fbec19a20 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 23:13:12 +0000 Subject: [PATCH 089/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.12.0 → v3.13.0](https://github.com/asottile/reorder-python-imports/compare/v3.12.0...v3.13.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 541017f9..f1eec7fd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.12.0 + rev: v3.13.0 hooks: - id: reorder-python-imports args: [--py38-plus] From 5be6ca17fcd04b5e8d7bf4d8c57daf9cd17b0bea Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 23:29:23 +0000 Subject: [PATCH 090/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.15.2 → v3.16.0](https://github.com/asottile/pyupgrade/compare/v3.15.2...v3.16.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f1eec7fd..9abea703 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.15.2 + rev: v3.16.0 hooks: - id: pyupgrade args: [--py38-plus] From 3cedd4c74b5f467fc4c1cde5a8ac2503a54b60db Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jun 2024 14:29:53 -0400 Subject: [PATCH 091/125] add new error E204 for whitespace after decorator @ --- docs/intro.rst | 2 ++ pycodestyle.py | 7 +++++++ testing/data/E20.py | 17 +++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/docs/intro.rst b/docs/intro.rst index 8c74c782..a7187a27 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -262,6 +262,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E203 | whitespace before ',', ';', or ':' | +------------+----------------------------------------------------------------------+ +| E204 | whitespace after decorator '@' | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | E211 | whitespace before '(' | +------------+----------------------------------------------------------------------+ diff --git a/pycodestyle.py b/pycodestyle.py index 6443a42a..139ae2ba 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -120,6 +120,7 @@ ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b') DOCSTRING_REGEX = re.compile(r'u?r?["\']') EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[\[({][ \t]|[ \t][\]}),;:](?!=)') +WHITESPACE_AFTER_DECORATOR_REGEX = re.compile(r'@\s') WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)') COMPARE_SINGLETON_REGEX = re.compile(r'(\bNone|\bFalse|\bTrue)?\s*([=!]=)' r'\s*(?(1)|(None|False|True))\b') @@ -438,6 +439,9 @@ def extraneous_whitespace(logical_line): E203: if x == 4: print x, y; x, y = y , x E203: if x == 4: print x, y ; x, y = y, x E203: if x == 4 : print x, y; x, y = y, x + + Okay: @decorator + E204: @ decorator """ line = logical_line for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line): @@ -451,6 +455,9 @@ def extraneous_whitespace(logical_line): code = ('E202' if char in '}])' else 'E203') # if char in ',;:' yield found, f"{code} whitespace before '{char}'" + if WHITESPACE_AFTER_DECORATOR_REGEX.match(logical_line): + yield 1, "E204 whitespace after decorator '@'" + @register_check def whitespace_around_keywords(logical_line): diff --git a/testing/data/E20.py b/testing/data/E20.py index 20c6dfd8..ed21b213 100644 --- a/testing/data/E20.py +++ b/testing/data/E20.py @@ -75,4 +75,21 @@ x, y = y, x a[b1, :] == a[b1, ...] b = a[:, b1] +#: E204:1:2 +@ decorator +def f(): + pass +#: E204:1:2 +@ decorator +def f(): + pass +#: E204:1:2 +@ decorator +def f(): + pass +#: E204:2:6 +if True: + @ decorator + def f(): + pass #: From 6f60985ab563c3657da8d44ba391128b40c9ea24 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 15 Jun 2024 17:28:03 -0400 Subject: [PATCH 092/125] Release 2.12.0 --- CHANGES.txt | 9 +++++++++ pycodestyle.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 55c000aa..6e8f9f67 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,15 @@ Changelog ========= +2.12.0 (2024-06-15) +------------------- + +Changes: + +* E721: Fix false positive of the form `x.type(...) ==`. PR #1228. +* E502: Fix false-negative with a backslash escape in a comment. PR #1234. +* E204: New lint forbidding whitespace after decorator `@`. PR #1247. + 2.11.1 (2023-10-12) ------------------- diff --git a/pycodestyle.py b/pycodestyle.py index 139ae2ba..19f88c2a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Mon, 17 Jun 2024 23:33:17 +0000 Subject: [PATCH 093/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.0.0 → 7.1.0](https://github.com/pycqa/flake8/compare/7.0.0...7.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9abea703..0681be99 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.1.0 hooks: - id: flake8 From 28aeabb3cbd13154ce0171c2c2d2c02ff577493c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 23:25:52 +0000 Subject: [PATCH 094/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.16.0 → v3.17.0](https://github.com/asottile/pyupgrade/compare/v3.16.0...v3.17.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0681be99..37793c4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py38-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.16.0 + rev: v3.17.0 hooks: - id: pyupgrade args: [--py38-plus] From 37c9f605259c686daeb9c00f33df886cd83b3d1c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 4 Aug 2024 16:16:13 -0400 Subject: [PATCH 095/125] adjust logical line for FSTRING_MIDDLE brace escaping --- pycodestyle.py | 5 ++++- tests/test_pycodestyle.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 19f88c2a..4df30f62 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1949,7 +1949,10 @@ def build_tokens_line(self): if token_type == tokenize.STRING: text = mute_string(text) elif token_type == FSTRING_MIDDLE: # pragma: >=3.12 cover - text = 'x' * len(text) + # fstring tokens are "unescaped" braces -- re-escape! + brace_count = text.count('{') + text.count('}') + text = 'x' * (len(text) + brace_count) + end = (end[0], end[1] + brace_count) if prev_row: (start_row, start_col) = start if prev_row != start_row: # different row diff --git a/tests/test_pycodestyle.py b/tests/test_pycodestyle.py index 8885f0d8..444d59f0 100644 --- a/tests/test_pycodestyle.py +++ b/tests/test_pycodestyle.py @@ -1,5 +1,10 @@ +import io +import sys +import tokenize + import pytest +from pycodestyle import Checker from pycodestyle import expand_indent from pycodestyle import mute_string @@ -27,3 +32,17 @@ def test_expand_indent(s, expected): ) def test_mute_string(s, expected): assert mute_string(s) == expected + + +def test_fstring_logical_line(): + src = '''\ +f'hello {{ {thing} }} world' +''' + checker = Checker(lines=src.splitlines()) + checker.tokens = list(tokenize.generate_tokens(io.StringIO(src).readline)) + checker.build_tokens_line() + + if sys.version_info >= (3, 12): # pragma: >3.12 cover + assert checker.logical_line == "f'xxxxxxxxx{thing}xxxxxxxxx'" + else: + assert checker.logical_line == "f'xxxxxxxxxxxxxxxxxxxxxxxxx'" From 5cff01b3a8ea81961483b7ff885df66db6b44e4e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 4 Aug 2024 16:25:54 -0400 Subject: [PATCH 096/125] Release 2.12.1 --- CHANGES.txt | 8 ++++++++ pycodestyle.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 6e8f9f67..9834484f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,14 @@ Changelog ========= +2.12.1 (2024-08-04) +------------------- + +Changes: + +* Properly preserve escaped `{` and `}` in fstrings in logical lines in 3.12+. + PR #1252. + 2.12.0 (2024-06-15) ------------------- diff --git a/pycodestyle.py b/pycodestyle.py index 4df30f62..c4c8dbaa 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Tue, 6 Aug 2024 00:22:09 +0000 Subject: [PATCH 097/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.1.0 → 7.1.1](https://github.com/pycqa/flake8/compare/7.1.0...7.1.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 37793c4c..2827172a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 7.1.0 + rev: 7.1.1 hooks: - id: flake8 From 035b52f15eb6ecca44460567d1961ab47a758c12 Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Fri, 9 Aug 2024 04:30:09 -0400 Subject: [PATCH 098/125] Combine rstrip calls in trailing_whitespace check This provides a small speed-up on large codebases. --- pycodestyle.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index c4c8dbaa..ec07aaac 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -233,9 +233,11 @@ def trailing_whitespace(physical_line): W291: spam(1) \n# W293: class Foo(object):\n \n bang = 12 """ - physical_line = physical_line.rstrip('\n') # chr(10), newline - physical_line = physical_line.rstrip('\r') # chr(13), carriage return - physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L + # Strip these trailing characters: + # - chr(10), newline + # - chr(13), carriage return + # - chr(12), form feed, ^L + physical_line = physical_line.rstrip('\n\r\x0c') stripped = physical_line.rstrip(' \t\v') if physical_line != stripped: if stripped: From bb413524046e46b65162c4248a3fe2979d3b1c06 Mon Sep 17 00:00:00 2001 From: correctmost <134317971+correctmost@users.noreply.github.com> Date: Fri, 9 Aug 2024 04:41:43 -0400 Subject: [PATCH 099/125] Use list comprehensions to avoid append() calls This provides a small speed-up on large codebases. --- pycodestyle.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index c4c8dbaa..3d32b581 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1910,9 +1910,7 @@ def readline(self): def run_check(self, check, argument_names): """Run a check plugin.""" - arguments = [] - for name in argument_names: - arguments.append(getattr(self, name)) + arguments = [getattr(self, name) for name in argument_names] return check(*arguments) def init_checker_state(self, name, argument_names): From 6a19e1ff9357e8e02c07b22050dfb99dc048f770 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 01:47:21 +0000 Subject: [PATCH 100/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2827172a..6894e4c1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testing/data/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-yaml - id: debug-statements From 414f4700ee5dea6e41e9a0c36cb373b86e0031a7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Oct 2024 20:42:55 -0400 Subject: [PATCH 101/125] py39-plus --- .github/workflows/main.yml | 15 +++++++++------ .pre-commit-config.yaml | 4 ++-- pycodestyle.py | 7 ++----- setup.cfg | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 393067b1..c53a94f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,6 @@ jobs: - os: ubuntu-latest py: pypy3.10 toxenv: py - - os: ubuntu-latest - py: 3.8 - toxenv: py - os: ubuntu-latest py: 3.9 toxenv: py @@ -29,7 +26,13 @@ jobs: py: '3.11' toxenv: py - os: ubuntu-latest - py: '3.12-dev' + py: '3.12' + toxenv: py + - os: ubuntu-latest + py: '3.13' + toxenv: py + - os: ubuntu-latest + py: '3.14-dev' toxenv: py - os: ubuntu-latest py: 3.9 @@ -40,10 +43,10 @@ jobs: - uses: actions/setup-python@v4 with: python-version: ${{ matrix.py }} - if: matrix.py != '3.12-dev' + if: matrix.py != '3.14-dev' - uses: deadsnakes/action@v3.0.1 with: python-version: ${{ matrix.py }} - if: matrix.py == '3.12-dev' + if: matrix.py == '3.14-dev' - run: pip install tox - run: tox -e ${{ matrix.toxenv }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6894e4c1..ca1632dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,12 +11,12 @@ repos: rev: v3.13.0 hooks: - id: reorder-python-imports - args: [--py38-plus] + args: [--py39-plus] - repo: https://github.com/asottile/pyupgrade rev: v3.17.0 hooks: - id: pyupgrade - args: [--py38-plus] + args: [--py39-plus] - repo: https://github.com/asottile/setup-cfg-fmt rev: v2.5.0 hooks: diff --git a/pycodestyle.py b/pycodestyle.py index 6425596e..07eeb49e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -785,7 +785,6 @@ def whitespace_before_parameters(logical_line, tokens): # Allow "return (a.foo for a in range(5))" not keyword.iskeyword(prev_text) and ( - sys.version_info < (3, 9) or # 3.12+: type is a soft keyword but no braces after prev_text == 'type' or not keyword.issoftkeyword(prev_text) @@ -960,10 +959,8 @@ def missing_whitespace(logical_line, tokens): # Allow argument unpacking: foo(*args, **kwargs). if prev_type == tokenize.OP and prev_text in '}])' or ( prev_type != tokenize.OP and - prev_text not in KEYWORDS and ( - sys.version_info < (3, 9) or - not keyword.issoftkeyword(prev_text) - ) + prev_text not in KEYWORDS and + not keyword.issoftkeyword(prev_text) ): need_space = None elif text in WS_OPTIONAL_OPERATORS: diff --git a/setup.cfg b/setup.cfg index 24c006f0..09a89190 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,7 @@ project_urls = [options] py_modules = pycodestyle -python_requires = >=3.8 +python_requires = >=3.9 include_package_data = True zip_safe = False From 03e3a3aff319c688104aa117ab49eaef2b8284b3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 00:07:58 +0000 Subject: [PATCH 102/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.13.0 → v3.14.0](https://github.com/asottile/reorder-python-imports/compare/v3.13.0...v3.14.0) - [github.com/asottile/pyupgrade: v3.17.0 → v3.18.0](https://github.com/asottile/pyupgrade/compare/v3.17.0...v3.18.0) - [github.com/asottile/setup-cfg-fmt: v2.5.0 → v2.7.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.5.0...v2.7.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca1632dc..73815320 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,17 +8,17 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.13.0 + rev: v3.14.0 hooks: - id: reorder-python-imports args: [--py39-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.17.0 + rev: v3.18.0 hooks: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.5.0 + rev: v2.7.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 From c226236652c264c36c544c05345c24a5eff6f8a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 00:36:31 +0000 Subject: [PATCH 103/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.18.0 → v3.19.0](https://github.com/asottile/pyupgrade/compare/v3.18.0...v3.19.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 73815320..f3d0f804 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py39-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.18.0 + rev: v3.19.0 hooks: - id: pyupgrade args: [--py39-plus] From bf4c16d2891150d0d43df3a6e9f7f5b6361544ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:26:08 +0000 Subject: [PATCH 104/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.19.0 → v3.19.1](https://github.com/asottile/pyupgrade/compare/v3.19.0...v3.19.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f3d0f804..b353c3cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py39-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.19.0 + rev: v3.19.1 hooks: - id: pyupgrade args: [--py39-plus] From 5f7b5d261996c2c25530a82eae2cf5eeffc22c3c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:43:58 +0000 Subject: [PATCH 105/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.1.1 → 7.1.2](https://github.com/pycqa/flake8/compare/7.1.1...7.1.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b353c3cd..63996874 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 7.1.1 + rev: 7.1.2 hooks: - id: flake8 From 30627b318756c66ee0ff37a798f8b9339a7c0a83 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 18 Mar 2025 13:49:20 -0400 Subject: [PATCH 106/125] add support for PEP 696 generic defaults --- pycodestyle.py | 28 +++++++++++++++++++--------- testing/data/python313.py | 9 +++++++++ tests/test_E901.py | 4 ++-- 3 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 testing/data/python313.py diff --git a/pycodestyle.py b/pycodestyle.py index 07eeb49e..3324850a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -135,6 +135,7 @@ LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b') +STARTSWITH_GENERIC_REGEX = re.compile(r'^(async\s+def|def|class|type)\s+\w+\[') STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( r'^\s*({})\b'.format('|'.join(s.replace(' ', r'\s+') for s in ( @@ -1019,12 +1020,13 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): E251: return magic(r = real, i = imag) E252: def complex(real, image: float=0.0): """ - parens = 0 + paren_stack = [] no_space = False require_space = False prev_end = None annotated_func_arg = False in_def = bool(STARTSWITH_DEF_REGEX.match(logical_line)) + in_generic = bool(STARTSWITH_GENERIC_REGEX.match(logical_line)) message = "E251 unexpected spaces around keyword / parameter equals" missing_message = "E252 missing whitespace around parameter equals" @@ -1042,15 +1044,23 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): yield (prev_end, missing_message) if token_type == tokenize.OP: if text in '([': - parens += 1 - elif text in ')]': - parens -= 1 - elif in_def and text == ':' and parens == 1: + paren_stack.append(text) + elif text in ')]' and paren_stack: + paren_stack.pop() + elif ( + text == ':' and ( + # def f(arg: tp = default): ... + (in_def and paren_stack == ['(']) or + # def f[T: tp = default](): ... + # class C[T: tp = default](): ... + (in_generic and paren_stack == ['[']) + ) + ): annotated_func_arg = True - elif parens == 1 and text == ',': + elif len(paren_stack) == 1 and text == ',': annotated_func_arg = False - elif parens and text == '=': - if annotated_func_arg and parens == 1: + elif paren_stack and text == '=': + if annotated_func_arg and len(paren_stack) == 1: require_space = True if start == prev_end: yield (prev_end, missing_message) @@ -1058,7 +1068,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): no_space = True if start != prev_end: yield (prev_end, message) - if not parens: + if not paren_stack: annotated_func_arg = False prev_end = end diff --git a/testing/data/python313.py b/testing/data/python313.py new file mode 100644 index 00000000..5247ae9c --- /dev/null +++ b/testing/data/python313.py @@ -0,0 +1,9 @@ +type Alias[T: (int, str) = str] = list[T] + + +class C[T: (int, str) = str]: + pass + + +def f[T: (int, str) = str](t: T) -> T: + pass diff --git a/tests/test_E901.py b/tests/test_E901.py index a1dbcfa9..caf68d50 100644 --- a/tests/test_E901.py +++ b/tests/test_E901.py @@ -23,7 +23,7 @@ def lasting(self, duration=300): ''' errors = errors_from_src(src) if sys.version_info < (3, 12): # pragma: <3.12 cover - expected = ['E122:4:1', 'E251:5:13', 'E251:5:15'] + expected = ['E122:4:1'] else: # pragma: >=3.12 cover - expected = ['E122:4:1', 'E251:5:13', 'E251:5:15', 'E901:5:1'] # noqa: E501 + expected = ['E122:4:1', 'E901:5:1'] # noqa: E501 self.assertEqual(errors, expected) From 778d00d11e66f71af5bac0cc5888caa37f327fae Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 19 Mar 2025 18:27:10 -0400 Subject: [PATCH 107/125] handle PEP 696 generics without bounds --- pycodestyle.py | 17 ++++++++--------- testing/data/python313.py | 9 +++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 3324850a..5f79a257 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1045,17 +1045,16 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): if token_type == tokenize.OP: if text in '([': paren_stack.append(text) + # PEP 696 defaults always use spaced-style `=` + # type A[T = default] = ... + # def f[T = default](): ... + # class C[T = default](): ... + if in_generic and paren_stack == ['[']: + annotated_func_arg = True elif text in ')]' and paren_stack: paren_stack.pop() - elif ( - text == ':' and ( - # def f(arg: tp = default): ... - (in_def and paren_stack == ['(']) or - # def f[T: tp = default](): ... - # class C[T: tp = default](): ... - (in_generic and paren_stack == ['[']) - ) - ): + # def f(arg: tp = default): ... + elif text == ':' and in_def and paren_stack == ['(']: annotated_func_arg = True elif len(paren_stack) == 1 and text == ',': annotated_func_arg = False diff --git a/testing/data/python313.py b/testing/data/python313.py index 5247ae9c..5540aced 100644 --- a/testing/data/python313.py +++ b/testing/data/python313.py @@ -1,9 +1,18 @@ type Alias[T: (int, str) = str] = list[T] +type Alias2[T = str] = list[T] class C[T: (int, str) = str]: pass +class C2[T = str]: + pass + + def f[T: (int, str) = str](t: T) -> T: pass + + +def f2[T = str](t: T) -> T: + pass From 3f52426121985a1717c214aeac47650d5c729621 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Mar 2025 13:34:59 -0400 Subject: [PATCH 108/125] Release 2.13.0 --- CHANGES.txt | 9 +++++++++ pycodestyle.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9834484f..aeaef654 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,15 @@ Changelog ========= +2.13.0 (2025-03-29) +------------------- + +Changes: + +* Improve performance. PR #1254. PR #1255. +* Drop EOL python 3.8. PR #1267. +* E251: fix false positive for PEP 696 defaults. PR #1278. PR #1279. + 2.12.1 (2024-08-04) ------------------- diff --git a/pycodestyle.py b/pycodestyle.py index 5f79a257..c38afb39 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Mon, 31 Mar 2025 20:51:51 +0000 Subject: [PATCH 109/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v2.7.0 → v2.8.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.7.0...v2.8.0) - [github.com/pycqa/flake8: 7.1.2 → 7.2.0](https://github.com/pycqa/flake8/compare/7.1.2...7.2.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 63996874..f4218f9f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,10 +18,10 @@ repos: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.7.0 + rev: v2.8.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 7.1.2 + rev: 7.2.0 hooks: - id: flake8 From 7182ac8a56cdff4a05305b85e5e140922aaf56ba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 20:52:10 +0000 Subject: [PATCH 110/125] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 09a89190..7565ec63 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,6 @@ classifiers = Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers - License :: OSI Approved :: MIT License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 From ae41b3420360a9f6732b316f5746c56146a37932 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Fri, 23 May 2025 16:07:37 -0400 Subject: [PATCH 111/125] updates for python 3.14 --- pycodestyle.py | 42 +++++++++++++++++++++++++++++++-------- testing/data/python314.py | 19 ++++++++++++++++++ 2 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 testing/data/python314.py diff --git a/pycodestyle.py b/pycodestyle.py index c38afb39..96291b0d 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -158,6 +158,13 @@ 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 + _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} @@ -697,7 +704,12 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if verbose >= 4: print(f"bracket depth {depth} indent to {start[1]}") # deal with implicit string concatenation - elif token_type in (tokenize.STRING, tokenize.COMMENT, FSTRING_START): + elif token_type in { + tokenize.STRING, + tokenize.COMMENT, + FSTRING_START, + TSTRING_START + }: indent_chances[start[1]] = str # visual indent after assert/raise/with elif not row and not depth and text in ["assert", "raise", "with"]: @@ -873,6 +885,8 @@ def missing_whitespace(logical_line, tokens): brace_stack.append(text) elif token_type == FSTRING_START: # pragma: >=3.12 cover brace_stack.append('f') + elif token_type == TSTRING_START: # pragma: >=3.14 cover + brace_stack.append('t') elif token_type == tokenize.NAME and text == 'lambda': brace_stack.append('l') elif brace_stack: @@ -880,6 +894,8 @@ def missing_whitespace(logical_line, tokens): brace_stack.pop() elif token_type == FSTRING_END: # pragma: >=3.12 cover brace_stack.pop() + elif token_type == TSTRING_END: # pragma: >=3.14 cover + brace_stack.pop() elif ( brace_stack[-1] == 'l' and token_type == tokenize.OP and @@ -899,6 +915,9 @@ def missing_whitespace(logical_line, tokens): # 3.12+ fstring format specifier elif text == ':' and brace_stack[-2:] == ['f', '{']: # pragma: >=3.12 cover # noqa: E501 pass + # 3.14+ tstring format specifier + elif text == ':' and brace_stack[-2:] == ['t', '{']: # pragma: >=3.14 cover # noqa: E501 + pass # tuple (and list for some reason?) elif text == ',' and next_char in ')]': pass @@ -948,7 +967,9 @@ def missing_whitespace(logical_line, tokens): # allow keyword args or defaults: foo(bar=None). brace_stack[-1:] == ['('] or # allow python 3.8 fstring repr specifier - brace_stack[-2:] == ['f', '{'] + brace_stack[-2:] == ['f', '{'] or + # allow python 3.8 fstring repr specifier + brace_stack[-2:] == ['t', '{'] ) ): pass @@ -1639,11 +1660,11 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): prefixes = [] for token_type, text, start, _, _ in tokens: - if token_type in {tokenize.STRING, FSTRING_START}: + if token_type in {tokenize.STRING, FSTRING_START, TSTRING_START}: # Extract string modifiers (e.g. u or r) prefixes.append(text[:text.index(text[-1])].lower()) - if token_type in {tokenize.STRING, FSTRING_MIDDLE}: + if token_type in {tokenize.STRING, FSTRING_MIDDLE, TSTRING_MIDDLE}: if 'r' not in prefixes[-1]: start_line, start_col = start pos = text.find('\\') @@ -1661,7 +1682,7 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): ) pos = text.find('\\', pos + 1) - if token_type in {tokenize.STRING, FSTRING_END}: + if token_type in {tokenize.STRING, FSTRING_END, TSTRING_END}: prefixes.pop() @@ -1859,7 +1880,7 @@ def __init__(self, filename=None, lines=None, self.max_line_length = options.max_line_length self.max_doc_length = options.max_doc_length self.indent_size = options.indent_size - self.fstring_start = 0 + self.fstring_start = self.tstring_start = 0 self.multiline = False # in a multiline string? self.hang_closing = options.hang_closing self.indent_size = options.indent_size @@ -1954,7 +1975,7 @@ def build_tokens_line(self): continue if token_type == tokenize.STRING: text = mute_string(text) - elif token_type == FSTRING_MIDDLE: # pragma: >=3.12 cover + elif token_type in {FSTRING_MIDDLE, TSTRING_MIDDLE}: # pragma: >=3.12 cover # noqa: E501 # fstring tokens are "unescaped" braces -- re-escape! brace_count = text.count('{') + text.count('}') text = 'x' * (len(text) + brace_count) @@ -2046,6 +2067,8 @@ def maybe_check_physical(self, token, prev_physical): if token.type == FSTRING_START: # pragma: >=3.12 cover self.fstring_start = token.start[0] + elif token.type == TSTRING_START: # pragma: >=3.14 cover + self.tstring_start = token.start[0] # a newline token ends a single physical line. elif _is_eol_token(token): # if the file does not end with a newline, the NEWLINE @@ -2057,7 +2080,8 @@ def maybe_check_physical(self, token, prev_physical): self.check_physical(token.line) elif ( token.type == tokenize.STRING and '\n' in token.string or - token.type == FSTRING_END + token.type == FSTRING_END or + token.type == TSTRING_END ): # Less obviously, a string that contains newlines is a # multiline string, either triple-quoted or with internal @@ -2078,6 +2102,8 @@ def maybe_check_physical(self, token, prev_physical): return if token.type == FSTRING_END: # pragma: >=3.12 cover start = self.fstring_start + elif token.type == TSTRING_END: # pragma: >=3.12 cover + start = self.tstring_start else: start = token.start[0] end = token.end[0] diff --git a/testing/data/python314.py b/testing/data/python314.py new file mode 100644 index 00000000..6f96df7f --- /dev/null +++ b/testing/data/python314.py @@ -0,0 +1,19 @@ +#: Okay +try: + raise AssertionError('hi') +except AssertionError, ValueError: + pass + +t'hello {world}' +t'{hello}:{world}' +t'in{x}' +t'hello{world=}' +#: Okay +# new nested f-strings +t'{ + thing +} {t'{other} {thing}'}' +#: E201:1:4 E202:1:17 +t'{ an_error_now }' +#: Okay +t'{x:02x}' From a98638490e3c799efeebf0af638940d5a581b3c9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 20:57:48 +0000 Subject: [PATCH 112/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.14.0 → v3.15.0](https://github.com/asottile/reorder-python-imports/compare/v3.14.0...v3.15.0) - [github.com/asottile/pyupgrade: v3.19.1 → v3.20.0](https://github.com/asottile/pyupgrade/compare/v3.19.1...v3.20.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4218f9f..a3cda000 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.14.0 + rev: v3.15.0 hooks: - id: reorder-python-imports args: [--py39-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.19.1 + rev: v3.20.0 hooks: - id: pyupgrade args: [--py39-plus] From 46bc333dc127b73f1af2127bf44dbd74e7bf34cb Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sun, 8 Jun 2025 13:44:28 -0400 Subject: [PATCH 113/125] add sphinx configuration for rtfd --- .readthedocs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 4b74802e..87bb5a6a 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,3 +4,5 @@ build: os: ubuntu-22.04 tools: python: "3.11" +sphinx: + configuration: docs/conf.py From 8621e318655267c2a6cfa15bfd3f7cc02a60881f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 11 Jun 2025 11:53:02 -0400 Subject: [PATCH 114/125] fix false positive with TypeVar defaults with more than one argument --- pycodestyle.py | 15 ++++++++------- testing/data/python313.py | 4 ++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 96291b0d..3439f35a 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1066,12 +1066,6 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): if token_type == tokenize.OP: if text in '([': paren_stack.append(text) - # PEP 696 defaults always use spaced-style `=` - # type A[T = default] = ... - # def f[T = default](): ... - # class C[T = default](): ... - if in_generic and paren_stack == ['[']: - annotated_func_arg = True elif text in ')]' and paren_stack: paren_stack.pop() # def f(arg: tp = default): ... @@ -1080,7 +1074,14 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): elif len(paren_stack) == 1 and text == ',': annotated_func_arg = False elif paren_stack and text == '=': - if annotated_func_arg and len(paren_stack) == 1: + if ( + # PEP 696 defaults always use spaced-style `=` + # type A[T = default] = ... + # def f[T = default](): ... + # class C[T = default](): ... + (in_generic and paren_stack == ['[']) or + (annotated_func_arg and paren_stack == ['(']) + ): require_space = True if start == prev_end: yield (prev_end, missing_message) diff --git a/testing/data/python313.py b/testing/data/python313.py index 5540aced..ae70e427 100644 --- a/testing/data/python313.py +++ b/testing/data/python313.py @@ -10,6 +10,10 @@ class C2[T = str]: pass +class C3[T, U: str = str]: + pass + + def f[T: (int, str) = str](t: T) -> T: pass From 814a0d1259444a21ed318e64edaf6a530c2aeeb8 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Fri, 20 Jun 2025 14:48:57 -0400 Subject: [PATCH 115/125] Release 2.14.0 --- CHANGES.txt | 9 +++++++++ pycodestyle.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index aeaef654..aef35485 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,15 @@ Changelog ========= +2.14.0 (2025-06-20) +------------------- + +Changes: + +* Add support for python 3.14. PR #1283. +* Fix false positive for TypeVar defaults with more than one argument. + PR #1286. + 2.13.0 (2025-03-29) ------------------- diff --git a/pycodestyle.py b/pycodestyle.py index 3439f35a..9df2ae4c 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -68,7 +68,7 @@ ): # pragma: no cover ( Date: Mon, 23 Jun 2025 21:13:40 +0000 Subject: [PATCH 116/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 7.2.0 → 7.3.0](https://github.com/pycqa/flake8/compare/7.2.0...7.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3cda000..112097f1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,6 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 - rev: 7.2.0 + rev: 7.3.0 hooks: - id: flake8 From 24cec77f1a0f66983bcf43079a6f34b9438a0818 Mon Sep 17 00:00:00 2001 From: namaevae Date: Thu, 17 Jul 2025 09:33:35 +0300 Subject: [PATCH 117/125] this reduces execution time from ~3300 ns to ~2800 --- pycodestyle.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 9df2ae4c..b3b38377 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -493,6 +493,17 @@ def whitespace_around_keywords(logical_line): yield match.start(2), "E271 multiple spaces after keyword" +if sys.version_info < (3, 10): + from itertools import tee + + def pairwise(iterable): + a, b = tee(iterable) + next(b, None) + return zip(a, b) +else: + from itertools import pairwise + + @register_check def missing_whitespace_after_keyword(logical_line, tokens): r"""Keywords should be followed by whitespace. @@ -502,7 +513,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): E275: from importable.module import(bar, baz) E275: if(foo): bar """ - for tok0, tok1 in zip(tokens, tokens[1:]): + for tok0, tok1 in pairwise(tokens): # This must exclude the True/False/None singletons, which can # appear e.g. as "if x is None:", and async/await, which were # valid identifier names in old Python versions. @@ -512,7 +523,7 @@ def missing_whitespace_after_keyword(logical_line, tokens): tok0.string not in SINGLETONS and not (tok0.string == 'except' and tok1.string == '*') and not (tok0.string == 'yield' and tok1.string == ')') and - tok1.string not in ':\n'): + (tok1.string and tok1.string != ':' and tok1.string != '\n')): yield tok0.end, "E275 missing whitespace after keyword" From 41e842c87673f4ceaf66958472715c5c5ef64edf Mon Sep 17 00:00:00 2001 From: namaevae Date: Thu, 17 Jul 2025 09:55:18 +0300 Subject: [PATCH 118/125] this reduces execution time from ~1500 ns to ~1250 ns --- pycodestyle.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 9df2ae4c..97714220 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1160,6 +1160,22 @@ def imports_on_separate_lines(logical_line): yield found, "E401 multiple imports on one line" +_STRING_PREFIXES = frozenset(('u', 'U', 'b', 'B', 'r', 'R')) + + +def _is_string_literal(line): + if line: + first_char = line[0] + if first_char in _STRING_PREFIXES: + first_char = line[1] + return first_char == '"' or first_char == "'" + return False + + +_ALLOWED_KEYWORDS_IN_IMPORTS = frozenset([ + 'try', 'except', 'else', 'finally', 'with', 'if', 'elif']) + + @register_check def module_imports_on_top_of_file( logical_line, indent_level, checker_state, noqa): @@ -1178,15 +1194,6 @@ def module_imports_on_top_of_file( Okay: if x:\n import os """ # noqa - def is_string_literal(line): - if line[0] in 'uUbB': - line = line[1:] - if line and line[0] in 'rR': - line = line[1:] - return line and (line[0] == '"' or line[0] == "'") - - allowed_keywords = ( - 'try', 'except', 'else', 'finally', 'with', 'if', 'elif') if indent_level: # Allow imports in conditional statement/function return @@ -1194,17 +1201,17 @@ def is_string_literal(line): return if noqa: return - line = logical_line - if line.startswith('import ') or line.startswith('from '): + if logical_line.startswith('import ') or logical_line.startswith('from '): if checker_state.get('seen_non_imports', False): yield 0, "E402 module level import not at top of file" - elif re.match(DUNDER_REGEX, line): + elif re.match(DUNDER_REGEX, logical_line): return - elif any(line.startswith(kw) for kw in allowed_keywords): + elif any(logical_line.startswith(kw) + for kw in _ALLOWED_KEYWORDS_IN_IMPORTS): # Allow certain keywords intermixed with imports in order to # support conditional or filtered importing return - elif is_string_literal(line): + elif _is_string_literal(logical_line): # The first literal is a docstring, allow it. Otherwise, report # error. if checker_state.get('seen_docstring', False): From edab88367756980f73ab9f40f9ba9c4764c8e9b4 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 2 Aug 2025 12:59:30 -0400 Subject: [PATCH 119/125] further improvements to module_imports_on_top_of_file --- pycodestyle.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 97714220..dbd2afa7 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1172,8 +1172,8 @@ def _is_string_literal(line): return False -_ALLOWED_KEYWORDS_IN_IMPORTS = frozenset([ - 'try', 'except', 'else', 'finally', 'with', 'if', 'elif']) +_ALLOWED_KEYWORDS_IN_IMPORTS = ( + 'try', 'except', 'else', 'finally', 'with', 'if', 'elif') @register_check @@ -1201,25 +1201,25 @@ def module_imports_on_top_of_file( return if noqa: return - if logical_line.startswith('import ') or logical_line.startswith('from '): + if logical_line.startswith(('import ', 'from ')): if checker_state.get('seen_non_imports', False): yield 0, "E402 module level import not at top of file" - elif re.match(DUNDER_REGEX, logical_line): - return - elif any(logical_line.startswith(kw) - for kw in _ALLOWED_KEYWORDS_IN_IMPORTS): - # Allow certain keywords intermixed with imports in order to - # support conditional or filtered importing - return - elif _is_string_literal(logical_line): - # The first literal is a docstring, allow it. Otherwise, report - # error. - if checker_state.get('seen_docstring', False): - checker_state['seen_non_imports'] = True + elif not checker_state.get('seen_non_imports', False): + if DUNDER_REGEX.match(logical_line): + return + elif logical_line.startswith(_ALLOWED_KEYWORDS_IN_IMPORTS): + # Allow certain keywords intermixed with imports in order to + # support conditional or filtered importing + return + elif _is_string_literal(logical_line): + # The first literal is a docstring, allow it. Otherwise, + # report error. + if checker_state.get('seen_docstring', False): + checker_state['seen_non_imports'] = True + else: + checker_state['seen_docstring'] = True else: - checker_state['seen_docstring'] = True - else: - checker_state['seen_non_imports'] = True + checker_state['seen_non_imports'] = True @register_check From f9301e4dfa829460d3ce1c066d763a48a4bdd372 Mon Sep 17 00:00:00 2001 From: namaevae Date: Thu, 17 Jul 2025 09:58:07 +0300 Subject: [PATCH 120/125] this reduces execution time from ~5700 ns to ~3400 ns --- pycodestyle.py | 65 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 9df2ae4c..b86f9875 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1627,6 +1627,29 @@ def ambiguous_identifier(logical_line, tokens): prev_start = start +# https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals +_PYTHON_3000_VALID_ESC = frozenset([ + '\n', + '\\', + '\'', + '"', + 'a', + 'b', + 'f', + 'n', + 'r', + 't', + 'v', + '0', '1', '2', '3', '4', '5', '6', '7', + 'x', + + # Escape sequences only recognized in string literals + 'N', + 'u', + 'U', +]) + + @register_check def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): r"""Invalid escape sequences are deprecated in Python 3.6. @@ -1637,41 +1660,27 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): if noqa: return - # https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals - valid = [ - '\n', - '\\', - '\'', - '"', - 'a', - 'b', - 'f', - 'n', - 'r', - 't', - 'v', - '0', '1', '2', '3', '4', '5', '6', '7', - 'x', - - # Escape sequences only recognized in string literals - 'N', - 'u', - 'U', - ] - prefixes = [] for token_type, text, start, _, _ in tokens: - if token_type in {tokenize.STRING, FSTRING_START, TSTRING_START}: + if ( + token_type == tokenize.STRING or + token_type == FSTRING_START or + token_type == TSTRING_START + ): # Extract string modifiers (e.g. u or r) prefixes.append(text[:text.index(text[-1])].lower()) - if token_type in {tokenize.STRING, FSTRING_MIDDLE, TSTRING_MIDDLE}: + if ( + token_type == tokenize.STRING or + token_type == FSTRING_MIDDLE or + token_type == TSTRING_MIDDLE + ): if 'r' not in prefixes[-1]: start_line, start_col = start pos = text.find('\\') while pos >= 0: pos += 1 - if text[pos] not in valid: + if text[pos] not in _PYTHON_3000_VALID_ESC: line = start_line + text.count('\n', 0, pos) if line == start_line: col = start_col + pos @@ -1683,7 +1692,11 @@ def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): ) pos = text.find('\\', pos + 1) - if token_type in {tokenize.STRING, FSTRING_END, TSTRING_END}: + if ( + token_type == tokenize.STRING or + token_type == FSTRING_END or + token_type == TSTRING_END + ): prefixes.pop() From 76bbcb23cbcd0bc2443ec959489e5164372de9ff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 22:22:17 +0000 Subject: [PATCH 121/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 112097f1..66058378 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^testing/data/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-yaml - id: debug-statements From 0b91db909186b3762f6ee68ef3115bdb6b56573b Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Wed, 15 Oct 2025 14:02:21 -0400 Subject: [PATCH 122/125] py310+ --- .github/workflows/main.yml | 12 ++++++------ .pre-commit-config.yaml | 10 +++++----- pycodestyle.py | 19 +------------------ setup.cfg | 2 +- tests/test_api.py | 7 +------ 5 files changed, 14 insertions(+), 36 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c53a94f7..75fc886a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,14 +11,11 @@ jobs: matrix: include: - os: windows-latest - py: 3.9 + py: '3.10' toxenv: py - os: ubuntu-latest py: pypy3.10 toxenv: py - - os: ubuntu-latest - py: 3.9 - toxenv: py - os: ubuntu-latest py: '3.10' toxenv: py @@ -32,10 +29,13 @@ jobs: py: '3.13' toxenv: py - os: ubuntu-latest - py: '3.14-dev' + py: '3.14' toxenv: py - os: ubuntu-latest - py: 3.9 + py: '3.15-dev' + toxenv: py + - os: ubuntu-latest + py: '3.10' toxenv: flake8 runs-on: ${{ matrix.os }} steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 66058378..1c698753 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,17 +8,17 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/reorder-python-imports - rev: v3.15.0 + rev: v3.16.0 hooks: - id: reorder-python-imports - args: [--py39-plus] + args: [--py310-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.20.0 + rev: v3.21.0 hooks: - id: pyupgrade - args: [--py39-plus] + args: [--py310-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.8.0 + rev: v3.1.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8 diff --git a/pycodestyle.py b/pycodestyle.py index b3bf4ffc..868e79d5 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -59,15 +59,9 @@ import warnings from fnmatch import fnmatch from functools import lru_cache +from itertools import pairwise from optparse import OptionParser -# this is a performance hack. see https://bugs.python.org/issue43014 -if ( - sys.version_info < (3, 10) and - callable(getattr(tokenize, '_compile', None)) -): # pragma: no cover (=3.9 +python_requires = >=3.10 include_package_data = True zip_safe = False diff --git a/tests/test_api.py b/tests/test_api.py index 32f6ad2a..50cb1b83 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -352,12 +352,7 @@ def test_styleguide_unmatched_triple_quotes(self): pep8style.input_file('stdin', lines=lines) stdout = sys.stdout.getvalue() - if sys.version_info < (3, 10): # pragma: <3.10 cover - expected = [ - 'stdin:2:5: E901 TokenError: EOF in multi-line string', - 'stdin:2:26: E901 SyntaxError: EOF while scanning triple-quoted string literal', # noqa: E501 - ] - elif sys.version_info < (3, 12): # pragma: >=3.10 cover # pragma: <3.12 cover # noqa: E501 + if sys.version_info < (3, 12): # pragma: <3.12 cover # noqa: E501 expected = [ 'stdin:2:5: E901 TokenError: EOF in multi-line string', 'stdin:2:6: E901 SyntaxError: unterminated triple-quoted string literal (detected at line 2)', # noqa: E501 From 5d55de658aeab64caae7d45f55833d1ad718144f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 22:39:02 +0000 Subject: [PATCH 123/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.21.0 → v3.21.1](https://github.com/asottile/pyupgrade/compare/v3.21.0...v3.21.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1c698753..047359cc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py310-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.21.0 + rev: v3.21.1 hooks: - id: pyupgrade args: [--py310-plus] From 4e84824cd1b80d020e953621857de8a837d87200 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:27:28 +0000 Subject: [PATCH 124/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.21.1 → v3.21.2](https://github.com/asottile/pyupgrade/compare/v3.21.1...v3.21.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 047359cc..ef283190 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: reorder-python-imports args: [--py310-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.21.1 + rev: v3.21.2 hooks: - id: pyupgrade args: [--py310-plus] From fb966ce221ce6d612e310409ac7b88f2ef09ebc5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 21:52:33 +0000 Subject: [PATCH 125/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ef283190..a1ea107d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: pyupgrade args: [--py310-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v3.1.0 + rev: v3.2.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/pycqa/flake8