From c8cb47210d6ae828d479cbe80e9167e81c6df287 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 23 Aug 2023 11:22:14 +0200 Subject: [PATCH] Maybe parenthesize long constants and names --- Cargo.lock | 1 + crates/ruff_formatter/src/builders.rs | 4 +- .../src/format_element/document.rs | 8 +- crates/ruff_python_formatter/Cargo.toml | 1 + .../test/fixtures/ruff/expression/unary.py | 6 + .../fixtures/ruff/expression/unsplittable.py | 54 ++++ .../src/expression/expr_call.rs | 5 +- .../src/expression/expr_constant.rs | 26 +- .../src/expression/expr_f_string.rs | 21 +- .../src/expression/expr_name.rs | 11 +- .../src/expression/expr_subscript.rs | 5 +- .../src/expression/expr_unary_op.rs | 4 +- .../src/expression/mod.rs | 61 +++- .../src/expression/parentheses.rs | 9 +- crates/ruff_python_formatter/src/lib.rs | 17 +- ...aneous__long_strings_flag_disabled.py.snap | 43 +-- ...bility@simple_cases__remove_parens.py.snap | 281 ------------------ ...__trailing_commas_in_leading_parts.py.snap | 12 +- .../format@expression__unary.py.snap | 13 + .../format@expression__unspittable.py.snap | 126 ++++++++ 20 files changed, 345 insertions(+), 363 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unsplittable.py delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_parens.py.snap create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__unspittable.py.snap diff --git a/Cargo.lock b/Cargo.lock index 386f03e63088a3..a206ca6caa39fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2340,6 +2340,7 @@ dependencies = [ "insta", "is-macro", "itertools", + "memchr", "once_cell", "ruff_formatter", "ruff_python_ast", diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index 1e66f0b6214ee4..881a80e557cd6a 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -2534,17 +2534,17 @@ impl<'a, Context> BestFitting<'a, Context> { impl Format for BestFitting<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut buffer = VecBuffer::new(f.state_mut()); let variants = self.variants.items(); let mut formatted_variants = Vec::with_capacity(variants.len()); for variant in variants { + let mut buffer = VecBuffer::with_capacity(8, f.state_mut()); buffer.write_element(FormatElement::Tag(StartEntry)); buffer.write_fmt(Arguments::from(variant))?; buffer.write_element(FormatElement::Tag(EndEntry)); - formatted_variants.push(buffer.take_vec().into_boxed_slice()); + formatted_variants.push(buffer.into_vec().into_boxed_slice()); } // SAFETY: The constructor guarantees that there are always at least two variants. It's, therefore, diff --git a/crates/ruff_formatter/src/format_element/document.rs b/crates/ruff_formatter/src/format_element/document.rs index 07670d63e1d833..add834202fbecc 100644 --- a/crates/ruff_formatter/src/format_element/document.rs +++ b/crates/ruff_formatter/src/format_element/document.rs @@ -122,8 +122,12 @@ impl Document { expands } - let mut enclosing: Vec = Vec::new(); - let mut interned: FxHashMap<&Interned, bool> = FxHashMap::default(); + let mut enclosing = Vec::with_capacity(if self.is_empty() { + 0 + } else { + self.len().ilog2() as usize + }); + let mut interned = FxHashMap::default(); propagate_expands(self, &mut enclosing, &mut interned); } diff --git a/crates/ruff_python_formatter/Cargo.toml b/crates/ruff_python_formatter/Cargo.toml index 637276bab11bd8..e63bbeb8720b75 100644 --- a/crates/ruff_python_formatter/Cargo.toml +++ b/crates/ruff_python_formatter/Cargo.toml @@ -25,6 +25,7 @@ clap = { workspace = true } countme = "3.0.1" is-macro = { workspace = true } itertools = { workspace = true } +memchr = { workspace = true } once_cell = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true, optional = true } diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unary.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unary.py index 11106d7d2396a9..9b08dc9baaa36b 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unary.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unary.py @@ -140,3 +140,9 @@ # Regression: https://github.com/astral-sh/ruff/issues/5338 if a and not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: ... + +if ( + not + # comment + a): + ... diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unsplittable.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unsplittable.py new file mode 100644 index 00000000000000..83a1158ec32aed --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unsplittable.py @@ -0,0 +1,54 @@ +x = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +x_aa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +xxxxx = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +while ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + pass + +while aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: + pass + +# Only applies in `Parenthesize::IfBreaks` positions +raise aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +raise ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) + +raise a from aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +raise a from aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +# Can never apply on expression statement level +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +# Is it only relevant for items that can't break + +aaaaaaa = 111111111111111111111111111111111111111111111111111111111111111111111111111111 +aaaaaaa = ( + 1111111111111111111111111111111111111111111111111111111111111111111111111111111 +) + +aaaaaaa = """111111111111111111111111111111111111111111111111111111111111111111111111111 +1111111111111111111111111111111111111111111111111111111111111111111111111111111111111""" + +# Never parenthesize multiline strings +aaaaaaa = ( + """1111111111111111111111111111111111111111111111111111111111111111111111111111 +1111111111111111111111111111111111111111111111111111111111111111111111111111111111111""" +) + + + +aaaaaaaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbb +aaaaaaaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + +aaaaaaaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + + +for converter in connection.ops.get_db_converters( + expression +) + expression.get_db_converters(connection): + ... diff --git a/crates/ruff_python_formatter/src/expression/expr_call.rs b/crates/ruff_python_formatter/src/expression/expr_call.rs index 5483aabdcaee8b..fe2813626f61c5 100644 --- a/crates/ruff_python_formatter/src/expression/expr_call.rs +++ b/crates/ruff_python_formatter/src/expression/expr_call.rs @@ -69,7 +69,10 @@ impl NeedsParentheses for ExprCall { { OptionalParentheses::Multiline } else { - self.func.needs_parentheses(self.into(), context) + match self.func.needs_parentheses(self.into(), context) { + OptionalParentheses::BestFit => OptionalParentheses::Never, + parentheses => parentheses, + } } } } diff --git a/crates/ruff_python_formatter/src/expression/expr_constant.rs b/crates/ruff_python_formatter/src/expression/expr_constant.rs index 64cfb8cd4c20e6..3ff61daadd0323 100644 --- a/crates/ruff_python_formatter/src/expression/expr_constant.rs +++ b/crates/ruff_python_formatter/src/expression/expr_constant.rs @@ -1,5 +1,5 @@ use crate::comments::SourceComment; -use ruff_formatter::FormatRuleWithOptions; +use ruff_formatter::{FormatContext, FormatOptions, FormatRuleWithOptions}; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::{Constant, ExprConstant, Ranged}; use ruff_text_size::{TextLen, TextRange}; @@ -79,15 +79,22 @@ impl NeedsParentheses for ExprConstant { _parent: AnyNodeRef, context: &PyFormatContext, ) -> OptionalParentheses { - if self.value.is_implicit_concatenated() { - // Don't wrap triple quoted strings - if is_multiline_string(self, context.source()) { - OptionalParentheses::Never + if is_multiline_string(self, context.source()) + || self.value.is_none() + || self.value.is_bool() + || self.value.is_ellipsis() + { + OptionalParentheses::Never + } else if self.value.is_implicit_concatenated() { + OptionalParentheses::Multiline + } else { + let text_len = context.source()[self.range].len(); + + if text_len > 4 && text_len < context.options().line_width().value() as usize { + OptionalParentheses::BestFit } else { - OptionalParentheses::Multiline + OptionalParentheses::Never } - } else { - OptionalParentheses::Never } } } @@ -99,7 +106,8 @@ pub(super) fn is_multiline_string(constant: &ExprConstant, source: &str) -> bool let quotes = StringQuotes::parse(&contents[TextRange::new(prefix.text_len(), contents.text_len())]); - quotes.is_some_and(StringQuotes::is_triple) && contents.contains(['\n', '\r']) + quotes.is_some_and(StringQuotes::is_triple) + && memchr::memchr2(b'\n', b'\r', contents.as_bytes()).is_some() } else { false } diff --git a/crates/ruff_python_formatter/src/expression/expr_f_string.rs b/crates/ruff_python_formatter/src/expression/expr_f_string.rs index 0c0165dd3ff3f6..89b082e11dbc8c 100644 --- a/crates/ruff_python_formatter/src/expression/expr_f_string.rs +++ b/crates/ruff_python_formatter/src/expression/expr_f_string.rs @@ -1,9 +1,11 @@ use super::string::{AnyString, FormatString}; use crate::context::PyFormatContext; +use memchr::memchr2; + use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::prelude::*; use crate::{FormatNodeRule, PyFormatter}; -use ruff_formatter::FormatResult; +use ruff_formatter::{FormatContext, FormatOptions, FormatResult}; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::ExprFString; @@ -20,8 +22,21 @@ impl NeedsParentheses for ExprFString { fn needs_parentheses( &self, _parent: AnyNodeRef, - _context: &PyFormatContext, + context: &PyFormatContext, ) -> OptionalParentheses { - OptionalParentheses::Multiline + if self.implicit_concatenated { + OptionalParentheses::Multiline + } else { + let text = &context.source()[self.range]; + + if memchr2(b'\n', b'\r', text.as_bytes()).is_none() + && text.len() > 4 + && text.len() < context.options().line_width().value() as usize + { + OptionalParentheses::BestFit + } else { + OptionalParentheses::Never + } + } } } diff --git a/crates/ruff_python_formatter/src/expression/expr_name.rs b/crates/ruff_python_formatter/src/expression/expr_name.rs index a2aef805935446..0705c0f52e644c 100644 --- a/crates/ruff_python_formatter/src/expression/expr_name.rs +++ b/crates/ruff_python_formatter/src/expression/expr_name.rs @@ -2,7 +2,7 @@ use crate::comments::SourceComment; use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::prelude::*; use crate::FormatNodeRule; -use ruff_formatter::{write, FormatContext}; +use ruff_formatter::{write, FormatContext, FormatOptions}; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::ExprName; @@ -38,9 +38,14 @@ impl NeedsParentheses for ExprName { fn needs_parentheses( &self, _parent: AnyNodeRef, - _context: &PyFormatContext, + context: &PyFormatContext, ) -> OptionalParentheses { - OptionalParentheses::Never + let text_len = context.source()[self.range].len(); + if text_len > 4 && text_len < context.options().line_width().value() as usize { + OptionalParentheses::BestFit + } else { + OptionalParentheses::Never + } } } diff --git a/crates/ruff_python_formatter/src/expression/expr_subscript.rs b/crates/ruff_python_formatter/src/expression/expr_subscript.rs index 90c1d5c32d3cf9..169fbe9c4a50c5 100644 --- a/crates/ruff_python_formatter/src/expression/expr_subscript.rs +++ b/crates/ruff_python_formatter/src/expression/expr_subscript.rs @@ -101,7 +101,10 @@ impl NeedsParentheses for ExprSubscript { { OptionalParentheses::Multiline } else { - self.value.needs_parentheses(self.into(), context) + match self.value.needs_parentheses(self.into(), context) { + OptionalParentheses::BestFit => OptionalParentheses::Never, + parentheses => parentheses, + } } } } diff --git a/crates/ruff_python_formatter/src/expression/expr_unary_op.rs b/crates/ruff_python_formatter/src/expression/expr_unary_op.rs index 7523edc77728ad..971f7a2d09d465 100644 --- a/crates/ruff_python_formatter/src/expression/expr_unary_op.rs +++ b/crates/ruff_python_formatter/src/expression/expr_unary_op.rs @@ -3,7 +3,7 @@ use ruff_python_ast::{ExprUnaryOp, Ranged}; use ruff_text_size::{TextLen, TextRange}; use ruff_formatter::prelude::{hard_line_break, space, text}; -use ruff_formatter::{Format, FormatContext, FormatResult}; +use ruff_formatter::{Format, FormatResult}; use ruff_python_ast::node::AnyNodeRef; use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; @@ -57,7 +57,7 @@ impl FormatNodeRule for FormatExprUnaryOp { // a) // ``` if !leading_operand_comments.is_empty() - && !is_operand_parenthesized(item, f.context().source_code().as_str()) + && !is_operand_parenthesized(item, f.context().source()) { hard_line_break().fmt(f)?; } else if op.is_not() { diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 75dce1be2296dc..6a2a9eb26d53e6 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use ruff_formatter::{ - write, FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions, + format_args, write, FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions, }; use ruff_python_ast as ast; use ruff_python_ast::node::AnyNodeRef; @@ -220,6 +220,64 @@ impl Format> for MaybeParenthesizeExpression<'_> { } } }, + OptionalParentheses::BestFit => match parenthesize { + Parenthesize::IfBreaksOrIfRequired => { + parenthesize_if_expands(&expression.format().with_options(Parentheses::Never)) + .fmt(f) + } + + Parenthesize::Optional | Parenthesize::IfRequired => { + expression.format().with_options(Parentheses::Never).fmt(f) + } + Parenthesize::IfBreaks => { + let group_id = f.group_id("optional_parentheses"); + let f = &mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f); + let mut format_expression = expression + .format() + .with_options(Parentheses::Never) + .memoized(); + + // Don't use best fitting if it is known that the expression can never fit + if format_expression.inspect(f)?.will_break() { + // The group here is necessary because `format_expression` may contain IR elements + // that refer to the group id + group(&format_expression) + .with_group_id(Some(group_id)) + .should_expand(true) + .fmt(f) + } else { + // Only add parentheses if it makes the expression fit on the line. + // Using the flat version as the most expanded version gives a left-to-right splitting behavior + // which differs from when using regular groups, because they split right-to-left. + best_fitting![ + // --------------------------------------------------------------------- + // Variant 1: + // Try to fit the expression without any parentheses + group(&format_expression).with_group_id(Some(group_id)), + // --------------------------------------------------------------------- + // Variant 2: + // Try to fit the expression by adding parentheses and indenting the expression. + group(&format_args![ + text("("), + soft_block_indent(&format_expression), + text(")") + ]) + .with_group_id(Some(group_id)) + .should_expand(true), + // --------------------------------------------------------------------- + // Variant 3: Fallback, no parentheses + // Expression doesn't fit regardless of adding the parentheses. Remove the parentheses again. + group(&format_expression) + .with_group_id(Some(group_id)) + .should_expand(true) + ] + // Measure all lines, to avoid that the printer decides that this fits right after hitting + // the `(`. + .with_mode(BestFittingMode::AllLines) + .fmt(f) + } + } + }, OptionalParentheses::Never => match parenthesize { Parenthesize::IfBreaksOrIfRequired => { parenthesize_if_expands(&expression.format().with_options(Parentheses::Never)) @@ -230,6 +288,7 @@ impl Format> for MaybeParenthesizeExpression<'_> { expression.format().with_options(Parentheses::Never).fmt(f) } }, + OptionalParentheses::Always => { expression.format().with_options(Parentheses::Always).fmt(f) } diff --git a/crates/ruff_python_formatter/src/expression/parentheses.rs b/crates/ruff_python_formatter/src/expression/parentheses.rs index 748583ae0cc8a2..499c285b9e1258 100644 --- a/crates/ruff_python_formatter/src/expression/parentheses.rs +++ b/crates/ruff_python_formatter/src/expression/parentheses.rs @@ -15,11 +15,16 @@ pub(crate) enum OptionalParentheses { /// Add parentheses if the expression expands over multiple lines Multiline, - /// Always set parentheses regardless if the expression breaks or if they were + /// Always set parentheses regardless if the expression breaks or if they are /// present in the source. Always, - /// Never add parentheses + /// Add parentheses if it helps to make this expression fit. Otherwise never add parentheses. + /// This mode should only be used for expressions that don't have their own split points, e.g. identifiers, + /// or constants. + BestFit, + + /// Never add parentheses. Use it for expressions that have their own parentheses or if the expression body always spans multiple lines (multiline strings). Never, } diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index 39ac5ed30dda55..4303eac0d8f067 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -249,12 +249,10 @@ if True: #[test] fn quick_test() { let src = r#" -@MyDecorator(list = a) # fmt: skip -# trailing comment -class Test: - pass - - +for converter in connection.ops.get_db_converters( + expression +) + expression.get_db_converters(connection): + ... "#; // Tokenize once let mut tokens = Vec::new(); @@ -291,9 +289,10 @@ class Test: assert_eq!( printed.as_code(), - r#"while True: - if something.changed: - do.stuff() # trailing comment + r#"for converter in connection.ops.get_db_converters( + expression +) + expression.get_db_converters(connection): + ... "# ); } diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap index 6e2a6ac805316e..8be3c1f3eab42b 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap @@ -304,23 +304,7 @@ long_unmergable_string_with_pragma = ( ```diff --- Black +++ Ruff -@@ -143,9 +143,13 @@ - ) - ) - --fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." -+fstring = ( -+ f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." -+) - --fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." -+fstring_with_no_fexprs = ( -+ f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." -+) - - 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. - -@@ -165,13 +169,9 @@ +@@ -165,13 +165,9 @@ triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" @@ -336,7 +320,7 @@ long_unmergable_string_with_pragma = ( "formatting" ) -@@ -221,8 +221,8 @@ +@@ -221,8 +217,8 @@ func_with_bad_comma( ( "This is a really long string argument to a function that has a trailing comma" @@ -347,17 +331,6 @@ long_unmergable_string_with_pragma = ( ) func_with_bad_parens_that_wont_fit_in_one_line( -@@ -274,7 +274,9 @@ - yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - - --x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." -+x = ( -+ f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." -+) - - long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore ``` ## Ruff Output @@ -508,13 +481,9 @@ old_fmt_string3 = ( ) ) -fstring = ( - f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." -) +fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." -fstring_with_no_fexprs = ( - f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." -) +fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." 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. @@ -639,9 +608,7 @@ def foo(): yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." -x = ( - f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." -) +x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." long_unmergable_string_with_pragma = ( "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_parens.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_parens.py.snap deleted file mode 100644 index 9cb91d4c09e58f..00000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_parens.py.snap +++ /dev/null @@ -1,281 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_parens.py ---- -## Input - -```py -x = (1) -x = (1.2) - -data = ( - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -).encode() - -async def show_status(): - while True: - try: - if report_host: - data = ( - f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - ).encode() - except Exception as e: - pass - -def example(): - return (("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")) - - -def example1(): - return ((1111111111111111111111111111111111111111111111111111111111111111111111111111111111111)) - - -def example1point5(): - return ((((((1111111111111111111111111111111111111111111111111111111111111111111111111111111111111)))))) - - -def example2(): - return (("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")) - - -def example3(): - return ((1111111111111111111111111111111111111111111111111111111111111111111111111111111)) - - -def example4(): - return ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((True)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) - - -def example5(): - return ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) - - -def example6(): - return ((((((((({a:a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}))))))))) - - -def example7(): - return ((((((((({a:a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20000000000000000000]}))))))))) - - -def example8(): - return (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((None))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -11,8 +11,10 @@ - try: - if report_host: - data = ( -- f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -- ).encode() -+ ( -+ f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -+ ).encode() -+ ) - except Exception as e: - pass - -@@ -30,15 +32,11 @@ - - - def example2(): -- return ( -- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -- ) -+ return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - - - def example3(): -- return ( -- 1111111111111111111111111111111111111111111111111111111111111111111111111111111 -- ) -+ return 1111111111111111111111111111111111111111111111111111111111111111111111111111111 - - - def example4(): -``` - -## Ruff Output - -```py -x = 1 -x = 1.2 - -data = ( - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -).encode() - - -async def show_status(): - while True: - try: - if report_host: - data = ( - ( - f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - ).encode() - ) - except Exception as e: - pass - - -def example(): - return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - - -def example1(): - return 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 - - -def example1point5(): - return 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 - - -def example2(): - return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - - -def example3(): - return 1111111111111111111111111111111111111111111111111111111111111111111111111111111 - - -def example4(): - return True - - -def example5(): - return () - - -def example6(): - return {a: a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]} - - -def example7(): - return { - a: a - for a in [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20000000000000000000, - ] - } - - -def example8(): - return None -``` - -## Black Output - -```py -x = 1 -x = 1.2 - -data = ( - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -).encode() - - -async def show_status(): - while True: - try: - if report_host: - data = ( - f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - ).encode() - except Exception as e: - pass - - -def example(): - return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - - -def example1(): - return 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 - - -def example1point5(): - return 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 - - -def example2(): - return ( - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - ) - - -def example3(): - return ( - 1111111111111111111111111111111111111111111111111111111111111111111111111111111 - ) - - -def example4(): - return True - - -def example5(): - return () - - -def example6(): - return {a: a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]} - - -def example7(): - return { - a: a - for a in [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20000000000000000000, - ] - } - - -def example8(): - return None -``` - - diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__trailing_commas_in_leading_parts.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__trailing_commas_in_leading_parts.py.snap index 6a978187fcc969..3abf27d802c1b1 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__trailing_commas_in_leading_parts.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__trailing_commas_in_leading_parts.py.snap @@ -56,14 +56,6 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( # Example from https://github.com/psf/black/issues/3229 -@@ -45,6 +43,4 @@ - # Regression test for https://github.com/psf/black/issues/3414. - assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( - xxxxxxxxx --).xxxxxxxxxxxxxxxxxx(), ( -- "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" --) -+).xxxxxxxxxxxxxxxxxx(), "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ``` ## Ruff Output @@ -114,7 +106,9 @@ assert ( # Regression test for https://github.com/psf/black/issues/3414. assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( xxxxxxxxx -).xxxxxxxxxxxxxxxxxx(), "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +).xxxxxxxxxxxxxxxxxx(), ( + "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +) ``` ## Black Output diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__unary.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__unary.py.snap index e0325c897bd0b3..b9d8bd127e2c2f 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__unary.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__unary.py.snap @@ -146,6 +146,12 @@ if not \ # Regression: https://github.com/astral-sh/ruff/issues/5338 if a and not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: ... + +if ( + not + # comment + a): + ... ``` ## Output @@ -304,6 +310,13 @@ if ( & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): ... + +if ( + not + # comment + a +): + ... ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__unspittable.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__unspittable.py.snap new file mode 100644 index 00000000000000..8d9fb5b06e18aa --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__unspittable.py.snap @@ -0,0 +1,126 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unspittable.py +--- +## Input +```py +x = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +x_aa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +xxxxx = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +while ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + pass + +while aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: + pass + +# Only applies in `Parenthesize::IfBreaks` positions +raise aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +raise ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) + +raise a from aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +raise a from aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +# Can never apply on expression statement level +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +# Is it only relevant for items that can't break + +aaaaaaa = 111111111111111111111111111111111111111111111111111111111111111111111111111111 +aaaaaaa = ( + 1111111111111111111111111111111111111111111111111111111111111111111111111111111 +) + +aaaaaaa = """111111111111111111111111111111111111111111111111111111111111111111111111111 +1111111111111111111111111111111111111111111111111111111111111111111111111111111111111""" + +# Never parenthesize multiline strings +aaaaaaa = ( + """1111111111111111111111111111111111111111111111111111111111111111111111111111 +1111111111111111111111111111111111111111111111111111111111111111111111111111111111111""" +) + + + +aaaaaaaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbb +aaaaaaaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + +aaaaaaaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + + +for converter in connection.ops.get_db_converters( + expression +) + expression.get_db_converters(connection): + ... +``` + +## Output +```py +x = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +x_aa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +xxxxx = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) + +while ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + pass + +while aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: + pass + +# Only applies in `Parenthesize::IfBreaks` positions +raise aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +raise ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) + +raise a from aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +raise a from aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +# Can never apply on expression statement level +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +# Is it only relevant for items that can't break + +aaaaaaa = 111111111111111111111111111111111111111111111111111111111111111111111111111111 +aaaaaaa = ( + 1111111111111111111111111111111111111111111111111111111111111111111111111111111 +) + +aaaaaaa = """111111111111111111111111111111111111111111111111111111111111111111111111111 +1111111111111111111111111111111111111111111111111111111111111111111111111111111111111""" + +# Never parenthesize multiline strings +aaaaaaa = """1111111111111111111111111111111111111111111111111111111111111111111111111111 +1111111111111111111111111111111111111111111111111111111111111111111111111111111111111""" + + +aaaaaaaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbb +aaaaaaaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +) + +aaaaaaaa = ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +) + + +for converter in connection.ops.get_db_converters( + expression +) + expression.get_db_converters(connection): + ... +``` + + +