Skip to content

Commit a1509df

Browse files
authored
Use correct start location for class/function clause header (#7802)
## Summary This PR fixes the bug where the formatter would panic if a class/function with decorators had a suppression comment. The fix is to use to correct start location to find the `async`/`def`/`class` keyword when decorators are present which is the end of the last decorator. ## Test Plan Add test cases for the fix and update the snapshots.
1 parent 7b4fb4f commit a1509df

File tree

3 files changed

+50
-2
lines changed

3 files changed

+50
-2
lines changed

crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_skip/decorators.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,16 @@ class Test:
1717
def test():
1818
pass
1919

20+
21+
# Regression test for https://github.com/astral-sh/ruff/issues/7735
22+
@decorator1
23+
@decorator2
24+
class Foo: # fmt: skip
25+
pass
26+
27+
28+
# Regression test for https://github.com/astral-sh/ruff/issues/7735
29+
@decorator1
30+
@decorator2
31+
def foo(): # fmt: skip
32+
pass

crates/ruff_python_formatter/src/statement/clause.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,23 @@ impl<'a> ClauseHeader<'a> {
203203
fn first_keyword_range(self, source: &str) -> FormatResult<TextRange> {
204204
match self {
205205
ClauseHeader::Class(header) => {
206-
find_keyword(header.start(), SimpleTokenKind::Class, source)
206+
let start_position = header
207+
.decorator_list
208+
.last()
209+
.map_or_else(|| header.start(), Ranged::end);
210+
find_keyword(start_position, SimpleTokenKind::Class, source)
207211
}
208212
ClauseHeader::Function(header) => {
213+
let start_position = header
214+
.decorator_list
215+
.last()
216+
.map_or_else(|| header.start(), Ranged::end);
209217
let keyword = if header.is_async {
210218
SimpleTokenKind::Async
211219
} else {
212220
SimpleTokenKind::Def
213221
};
214-
find_keyword(header.start(), keyword, source)
222+
find_keyword(start_position, keyword, source)
215223
}
216224
ClauseHeader::If(header) => find_keyword(header.start(), SimpleTokenKind::If, source),
217225
ClauseHeader::ElifElse(ElifElseClause {

crates/ruff_python_formatter/tests/snapshots/format@fmt_skip__decorators.py.snap

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ class Test:
2323
def test():
2424
pass
2525
26+
27+
# Regression test for https://github.com/astral-sh/ruff/issues/7735
28+
@decorator1
29+
@decorator2
30+
class Foo: # fmt: skip
31+
pass
32+
33+
34+
# Regression test for https://github.com/astral-sh/ruff/issues/7735
35+
@decorator1
36+
@decorator2
37+
def foo(): # fmt: skip
38+
pass
2639
```
2740

2841
## Output
@@ -43,6 +56,20 @@ class Test:
4356
# leading class comment
4457
def test():
4558
pass
59+
60+
61+
# Regression test for https://github.com/astral-sh/ruff/issues/7735
62+
@decorator1
63+
@decorator2
64+
class Foo: # fmt: skip
65+
pass
66+
67+
68+
# Regression test for https://github.com/astral-sh/ruff/issues/7735
69+
@decorator1
70+
@decorator2
71+
def foo(): # fmt: skip
72+
pass
4673
```
4774

4875

0 commit comments

Comments
 (0)