Format comment before parameter default correctly (#7870)

**Summary** Handle comment before the default values of function
parameters correctly by inserting a line break instead of space after
the equals sign where required.

```python
def f(
    a = # parameter trailing comment; needs line break
    1,
    b =
    # default leading comment; needs line break
    2,
    c = ( # the default leading can only be end-of-line with parentheses; no line break
        3
    ),
    d = (
        # own line leading comment with parentheses; no line break
        4
    )
)
```

Fixes #7603

**Test Plan** Added the different cases and one more complex case as
fixtures.
This commit is contained in:
konsti 2023-10-12 17:50:12 +02:00 committed by GitHub
parent cb06b7956c
commit 3944c42d4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 126 additions and 1 deletions

View file

@ -384,3 +384,29 @@ try:
#comment
except ImportError:
pass
# https://github.com/astral-sh/ruff/issues/7603
def default_arg_comments(
a: str = #a
"a",
b: str =
#b
"b",
c: str =( #c
"c"
),
d: str =(
#d
"d"
)
):
print(a, b, c, d)
def default_arg_comments2(#
x: int#=
= #
#
123#
#
):
print(x)

View file

@ -1,5 +1,7 @@
use ruff_formatter::write;
use ruff_python_ast::ParameterWithDefault;
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange};
use crate::prelude::*;
@ -18,7 +20,51 @@ impl FormatNodeRule<ParameterWithDefault> for FormatParameterWithDefault {
if let Some(default) = default {
let space = parameter.annotation.is_some().then_some(space());
write!(f, [space, token("="), space, group(&default.format())])?;
// ```python
// def f(
// a = # parameter trailing comment; needs line break
// 1,
// b =
// # default leading comment; needs line break
// 2,
// c = ( # the default leading can only be end-of-line with parentheses; no line break
// 3
// ),
// d = (
// # own line leading comment with parentheses; no line break
// 4
// )
// )
// ```
let needs_line_break_trailing = f.context().comments().has_trailing(parameter);
let default_first_comment = f.context().comments().leading(default.as_ref()).first();
let needs_line_break_leading = default_first_comment.is_some_and(|default_leading_comment| {
let mut tokenizer = SimpleTokenizer::new(
f.context().source(),
TextRange::new(parameter.end(), default_leading_comment.start()),
)
.skip_trivia()
.skip_while(|token| token.kind == SimpleTokenKind::RParen);
let equals = tokenizer.next();
debug_assert!(equals.is_some_and(|token| token.kind == SimpleTokenKind::Equals));
let lparens = tokenizer.next();
debug_assert!(lparens
.as_ref()
.map_or(true, |token| token.kind == SimpleTokenKind::LParen));
lparens.is_none()
});
let needs_line_break = needs_line_break_trailing || needs_line_break_leading;
write!(
f,
[
space,
token("="),
(!needs_line_break).then_some(space),
needs_line_break.then_some(hard_line_break()),
group(&default.format())
]
)?;
}
Ok(())

View file

@ -390,6 +390,32 @@ try:
#comment
except ImportError:
pass
# https://github.com/astral-sh/ruff/issues/7603
def default_arg_comments(
a: str = #a
"a",
b: str =
#b
"b",
c: str =( #c
"c"
),
d: str =(
#d
"d"
)
):
print(a, b, c, d)
def default_arg_comments2(#
x: int#=
= #
#
123#
#
):
print(x)
```
## Output
@ -940,6 +966,33 @@ try:
# comment
except ImportError:
pass
# https://github.com/astral-sh/ruff/issues/7603
def default_arg_comments(
a: str = # a
"a",
b: str =
# b
"b",
c: str = ( # c
"c"
),
d: str = (
# d
"d"
),
):
print(a, b, c, d)
def default_arg_comments2( #
x: int = # = #
#
123, #
#
):
print(x)
```