diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py index 4a7090ff13..660d5644e9 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py @@ -228,3 +228,24 @@ def a(): g = 10 ) +( + lambda + * # comment 2 + x: + x +) + +( + lambda # comment 1 + * # comment 2 + x: + x +) + +( + lambda # comment 1 + y, + * # comment 2 + x: + x +) diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index f28f9b18a8..28397b6dcf 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -1816,7 +1816,7 @@ fn handle_lambda_comment<'a>( source: &str, ) -> CommentPlacement<'a> { if let Some(parameters) = lambda.parameters.as_deref() { - // Comments between the `lambda` and the parameters are dangling on the lambda: + // End-of-line comments between the `lambda` and the parameters are dangling on the lambda: // ```python // ( // lambda # comment @@ -1824,8 +1824,24 @@ fn handle_lambda_comment<'a>( // y // ) // ``` + // + // But own-line comments are leading on the first parameter, if it exists: + // ```python + // ( + // lambda + // # comment + // x: + // y + // ) + // ``` if comment.start() < parameters.start() { - return CommentPlacement::dangling(comment.enclosing_node(), comment); + return if let Some(first) = parameters.iter().next() + && comment.line_position().is_own_line() + { + CommentPlacement::leading(first.as_parameter(), comment) + } else { + CommentPlacement::dangling(comment.enclosing_node(), comment) + }; } // Comments between the parameters and the body are dangling on the lambda: diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index c5890fba24..335f112323 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -32,7 +32,69 @@ impl FormatNodeRule for FormatExprLambda { .split_at(dangling.partition_point(|comment| comment.end() < parameters.start())); if dangling_before_parameters.is_empty() { - write!(f, [space()])?; + // If the first parameter has a leading comment, insert a hard line break. This + // comment is associated as a leading comment on the first parameter: + // + // ```py + // ( + // lambda + // * # comment + // x: + // x + // ) + // ``` + // + // so a hard line break is needed to avoid formatting it like: + // + // ```py + // ( + // lambda # comment + // *x: x + // ) + // ``` + // + // which is unstable because it's missing the second space before the comment. + // + // Inserting the line break causes it to format like: + // + // ```py + // ( + // lambda + // # comment + // *x :x + // ) + // ``` + // + // which is also consistent with the formatting in the presence of an actual + // dangling comment on the lambda: + // + // ```py + // ( + // lambda # comment 1 + // * # comment 2 + // x: + // x + // ) + // ``` + // + // formats to: + // + // ```py + // ( + // lambda # comment 1 + // # comment 2 + // *x: x + // ) + // ``` + if parameters + .iter() + .next() + .is_some_and(|parameter| comments.has_leading(parameter.as_parameter())) + { + hard_line_break().fmt(f)?; + } else { + write!(f, [space()])?; + } } else { write!(f, [dangling_comments(dangling_before_parameters)])?; } diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda.py.snap index 03332c6f92..3009dfaefc 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py -snapshot_kind: text --- ## Input ```python @@ -235,6 +234,27 @@ def a(): g = 10 ) +( + lambda + * # comment 2 + x: + x +) + +( + lambda # comment 1 + * # comment 2 + x: + x +) + +( + lambda # comment 1 + y, + * # comment 2 + x: + x +) ``` ## Output @@ -473,4 +493,24 @@ def a(): g=2: d, g=10, ) + + +( + lambda + # comment 2 + *x: x +) + +( + lambda # comment 1 + # comment 2 + *x: x +) + +( + lambda # comment 1 + y, + # comment 2 + *x: x +) ```