mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-24 05:25:17 +00:00
Try statements have a body: Fix formatter instability (#5558)
## Summary
The following code was previously leading to unstable formatting:
```python
try:
try:
pass
finally:
print(1) # issue7208
except A:
pass
```
The comment would be formatted as a trailing comment of `try` which is
unstable as an end-of-line comment gets two extra whitespaces.
This was originally found in
99b00efd5e/Lib/getpass.py (L68-L91)
## Test Plan
I added a regression test
This commit is contained in:
parent
25981420c4
commit
8184235f93
3 changed files with 88 additions and 76 deletions
|
@ -4281,6 +4281,8 @@ impl AnyNodeRef<'_> {
|
||||||
| AnyNodeRef::StmtFunctionDef(_)
|
| AnyNodeRef::StmtFunctionDef(_)
|
||||||
| AnyNodeRef::StmtAsyncFunctionDef(_)
|
| AnyNodeRef::StmtAsyncFunctionDef(_)
|
||||||
| AnyNodeRef::StmtClassDef(_)
|
| AnyNodeRef::StmtClassDef(_)
|
||||||
|
| AnyNodeRef::StmtTry(_)
|
||||||
|
| AnyNodeRef::StmtTryStar(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,3 +89,13 @@ else:
|
||||||
# before finally
|
# before finally
|
||||||
finally:
|
finally:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
# try and try star are statements with body
|
||||||
|
# Minimized from https://github.com/python/cpython/blob/99b00efd5edfd5b26bf9e2a35cbfc96277fdcbb1/Lib/getpass.py#L68-L91
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
print(1) # issue7208
|
||||||
|
except A:
|
||||||
|
pass
|
||||||
|
|
|
@ -335,89 +335,89 @@ fn handle_in_between_bodies_end_of_line_comment<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// The comment must be between two statements...
|
// The comment must be between two statements...
|
||||||
if let (Some(preceding), Some(following)) = (comment.preceding_node(), comment.following_node())
|
let (Some(preceding), Some(following)) = (comment.preceding_node(), comment.following_node())
|
||||||
{
|
else {
|
||||||
// ...and the following statement must be the first statement in an alternate body of the parent...
|
return CommentPlacement::Default(comment);
|
||||||
if !is_first_statement_in_enclosing_alternate_body(following, comment.enclosing_node()) {
|
};
|
||||||
// ```python
|
|
||||||
// if test:
|
|
||||||
// a
|
|
||||||
// # comment
|
|
||||||
// b
|
|
||||||
// ```
|
|
||||||
return CommentPlacement::Default(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
if locator.contains_line_break(TextRange::new(preceding.end(), comment.slice().start())) {
|
// ...and the following statement must be the first statement in an alternate body of the parent...
|
||||||
// The `elif` or except handlers have their own body to which we can attach the trailing comment
|
if !is_first_statement_in_enclosing_alternate_body(following, comment.enclosing_node()) {
|
||||||
|
// ```python
|
||||||
|
// if test:
|
||||||
|
// a
|
||||||
|
// # comment
|
||||||
|
// b
|
||||||
|
// ```
|
||||||
|
return CommentPlacement::Default(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
if locator.contains_line_break(TextRange::new(preceding.end(), comment.slice().start())) {
|
||||||
|
// The `elif` or except handlers have their own body to which we can attach the trailing comment
|
||||||
|
// ```python
|
||||||
|
// if test:
|
||||||
|
// a
|
||||||
|
// elif c: # comment
|
||||||
|
// b
|
||||||
|
// ```
|
||||||
|
if following.is_except_handler() {
|
||||||
|
return CommentPlacement::trailing(following, comment);
|
||||||
|
} else if following.is_stmt_if() {
|
||||||
|
// We have to exclude for following if statements that are not elif by checking the
|
||||||
|
// indentation
|
||||||
// ```python
|
// ```python
|
||||||
// if test:
|
// if True:
|
||||||
// a
|
// pass
|
||||||
// elif c: # comment
|
// else: # Comment
|
||||||
// b
|
// if False:
|
||||||
// ```
|
// pass
|
||||||
if following.is_except_handler() {
|
|
||||||
return CommentPlacement::trailing(following, comment);
|
|
||||||
} else if following.is_stmt_if() {
|
|
||||||
// We have to exclude for following if statements that are not elif by checking the
|
|
||||||
// indentation
|
|
||||||
// ```python
|
|
||||||
// if True:
|
|
||||||
// pass
|
|
||||||
// else: # Comment
|
|
||||||
// if False:
|
|
||||||
// pass
|
|
||||||
// pass
|
|
||||||
// ```
|
|
||||||
let base_if_indent =
|
|
||||||
whitespace::indentation_at_offset(locator, following.range().start());
|
|
||||||
let maybe_elif_indent = whitespace::indentation_at_offset(
|
|
||||||
locator,
|
|
||||||
comment.enclosing_node().range().start(),
|
|
||||||
);
|
|
||||||
if base_if_indent == maybe_elif_indent {
|
|
||||||
return CommentPlacement::trailing(following, comment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// There are no bodies for the "else" branch and other bodies that are represented as a `Vec<Stmt>`.
|
|
||||||
// This means, there's no good place to attach the comments to.
|
|
||||||
// Make this a dangling comments and manually format the comment in
|
|
||||||
// in the enclosing node's formatting logic. For `try`, it's the formatters responsibility
|
|
||||||
// to correctly identify the comments for the `finally` and `orelse` block by looking
|
|
||||||
// at the comment's range.
|
|
||||||
//
|
|
||||||
// ```python
|
|
||||||
// while x == y:
|
|
||||||
// pass
|
// pass
|
||||||
// else: # trailing
|
|
||||||
// print("nooop")
|
|
||||||
// ```
|
// ```
|
||||||
CommentPlacement::dangling(comment.enclosing_node(), comment)
|
let base_if_indent =
|
||||||
} else {
|
whitespace::indentation_at_offset(locator, following.range().start());
|
||||||
// Trailing comment of the preceding statement
|
let maybe_elif_indent = whitespace::indentation_at_offset(
|
||||||
// ```python
|
locator,
|
||||||
// while test:
|
comment.enclosing_node().range().start(),
|
||||||
// a # comment
|
);
|
||||||
// else:
|
if base_if_indent == maybe_elif_indent {
|
||||||
// b
|
return CommentPlacement::trailing(following, comment);
|
||||||
// ```
|
|
||||||
if preceding.is_node_with_body() {
|
|
||||||
// We can't set this as a trailing comment of the function declaration because it
|
|
||||||
// will then move behind the function block instead of sticking with the pass
|
|
||||||
// ```python
|
|
||||||
// if True:
|
|
||||||
// def f():
|
|
||||||
// pass # a
|
|
||||||
// else:
|
|
||||||
// pass
|
|
||||||
// ```
|
|
||||||
CommentPlacement::Default(comment)
|
|
||||||
} else {
|
|
||||||
CommentPlacement::trailing(preceding, comment)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// There are no bodies for the "else" branch and other bodies that are represented as a `Vec<Stmt>`.
|
||||||
|
// This means, there's no good place to attach the comments to.
|
||||||
|
// Make this a dangling comments and manually format the comment in
|
||||||
|
// in the enclosing node's formatting logic. For `try`, it's the formatters responsibility
|
||||||
|
// to correctly identify the comments for the `finally` and `orelse` block by looking
|
||||||
|
// at the comment's range.
|
||||||
|
//
|
||||||
|
// ```python
|
||||||
|
// while x == y:
|
||||||
|
// pass
|
||||||
|
// else: # trailing
|
||||||
|
// print("nooop")
|
||||||
|
// ```
|
||||||
|
CommentPlacement::dangling(comment.enclosing_node(), comment)
|
||||||
} else {
|
} else {
|
||||||
CommentPlacement::Default(comment)
|
// Trailing comment of the preceding statement
|
||||||
|
// ```python
|
||||||
|
// while test:
|
||||||
|
// a # comment
|
||||||
|
// else:
|
||||||
|
// b
|
||||||
|
// ```
|
||||||
|
if preceding.is_node_with_body() {
|
||||||
|
// We can't set this as a trailing comment of the function declaration because it
|
||||||
|
// will then move behind the function block instead of sticking with the pass
|
||||||
|
// ```python
|
||||||
|
// if True:
|
||||||
|
// def f():
|
||||||
|
// pass # a
|
||||||
|
// else:
|
||||||
|
// pass
|
||||||
|
// ```
|
||||||
|
CommentPlacement::Default(comment)
|
||||||
|
} else {
|
||||||
|
CommentPlacement::trailing(preceding, comment)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue