Skip to content

Commit 9b6d2ce

Browse files
authored
Fix incorect placement of trailing stub function comments (#11632)
1 parent 889667a commit 9b6d2ce

File tree

6 files changed

+142
-44
lines changed

6 files changed

+142
-44
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Regression tests for https://github.com/astral-sh/ruff/issues/11569
2+
3+
4+
# comment 1
5+
def foo(self) -> None: ...
6+
def bar(self) -> None: ...
7+
# comment 2
8+
9+
# comment 3
10+
def baz(self) -> None:
11+
return None
12+
# comment 4
13+
14+
15+
def foo(self) -> None: ...
16+
# comment 5
17+
18+
def baz(self) -> None:
19+
return None
20+
21+
22+
def foo(self) -> None:
23+
... # comment 5
24+
def baz(self) -> None:
25+
return None
26+
27+
def foo(self) -> None: ...
28+
# comment 5

crates/ruff_python_formatter/src/comments/format.rs

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ impl Format<PyFormatContext<'_>> for FormatTrailingComments<'_> {
164164
line_suffix(
165165
&format_args![
166166
empty_lines(lines_before_comment),
167-
format_comment(trailing)
167+
format_comment(trailing),
168168
],
169169
// Reserving width isn't necessary because we don't split
170170
// comments and the empty lines expand any enclosing group.
@@ -535,31 +535,21 @@ fn strip_comment_prefix(comment_text: &str) -> FormatResult<&str> {
535535
/// ```
536536
///
537537
/// This builder will insert a single empty line before the comment.
538-
pub(crate) fn empty_lines_before_trailing_comments<'a>(
539-
f: &PyFormatter,
540-
comments: &'a [SourceComment],
538+
pub(crate) fn empty_lines_before_trailing_comments(
539+
comments: &[SourceComment],
541540
node_kind: NodeKind,
542-
) -> FormatEmptyLinesBeforeTrailingComments<'a> {
543-
// Black has different rules for stub vs. non-stub and top level vs. indented
544-
let empty_lines = match (f.options().source_type(), f.context().node_level()) {
545-
(PySourceType::Stub, NodeLevel::TopLevel(_)) => 1,
546-
(PySourceType::Stub, _) => u32::from(node_kind == NodeKind::StmtClassDef),
547-
(_, NodeLevel::TopLevel(_)) => 2,
548-
(_, _) => 1,
549-
};
550-
541+
) -> FormatEmptyLinesBeforeTrailingComments {
551542
FormatEmptyLinesBeforeTrailingComments {
552543
comments,
553-
empty_lines,
544+
node_kind,
554545
}
555546
}
556547

557548
#[derive(Copy, Clone, Debug)]
558549
pub(crate) struct FormatEmptyLinesBeforeTrailingComments<'a> {
559550
/// The trailing comments of the node.
560551
comments: &'a [SourceComment],
561-
/// The expected number of empty lines before the trailing comments.
562-
empty_lines: u32,
552+
node_kind: NodeKind,
563553
}
564554

565555
impl Format<PyFormatContext<'_>> for FormatEmptyLinesBeforeTrailingComments<'_> {
@@ -569,9 +559,17 @@ impl Format<PyFormatContext<'_>> for FormatEmptyLinesBeforeTrailingComments<'_>
569559
.iter()
570560
.find(|comment| comment.line_position().is_own_line())
571561
{
562+
// Black has different rules for stub vs. non-stub and top level vs. indented
563+
let empty_lines = match (f.options().source_type(), f.context().node_level()) {
564+
(PySourceType::Stub, NodeLevel::TopLevel(_)) => 1,
565+
(PySourceType::Stub, _) => u32::from(self.node_kind == NodeKind::StmtClassDef),
566+
(_, NodeLevel::TopLevel(_)) => 2,
567+
(_, _) => 1,
568+
};
569+
572570
let actual = lines_before(comment.start(), f.context().source()).saturating_sub(1);
573-
for _ in actual..self.empty_lines {
574-
write!(f, [empty_line()])?;
571+
for _ in actual..empty_lines {
572+
empty_line().fmt(f)?;
575573
}
576574
}
577575
Ok(())
@@ -590,30 +588,16 @@ impl Format<PyFormatContext<'_>> for FormatEmptyLinesBeforeTrailingComments<'_>
590588
///
591589
/// While `leading_comments` will preserve the existing empty line, this builder will insert an
592590
/// additional empty line before the comment.
593-
pub(crate) fn empty_lines_after_leading_comments<'a>(
594-
f: &PyFormatter,
595-
comments: &'a [SourceComment],
596-
) -> FormatEmptyLinesAfterLeadingComments<'a> {
597-
// Black has different rules for stub vs. non-stub and top level vs. indented
598-
let empty_lines = match (f.options().source_type(), f.context().node_level()) {
599-
(PySourceType::Stub, NodeLevel::TopLevel(_)) => 1,
600-
(PySourceType::Stub, _) => 0,
601-
(_, NodeLevel::TopLevel(_)) => 2,
602-
(_, _) => 1,
603-
};
604-
605-
FormatEmptyLinesAfterLeadingComments {
606-
comments,
607-
empty_lines,
608-
}
591+
pub(crate) fn empty_lines_after_leading_comments(
592+
comments: &[SourceComment],
593+
) -> FormatEmptyLinesAfterLeadingComments {
594+
FormatEmptyLinesAfterLeadingComments { comments }
609595
}
610596

611597
#[derive(Copy, Clone, Debug)]
612598
pub(crate) struct FormatEmptyLinesAfterLeadingComments<'a> {
613599
/// The leading comments of the node.
614600
comments: &'a [SourceComment],
615-
/// The expected number of empty lines after the leading comments.
616-
empty_lines: u32,
617601
}
618602

619603
impl Format<PyFormatContext<'_>> for FormatEmptyLinesAfterLeadingComments<'_> {
@@ -624,6 +608,14 @@ impl Format<PyFormatContext<'_>> for FormatEmptyLinesAfterLeadingComments<'_> {
624608
.rev()
625609
.find(|comment| comment.line_position().is_own_line())
626610
{
611+
// Black has different rules for stub vs. non-stub and top level vs. indented
612+
let empty_lines = match (f.options().source_type(), f.context().node_level()) {
613+
(PySourceType::Stub, NodeLevel::TopLevel(_)) => 1,
614+
(PySourceType::Stub, _) => 0,
615+
(_, NodeLevel::TopLevel(_)) => 2,
616+
(_, _) => 1,
617+
};
618+
627619
let actual = lines_after(comment.end(), f.context().source()).saturating_sub(1);
628620
// If there are no empty lines, keep the comment tight to the node.
629621
if actual == 0 {
@@ -632,12 +624,12 @@ impl Format<PyFormatContext<'_>> for FormatEmptyLinesAfterLeadingComments<'_> {
632624

633625
// If there are more than enough empty lines already, `leading_comments` will
634626
// trim them as necessary.
635-
if actual >= self.empty_lines {
627+
if actual >= empty_lines {
636628
return Ok(());
637629
}
638630

639-
for _ in actual..self.empty_lines {
640-
write!(f, [empty_line()])?;
631+
for _ in actual..empty_lines {
632+
empty_line().fmt(f)?;
641633
}
642634
}
643635
Ok(())

crates/ruff_python_formatter/src/statement/stmt_class_def.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
5555
// newline between the comment and the node, but we _require_ two newlines. If there are
5656
// _no_ newlines between the comment and the node, we don't insert _any_ newlines; if there
5757
// are more than two, then `leading_comments` will preserve the correct number of newlines.
58-
empty_lines_after_leading_comments(f, comments.leading(item)).fmt(f)?;
58+
empty_lines_after_leading_comments(comments.leading(item)).fmt(f)?;
5959

6060
write!(
6161
f,
@@ -152,7 +152,7 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
152152
//
153153
// # comment
154154
// ```
155-
empty_lines_before_trailing_comments(f, comments.trailing(item), NodeKind::StmtClassDef)
155+
empty_lines_before_trailing_comments(comments.trailing(item), NodeKind::StmtClassDef)
156156
.fmt(f)?;
157157

158158
Ok(())

crates/ruff_python_formatter/src/statement/stmt_function_def.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
5252
// newline between the comment and the node, but we _require_ two newlines. If there are
5353
// _no_ newlines between the comment and the node, we don't insert _any_ newlines; if there
5454
// are more than two, then `leading_comments` will preserve the correct number of newlines.
55-
empty_lines_after_leading_comments(f, comments.leading(item)).fmt(f)?;
55+
empty_lines_after_leading_comments(comments.leading(item)).fmt(f)?;
5656

5757
write!(
5858
f,
@@ -86,7 +86,7 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
8686
//
8787
// # comment
8888
// ```
89-
empty_lines_before_trailing_comments(f, comments.trailing(item), NodeKind::StmtFunctionDef)
89+
empty_lines_before_trailing_comments(comments.trailing(item), NodeKind::StmtFunctionDef)
9090
.fmt(f)
9191
}
9292
}

crates/ruff_python_formatter/src/statement/suite.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
240240
preceding_stub.end(),
241241
f.context().source(),
242242
) < 2
243-
});
243+
})
244+
&& !preceding_comments.has_trailing_own_line();
244245

245246
if !is_preceding_stub_function_without_empty_line {
246247
match self.kind {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
source: crates/ruff_python_formatter/tests/fixtures.rs
3+
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/stub_functions_trailing_comments.py
4+
---
5+
## Input
6+
```python
7+
# Regression tests for https://github.com/astral-sh/ruff/issues/11569
8+
9+
10+
# comment 1
11+
def foo(self) -> None: ...
12+
def bar(self) -> None: ...
13+
# comment 2
14+
15+
# comment 3
16+
def baz(self) -> None:
17+
return None
18+
# comment 4
19+
20+
21+
def foo(self) -> None: ...
22+
# comment 5
23+
24+
def baz(self) -> None:
25+
return None
26+
27+
28+
def foo(self) -> None:
29+
... # comment 5
30+
def baz(self) -> None:
31+
return None
32+
33+
def foo(self) -> None: ...
34+
# comment 5
35+
```
36+
37+
## Output
38+
```python
39+
# Regression tests for https://github.com/astral-sh/ruff/issues/11569
40+
41+
42+
# comment 1
43+
def foo(self) -> None: ...
44+
def bar(self) -> None: ...
45+
46+
47+
# comment 2
48+
49+
50+
# comment 3
51+
def baz(self) -> None:
52+
return None
53+
54+
55+
# comment 4
56+
57+
58+
def foo(self) -> None: ...
59+
60+
61+
# comment 5
62+
63+
64+
def baz(self) -> None:
65+
return None
66+
67+
68+
def foo(self) -> None: ... # comment 5
69+
def baz(self) -> None:
70+
return None
71+
72+
73+
def foo(self) -> None: ...
74+
75+
76+
# comment 5
77+
```

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy