Add dangling comment handling for lambda expressions (#7493)

## Summary

This PR adds dangling comment handling for `lambda` expressions. In
short, comments around the `lambda` and the `:` are all considered
dangling. Comments that come between the `lambda` and the `:` may be
moved after the colon for simplicity (this is an odd position for a
comment anyway), unless they also precede the lambda parameters, in
which case they're formatted before the parameters.

Closes https://github.com/astral-sh/ruff/issues/7470.

## Test Plan

`cargo test`

No change in similarity.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 398 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99929 | 648 | 16 |
| zulip | 0.99962 | 1437 | 22 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99982 | 2760 | 37 |
| transformers | 0.99957 | 2587 | 398 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99929 | 648 | 16 |
| zulip | 0.99962 | 1437 | 22 |
This commit is contained in:
Charlie Marsh 2023-09-19 15:23:51 -04:00 committed by GitHub
parent e07670ad97
commit 4c4eceee36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 310 additions and 33 deletions

View file

@ -229,6 +229,7 @@ fn handle_enclosed_comment<'a>(
}
AnyNodeRef::ExprUnaryOp(unary_op) => handle_unary_op_comment(comment, unary_op, locator),
AnyNodeRef::ExprNamedExpr(_) => handle_named_expr_comment(comment, locator),
AnyNodeRef::ExprLambda(lambda) => handle_lambda_comment(comment, lambda, locator),
AnyNodeRef::ExprDict(_) => handle_dict_unpacking_comment(comment, locator)
.or_else(|comment| handle_bracketed_end_of_line_comment(comment, locator))
.or_else(|comment| handle_key_value_comment(comment, locator)),
@ -1687,6 +1688,119 @@ fn handle_named_expr_comment<'a>(
}
}
/// Handles comments around the `:` token in a lambda expression.
///
/// For parameterized lambdas, both the comments between the `lambda` and the parameters, and the
/// comments between the parameters and the body, are considered dangling, as is the case for all
/// of the following:
///
/// ```python
/// (
/// lambda # 1
/// # 2
/// x
/// : # 3
/// # 4
/// y
/// )
/// ```
///
/// For non-parameterized lambdas, all comments before the body are considered dangling, as is the
/// case for all of the following:
///
/// ```python
/// (
/// lambda # 1
/// # 2
/// : # 3
/// # 4
/// y
/// )
/// ```
fn handle_lambda_comment<'a>(
comment: DecoratedComment<'a>,
lambda: &'a ast::ExprLambda,
locator: &Locator,
) -> CommentPlacement<'a> {
if let Some(parameters) = lambda.parameters.as_deref() {
// Comments between the `lambda` and the parameters are dangling on the lambda:
// ```python
// (
// lambda # comment
// x:
// y
// )
// ```
if comment.start() < parameters.start() {
return CommentPlacement::dangling(comment.enclosing_node(), comment);
}
// Comments between the parameters and the body are dangling on the lambda:
// ```python
// (
// lambda x: # comment
// y
// )
// ```
if parameters.end() < comment.start() && comment.start() < lambda.body.start() {
// If the value is parenthesized, and the comment is within the parentheses, it should
// be a leading comment on the value, not a dangling comment in the lambda, as in:
// ```python
// (
// lambda x: ( # comment
// y
// )
// )
// ```
let tokenizer = SimpleTokenizer::new(
locator.contents(),
TextRange::new(parameters.end(), comment.start()),
);
if tokenizer
.skip_trivia()
.any(|token| token.kind == SimpleTokenKind::LParen)
{
return CommentPlacement::Default(comment);
}
return CommentPlacement::dangling(comment.enclosing_node(), comment);
}
} else {
// Comments between the lambda and the body are dangling on the lambda:
// ```python
// (
// lambda: # comment
// y
// )
// ```
if comment.start() < lambda.body.start() {
// If the value is parenthesized, and the comment is within the parentheses, it should
// be a leading comment on the value, not a dangling comment in the lambda, as in:
// ```python
// (
// lambda: ( # comment
// y
// )
// )
// ```
let tokenizer = SimpleTokenizer::new(
locator.contents(),
TextRange::new(lambda.start(), comment.start()),
);
if tokenizer
.skip_trivia()
.any(|token| token.kind == SimpleTokenKind::LParen)
{
return CommentPlacement::Default(comment);
}
return CommentPlacement::dangling(comment.enclosing_node(), comment);
}
}
CommentPlacement::Default(comment)
}
/// Attach trailing end-of-line comments on the operator as dangling comments on the enclosing
/// node.
///