From 0f1ea90c5cff4757ba4515c7c4b504918d493020 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Thu, 11 Dec 2025 11:43:03 -0500 Subject: [PATCH] reposition dangling comments --- .../test/fixtures/ruff/expression/lambda.py | 15 +++ .../src/expression/expr_lambda.rs | 83 +++++++++++++--- .../format@expression__lambda.py.snap | 98 ++++++++++++------- 3 files changed, 148 insertions(+), 48 deletions(-) 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 8913fbc8f6..036ef2c3c8 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 @@ -789,3 +789,18 @@ transform = lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date).betwe x: x ) + +( + lambda: # dangling-end-of-line + # dangling-own-line + ( # leading-body-end-of-line + x + ) +) + +( + lambda: # dangling-end-of-line + ( # leading-body-end-of-line + x + ) +) diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index bf8e422401..33f809a08e 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -4,7 +4,9 @@ use ruff_text_size::Ranged; use crate::builders::parenthesize_if_expands; use crate::comments::{SourceComment, dangling_comments, leading_comments, trailing_comments}; -use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; +use crate::expression::parentheses::{ + NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized, +}; use crate::expression::{CallChainLayout, has_own_parentheses}; use crate::other::parameters::ParametersParentheses; use crate::prelude::*; @@ -377,19 +379,72 @@ impl Format> for FormatBody<'_> { ); let fmt_body = format_with(|f: &mut PyFormatter| { - write!( - f, - [ - space(), - token("("), - trailing_comments(after_parameters_end_of_line), - block_indent(&format_args!( - leading_comments(leading_body_comments), - body.format().with_options(Parentheses::Never) - )), - token(")") - ] - ) + // If the body is parenthesized and has its own leading comments, preserve the + // separation between the dangling lambda comments and the body comments. For example, + // preserve this comment positioning: + // + // ```python + // ( + // lambda: # 1 + // # 2 + // ( # 3 + // x + // ) + // ) + // ``` + // + // 1 and 2 are dangling on the lambda and emitted first, followed by a hard line break + // and the parenthesized body with its leading comments. + // + // However, when removing 2, 1 and 3 can instead be formatted on the same line: + // + // ```python + // ( + // lambda: ( # 1 # 3 + // x + // ) + // ) + // ``` + if is_expression_parenthesized( + (*body).into(), + f.context().comments().ranges(), + f.context().source(), + ) && f.context().comments().has_leading(*body) + { + if leading_body_comments.is_empty() { + write!( + f, + [ + space(), + trailing_comments(dangling), + body.format().with_options(Parentheses::Always), + ] + ) + } else { + write!( + f, + [ + trailing_comments(dangling), + hard_line_break(), + body.format().with_options(Parentheses::Always) + ] + ) + } + } else { + write!( + f, + [ + space(), + token("("), + trailing_comments(after_parameters_end_of_line), + block_indent(&format_args!( + leading_comments(leading_body_comments), + body.format().with_options(Parentheses::Never) + )), + token(")") + ] + ) + } }); match layout { 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 bb69b55e95..82ee1639d7 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 @@ -795,6 +795,21 @@ transform = lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date).betwe x: x ) + +( + lambda: # dangling-end-of-line + # dangling-own-line + ( # leading-body-end-of-line + x + ) +) + +( + lambda: # dangling-end-of-line + ( # leading-body-end-of-line + x + ) +) ``` ## Output @@ -1619,6 +1634,21 @@ transform = ( # comment 1 **x: x ) + +( + lambda: # dangling-end-of-line + # dangling-own-line + ( # leading-body-end-of-line + x + ) +) + +( + lambda: # dangling-end-of-line + ( # leading-body-end-of-line + x + ) +) ``` @@ -1738,7 +1768,7 @@ transform = ( ) lambda *x: x -@@ -161,30 +147,34 @@ +@@ -161,30 +147,33 @@ ) ( @@ -1780,12 +1810,11 @@ transform = ( ( - lambda: # comment - ( # comment -+ lambda: ( # comment -+ # comment ++ lambda: ( # comment # comment x ) ) -@@ -192,11 +182,12 @@ +@@ -192,11 +181,12 @@ ( lambda # 1 # 2 @@ -1803,7 +1832,7 @@ transform = ( ) ( -@@ -204,9 +195,10 @@ +@@ -204,9 +194,10 @@ # 2 x, # 3 # 4 @@ -1817,7 +1846,7 @@ transform = ( ) ( -@@ -218,71 +210,79 @@ +@@ -218,71 +209,79 @@ # Leading lambda x: ( @@ -1956,7 +1985,7 @@ transform = ( # Regression tests for https://github.com/astral-sh/ruff/issues/8179 -@@ -291,9 +291,9 @@ +@@ -291,9 +290,9 @@ c, d, e, @@ -1969,7 +1998,7 @@ transform = ( ) -@@ -302,15 +302,9 @@ +@@ -302,15 +301,9 @@ c, d, e, @@ -1988,7 +2017,7 @@ transform = ( g=10, ) -@@ -320,9 +314,9 @@ +@@ -320,9 +313,9 @@ c, d, e, @@ -2001,7 +2030,7 @@ transform = ( ) -@@ -338,9 +332,9 @@ +@@ -338,9 +331,9 @@ class C: function_dict: Dict[Text, Callable[[CRFToken], Any]] = { @@ -2014,7 +2043,7 @@ transform = ( } -@@ -352,42 +346,40 @@ +@@ -352,42 +345,40 @@ def foo(): if True: if True: @@ -2073,7 +2102,7 @@ transform = ( CREATE TABLE {table} AS SELECT ROW_NUMBER() OVER () AS id, {var} FROM ( -@@ -401,18 +393,19 @@ +@@ -401,18 +392,19 @@ long_assignment_target.with_attribute.and_a_slice[with_an_index] = ( # 1 # 2 @@ -2100,7 +2129,7 @@ transform = ( ) very_long_variable_name_x, very_long_variable_name_y = ( -@@ -420,8 +413,8 @@ +@@ -420,8 +412,8 @@ lambda b: b * another_very_long_expression_here, ) @@ -2111,7 +2140,7 @@ transform = ( x, more_args, additional_parameters ) ) -@@ -457,12 +450,12 @@ +@@ -457,12 +449,12 @@ [ # Not fluent param( @@ -2126,7 +2155,7 @@ transform = ( ), param( lambda left, right: ( -@@ -471,9 +464,9 @@ +@@ -471,9 +463,9 @@ ), param(lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date)), param( @@ -2139,7 +2168,7 @@ transform = ( ), # This is too long on one line in the lambda body and gets wrapped # inside the body. -@@ -507,16 +500,18 @@ +@@ -507,16 +499,18 @@ ] # adds parentheses around the body @@ -2161,7 +2190,7 @@ transform = ( lambda x, y, z: ( x + y + z -@@ -527,7 +522,7 @@ +@@ -527,7 +521,7 @@ x + y + z # trailing eol body ) @@ -2170,7 +2199,7 @@ transform = ( lambda x, y, z: ( # leading body -@@ -539,21 +534,23 @@ +@@ -539,21 +533,23 @@ ) ( @@ -2204,7 +2233,7 @@ transform = ( # dangling header comment source_bucket if name == source_bucket_name -@@ -561,8 +558,7 @@ +@@ -561,8 +557,7 @@ ) ( @@ -2214,7 +2243,7 @@ transform = ( source_bucket if name == source_bucket_name else storage.Bucket(mock_service, destination_bucket_name) -@@ -570,61 +566,70 @@ +@@ -570,61 +565,70 @@ ) ( @@ -2317,7 +2346,7 @@ transform = ( ) ( -@@ -637,33 +642,37 @@ +@@ -637,27 +641,31 @@ ( lambda # comment @@ -2357,16 +2386,7 @@ transform = ( ) ( -- lambda *args, **kwargs: -- # comment 1 -- ( # comment 2 -+ lambda *args, **kwargs: ( -+ # comment 1 -+ # comment 2 - # comment 3 - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) - + 1 # comment 4 -@@ -672,25 +681,28 @@ +@@ -672,25 +680,28 @@ ) ( @@ -2407,7 +2427,7 @@ transform = ( ) ( -@@ -698,9 +710,9 @@ +@@ -698,9 +709,9 @@ # comment 1 *ergs, # comment 2 @@ -2420,7 +2440,7 @@ transform = ( ) ( -@@ -708,19 +720,20 @@ +@@ -708,19 +719,20 @@ # 2 left, # 3 # 4 @@ -2451,7 +2471,7 @@ transform = ( ) ) ) -@@ -738,48 +751,52 @@ +@@ -738,48 +750,52 @@ foo( lambda from_ts, # but still wrap the body if it gets too long to_ts, @@ -2528,4 +2548,14 @@ transform = ( ) ( +@@ -828,8 +844,7 @@ + ) + + ( +- lambda: # dangling-end-of-line +- ( # leading-body-end-of-line ++ lambda: ( # dangling-end-of-line # leading-body-end-of-line + x + ) + ) ```