From aa63c24b8f4a82f5f54dc90c4e6b5adadbf3e1b2 Mon Sep 17 00:00:00 2001 From: Dan Parizher <105245560+danparizher@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:00:07 -0400 Subject: [PATCH] [`pycodestyle`] Fix `E301` to only trigger for functions immediately within a class (#19768) ## Summary Fixes #19752 --------- Co-authored-by: Brent Westbrook --- .../test/fixtures/pycodestyle/E30.py | 36 ++++++++++++ .../rules/pycodestyle/rules/blank_lines.rs | 3 + ...ules__pycodestyle__tests__E301_E30.py.snap | 19 +++++++ ...ules__pycodestyle__tests__E306_E30.py.snap | 57 +++++++++++++++++++ 4 files changed, 115 insertions(+) diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py index e134c3e001..df30f0c969 100644 --- a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py @@ -974,3 +974,39 @@ BANANA = 100 APPLE = 200 # end + + +# https://github.com/astral-sh/ruff/issues/19752 +class foo: + async def recv(self, *, length=65536): + loop = asyncio.get_event_loop() + def callback(): + loop.remove_reader(self._fd) + loop.add_reader(self._fd, callback) +# end + + +# E301 +class Foo: + if True: + print("conditional") + def test(): + pass +# end + + +# Test case for nested class scenario +class Bar: + def f(): + x = 1 + def g(): + return 1 + return 2 + + def f(): + class Baz: + x = 1 + def g(): + return 1 + return 2 +# end diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs index 07f5627f36..5e44676ca4 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs @@ -836,7 +836,10 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> { // Allow groups of one-liners. && !(state.follows.is_any_def() && line.last_token != TokenKind::Colon) && !state.follows.follows_def_with_dummy_body() + // Only for class scope: we must be inside a class block && matches!(state.class_status, Status::Inside(_)) + // But NOT inside a function body; nested defs inside methods are handled by E306 + && matches!(state.fn_status, Status::Outside | Status::CommentAfter(_)) // The class/parent method's docstring can directly precede the def. // Allow following a decorator (if there is an error it will be triggered on the first decorator). && !matches!(state.follows, Follows::Docstring | Follows::Decorator) diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E301_E30.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E301_E30.py.snap index e4c13172b2..13360489e4 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E301_E30.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E301_E30.py.snap @@ -94,3 +94,22 @@ help: Add missing blank line 527 | def bar(self, x: int | str) -> int | str: 528 | return x 529 | # end + +E301 [*] Expected 1 blank line, found 0 + --> E30.py:993:9 + | +991 | if True: +992 | print("conditional") +993 | def test(): + | ^^^ +994 | pass +995 | # end + | +help: Add missing blank line +990 | class Foo: +991 | if True: +992 | print("conditional") +993 + +994 | def test(): +995 | pass +996 | # end diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E306_E30.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E306_E30.py.snap index 204834fc20..b3cba08a97 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E306_E30.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E306_E30.py.snap @@ -208,3 +208,60 @@ help: Add missing blank line 926 | async def b(): 927 | pass 928 | # end + +E306 [*] Expected 1 blank line before a nested definition, found 0 + --> E30.py:983:9 + | +981 | async def recv(self, *, length=65536): +982 | loop = asyncio.get_event_loop() +983 | def callback(): + | ^^^ +984 | loop.remove_reader(self._fd) +985 | loop.add_reader(self._fd, callback) + | +help: Add missing blank line +980 | class foo: +981 | async def recv(self, *, length=65536): +982 | loop = asyncio.get_event_loop() +983 + +984 | def callback(): +985 | loop.remove_reader(self._fd) +986 | loop.add_reader(self._fd, callback) + +E306 [*] Expected 1 blank line before a nested definition, found 0 + --> E30.py:1002:9 + | +1000 | def f(): +1001 | x = 1 +1002 | def g(): + | ^^^ +1003 | return 1 +1004 | return 2 + | +help: Add missing blank line +999 | class Bar: +1000 | def f(): +1001 | x = 1 +1002 + +1003 | def g(): +1004 | return 1 +1005 | return 2 + +E306 [*] Expected 1 blank line before a nested definition, found 0 + --> E30.py:1009:13 + | +1007 | class Baz: +1008 | x = 1 +1009 | def g(): + | ^^^ +1010 | return 1 +1011 | return 2 + | +help: Add missing blank line +1006 | def f(): +1007 | class Baz: +1008 | x = 1 +1009 + +1010 | def g(): +1011 | return 1 +1012 | return 2