Avoid syntax error when formatting attribute expressions with outer parentheses, parenthesized value, and trailing comment on value (#20418)

Closes #19350 

This fixes a syntax error caused by formatting. However, the new tests reveal that there are some cases where formatting attributes with certain comments behaves strangely, both before and after this PR, so some more polish may be in order.

For example, without parentheses around the value, and both before and after this PR, we have:

```python
# unformatted
variable = (
    something # a comment
    .first_method("some string")
)

# formatted
variable = something.first_method("some string")  # a comment
```

which is probably not where the comment ought to go.
This commit is contained in:
Dylan 2025-11-17 09:11:36 -06:00 committed by GitHub
parent d063c71177
commit 8156b45173
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 166 additions and 1 deletions

View file

@ -169,3 +169,53 @@ result = (
# dangling before dot # dangling before dot
.b # trailing end-of-line .b # trailing end-of-line
) )
# Regression test for https://github.com/astral-sh/ruff/issues/19350
variable = (
(something) # a comment
.first_method("some string")
)
variable = (
something # a commentdddddddddddddddddddddddddddddd
.first_method("some string")
)
if (
(something) # a commentdddddddddddddddddddddddddddddd
.first_method("some string")
): pass
variable = (
(something # a comment
).first_method("some string")
)
if (
(something # a comment
).first_method("some string") # second comment
): pass
variable = ( # 1
# 2
(something) # 3
# 4
.first_method("some string") # 5
# 6
) # 7
if (
(something
# trailing own line on value
)
.first_method("some string")
): ...
variable = (
(something
# 1
) # 2
.first_method("some string")
)

View file

@ -179,7 +179,22 @@ impl NeedsParentheses for ExprAttribute {
context.comments().ranges(), context.comments().ranges(),
context.source(), context.source(),
) { ) {
OptionalParentheses::Never // We have to avoid creating syntax errors like
// ```python
// variable = (something) # trailing
// .my_attribute
// ```
// See https://github.com/astral-sh/ruff/issues/19350
if context
.comments()
.trailing(self.value.as_ref())
.iter()
.any(|comment| comment.line_position().is_end_of_line())
{
OptionalParentheses::Multiline
} else {
OptionalParentheses::Never
}
} else { } else {
self.value.needs_parentheses(self.into(), context) self.value.needs_parentheses(self.into(), context)
} }

View file

@ -175,6 +175,56 @@ result = (
# dangling before dot # dangling before dot
.b # trailing end-of-line .b # trailing end-of-line
) )
# Regression test for https://github.com/astral-sh/ruff/issues/19350
variable = (
(something) # a comment
.first_method("some string")
)
variable = (
something # a commentdddddddddddddddddddddddddddddd
.first_method("some string")
)
if (
(something) # a commentdddddddddddddddddddddddddddddd
.first_method("some string")
): pass
variable = (
(something # a comment
).first_method("some string")
)
if (
(something # a comment
).first_method("some string") # second comment
): pass
variable = ( # 1
# 2
(something) # 3
# 4
.first_method("some string") # 5
# 6
) # 7
if (
(something
# trailing own line on value
)
.first_method("some string")
): ...
variable = (
(something
# 1
) # 2
.first_method("some string")
)
``` ```
## Output ## Output
@ -328,4 +378,54 @@ result = (
# dangling before dot # dangling before dot
.b # trailing end-of-line .b # trailing end-of-line
) )
# Regression test for https://github.com/astral-sh/ruff/issues/19350
variable = (
(something) # a comment
.first_method("some string")
)
variable = something.first_method( # a commentdddddddddddddddddddddddddddddd
"some string"
)
if (
(something) # a commentdddddddddddddddddddddddddddddd
.first_method("some string")
):
pass
variable = (
something # a comment
).first_method("some string")
if (
(
something # a comment
).first_method("some string") # second comment
):
pass
variable = ( # 1
# 2
(something) # 3
# 4
.first_method("some string") # 5
# 6
) # 7
if (
something
# trailing own line on value
).first_method("some string"):
...
variable = (
(
something
# 1
) # 2
.first_method("some string")
)
``` ```