From 64ee0d62ea9d02a5b9fd1a8c14ec6b13a16e7821 Mon Sep 17 00:00:00 2001 From: Chris Pryer Date: Sat, 26 Aug 2023 13:27:18 -0400 Subject: [PATCH] Use `LineSuffix.reserved_width` in `ruff_python_formatter` --- crates/ruff_formatter/src/builders.rs | 56 ++++- .../src/format_element/document.rs | 16 +- .../ruff_formatter/src/format_element/tag.rs | 11 +- crates/ruff_formatter/src/printer/mod.rs | 41 +++- .../test/fixtures/ruff/expression/tuple.py | 2 + .../src/comments/format.rs | 53 +++- .../src/expression/mod.rs | 11 +- ...patibility@simple_cases__comments6.py.snap | 11 +- ...atibility@simple_cases__expression.py.snap | 19 +- ...ity@simple_cases__power_op_spacing.py.snap | 232 ++++++++++++++++++ ...@simple_cases__remove_await_parens.py.snap | 13 +- ...ompatibility@simple_cases__torture.py.snap | 15 +- .../format@expression__tuple.py.snap | 6 + .../format@parentheses__call_chains.py.snap | 4 +- .../format@statement__delete.py.snap | 8 +- .../snapshots/format@statement__try.py.snap | 4 +- .../snapshots/format@statement__while.py.snap | 4 +- 17 files changed, 456 insertions(+), 50 deletions(-) create mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__power_op_spacing.py.snap diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index 2605bf87e6c9d4..a0060452f661c5 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -435,46 +435,78 @@ fn debug_assert_no_newlines(text: &str) { debug_assert!(!text.contains('\r'), "The content '{text}' contains an unsupported '\\r' line terminator character but text must only use line feeds '\\n' as line separator. Use '\\n' instead of '\\r' and '\\r\\n' to insert a line break in strings."); } -/// Pushes some content to the end of the current line +/// Pushes some content to the end of the current line. /// /// ## Examples /// -/// ``` -/// use ruff_formatter::{format}; +/// ```rust +/// use ruff_formatter::format; /// use ruff_formatter::prelude::*; /// -/// fn main() -> FormatResult<()> { +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// text("a"), -/// line_suffix(&text("c")), +/// line_suffix(&text("c"), 0), /// text("b") /// ])?; /// -/// assert_eq!( -/// "abc", -/// elements.print()?.as_code() -/// ); +/// assert_eq!("abc", elements.print()?.as_code()); +/// # Ok(()) +/// # } +/// ``` +/// +/// Provide reserved width for the line suffix to include it during measurement. +/// ```rust +/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatContext, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(10).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// // Breaks +/// group(&format_args![ +/// if_group_breaks(&text("(")), +/// soft_block_indent(&format_args![text("a"), line_suffix(&text(" // a comment"), 13)]), +/// if_group_breaks(&text(")")) +/// ]), +/// +/// // Fits +/// group(&format_args![ +/// if_group_breaks(&text("(")), +/// soft_block_indent(&format_args![text("a"), line_suffix(&text(" // a comment"), 0)]), +/// if_group_breaks(&text(")")) +/// ]), +/// ])?; +/// # assert_eq!("(\n\ta // a comment\n)a // a comment", elements.print()?.as_code()); /// # Ok(()) /// # } /// ``` #[inline] -pub fn line_suffix(inner: &Content) -> LineSuffix +pub fn line_suffix(inner: &Content, reserved_width: u32) -> LineSuffix where Content: Format, { LineSuffix { content: Argument::new(inner), + reserved_width, } } #[derive(Copy, Clone)] pub struct LineSuffix<'a, Context> { content: Argument<'a, Context>, + reserved_width: u32, } impl Format for LineSuffix<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - f.write_element(FormatElement::Tag(StartLineSuffix)); + f.write_element(FormatElement::Tag(StartLineSuffix { + reserved_width: self.reserved_width, + })); Arguments::from(&self.content).fmt(f)?; f.write_element(FormatElement::Tag(EndLineSuffix)); @@ -501,7 +533,7 @@ impl std::fmt::Debug for LineSuffix<'_, Context> { /// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// text("a"), -/// line_suffix(&text("c")), +/// line_suffix(&text("c"), 0), /// text("b"), /// line_suffix_boundary(), /// text("d") diff --git a/crates/ruff_formatter/src/format_element/document.rs b/crates/ruff_formatter/src/format_element/document.rs index 23b64645aa9c94..1d682218746dae 100644 --- a/crates/ruff_formatter/src/format_element/document.rs +++ b/crates/ruff_formatter/src/format_element/document.rs @@ -459,8 +459,16 @@ impl Format> for &[FormatElement] { )?; } - StartLineSuffix => { - write!(f, [text("line_suffix(")])?; + StartLineSuffix { reserved_width } => { + write!( + f, + [ + text("line_suffix("), + dynamic_text(&std::format!("{reserved_width:?}"), None), + text(","), + space(), + ] + )?; } StartVerbatim(_) => { @@ -672,7 +680,9 @@ impl FormatElements for [FormatElement] { match element { // Line suffix // Ignore if any of its content breaks - FormatElement::Tag(Tag::StartLineSuffix | Tag::StartFitsExpanded(_)) => { + FormatElement::Tag( + Tag::StartLineSuffix { reserved_width: _ } | Tag::StartFitsExpanded(_), + ) => { ignore_depth += 1; } FormatElement::Tag(Tag::EndLineSuffix | Tag::EndFitsExpanded) => { diff --git a/crates/ruff_formatter/src/format_element/tag.rs b/crates/ruff_formatter/src/format_element/tag.rs index 91609b82c4800e..da69013faa271d 100644 --- a/crates/ruff_formatter/src/format_element/tag.rs +++ b/crates/ruff_formatter/src/format_element/tag.rs @@ -63,8 +63,11 @@ pub enum Tag { StartEntry, EndEntry, - /// Delay the printing of its content until the next line break - StartLineSuffix, + /// Delay the printing of its content until the next line break. Using reserved width will include + /// the associated line suffix during measurement. + StartLineSuffix { + reserved_width: u32, + }, EndLineSuffix, /// A token that tracks tokens/nodes that are printed as verbatim. @@ -96,7 +99,7 @@ impl Tag { | Tag::StartIndentIfGroupBreaks(_) | Tag::StartFill | Tag::StartEntry - | Tag::StartLineSuffix + | Tag::StartLineSuffix { reserved_width: _ } | Tag::StartVerbatim(_) | Tag::StartLabelled(_) | Tag::StartFitsExpanded(_) @@ -122,7 +125,7 @@ impl Tag { StartIndentIfGroupBreaks(_) | EndIndentIfGroupBreaks => TagKind::IndentIfGroupBreaks, StartFill | EndFill => TagKind::Fill, StartEntry | EndEntry => TagKind::Entry, - StartLineSuffix | EndLineSuffix => TagKind::LineSuffix, + StartLineSuffix { reserved_width: _ } | EndLineSuffix => TagKind::LineSuffix, StartVerbatim(_) | EndVerbatim => TagKind::Verbatim, StartLabelled(_) | EndLabelled => TagKind::Labelled, StartFitsExpanded { .. } | EndFitsExpanded => TagKind::FitsExpanded, diff --git a/crates/ruff_formatter/src/printer/mod.rs b/crates/ruff_formatter/src/printer/mod.rs index a096bd31fae893..57ceddca260f63 100644 --- a/crates/ruff_formatter/src/printer/mod.rs +++ b/crates/ruff_formatter/src/printer/mod.rs @@ -233,7 +233,8 @@ impl<'a> Printer<'a> { stack.push(TagKind::IndentIfGroupBreaks, args); } - FormatElement::Tag(StartLineSuffix) => { + FormatElement::Tag(StartLineSuffix { reserved_width }) => { + self.state.line_width += reserved_width; self.state .line_suffixes .extend(args, queue.iter_content(TagKind::LineSuffix)); @@ -1188,7 +1189,11 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { } } - FormatElement::Tag(StartLineSuffix) => { + FormatElement::Tag(StartLineSuffix { reserved_width }) => { + self.state.line_width += reserved_width; + if self.state.line_width > self.options().print_width.into() { + return Ok(Fits::No); + } self.queue.skip_content(TagKind::LineSuffix); self.state.has_line_suffix = true; } @@ -1724,12 +1729,42 @@ two lines`, text("]") ]), text(";"), - line_suffix(&format_args![space(), text("// trailing")]) + line_suffix(&format_args![space(), text("// trailing")], 0) ]); assert_eq!(printed.as_code(), "[1, 2, 3]; // trailing"); } + #[test] + fn line_suffix_with_reserved_width() { + let printed = format(&format_args![ + group(&format_args![ + text("["), + soft_block_indent(&format_with(|f| { + f.fill() + .entry( + &soft_line_break_or_space(), + &format_args!(text("1"), text(",")), + ) + .entry( + &soft_line_break_or_space(), + &format_args!(text("2"), text(",")), + ) + .entry( + &soft_line_break_or_space(), + &format_args!(text("3"), if_group_breaks(&text(","))), + ) + .finish() + })), + text("]") + ]), + text(";"), + line_suffix(&format_args![space(), text("// Using reserved width causes this content to not fit even though it's a line suffix element")], 93) + ]); + + assert_eq!(printed.as_code(), "[\n 1, 2, 3\n]; // Using reserved width causes this content to not fit even though it's a line suffix element"); + } + #[test] fn conditional_with_group_id_in_fits() { let content = format_with(|f| { diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tuple.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tuple.py index 9c6c5a6d5b1138..f1078072139cc3 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tuple.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/tuple.py @@ -66,3 +66,5 @@ h1 = ((((1, 2)))) h2 = ((((1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq")))) h3 = 1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq" + +i1 = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # This should break diff --git a/crates/ruff_python_formatter/src/comments/format.rs b/crates/ruff_python_formatter/src/comments/format.rs index 7a36a3a406c2aa..fce895429fb5dc 100644 --- a/crates/ruff_python_formatter/src/comments/format.rs +++ b/crates/ruff_python_formatter/src/comments/format.rs @@ -1,9 +1,10 @@ use ruff_python_ast::Ranged; use ruff_text_size::{TextLen, TextRange, TextSize}; -use ruff_formatter::{format_args, write, FormatError, SourceCode}; +use ruff_formatter::{format_args, write, FormatError, FormatOptions, SourceCode}; use ruff_python_ast::node::{AnyNodeRef, AstNode}; use ruff_python_trivia::{lines_after, lines_after_ignoring_trivia, lines_before}; +use unicode_width::UnicodeWidthChar; use crate::comments::{CommentLinePosition, SourceComment}; use crate::context::NodeLevel; @@ -151,18 +152,33 @@ impl Format> for FormatTrailingComments<'_> { write!( f, [ - line_suffix(&format_args![ - empty_lines(lines_before_comment), - format_comment(trailing) - ]), + line_suffix( + &format_args![ + empty_lines(lines_before_comment), + format_comment(trailing) + ], + 0 // Reserving width isn't necessary because we don't split comments and the empty lines expand any enclosing group. + ), expand_parent() ] )?; } else { + // A trailing comment at the end of a line has a reserved width to consider during line measurement. + // ```python + // tup = ( + // "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + // ) # Some comment + // ``` write!( f, [ - line_suffix(&format_args![space(), space(), format_comment(trailing)]), + line_suffix( + &format_args![space(), space(), format_comment(trailing)], + measure_text( + &f.context().source()[slice.range()], + f.options().tab_width().value() + ) + 2 // Account for two added spaces + ), expand_parent() ] )?; @@ -264,10 +280,18 @@ impl Format> for FormatDanglingOpenParenthesisComments<'_> { "Expected dangling comment to be at the end of the line" ); + let slice = comment.slice(); write!( f, [ - line_suffix(&format_args!(space(), space(), format_comment(comment))), + line_suffix( + &format_args!(space(), space(), format_comment(comment)), + // Marking the comment as a line suffix with reserved width is safe since we expect the comment to be end of line. + measure_text( + &f.context().source()[slice.range()], + f.options().tab_width().value() + ) + 2 // Account for two added spaces + ), expand_parent() ] )?; @@ -374,3 +398,18 @@ impl Format> for FormatEmptyLines { } } } + +/// Helper for measuring text. +fn measure_text(text: &str, tab_width: u32) -> u32 { + let mut width = 0; + for c in text.chars() { + let char_width = match c { + '\t' => tab_width, + // SAFETY: A u32 is sufficient to format files <= 4GB + #[allow(clippy::cast_possible_truncation)] + c => c.width().unwrap_or(0) as u32, + }; + width += char_width; + } + width +} diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 52255ec4f4611e..363eff9501d300 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -247,10 +247,13 @@ impl Format> for MaybeParenthesizeExpression<'_> { 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) + group(&format_args![ + text("("), + soft_block_indent(&format_expression), + text(")") + ]) + .with_group_id(Some(group_id)) + .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 diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments6.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments6.py.snap index 6725d8d840c1f6..f48ec014b3d7c5 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments6.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__comments6.py.snap @@ -156,7 +156,7 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite ) -@@ -108,11 +112,18 @@ +@@ -108,11 +112,20 @@ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) @@ -176,7 +176,10 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite + ], # type: ignore ) - aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] +-aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] ++aaaaaaaaaaaaa, bbbbbbbbb = map( ++ list, map(itertools.chain.from_iterable, zip(*items)) ++) # type: ignore[arg-type] ``` ## Ruff Output @@ -310,7 +313,9 @@ call_to_some_function_asdf( ], # type: ignore ) -aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] +aaaaaaaaaaaaa, bbbbbbbbb = map( + list, map(itertools.chain.from_iterable, zip(*items)) +) # type: ignore[arg-type] ``` ## Black Output diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap index 86a5b37df98839..e55f593032574d 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap @@ -300,7 +300,18 @@ last_call() ) # note: no trailing comma pre-3.6 call(*gidgets[:2]) call(a, *gidgets[:2]) -@@ -328,13 +329,18 @@ +@@ -142,7 +143,9 @@ + xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore + sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) + ) +-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore ++xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[ ++ ..., List[SomeClass] ++] = classmethod( # type: ignore + sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) + ) + xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( +@@ -328,13 +331,18 @@ ): return True if ( @@ -322,7 +333,7 @@ last_call() ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n ): return True -@@ -342,7 +348,8 @@ +@@ -342,7 +350,8 @@ ~aaaaaaaaaaaaaaaa.a + aaaaaaaaaaaaaaaa.b - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e @@ -482,7 +493,9 @@ very_long_variable_name_filters: t.List[ xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) ) -xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[ + ..., List[SomeClass] +] = classmethod( # type: ignore sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) ) xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__power_op_spacing.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__power_op_spacing.py.snap new file mode 100644 index 00000000000000..de37c7048668de --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__power_op_spacing.py.snap @@ -0,0 +1,232 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/power_op_spacing.py +--- +## Input + +```py +def function(**kwargs): + t = a**2 + b**3 + return t ** 2 + + +def function_replace_spaces(**kwargs): + t = a **2 + b** 3 + c ** 4 + + +def function_dont_replace_spaces(): + {**a, **b, **c} + + +a = 5**~4 +b = 5 ** f() +c = -(5**2) +d = 5 ** f["hi"] +e = lazy(lambda **kwargs: 5) +f = f() ** 5 +g = a.b**c.d +h = 5 ** funcs.f() +i = funcs.f() ** 5 +j = super().name ** 5 +k = [(2**idx, value) for idx, value in pairs] +l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001) +m = [([2**63], [1, 2**63])] +n = count <= 10**5 +o = settings(max_examples=10**6) +p = {(k, k**2): v**2 for k, v in pairs} +q = [10**i for i in range(6)] +r = x**y + +a = 5.0**~4.0 +b = 5.0 ** f() +c = -(5.0**2.0) +d = 5.0 ** f["hi"] +e = lazy(lambda **kwargs: 5) +f = f() ** 5.0 +g = a.b**c.d +h = 5.0 ** funcs.f() +i = funcs.f() ** 5.0 +j = super().name ** 5.0 +k = [(2.0**idx, value) for idx, value in pairs] +l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001) +m = [([2.0**63.0], [1.0, 2**63.0])] +n = count <= 10**5.0 +o = settings(max_examples=10**6.0) +p = {(k, k**2): v**2.0 for k, v in pairs} +q = [10.5**i for i in range(6)] + + +# WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873) +if hasattr(view, "sum_of_weights"): + return np.divide( # type: ignore[no-any-return] + view.variance, # type: ignore[union-attr] + view.sum_of_weights, # type: ignore[union-attr] + out=np.full(view.sum_of_weights.shape, np.nan), # type: ignore[union-attr] + where=view.sum_of_weights**2 > view.sum_of_weights_squared, # type: ignore[union-attr] + ) + +return np.divide( + where=view.sum_of_weights_of_weight_long**2 > view.sum_of_weights_squared, # type: ignore +) +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -55,9 +55,11 @@ + view.variance, # type: ignore[union-attr] + view.sum_of_weights, # type: ignore[union-attr] + out=np.full(view.sum_of_weights.shape, np.nan), # type: ignore[union-attr] +- where=view.sum_of_weights**2 > view.sum_of_weights_squared, # type: ignore[union-attr] ++ where=view.sum_of_weights**2 ++ > view.sum_of_weights_squared, # type: ignore[union-attr] + ) + + return np.divide( +- where=view.sum_of_weights_of_weight_long**2 > view.sum_of_weights_squared, # type: ignore ++ where=view.sum_of_weights_of_weight_long**2 ++ > view.sum_of_weights_squared, # type: ignore + ) +``` + +## Ruff Output + +```py +def function(**kwargs): + t = a**2 + b**3 + return t**2 + + +def function_replace_spaces(**kwargs): + t = a**2 + b**3 + c**4 + + +def function_dont_replace_spaces(): + {**a, **b, **c} + + +a = 5**~4 +b = 5 ** f() +c = -(5**2) +d = 5 ** f["hi"] +e = lazy(lambda **kwargs: 5) +f = f() ** 5 +g = a.b**c.d +h = 5 ** funcs.f() +i = funcs.f() ** 5 +j = super().name ** 5 +k = [(2**idx, value) for idx, value in pairs] +l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001) +m = [([2**63], [1, 2**63])] +n = count <= 10**5 +o = settings(max_examples=10**6) +p = {(k, k**2): v**2 for k, v in pairs} +q = [10**i for i in range(6)] +r = x**y + +a = 5.0**~4.0 +b = 5.0 ** f() +c = -(5.0**2.0) +d = 5.0 ** f["hi"] +e = lazy(lambda **kwargs: 5) +f = f() ** 5.0 +g = a.b**c.d +h = 5.0 ** funcs.f() +i = funcs.f() ** 5.0 +j = super().name ** 5.0 +k = [(2.0**idx, value) for idx, value in pairs] +l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001) +m = [([2.0**63.0], [1.0, 2**63.0])] +n = count <= 10**5.0 +o = settings(max_examples=10**6.0) +p = {(k, k**2): v**2.0 for k, v in pairs} +q = [10.5**i for i in range(6)] + + +# WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873) +if hasattr(view, "sum_of_weights"): + return np.divide( # type: ignore[no-any-return] + view.variance, # type: ignore[union-attr] + view.sum_of_weights, # type: ignore[union-attr] + out=np.full(view.sum_of_weights.shape, np.nan), # type: ignore[union-attr] + where=view.sum_of_weights**2 + > view.sum_of_weights_squared, # type: ignore[union-attr] + ) + +return np.divide( + where=view.sum_of_weights_of_weight_long**2 + > view.sum_of_weights_squared, # type: ignore +) +``` + +## Black Output + +```py +def function(**kwargs): + t = a**2 + b**3 + return t**2 + + +def function_replace_spaces(**kwargs): + t = a**2 + b**3 + c**4 + + +def function_dont_replace_spaces(): + {**a, **b, **c} + + +a = 5**~4 +b = 5 ** f() +c = -(5**2) +d = 5 ** f["hi"] +e = lazy(lambda **kwargs: 5) +f = f() ** 5 +g = a.b**c.d +h = 5 ** funcs.f() +i = funcs.f() ** 5 +j = super().name ** 5 +k = [(2**idx, value) for idx, value in pairs] +l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001) +m = [([2**63], [1, 2**63])] +n = count <= 10**5 +o = settings(max_examples=10**6) +p = {(k, k**2): v**2 for k, v in pairs} +q = [10**i for i in range(6)] +r = x**y + +a = 5.0**~4.0 +b = 5.0 ** f() +c = -(5.0**2.0) +d = 5.0 ** f["hi"] +e = lazy(lambda **kwargs: 5) +f = f() ** 5.0 +g = a.b**c.d +h = 5.0 ** funcs.f() +i = funcs.f() ** 5.0 +j = super().name ** 5.0 +k = [(2.0**idx, value) for idx, value in pairs] +l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001) +m = [([2.0**63.0], [1.0, 2**63.0])] +n = count <= 10**5.0 +o = settings(max_examples=10**6.0) +p = {(k, k**2): v**2.0 for k, v in pairs} +q = [10.5**i for i in range(6)] + + +# WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873) +if hasattr(view, "sum_of_weights"): + return np.divide( # type: ignore[no-any-return] + view.variance, # type: ignore[union-attr] + view.sum_of_weights, # type: ignore[union-attr] + out=np.full(view.sum_of_weights.shape, np.nan), # type: ignore[union-attr] + where=view.sum_of_weights**2 > view.sum_of_weights_squared, # type: ignore[union-attr] + ) + +return np.divide( + where=view.sum_of_weights_of_weight_long**2 > view.sum_of_weights_squared, # type: ignore +) +``` + + 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 428ca613760819..628dbb5c6a0487 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,7 +21,9 @@ +@@ -21,11 +21,15 @@ # Check comments async def main(): @@ -103,6 +103,13 @@ async def main(): + ) + async def main(): +- await asyncio.sleep(1) # Hello ++ await ( ++ asyncio.sleep(1) # Hello ++ ) + + async def main(): ``` @@ -138,7 +145,9 @@ 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/black_compatibility@simple_cases__torture.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__torture.py.snap index f44e1053f0b4bd..3a57c5b7a90ffc 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__torture.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__torture.py.snap @@ -50,14 +50,17 @@ assert ( ) # assert sort_by_dependency( -@@ -25,9 +25,9 @@ +@@ -25,9 +25,11 @@ class A: def foo(self): for _ in range(10): - aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( -+ aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( # pylint: disable=no-member - xxxxxxxxxxxx +- xxxxxxxxxxxx - ) # pylint: disable=no-member ++ aaaaaaaaaaaaaaaaaaa = ( ++ bbbbbbbbbbbbbbb.cccccccccc( # pylint: disable=no-member ++ xxxxxxxxxxxx ++ ) + ) @@ -94,8 +97,10 @@ importA class A: def foo(self): for _ in range(10): - aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( # pylint: disable=no-member - xxxxxxxxxxxx + aaaaaaaaaaaaaaaaaaa = ( + bbbbbbbbbbbbbbb.cccccccccc( # pylint: disable=no-member + xxxxxxxxxxxx + ) ) diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__tuple.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__tuple.py.snap index 85dfa81fff34f7..bb1b2aebaf01a1 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__tuple.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__tuple.py.snap @@ -72,6 +72,8 @@ g2 = ( # a h1 = ((((1, 2)))) h2 = ((((1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq")))) h3 = 1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq" + +i1 = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # This should break ``` ## Output @@ -275,6 +277,10 @@ h3 = ( 1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq", ) + +i1 = ( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", +) # This should break ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@parentheses__call_chains.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@parentheses__call_chains.py.snap index 8c8c952a79d7f3..1bd2089f6b76f6 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@parentheses__call_chains.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@parentheses__call_chains.py.snap @@ -268,7 +268,9 @@ d13 = ( ) # Doesn't fit, default -d2 = x.e().esadjkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkfsdddd() # +d2 = ( + x.e().esadjkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkfsdddd() # +) # Doesn't fit, fluent style d3 = ( diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__delete.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__delete.py.snap index ce4add0b9462cf..2afea0bb26dd29 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__delete.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__delete.py.snap @@ -204,7 +204,13 @@ del ( # NOTE: This shouldn't format. See https://github.com/astral-sh/ruff/issues/5630. # Delete something -del x, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, b, c, d # Delete these +del ( + x, + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + b, + c, + d, +) # Delete these # Ready to delete # Delete something diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__try.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__try.py.snap index 589d8a25e754ca..e4fe04e4348cb7 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__try.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__try.py.snap @@ -324,7 +324,9 @@ finally: try: # 1 preceding: any, following: first in body, enclosing: try print(1) # 2 preceding: last in body, following: fist in alt body, enclosing: try -except ZeroDivisionError: # 3 preceding: test, following: fist in alt body, enclosing: try +except ( + ZeroDivisionError +): # 3 preceding: test, following: fist in alt body, enclosing: try print(2) # 4 preceding: last in body, following: fist in alt body, enclosing: exc except: # 5 preceding: last in body, following: fist in alt body, enclosing: try print(2) # 6 preceding: last in body, following: fist in alt body, enclosing: exc diff --git a/crates/ruff_python_formatter/tests/snapshots/format@statement__while.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__while.py.snap index fdc99df9a65261..9b09af15dbb594 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@statement__while.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__while.py.snap @@ -57,7 +57,9 @@ while aVeryLongConditionThatSpillsOverToTheNextLineBecauseItIsExtremelyLongAndGo else: ... -while some_condition(unformatted, args) and anotherCondition or aThirdCondition: # comment +while ( + some_condition(unformatted, args) and anotherCondition or aThirdCondition +): # comment print("Do something")