From 70968deba1a0d34d0316be5d3fe4c937f807c501 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 3 Nov 2023 13:13:05 +0900 Subject: [PATCH] Indent lambda parameters if parameters wrap --- .../ruff/expression/lambda.options.json | 8 + .../test/fixtures/ruff/expression/lambda.py | 65 +++ .../src/comments/placement.rs | 12 - crates/ruff_python_formatter/src/context.rs | 8 + .../src/expression/expr_lambda.rs | 46 +- .../src/expression/expr_named_expr.rs | 1 + crates/ruff_python_formatter/src/lib.rs | 9 +- crates/ruff_python_formatter/src/options.rs | 2 +- .../src/other/parameters.rs | 2 +- .../format@expression__lambda.py.snap | 543 ++++++++++++++++-- 10 files changed, 625 insertions(+), 71 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json new file mode 100644 index 00000000000000..92e14f0a45903b --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json @@ -0,0 +1,8 @@ +[ + { + "preview": "disabled" + }, + { + "preview": "enabled" + } +] 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 4a7090ff1329e9..bb4db84bb740b2 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 @@ -204,6 +204,71 @@ def f( z ) + +# Leading +lambda x: ( + lambda y: lambda z: x + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + z # Trailing +) # Trailing + + +# Leading +lambda x: lambda y: lambda z: [ + x, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + z +] # Trailing +# Trailing + lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d # Regression tests for https://github.com/astral-sh/ruff/issues/8179 diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 2858819c2e1405..c1996f813cfb9f 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -1805,18 +1805,6 @@ fn handle_lambda_comment<'a>( 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 // ( diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index 01caed9e6854d8..c63a03db9bfdd6 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -43,6 +43,14 @@ impl<'a> PyFormatContext<'a> { pub(crate) fn comments(&self) -> &Comments<'a> { &self.comments } + + pub(crate) const fn is_preview(&self) -> bool { + self.options.preview().is_enabled() + } + + pub(crate) const fn is_stable(&self) -> bool { + !self.is_preview() + } } impl FormatContext for PyFormatContext<'_> { diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index f2487fd6a488a8..aa4a6553592501 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -4,7 +4,8 @@ use ruff_python_ast::ExprLambda; use ruff_text_size::Ranged; use crate::comments::{dangling_comments, SourceComment}; -use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parenthesize}; +use crate::expression::{has_own_parentheses, maybe_parenthesize_expression}; use crate::other::parameters::ParametersParentheses; use crate::prelude::*; @@ -25,7 +26,7 @@ impl FormatNodeRule for FormatExprLambda { write!(f, [token("lambda")])?; if let Some(parameters) = parameters { - // In this context, a dangling comment can either be a comment between the `lambda` the + // In this context, a dangling comment can either be a comment between the `lambda` and the // parameters, or a comment between the parameters and the body. let (dangling_before_parameters, dangling_after_parameters) = dangling .split_at(dangling.partition_point(|comment| comment.end() < parameters.start())); @@ -36,20 +37,30 @@ impl FormatNodeRule for FormatExprLambda { write!(f, [dangling_comments(dangling_before_parameters)])?; } - write!( - f, - [parameters - .format() - .with_options(ParametersParentheses::Never)] - )?; + group(&format_with(|f: &mut PyFormatter| { + if f.context().node_level().is_parenthesized() { + soft_block_indent( + ¶meters + .format() + .with_options(ParametersParentheses::Never), + ) + .fmt(f) + } else { + parameters + .format() + .with_options(ParametersParentheses::Never) + .fmt(f) + }?; - write!(f, [token(":")])?; + token(":").fmt(f)?; - if dangling_after_parameters.is_empty() { - write!(f, [space()])?; - } else { - write!(f, [dangling_comments(dangling_after_parameters)])?; - } + if dangling_after_parameters.is_empty() { + space().fmt(f) + } else { + dangling_comments(dangling_after_parameters).fmt(f) + } + })) + .fmt(f)?; } else { write!(f, [token(":")])?; @@ -61,7 +72,12 @@ impl FormatNodeRule for FormatExprLambda { } } - write!(f, [body.format()]) + // Avoid parenthesizing lists, dictionaries, etc. + if f.context().is_stable() || has_own_parentheses(body, f.context()).is_some() { + body.format().fmt(f) + } else { + maybe_parenthesize_expression(body, item, Parenthesize::IfBreaksOrIfRequired).fmt(f) + } } fn fmt_dangling_comments( diff --git a/crates/ruff_python_formatter/src/expression/expr_named_expr.rs b/crates/ruff_python_formatter/src/expression/expr_named_expr.rs index 3fe831c3a0b9ad..aaf71fcbfa7102 100644 --- a/crates/ruff_python_formatter/src/expression/expr_named_expr.rs +++ b/crates/ruff_python_formatter/src/expression/expr_named_expr.rs @@ -69,6 +69,7 @@ impl NeedsParentheses for ExprNamedExpr { || parent.is_stmt_delete() || parent.is_stmt_for() || parent.is_stmt_function_def() + || parent.is_expr_lambda() { OptionalParentheses::Always } else { diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index 7697789fe072c0..0ee3d1963795f3 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -206,14 +206,7 @@ if True: #[test] fn quick_test() { let source = r#" -def main() -> None: - if True: - some_very_long_variable_name_abcdefghijk = Foo() - some_very_long_variable_name_abcdefghijk = some_very_long_variable_name_abcdefghijk[ - some_very_long_variable_name_abcdefghijk.some_very_long_attribute_name - == "This is a very long string abcdefghijk" - ] - +lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d "#; let source_type = PySourceType::Python; let (tokens, comment_ranges) = tokens_and_ranges(source, source_type).unwrap(); diff --git a/crates/ruff_python_formatter/src/options.rs b/crates/ruff_python_formatter/src/options.rs index b39c7b6ff79c64..eac037b3c067d0 100644 --- a/crates/ruff_python_formatter/src/options.rs +++ b/crates/ruff_python_formatter/src/options.rs @@ -108,7 +108,7 @@ impl PyFormatOptions { self.line_ending } - pub fn preview(&self) -> PreviewMode { + pub const fn preview(&self) -> PreviewMode { self.preview } diff --git a/crates/ruff_python_formatter/src/other/parameters.rs b/crates/ruff_python_formatter/src/other/parameters.rs index 86cad5869b2ce1..62e1551ce4ff2e 100644 --- a/crates/ruff_python_formatter/src/other/parameters.rs +++ b/crates/ruff_python_formatter/src/other/parameters.rs @@ -247,7 +247,7 @@ impl FormatNodeRule for FormatParameters { + usize::from(kwarg.is_some()); if self.parentheses == ParametersParentheses::Never { - write!(f, [group(&format_inner), dangling_comments(dangling)]) + write!(f, [format_inner, dangling_comments(dangling)]) } else if num_parameters == 0 { let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f); // No parameters, format any dangling comments between `()` 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 241f61a481c03e..613f5370f71308 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 @@ -210,6 +210,71 @@ lambda: ( # comment z ) + +# Leading +lambda x: ( + lambda y: lambda z: x + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + z # Trailing +) # Trailing + + +# Leading +lambda x: lambda y: lambda z: [ + x, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + z +] # Trailing +# Trailing + lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d # Regression tests for https://github.com/astral-sh/ruff/issues/8179 @@ -236,7 +301,17 @@ def a(): ``` -## Output +## Outputs +### Output 1 +``` +indent-style = space +line-width = 88 +indent-width = 4 +quote-style = Double +magic-trailing-comma = Respect +preview = Disabled +``` + ```py # Leading lambda x: x # Trailing @@ -300,8 +375,10 @@ a = ( ) a = ( - lambda x, # Dangling - y: 1 + lambda + x, # Dangling + y + : 1 ) # Regression test: lambda empty arguments ranges were too long, leading to unstable @@ -314,7 +391,9 @@ a = ( # lambda arguments don't have parentheses, so we never add a magic trailing comma ... def f( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = lambda x: y, + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = lambda + x + : y, ): pass @@ -361,33 +440,38 @@ lambda a, /, c: a ) ( - lambda - # comment - *x: x + lambda + # comment + *x + : x ) ( - lambda - # comment 1 - # comment 2 - *x: + lambda + # comment 1 + # comment 2 + *x + : # comment 3 x ) ( - lambda # comment 1 - # comment 2 - *x: # comment 3 + lambda + # comment 1 + # comment 2 + *x + : # comment 3 x ) lambda *x: x ( - lambda - # comment - *x: x + lambda + # comment + *x + : x ) lambda: ( # comment @@ -424,9 +508,11 @@ lambda: ( # comment ) ( - lambda # 1 - # 2 - x: # 3 + lambda + # 1 + # 2 + x + : # 3 # 4 # 5 # 6 @@ -434,11 +520,396 @@ lambda: ( # comment ) ( - lambda x, + lambda + x, + # comment + y + : z +) + + +# Leading +lambda x: ( + lambda y: lambda z: x + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + z # Trailing +) # Trailing + + +# Leading +lambda x: lambda y: lambda z: [ + x, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + z, +] # Trailing +# Trailing + +lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( + *args, **kwargs +), e=1, f=2, g=2: d + + +# Regression tests for https://github.com/astral-sh/ruff/issues/8179 +def a(): + return b( + c, + d, + e, + f=lambda + self, + *args, + **kwargs + : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), + ) + + +def a(): + return b( + c, + d, + e, + f=lambda + self, + araa, + kkkwargs, + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + args, + kwargs, + e=1, + f=2, + g=2 + : d, + g=10, + ) +``` + + +### Output 2 +``` +indent-style = space +line-width = 88 +indent-width = 4 +quote-style = Double +magic-trailing-comma = Respect +preview = Enabled +``` + +```py +# Leading +lambda x: x # Trailing +# Trailing + +# Leading +lambda x, y: x # Trailing +# Trailing + +# Leading +lambda x, y: x, y # Trailing +# Trailing + +# Leading +lambda x, /, y: x # Trailing +# Trailing + +# Leading +lambda x: lambda y: lambda z: x # Trailing +# Trailing + +# Leading +lambda x: lambda y: lambda z: (x, y, z) # Trailing +# Trailing + +# Leading +lambda x: lambda y: lambda z: (x, y, z) # Trailing +# Trailing + +# Leading +lambda x: ( + lambda y: ( + lambda z: (x, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, z) + ) +) # Trailing +# Trailing + +a = ( + lambda: # Dangling + 1 +) + +a = ( + lambda + x, # Dangling + y + : 1 +) + +# Regression test: lambda empty arguments ranges were too long, leading to unstable +# formatting +( + lambda: ( # + ), +) + + +# lambda arguments don't have parentheses, so we never add a magic trailing comma ... +def f( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = lambda + x + : y, +): + pass + + +# ...but we do preserve a trailing comma after the arguments +a = lambda b,: 0 + +lambda a,: 0 +lambda *args,: 0 +lambda **kwds,: 0 +lambda a, *args,: 0 +lambda a, **kwds,: 0 +lambda *args, b,: 0 +lambda *, b,: 0 +lambda *args, **kwds,: 0 +lambda a, *args, b,: 0 +lambda a, *, b,: 0 +lambda a, *args, **kwds,: 0 +lambda *args, b, **kwds,: 0 +lambda *, b, **kwds,: 0 +lambda a, *args, b, **kwds,: 0 +lambda a, *, b, **kwds,: 0 +lambda a, /: a +lambda a, /, c: a + +# Dangling comments without parameters. +( + lambda: # 3 + None +) + +( + lambda: + # 3 + None +) + +( + lambda: # 1 + # 2 + # 3 + # 4 + None # 5 +) + +( + lambda + # comment + *x + : x +) + +( + lambda + # comment 1 + # comment 2 + *x + : + # comment 3 + x +) + +( + lambda + # comment 1 + # comment 2 + *x + : # comment 3 + x +) + +lambda *x: x + +( + lambda + # comment + *x + : x +) + +lambda: ( # comment + x +) + +( + lambda: # comment + x +) + +( + lambda: # comment - y: z + x ) +( + lambda: # comment + x +) + +( + lambda: + # comment + x +) + +( + lambda: # comment + ( # comment + x + ) +) + +( + lambda + # 1 + # 2 + x + : # 3 + # 4 + # 5 + # 6 + x +) + +( + lambda + x, + # comment + y + : z +) + + +# Leading +lambda x: ( + lambda y: ( + lambda z: ( + x + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + y + + z + ) + ) # Trailing +) # Trailing + + +# Leading +lambda x: ( + lambda y: ( + lambda z: [ + x, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + y, + z, + ] + ) +) # Trailing +# Trailing + lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( *args, **kwargs ), e=1, f=2, g=2: d @@ -450,9 +921,11 @@ def a(): c, d, e, - f=lambda self, - *args, - **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), + f=lambda + self, + *args, + **kwargs + : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), ) @@ -461,15 +934,17 @@ def a(): c, d, e, - f=lambda self, - araa, - kkkwargs, - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, - args, - kwargs, - e=1, - f=2, - g=2: d, + f=lambda + self, + araa, + kkkwargs, + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + args, + kwargs, + e=1, + f=2, + g=2 + : d, g=10, ) ```