From afcf5896bcc887d3d897b688826760351f448ce1 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 16 Jan 2025 18:47:29 +0100 Subject: [PATCH] Fix bracket spacing for single-element tuples in f-string expressions --- .../test/fixtures/ruff/expression/fstring.py | 8 +++++ .../src/other/f_string_element.rs | 33 ++++++++++++------ .../format@expression__fstring.py.snap | 34 +++++++++++++++++++ 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py index 4ef0024380aa5..b90da551b0d3e 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py @@ -718,3 +718,11 @@ print(f"{({1, 2, 3}) - ({2})}") print(f"{1, 2, {3} }") print(f"{(1, 2, {3})}") + +# Regression tests for https://github.com/astral-sh/ruff/issues/15535 +print(f"{ {}, }") # A single item tuple gets parenthesized +print(f"{ {}.values(), }") +print(f"{ {}, 1 }") # A tuple with multiple elements doesn't get parenthesized +print(f"{ # Tuple with multiple elements that doesn't fit on a single line gets parenthesized + {}, 1, +}") diff --git a/crates/ruff_python_formatter/src/other/f_string_element.rs b/crates/ruff_python_formatter/src/other/f_string_element.rs index 073aad2160f1a..36193eaf96865 100644 --- a/crates/ruff_python_formatter/src/other/f_string_element.rs +++ b/crates/ruff_python_formatter/src/other/f_string_element.rs @@ -204,18 +204,13 @@ impl Format> for FormatFStringExpressionElement<'_> { // before the closing curly brace is not strictly necessary, but it's // added to maintain consistency. let bracket_spacing = - match left_most(expression, comments.ranges(), f.context().source()) { - Expr::Dict(_) | Expr::DictComp(_) | Expr::Set(_) | Expr::SetComp(_) => { - Some(format_with(|f| { - if self.context.can_contain_line_breaks() { - soft_line_break_or_space().fmt(f) - } else { - space().fmt(f) - } - })) + needs_bracket_spacing(expression, f.context()).then_some(format_with(|f| { + if self.context.can_contain_line_breaks() { + soft_line_break_or_space().fmt(f) + } else { + space().fmt(f) } - _ => None, - }; + })); let item = format_with(|f: &mut PyFormatter| { // Update the context to be inside the f-string expression element. @@ -290,3 +285,19 @@ impl Format> for FormatFStringExpressionElement<'_> { } } } + +fn needs_bracket_spacing(expr: &Expr, context: &PyFormatContext) -> bool { + // Ruff parenthesizes single element tuples, that's why we shouldn't insert + // a space around the curly braces for those. + if expr + .as_tuple_expr() + .is_some_and(|tuple| !tuple.parenthesized && tuple.elts.len() == 1) + { + return false; + } + + matches!( + left_most(expr, context.comments().ranges(), context.source()), + Expr::Dict(_) | Expr::DictComp(_) | Expr::Set(_) | Expr::SetComp(_) + ) +} diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap index cdcc2fba8e4f7..e2d1a899a11ae 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__fstring.py.snap @@ -724,6 +724,14 @@ print(f"{ {1: 2}.keys() }") print(f"{({1, 2, 3}) - ({2})}") print(f"{1, 2, {3} }") print(f"{(1, 2, {3})}") + +# Regression tests for https://github.com/astral-sh/ruff/issues/15535 +print(f"{ {}, }") # A single item tuple gets parenthesized +print(f"{ {}.values(), }") +print(f"{ {}, 1 }") # A tuple with multiple elements doesn't get parenthesized +print(f"{ # Tuple with multiple elements that doesn't fit on a single line gets parenthesized + {}, 1, +}") ``` ## Outputs @@ -1506,6 +1514,19 @@ print(f"{ {1: 2}.keys() }") print(f"{({1, 2, 3}) - ({2})}") print(f"{1, 2, {3}}") print(f"{(1, 2, {3})}") + +# Regression tests for https://github.com/astral-sh/ruff/issues/15535 +print(f"{({},)}") # A single item tuple gets parenthesized +print(f"{({}.values(),)}") +print(f"{ {}, 1 }") # A tuple with multiple elements doesn't get parenthesized +print( + f"{ # Tuple with multiple elements that doesn't fit on a single line gets parenthesized + ( + {}, + 1, + ) + }" +) ``` @@ -2288,4 +2309,17 @@ print(f"{ {1: 2}.keys() }") print(f"{({1, 2, 3}) - ({2})}") print(f"{1, 2, {3}}") print(f"{(1, 2, {3})}") + +# Regression tests for https://github.com/astral-sh/ruff/issues/15535 +print(f"{({},)}") # A single item tuple gets parenthesized +print(f"{({}.values(),)}") +print(f"{ {}, 1 }") # A tuple with multiple elements doesn't get parenthesized +print( + f"{ # Tuple with multiple elements that doesn't fit on a single line gets parenthesized + ( + {}, + 1, + ) + }" +) ```