diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/optional_parentheses_comments.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/optional_parentheses_comments.py new file mode 100644 index 0000000000..5fc4585113 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/optional_parentheses_comments.py @@ -0,0 +1,206 @@ +comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. + +# 88 characters unparenthesized +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c + +# 88 characters +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c + +# 89 characters parenthesized (collapse) +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +## Parenthesized + +# 88 characters unparenthesized +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c +) + +# 88 characters +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c +) + +## Expression and statement comments + +# 88 characters unparenthesized +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb # c +) # d + +# 88 characters +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvv # c +) # d + +# 89 characters parenthesized (collapse) +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c +) # d + +## Strings + +# 88 characters unparenthesized +____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv" # c + +# 88 characters +____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv" # c + +# 89 characters parenthesized (collapse) +____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv" # c + +# Always parenthesize if implicit concatenated +____aaa = ( + "aaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvv" +) # c + +## Numbers + +# 88 characters unparenthesized +____aaa = 1111111111111111111111111111111111111111111111111111111111111111111111111 # c + +# 88 characters +____aaa = 1111111111111111111111111111111111111111111111111111111111111111111111111111111 # c + +# 89 characters parenthesized (collapse) +____aaa = 11111111111111111111111111111111111111111111111111111111111111111111111111111111 # c + +## Breaking left + +# Should break `[a]` first +____[a] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c + +____[ + a +] = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # cc +) + +( + # some weird comments + ____[aaaaaaaaa] + # some weird comments 2 +) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c + +# Preserve trailing assignment comments when the expression has own line comments +____aaa = ( + # leading + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv + # trailing +) # cc + +def setUpTestData(cls): + cls.happening = ( + Happening.objects.create() + ) # make sure the defaults are working (#20158) + +def setUpTestData(cls): + cls.happening = ( + Happening.objects.create # make sure the defaults are working (#20158) + ) + +if True: + if True: + if True: + # Black layout + model.config.use_cache = ( + False # FSTM still requires this hack -> FSTM should probably be refactored s + ) + +## Annotated Assign + +# 88 characters unparenthesized +____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c + +# 88 characters +____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c + +# 89 characters parenthesized (collapse) +____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +# 88 characters unparenthesized +____a : a = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c +) + +# 88 characters +____a: a = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____a: a = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c +) + +_a: a[b] = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c +) + +## Augmented Assign + +# 88 characters unparenthesized +____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c + +# 88 characters +____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c + +# 89 characters parenthesized (collapse) +____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +# 88 characters unparenthesized +____aa += ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c +) + +# 88 characters +____aa += ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____aa += ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c +) + +## Return + +def test(): + # 88 characters unparenthesized + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c + +def test2(): + # 88 characters + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c + +def test3(): + # 89 characters parenthesized (collapse) + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvv # c + +## Return Parenthesized + +def test4(): + # 88 characters unparenthesized + return ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c + ) + +def test5(): + # 88 characters + return ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c + ) + +def test6(): + # 89 characters parenthesized (collapse) + return ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvv # c + ) + + + diff --git a/crates/ruff_python_formatter/src/expression/expr_await.rs b/crates/ruff_python_formatter/src/expression/expr_await.rs index da40802310..26521bb333 100644 --- a/crates/ruff_python_formatter/src/expression/expr_await.rs +++ b/crates/ruff_python_formatter/src/expression/expr_await.rs @@ -20,7 +20,7 @@ impl FormatNodeRule for FormatExprAwait { [ token("await"), space(), - maybe_parenthesize_expression(value, item, Parenthesize::IfBreaks) + maybe_parenthesize_expression(value, item, Parenthesize::IfRequired) ] ) } @@ -39,6 +39,7 @@ impl NeedsParentheses for ExprAwait { context.comments().ranges(), context.source(), ) { + // Prefer splitting the value if it is parenthesized. OptionalParentheses::Never } else { self.value.needs_parentheses(self.into(), context) diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index 20e274ec21..679cbef896 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -59,7 +59,10 @@ impl NeedsParentheses for AnyExpressionYield<'_> { OptionalParentheses::Never } else { // Ex) `x = yield f(1, 2, 3)` - value.needs_parentheses(self.into(), context) + match value.needs_parentheses(self.into(), context) { + OptionalParentheses::BestFit => OptionalParentheses::Never, + parentheses => parentheses, + } } } else { // Ex) `x = yield` diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 5c616328f9..707963f1ec 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -12,7 +12,9 @@ use ruff_python_trivia::CommentRanges; use ruff_text_size::Ranged; use crate::builders::parenthesize_if_expands; -use crate::comments::{leading_comments, trailing_comments, LeadingDanglingTrailingComments}; +use crate::comments::{ + leading_comments, trailing_comments, LeadingDanglingTrailingComments, SourceComment, +}; use crate::context::{NodeLevel, WithNodeLevel}; use crate::expression::expr_generator_exp::is_generator_parenthesized; use crate::expression::expr_tuple::is_tuple_parenthesized; @@ -374,10 +376,8 @@ impl Format> for MaybeParenthesizeExpression<'_> { return expression.format().with_options(Parentheses::Always).fmt(f); } - let node_comments = f - .context() - .comments() - .leading_dangling_trailing(*expression); + let comments = f.context().comments().clone(); + let node_comments = comments.leading_dangling_trailing(*expression); // If the expression has comments, we always want to preserve the parentheses. This also // ensures that we correctly handle parenthesized comments, and don't need to worry about @@ -426,15 +426,106 @@ impl Format> for MaybeParenthesizeExpression<'_> { expression.format().with_options(Parentheses::Never).fmt(f) } Parenthesize::IfBreaks => { - if node_comments.has_trailing() { - expression.format().with_options(Parentheses::Always).fmt(f) + // Is the expression the last token in the parent statement. + // Excludes `await` and `yield` for which Black doesn't seem to apply the layout? + let last_expression = parent.is_stmt_assign() + || parent.is_stmt_ann_assign() + || parent.is_stmt_aug_assign() + || parent.is_stmt_return(); + + // Format the statements and value's trailing end of line comments: + // * after the expression if the expression needs no parentheses (necessary or the `expand_parent` makes the group never fit). + // * inside the parentheses if the expression exceeds the line-width. + // + // ```python + // a = long # with_comment + // b = ( + // short # with_comment + // ) + // + // # formatted + // a = ( + // long # with comment + // ) + // b = short # with comment + // ``` + // This matches Black's formatting with the exception that ruff applies this style also for + // attribute chains and non-fluent call expressions. See https://github.com/psf/black/issues/4001#issuecomment-1786681792 + // + // This logic isn't implemented in [`place_comment`] by associating trailing statement comments to the expression because + // doing so breaks the suite empty lines formatting that relies on trailing comments to be stored on the statement. + let (inline_comments, expression_trailing_comments) = if last_expression + && !( + // Ignore non-fluent attribute chains for black compatibility. + // See https://github.com/psf/black/issues/4001#issuecomment-1786681792 + expression.is_attribute_expr() + || expression.is_call_expr() + || expression.is_yield_from_expr() + || expression.is_yield_expr() + || expression.is_await_expr() + ) { + let parent_trailing_comments = comments.trailing(*parent); + let after_end_of_line = parent_trailing_comments + .partition_point(|comment| comment.line_position().is_end_of_line()); + let (stmt_inline_comments, _) = + parent_trailing_comments.split_at(after_end_of_line); + + let after_end_of_line = node_comments + .trailing + .partition_point(|comment| comment.line_position().is_end_of_line()); + + let (expression_inline_comments, expression_trailing_comments) = + node_comments.trailing.split_at(after_end_of_line); + + ( + OptionalParenthesesInlinedComments { + expression: expression_inline_comments, + statement: stmt_inline_comments, + }, + expression_trailing_comments, + ) } else { + ( + OptionalParenthesesInlinedComments::default(), + node_comments.trailing, + ) + }; + + if expression_trailing_comments.is_empty() { // The group id is necessary because the nested expressions may reference it. let group_id = f.group_id("optional_parentheses"); let f = &mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f); - best_fit_parenthesize(&expression.format().with_options(Parentheses::Never)) - .with_group_id(Some(group_id)) - .fmt(f) + + best_fit_parenthesize(&format_with(|f| { + inline_comments.mark_formatted(); + + expression + .format() + .with_options(Parentheses::Never) + .fmt(f)?; + + if !inline_comments.is_empty() { + // If the expressions exceeds the line width, format the comments in the parentheses + if_group_breaks(&inline_comments) + .with_group_id(Some(group_id)) + .fmt(f)?; + } + + Ok(()) + })) + .with_group_id(Some(group_id)) + .fmt(f)?; + + if !inline_comments.is_empty() { + // If the line fits into the line width, format the comments after the parenthesized expression + if_group_fits_on_line(&inline_comments) + .with_group_id(Some(group_id)) + .fmt(f)?; + } + + Ok(()) + } else { + expression.format().with_options(Parentheses::Always).fmt(f) } } }, @@ -1069,3 +1160,41 @@ impl From for OperatorPrecedence { } } } + +#[derive(Debug, Default)] +struct OptionalParenthesesInlinedComments<'a> { + expression: &'a [SourceComment], + statement: &'a [SourceComment], +} + +impl<'a> OptionalParenthesesInlinedComments<'a> { + fn is_empty(&self) -> bool { + self.expression.is_empty() && self.statement.is_empty() + } + + fn iter_comments(&self) -> impl Iterator { + self.expression.iter().chain(self.statement) + } + + fn mark_formatted(&self) { + for comment in self.iter_comments() { + comment.mark_formatted(); + } + } +} + +impl Format> for OptionalParenthesesInlinedComments<'_> { + fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { + for comment in self.iter_comments() { + comment.mark_unformatted(); + } + + write!( + f, + [ + trailing_comments(self.expression), + trailing_comments(self.statement) + ] + ) + } +} diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap index 628dbb5c6a..428ca61376 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap @@ -93,7 +93,7 @@ async def main(): ```diff --- Black +++ Ruff -@@ -21,11 +21,15 @@ +@@ -21,7 +21,9 @@ # Check comments async def main(): @@ -103,13 +103,6 @@ async def main(): + ) - async def main(): -- await asyncio.sleep(1) # Hello -+ await ( -+ asyncio.sleep(1) # Hello -+ ) - - async def main(): ``` @@ -145,9 +138,7 @@ async def main(): async def main(): - await ( - asyncio.sleep(1) # Hello - ) + await asyncio.sleep(1) # Hello async def main(): diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__optional_parentheses_comments.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__optional_parentheses_comments.py.snap new file mode 100644 index 0000000000..1cf74fe785 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__optional_parentheses_comments.py.snap @@ -0,0 +1,424 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/optional_parentheses_comments.py +--- +## Input +```py +comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. + +# 88 characters unparenthesized +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c + +# 88 characters +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c + +# 89 characters parenthesized (collapse) +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +## Parenthesized + +# 88 characters unparenthesized +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c +) + +# 88 characters +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c +) + +## Expression and statement comments + +# 88 characters unparenthesized +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb # c +) # d + +# 88 characters +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvv # c +) # d + +# 89 characters parenthesized (collapse) +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c +) # d + +## Strings + +# 88 characters unparenthesized +____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv" # c + +# 88 characters +____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv" # c + +# 89 characters parenthesized (collapse) +____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv" # c + +# Always parenthesize if implicit concatenated +____aaa = ( + "aaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvv" +) # c + +## Numbers + +# 88 characters unparenthesized +____aaa = 1111111111111111111111111111111111111111111111111111111111111111111111111 # c + +# 88 characters +____aaa = 1111111111111111111111111111111111111111111111111111111111111111111111111111111 # c + +# 89 characters parenthesized (collapse) +____aaa = 11111111111111111111111111111111111111111111111111111111111111111111111111111111 # c + +## Breaking left + +# Should break `[a]` first +____[a] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c + +____[ + a +] = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # cc +) + +( + # some weird comments + ____[aaaaaaaaa] + # some weird comments 2 +) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c + +# Preserve trailing assignment comments when the expression has own line comments +____aaa = ( + # leading + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv + # trailing +) # cc + +def setUpTestData(cls): + cls.happening = ( + Happening.objects.create() + ) # make sure the defaults are working (#20158) + +def setUpTestData(cls): + cls.happening = ( + Happening.objects.create # make sure the defaults are working (#20158) + ) + +if True: + if True: + if True: + # Black layout + model.config.use_cache = ( + False # FSTM still requires this hack -> FSTM should probably be refactored s + ) + +## Annotated Assign + +# 88 characters unparenthesized +____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c + +# 88 characters +____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c + +# 89 characters parenthesized (collapse) +____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +# 88 characters unparenthesized +____a : a = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c +) + +# 88 characters +____a: a = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____a: a = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c +) + +_a: a[b] = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c +) + +## Augmented Assign + +# 88 characters unparenthesized +____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c + +# 88 characters +____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c + +# 89 characters parenthesized (collapse) +____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +# 88 characters unparenthesized +____aa += ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c +) + +# 88 characters +____aa += ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____aa += ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c +) + +## Return + +def test(): + # 88 characters unparenthesized + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c + +def test2(): + # 88 characters + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c + +def test3(): + # 89 characters parenthesized (collapse) + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvv # c + +## Return Parenthesized + +def test4(): + # 88 characters unparenthesized + return ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c + ) + +def test5(): + # 88 characters + return ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c + ) + +def test6(): + # 89 characters parenthesized (collapse) + return ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvv # c + ) + + + +``` + +## Output +```py +comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. + +# 88 characters unparenthesized +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c + +# 88 characters +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +## Parenthesized + +# 88 characters unparenthesized +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c + +# 88 characters +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +## Expression and statement comments + +# 88 characters unparenthesized +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbb # c # d + +# 88 characters +____aaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvv # c # d +) + +# 89 characters parenthesized (collapse) +____aaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c # d + +## Strings + +# 88 characters unparenthesized +____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv" # c + +# 88 characters +____aaa = ( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv" # c +) + +# 89 characters parenthesized (collapse) +____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv" # c + +# Always parenthesize if implicit concatenated +____aaa = ( + "aaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvv" +) # c + +## Numbers + +# 88 characters unparenthesized +____aaa = 1111111111111111111111111111111111111111111111111111111111111111111111111 # c + +# 88 characters +____aaa = ( + 1111111111111111111111111111111111111111111111111111111111111111111111111111111 # c +) + +# 89 characters parenthesized (collapse) +____aaa = 11111111111111111111111111111111111111111111111111111111111111111111111111111111 # c + +## Breaking left + +# Should break `[a]` first +____[ + a +] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c + +____[ + a +] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # cc + +( + # some weird comments + ____[aaaaaaaaa] + # some weird comments 2 +) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c + +# Preserve trailing assignment comments when the expression has own line comments +____aaa = ( + # leading + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv + # trailing +) # cc + + +def setUpTestData(cls): + cls.happening = ( + Happening.objects.create() + ) # make sure the defaults are working (#20158) + + +def setUpTestData(cls): + cls.happening = ( + Happening.objects.create # make sure the defaults are working (#20158) + ) + + +if True: + if True: + if True: + # Black layout + model.config.use_cache = False # FSTM still requires this hack -> FSTM should probably be refactored s + +## Annotated Assign + +# 88 characters unparenthesized +____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c + +# 88 characters +____a: a = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +# 88 characters unparenthesized +____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c + +# 88 characters +____a: a = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +_a: a[ + b +] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +## Augmented Assign + +# 88 characters unparenthesized +____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c + +# 88 characters +____aa += ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +# 88 characters unparenthesized +____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvv # c + +# 88 characters +____aa += ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvv # c +) + +# 89 characters parenthesized (collapse) +____aa += aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c + +## Return + + +def test(): + # 88 characters unparenthesized + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c + + +def test2(): + # 88 characters + return ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c + ) + + +def test3(): + # 89 characters parenthesized (collapse) + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvv # c + + +## Return Parenthesized + + +def test4(): + # 88 characters unparenthesized + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvv # c + + +def test5(): + # 88 characters + return ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvv # c + ) + + +def test6(): + # 89 characters parenthesized (collapse) + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvv # c +``` + + + diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__unsplittable.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__unsplittable.py.snap index b3b609ce6e..95ec4e212e 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__unsplittable.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__unsplittable.py.snap @@ -164,9 +164,7 @@ for converter in connection.ops.get_db_converters( pass -aaa = ( - bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # awkward comment -) +aaa = bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # awkward comment def test(): @@ -202,13 +200,9 @@ if True: if True: if True: # Black layout - model.config.use_cache = ( - False # FSTM still requires this hack -> FSTM should probably be refactored s - ) + model.config.use_cache = False # FSTM still requires this hack -> FSTM should probably be refactored s # Ruff layout - model.config.use_cache = ( - False - ) # FSTM still requires this hack -> FSTM should probably be refactored s + model.config.use_cache = False # FSTM still requires this hack -> FSTM should probably be refactored s # Regression test for https://github.com/astral-sh/ruff/issues/7463 diff --git a/crates/ruff_python_formatter/tests/snapshots/format@parentheses__expression_parentheses_comments.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@parentheses__expression_parentheses_comments.py.snap index f90a84c74c..c8afee3d43 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@parentheses__expression_parentheses_comments.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@parentheses__expression_parentheses_comments.py.snap @@ -146,9 +146,7 @@ list_with_parenthesized_elements5 = [ (2), # trailing outer ] -nested_parentheses1 = ( - 1 # i # j -) # k +nested_parentheses1 = 1 # i # j # k nested_parentheses2 = [ ( 1 # i