Fix comment formatting for yielded tuples (#6603)

## Summary
Closes https://github.com/astral-sh/ruff/issues/6384, although I think
the issue was fixed already on main, for the most part.

The linked issue is around formatting expressions like:

```python
def test():
    (
        yield 
        #comment 1
        * # comment 2
        # comment 3
        test # comment 4
    )

```

On main, prior to this PR, we now format like:

```python
def test():
    (
        yield (
            # comment 1
            # comment 2
            # comment 3
            *test
        )  # comment 4
    )
```

Which strikes me as reasonable. (We can't test this, since it's a syntax
error after for our parser, despite being a syntax error in both cases
from CPython's perspective.)

Meanwhile, Black does:

```python
def test():
    (
        yield
        # comment 1
        *  # comment 2
        # comment 3
        test  # comment 4
    )
```

So our formatting differs in that we move comments between the star and
the expression above the star.

As of this PR, we also support formatting this input, which is valid:

```python
def test():
    (
        yield 
        #comment 1
        * # comment 2
        # comment 3
        test, # comment 4
        1
    )
```

Like:

```python
def test():
    (
        yield (
            # comment 1
            (
                # comment 2
                # comment 3
                *test,  # comment 4
                1,
            )
        )
    )
```

There were two fixes here: (1) marking starred comments as dangling and
formatting them properly; and (2) supporting parenthesized comments for
tuples that don't contain their own parentheses, as is often the case
for yielded tuples (previously, we hit a debug assert).

Note that this diff

## Test Plan
cargo test
This commit is contained in:
Charlie Marsh 2023-08-16 09:41:07 -04:00 committed by GitHub
parent 7ee2ae8395
commit 12f3c4c931
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 88 additions and 21 deletions

View file

@ -9,6 +9,7 @@ use ruff_text_size::{TextLen, TextRange};
use crate::comments::visitor::{CommentPlacement, DecoratedComment};
use crate::expression::expr_slice::{assign_comment_in_slice, ExprSliceCommentSection};
use crate::expression::expr_tuple::is_tuple_parenthesized;
use crate::other::parameters::{
assign_argument_separator_comment_placement, find_parameter_separators,
};
@ -185,7 +186,7 @@ fn handle_enclosed_comment<'a>(
AnyNodeRef::ExprIfExp(expr_if) => handle_expr_if_comment(comment, expr_if, locator),
AnyNodeRef::ExprSlice(expr_slice) => handle_slice_comments(comment, expr_slice, locator),
AnyNodeRef::ExprStarred(starred) => {
handle_trailing_expression_starred_star_end_of_line_comment(comment, starred)
handle_trailing_expression_starred_star_end_of_line_comment(comment, starred, locator)
}
AnyNodeRef::ExprSubscript(expr_subscript) => {
if let Expr::Slice(expr_slice) = expr_subscript.slice.as_ref() {
@ -217,8 +218,10 @@ fn handle_enclosed_comment<'a>(
| AnyNodeRef::ExprGeneratorExp(_)
| AnyNodeRef::ExprListComp(_)
| AnyNodeRef::ExprSetComp(_)
| AnyNodeRef::ExprDictComp(_)
| AnyNodeRef::ExprTuple(_) => handle_bracketed_end_of_line_comment(comment, locator),
| AnyNodeRef::ExprDictComp(_) => handle_bracketed_end_of_line_comment(comment, locator),
AnyNodeRef::ExprTuple(tuple) if is_tuple_parenthesized(tuple, locator.contents()) => {
handle_bracketed_end_of_line_comment(comment, locator)
}
_ => CommentPlacement::Default(comment),
}
}
@ -1060,27 +1063,37 @@ fn handle_expr_if_comment<'a>(
CommentPlacement::Default(comment)
}
/// Moving
/// Handles trailing comments on between the `*` of a starred expression and the
/// expression itself. For example, attaches the first two comments here as leading
/// comments on the enclosing node, and the third to the `True` node.
/// ``` python
/// call(
/// # Leading starred comment
/// * # Trailing star comment
/// []
/// )
/// ```
/// to
/// ``` python
/// call(
/// # Leading starred comment
/// # Trailing star comment
/// * []
/// * # dangling end-of-line comment
/// # dangling own line comment
/// ( # leading comment on the expression
/// True
/// )
/// )
/// ```
fn handle_trailing_expression_starred_star_end_of_line_comment<'a>(
comment: DecoratedComment<'a>,
starred: &'a ast::ExprStarred,
locator: &Locator,
) -> CommentPlacement<'a> {
CommentPlacement::leading(starred, comment)
if comment.following_node().is_some() {
let tokenizer = SimpleTokenizer::new(
locator.contents(),
TextRange::new(starred.start(), comment.start()),
);
if !tokenizer
.skip_trivia()
.any(|token| token.kind() == SimpleTokenKind::LParen)
{
return CommentPlacement::leading(starred, comment);
}
}
CommentPlacement::Default(comment)
}
/// Handles trailing own line comments before the `as` keyword of a with item and

View file

@ -1,6 +1,6 @@
use ruff_python_ast::ExprStarred;
use crate::comments::SourceComment;
use crate::comments::{dangling_comments, SourceComment};
use ruff_formatter::write;
use ruff_python_ast::node::AnyNodeRef;
@ -20,7 +20,10 @@ impl FormatNodeRule<ExprStarred> for FormatExprStarred {
ctx: _,
} = item;
write!(f, [text("*"), value.format()])
let comments = f.context().comments().clone();
let dangling = comments.dangling_comments(item);
write!(f, [text("*"), dangling_comments(dangling), value.format()])
}
fn fmt_dangling_comments(

View file

@ -204,7 +204,7 @@ impl NeedsParentheses for ExprTuple {
}
/// Check if a tuple has already had parentheses in the input
fn is_tuple_parenthesized(tuple: &ExprTuple, source: &str) -> bool {
pub(crate) fn is_tuple_parenthesized(tuple: &ExprTuple, source: &str) -> bool {
let Some(elt) = tuple.elts.first() else {
return false;
};