diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments_non_breaking_space.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments_non_breaking_space.py index d1d42f0259..0c5f5660eb 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments_non_breaking_space.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments_non_breaking_space.py @@ -14,6 +14,6 @@ def function(a:int=42): a b """ - #  There's a NBSP + 3 spaces before + #    There's a NBSP + 3 spaces before # And 4 spaces on the next line pass 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 9c6c5a6d5b..af5273484d 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,6 @@ g2 = ( # a h1 = ((((1, 2)))) h2 = ((((1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq")))) h3 = 1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq" + +i1 = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # This should break + diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py new file mode 100644 index 0000000000..5bfe148cf5 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py @@ -0,0 +1,6 @@ +# As of adding this fixture Black adds a space before the non-breaking space if part of a type pragma. +# https://github.com/psf/black/blob/b4dca26c7d93f930bbd5a7b552807370b60d4298/src/black/comments.py#L122-L129 +i2 = "" #  type: Add space before leading NBSP followed by spaces +i3 = "" #type: A space is added +i4 = "" #  type: Add space before leading NBSP followed by a space +i5 = "" # type: Add space before leading NBSP diff --git a/crates/ruff_python_formatter/src/comments/format.rs b/crates/ruff_python_formatter/src/comments/format.rs index fcfa3b6811..7be01669e3 100644 --- a/crates/ruff_python_formatter/src/comments/format.rs +++ b/crates/ruff_python_formatter/src/comments/format.rs @@ -1,6 +1,9 @@ -use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +use std::borrow::Cow; +use unicode_width::UnicodeWidthChar; -use ruff_formatter::{format_args, write, FormatError, SourceCode}; +use ruff_text_size::{Ranged, TextLen, TextRange}; + +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}; @@ -151,19 +154,22 @@ impl Format> for FormatTrailingComments<'_> { empty_lines(lines_before_comment), format_comment(trailing) ], + // Reserving width isn't necessary because we don't split + // comments and the empty lines expand any enclosing group. 0 ), expand_parent() ] )?; } else { - write!( - f, - [ - line_suffix(&format_args![space(), space(), format_comment(trailing)], 0), - expand_parent() - ] - )?; + // A trailing comment at the end of a line has a reserved width to + // consider during line measurement. + // ```python + // tup = ( + // "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + // ) # Some comment + // ``` + trailing_end_of_line_comment(trailing).fmt(f)?; } trailing.mark_formatted(); @@ -262,13 +268,7 @@ impl Format> for FormatDanglingOpenParenthesisComments<'_> { "Expected dangling comment to be at the end of the line" ); - write!( - f, - [ - line_suffix(&format_args!(space(), space(), format_comment(comment)), 0), - expand_parent() - ] - )?; + trailing_end_of_line_comment(comment).fmt(f)?; comment.mark_formatted(); } @@ -291,50 +291,11 @@ pub(crate) struct FormatComment<'a> { impl Format> for FormatComment<'_> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { let slice = self.comment.slice(); - let comment_text = slice.text(SourceCode::new(f.context().source())); + let source = SourceCode::new(f.context().source()); - let trimmed = comment_text.trim_end(); - let trailing_whitespace_len = comment_text.text_len() - trimmed.text_len(); + let normalized_comment = normalize_comment(self.comment, source)?; - let Some(content) = trimmed.strip_prefix('#') else { - return Err(FormatError::syntax_error( - "Didn't find expected comment token `#`", - )); - }; - - // Fast path for correctly formatted comments: - // * Start with a `#` and are followed by a space - // * Have no trailing whitespace. - if trailing_whitespace_len == TextSize::new(0) && content.starts_with(' ') { - return source_text_slice(slice.range(), ContainsNewlines::No).fmt(f); - } - - write!(f, [source_position(slice.start()), text("#")])?; - - // Starts with a non breaking space - let start_offset = - if content.starts_with('\u{A0}') && !content.trim_start().starts_with("type:") { - // Replace non-breaking space with a space (if not followed by a normal space) - "#\u{A0}".text_len() - } else { - '#'.text_len() - }; - - // Add a space between the `#` and the text if the source contains none. - if !content.is_empty() && !content.starts_with([' ', '!', ':', '#', '\'']) { - write!(f, [space()])?; - } - - let start = slice.start() + start_offset; - let end = slice.end() - trailing_whitespace_len; - - write!( - f, - [ - source_text_slice(TextRange::new(start, end), ContainsNewlines::No), - source_position(slice.end()) - ] - ) + format_normalized_comment(normalized_comment, slice.range()).fmt(f) } } @@ -372,3 +333,145 @@ impl Format> for FormatEmptyLines { } } } + +/// A helper that constructs a formattable element using a reserved-width line-suffix +/// for normalized comments. +/// +/// * Black normalization of `SourceComment`. +/// * Line suffix with reserved width for the final, normalized content. +/// * Expands parent node. +pub(crate) const fn trailing_end_of_line_comment( + comment: &SourceComment, +) -> FormatTrailingEndOfLineComment { + FormatTrailingEndOfLineComment { comment } +} + +pub(crate) struct FormatTrailingEndOfLineComment<'a> { + comment: &'a SourceComment, +} + +impl Format> for FormatTrailingEndOfLineComment<'_> { + fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { + let slice = self.comment.slice(); + let source = SourceCode::new(f.context().source()); + + let normalized_comment = normalize_comment(self.comment, source)?; + + // Start with 2 because of the two leading spaces. + let mut reserved_width = 2; + + // SAFE: The formatted file is <= 4GB, and each comment should as well. + #[allow(clippy::cast_possible_truncation)] + for c in normalized_comment.chars() { + reserved_width += match c { + '\t' => f.options().tab_width().value(), + c => c.width().unwrap_or(0) as u32, + } + } + + write!( + f, + [ + line_suffix( + &format_args![ + space(), + space(), + format_normalized_comment(normalized_comment, slice.range()) + ], + reserved_width + ), + expand_parent() + ] + ) + } +} + +/// A helper that constructs formattable normalized comment text as efficiently as +/// possible. +/// +/// * If the content is unaltered then format with source text slice strategy and no +/// unnecessary allocations. +/// * If the content is modified then make as few allocations as possible and use +/// a dynamic text element at the original slice's start position. +pub(crate) const fn format_normalized_comment( + comment: Cow<'_, str>, + range: TextRange, +) -> FormatNormalizedComment<'_> { + FormatNormalizedComment { comment, range } +} + +pub(crate) struct FormatNormalizedComment<'a> { + comment: Cow<'a, str>, + range: TextRange, +} + +impl Format> for FormatNormalizedComment<'_> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + match self.comment { + Cow::Borrowed(borrowed) => source_text_slice( + TextRange::at(self.range.start(), borrowed.text_len()), + ContainsNewlines::No, + ) + .fmt(f), + + Cow::Owned(ref owned) => { + write!( + f, + [ + dynamic_text(owned, Some(self.range.start())), + source_position(self.range.end()) + ] + ) + } + } + } +} + +/// A helper for normalizing comments efficiently. +/// +/// * Return as fast as possible without making unnecessary allocations. +/// * Trim any trailing whitespace. +/// * Normalize for a leading '# '. +/// * Retain non-breaking spaces for 'type:' pragmas by leading with '# \u{A0}'. +fn normalize_comment<'a>( + comment: &'a SourceComment, + source: SourceCode<'a>, +) -> FormatResult> { + let slice = comment.slice(); + let comment_text = slice.text(source); + + let trimmed = comment_text.trim_end(); + + let Some(content) = trimmed.strip_prefix('#') else { + return Err(FormatError::syntax_error( + "Didn't find expected comment token `#`", + )); + }; + + if content.is_empty() { + return Ok(Cow::Borrowed("#")); + } + + // Fast path for correctly formatted comments: + // * Start with a `# '. + // * Have no trailing whitespace. + if content.starts_with([' ', '!', ':', '#', '\'']) { + return Ok(Cow::Borrowed(trimmed)); + } + + if content.starts_with('\u{A0}') { + let trimmed = content.trim_start_matches('\u{A0}'); + + // Black adds a space before the non-breaking space if part of a type pragma. + if trimmed.trim_start().starts_with("type:") { + return Ok(Cow::Owned(std::format!("# \u{A0}{trimmed}"))); + } + + // Black replaces the non-breaking space with a space if followed by a space. + if trimmed.starts_with(' ') { + return Ok(Cow::Owned(std::format!("# {trimmed}"))); + } + } + + Ok(Cow::Owned(std::format!("# {}", content.trim_start()))) +} diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 4e525bebc9..8904e470e4 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 6725d8d840..f48ec014b3 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 86a5b37df9..e55f593032 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 0000000000..de37c70486 --- /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 428ca61376..628dbb5c6a 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 f44e1053f0..3a57c5b7a9 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 85dfa81fff..0f8997294a 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,9 @@ g2 = ( # a h1 = ((((1, 2)))) h2 = ((((1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq")))) h3 = 1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq" + +i1 = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # This should break + ``` ## Output @@ -275,6 +278,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 8c8c952a79..1bd2089f6b 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 ce4add0b94..2afea0bb26 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 589d8a25e7..e4fe04e434 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 fdc99df9a6..9b09af15db 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") diff --git a/crates/ruff_python_formatter/tests/snapshots/format@trailing_comments.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@trailing_comments.py.snap new file mode 100644 index 0000000000..8ddbd6b6f5 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@trailing_comments.py.snap @@ -0,0 +1,26 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py +--- +## Input +```py +# As of adding this fixture Black adds a space before the non-breaking space if part of a type pragma. +# https://github.com/psf/black/blob/b4dca26c7d93f930bbd5a7b552807370b60d4298/src/black/comments.py#L122-L129 +i2 = "" #  type: Add space before leading NBSP followed by spaces +i3 = "" #type: A space is added +i4 = "" #  type: Add space before leading NBSP followed by a space +i5 = "" # type: Add space before leading NBSP +``` + +## Output +```py +# As of adding this fixture Black adds a space before the non-breaking space if part of a type pragma. +# https://github.com/psf/black/blob/b4dca26c7d93f930bbd5a7b552807370b60d4298/src/black/comments.py#L122-L129 +i2 = "" #   type: Add space before leading NBSP followed by spaces +i3 = "" # type: A space is added +i4 = "" #   type: Add space before leading NBSP followed by a space +i5 = "" #  type: Add space before leading NBSP +``` + + +