Skip to content

Commit 7e6b190

Browse files
Don't attach comments with mismatched indents (#12604)
## Summary Given: ```python def test_update(): pass # comment def test_clientmodel(): pass ``` We don't want `# comment` to be attached to `def test_clientmodel()`. Closes #12589.
1 parent 8e383b9 commit 7e6b190

File tree

3 files changed

+122
-10
lines changed

3 files changed

+122
-10
lines changed

crates/ruff_linter/resources/test/fixtures/pycodestyle/E30.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,3 +935,30 @@ def arrow_strip_whitespace(obj: Array, /, *cols: str) -> Array: ... # type: ign
935935
def arrow_strip_whitespace(obj, /, *cols):
936936
...
937937
# end
938+
939+
940+
# E302
941+
def test_update():
942+
pass
943+
# comment
944+
def test_clientmodel():
945+
pass
946+
# end
947+
948+
949+
# E302
950+
def test_update():
951+
pass
952+
# comment
953+
def test_clientmodel():
954+
pass
955+
# end
956+
957+
958+
# E302
959+
def test_update():
960+
pass
961+
# comment
962+
def test_clientmodel():
963+
pass
964+
# end

crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -352,13 +352,13 @@ struct LogicalLineInfo {
352352
kind: LogicalLineKind,
353353
first_token_range: TextRange,
354354

355-
// The kind of the last non-trivia token before the newline ending the logical line.
355+
/// The kind of the last non-trivia token before the newline ending the logical line.
356356
last_token: TokenKind,
357357

358-
// The end of the logical line including the newline.
358+
/// The end of the logical line including the newline.
359359
logical_line_end: TextSize,
360360

361-
// `true` if this is not a blank but only consists of a comment.
361+
/// `true` if this is not a blank but only consists of a comment.
362362
is_comment_only: bool,
363363

364364
/// If running on a notebook, whether the line is the first logical line (or a comment preceding it) of its cell.
@@ -721,6 +721,7 @@ impl<'a> BlankLinesChecker<'a> {
721721
/// E301, E302, E303, E304, E305, E306
722722
pub(crate) fn check_lines(&self, tokens: &Tokens, diagnostics: &mut Vec<Diagnostic>) {
723723
let mut prev_indent_length: Option<usize> = None;
724+
let mut prev_logical_line: Option<LogicalLineInfo> = None;
724725
let mut state = BlankLinesState::default();
725726
let line_preprocessor =
726727
LinePreprocessor::new(tokens, self.locator, self.indent_width, self.cell_offsets);
@@ -739,6 +740,23 @@ impl<'a> BlankLinesChecker<'a> {
739740
}
740741
}
741742

743+
// Reset the previous line end after an indent or dedent:
744+
// ```python
745+
// if True:
746+
// import test
747+
// # comment
748+
// a = 10
749+
// ```
750+
// The `# comment` should be attached to the `import` statement, rather than the
751+
// assignment.
752+
if let Some(prev_logical_line) = prev_logical_line {
753+
if prev_logical_line.is_comment_only {
754+
if prev_logical_line.indent_length != logical_line.indent_length {
755+
state.last_non_comment_line_end = prev_logical_line.logical_line_end;
756+
}
757+
}
758+
}
759+
742760
state.class_status.update(&logical_line);
743761
state.fn_status.update(&logical_line);
744762

@@ -793,6 +811,8 @@ impl<'a> BlankLinesChecker<'a> {
793811
if !logical_line.is_comment_only {
794812
prev_indent_length = Some(logical_line.indent_length);
795813
}
814+
815+
prev_logical_line = Some(logical_line);
796816
}
797817
}
798818

@@ -882,6 +902,8 @@ impl<'a> BlankLinesChecker<'a> {
882902
line.first_token_range,
883903
);
884904

905+
// Check if the preceding comment
906+
885907
if let Some(blank_lines_range) = line.blank_lines.range() {
886908
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
887909
self.stylist
@@ -891,9 +913,10 @@ impl<'a> BlankLinesChecker<'a> {
891913
)));
892914
} else {
893915
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
894-
self.stylist
895-
.line_ending()
896-
.repeat(expected_blank_lines_before_definition as usize),
916+
self.stylist.line_ending().repeat(
917+
(expected_blank_lines_before_definition
918+
- line.preceding_blank_lines.count()) as usize,
919+
),
897920
self.locator.line_start(state.last_non_comment_line_end),
898921
)));
899922
}

crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E302_E30.py.snap

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,9 @@ E30.py:602:1: E302 [*] Expected 2 blank lines, found 1
179179
599 599 | pass
180180
600 600 |
181181
601 |+
182-
602 |+
183-
601 603 | # comment
184-
602 604 | @decorator
185-
603 605 | def g():
182+
601 602 | # comment
183+
602 603 | @decorator
184+
603 604 | def g():
186185

187186
E30.py:624:1: E302 [*] Expected 2 blank lines, found 0
188187
|
@@ -223,3 +222,66 @@ E30.py:634:1: E302 [*] Expected 2 blank lines, found 1
223222
634 635 | def fn(a: int | str) -> int | str:
224223
635 636 | ...
225224
636 637 | # end
225+
226+
E30.py:944:1: E302 [*] Expected 2 blank lines, found 0
227+
|
228+
942 | pass
229+
943 | # comment
230+
944 | def test_clientmodel():
231+
| ^^^ E302
232+
945 | pass
233+
946 | # end
234+
|
235+
= help: Add missing blank line(s)
236+
237+
Safe fix
238+
941 941 | def test_update():
239+
942 942 | pass
240+
943 943 | # comment
241+
944 |+
242+
945 |+
243+
944 946 | def test_clientmodel():
244+
945 947 | pass
245+
946 948 | # end
246+
247+
E30.py:953:1: E302 [*] Expected 2 blank lines, found 0
248+
|
249+
951 | pass
250+
952 | # comment
251+
953 | def test_clientmodel():
252+
| ^^^ E302
253+
954 | pass
254+
955 | # end
255+
|
256+
= help: Add missing blank line(s)
257+
258+
Safe fix
259+
950 950 | def test_update():
260+
951 951 | pass
261+
952 952 | # comment
262+
953 |+
263+
954 |+
264+
953 955 | def test_clientmodel():
265+
954 956 | pass
266+
955 957 | # end
267+
268+
E30.py:962:1: E302 [*] Expected 2 blank lines, found 0
269+
|
270+
960 | pass
271+
961 | # comment
272+
962 | def test_clientmodel():
273+
| ^^^ E302
274+
963 | pass
275+
964 | # end
276+
|
277+
= help: Add missing blank line(s)
278+
279+
Safe fix
280+
958 958 | # E302
281+
959 959 | def test_update():
282+
960 960 | pass
283+
961 |+
284+
962 |+
285+
961 963 | # comment
286+
962 964 | def test_clientmodel():
287+
963 965 | pass

0 commit comments

Comments
 (0)