From 0b9af031fb8f51e7f9a72c31366bf5f87b90ca3c Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 7 Jul 2023 21:11:52 +0200 Subject: [PATCH] Format ExprIfExp (ternary operator) (#5597) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Format `ExprIfExp`, also known as the ternary operator or inline `if`. It can look like ```python a1 = 1 if True else 2 ``` but also ```python b1 = ( # We return "a" ... "a" # that's our True value # ... if this condition matches ... if True # that's our test # ... otherwise we return "b§ else "b" # that's our False value ) ``` This also fixes a visitor order bug. The jaccard index on django goes from 0.911 to 0.915. ## Test Plan I added fixtures without and with comments in strange places. --- .../ruff_python_ast/src/visitor/preorder.rs | 3 +- .../test/fixtures/ruff/expression/if.py | 33 ++++++ .../src/comments/placement.rs | 85 +++++++++++++- .../src/expression/expr_if_exp.rs | 35 ++++-- ...mpatibility@conditional_expression.py.snap | 99 +++++++--------- ...atibility@simple_cases__expression.py.snap | 111 ++++++------------ ...mpatibility@simple_cases__fmtonoff.py.snap | 48 ++------ ...mpatibility@simple_cases__function.py.snap | 42 +------ .../snapshots/format@expression__if.py.snap | 84 +++++++++++++ 9 files changed, 328 insertions(+), 212 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/if.py create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__if.py.snap diff --git a/crates/ruff_python_ast/src/visitor/preorder.rs b/crates/ruff_python_ast/src/visitor/preorder.rs index 1595ab11ee..4a21e82bad 100644 --- a/crates/ruff_python_ast/src/visitor/preorder.rs +++ b/crates/ruff_python_ast/src/visitor/preorder.rs @@ -476,8 +476,9 @@ where orelse, range: _range, }) => { - visitor.visit_expr(test); + // `body if test else orelse` visitor.visit_expr(body); + visitor.visit_expr(test); visitor.visit_expr(orelse); } diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/if.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/if.py new file mode 100644 index 0000000000..ed8c1ab97b --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/if.py @@ -0,0 +1,33 @@ +a1 = 1 if True else 2 + +a2 = "this is a very long text that will make the group break to check that parentheses are added" if True else 2 + +# These comment should be kept in place +b1 = ( + # We return "a" ... + "a" # that's our True value + # ... if this condition matches ... + if True # that's our test + # ... otherwise we return "b" + else "b" # that's our False value +) + +# These only need to be stable, bonus is we also keep the order +c1 = ( + "a" # 1 + if # 2 + True # 3 + else # 4 + "b" # 5 +) +c2 = ( + "a" # 1 + # 2 + if # 3 + # 4 + True # 5 + # 6 + else # 7 + # 8 + "b" # 9 +) diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 44ac6b5195..207b9a68a8 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -1,8 +1,8 @@ use std::cmp::Ordering; -use ruff_text_size::TextRange; +use ruff_text_size::{TextLen, TextRange}; use rustpython_parser::ast; -use rustpython_parser::ast::{Expr, ExprSlice, Ranged}; +use rustpython_parser::ast::{Expr, ExprIfExp, ExprSlice, Ranged}; use ruff_python_ast::node::{AnyNodeRef, AstNode}; use ruff_python_ast::source_code::Locator; @@ -14,7 +14,9 @@ use crate::expression::expr_slice::{assign_comment_in_slice, ExprSliceCommentSec use crate::other::arguments::{ assign_argument_separator_comment_placement, find_argument_separators, }; -use crate::trivia::{first_non_trivia_token_rev, SimpleTokenizer, Token, TokenKind}; +use crate::trivia::{ + first_non_trivia_token, first_non_trivia_token_rev, SimpleTokenizer, Token, TokenKind, +}; /// Implements the custom comment placement logic. pub(super) fn place_comment<'a>( @@ -37,6 +39,7 @@ pub(super) fn place_comment<'a>( handle_dict_unpacking_comment, handle_slice_comments, handle_attribute_comment, + handle_expr_if_comment, ]; for handler in HANDLERS { comment = match handler(comment, locator) { @@ -1154,6 +1157,82 @@ fn handle_attribute_comment<'a>( } } +/// Assign comments between `if` and `test` and `else` and `orelse` as leading to the respective +/// node. +/// +/// ```python +/// x = ( +/// "a" +/// if # leading comment of `True` +/// True +/// else # leading comment of `"b"` +/// "b" +/// ) +/// ``` +/// +/// This placement ensures comments remain in their previous order. This an edge case that only +/// happens if the comments are in a weird position but it also doesn't hurt handling it. +fn handle_expr_if_comment<'a>( + comment: DecoratedComment<'a>, + locator: &Locator, +) -> CommentPlacement<'a> { + let Some(expr_if) = comment.enclosing_node().expr_if_exp() else { + return CommentPlacement::Default(comment); + }; + let ExprIfExp { + range: _, + test, + body, + orelse, + } = expr_if; + + if comment.line_position().is_own_line() { + return CommentPlacement::Default(comment); + } + + // Find the if and the else + let if_token = + find_only_token_str_in_range(TextRange::new(body.end(), test.start()), locator, "if"); + let else_token = + find_only_token_str_in_range(TextRange::new(test.end(), orelse.start()), locator, "else"); + + // Between `if` and `test` + if if_token.range.start() < comment.slice().start() && comment.slice().start() < test.start() { + return CommentPlacement::leading(test.as_ref().into(), comment); + } + + // Between `else` and `orelse` + if else_token.range.start() < comment.slice().start() + && comment.slice().start() < orelse.start() + { + return CommentPlacement::leading(orelse.as_ref().into(), comment); + } + + CommentPlacement::Default(comment) +} + +/// Looks for a multi char token in the range that contains no other tokens. `SimpleTokenizer` only +/// works with single char tokens so we check that we have the right token by string comparison. +fn find_only_token_str_in_range(range: TextRange, locator: &Locator, token_str: &str) -> Token { + let token = + first_non_trivia_token(range.start(), locator.contents()).expect("Expected a token"); + debug_assert!( + locator.after(token.start()).starts_with(token_str), + "expected a `{token_str}` token", + ); + debug_assert!( + SimpleTokenizer::new( + locator.contents(), + TextRange::new(token.start() + token_str.text_len(), range.end()) + ) + .skip_trivia() + .next() + .is_none(), + "Didn't expect any other token" + ); + token +} + /// Returns `true` if `right` is `Some` and `left` and `right` are referentially equal. fn are_same_optional<'a, T>(left: AnyNodeRef, right: Option) -> bool where diff --git a/crates/ruff_python_formatter/src/expression/expr_if_exp.rs b/crates/ruff_python_formatter/src/expression/expr_if_exp.rs index 980571d853..0a2ffcf584 100644 --- a/crates/ruff_python_formatter/src/expression/expr_if_exp.rs +++ b/crates/ruff_python_formatter/src/expression/expr_if_exp.rs @@ -1,21 +1,42 @@ -use crate::comments::Comments; +use crate::comments::{leading_comments, Comments}; use crate::expression::parentheses::{ default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize, }; -use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter}; -use ruff_formatter::{write, Buffer, FormatResult}; +use crate::{AsFormat, FormatNodeRule, PyFormatter}; +use ruff_formatter::prelude::{group, soft_line_break_or_space, space, text}; +use ruff_formatter::{format_args, write, Buffer, FormatResult}; use rustpython_parser::ast::ExprIfExp; #[derive(Default)] pub struct FormatExprIfExp; impl FormatNodeRule for FormatExprIfExp { - fn fmt_fields(&self, _item: &ExprIfExp, f: &mut PyFormatter) -> FormatResult<()> { + fn fmt_fields(&self, item: &ExprIfExp, f: &mut PyFormatter) -> FormatResult<()> { + let ExprIfExp { + range: _, + test, + body, + orelse, + } = item; + let comments = f.context().comments().clone(); + // We place `if test` and `else orelse` on a single line, so the `test` and `orelse` leading + // comments go on the line before the `if` or `else` instead of directly ahead `test` or + // `orelse` write!( f, - [not_yet_implemented_custom_text( - "NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false" - )] + [group(&format_args![ + body.format(), + soft_line_break_or_space(), + leading_comments(comments.leading_comments(test.as_ref())), + text("if"), + space(), + test.format(), + soft_line_break_or_space(), + leading_comments(comments.leading_comments(orelse.as_ref())), + text("else"), + space(), + orelse.format() + ])] ) } } diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@conditional_expression.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@conditional_expression.py.snap index cc7b50679b..57ef1898fb 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@conditional_expression.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@conditional_expression.py.snap @@ -79,7 +79,7 @@ def something(): ```diff --- Black +++ Ruff -@@ -1,90 +1,50 @@ +@@ -1,20 +1,16 @@ long_kwargs_single_line = my_function( foo="test, this is a sample value", - bar=( @@ -87,7 +87,9 @@ def something(): - if some_boolean_variable - else some_fallback_value_foo_bar_baz - ), -+ bar=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, ++ bar=some_long_value_name_foo_bar_baz ++ if some_boolean_variable ++ else some_fallback_value_foo_bar_baz, baz="hello, this is a another value", ) @@ -98,33 +100,13 @@ def something(): - if some_boolean_variable - else some_fallback_value_foo_bar_baz - ), -+ bar=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, ++ bar=some_long_value_name_foo_bar_baz ++ if some_boolean_variable ++ else some_fallback_value_foo_bar_baz, baz="hello, this is a another value", ) - imploding_kwargs = my_function( - foo="test, this is a sample value", -- bar=a if foo else b, -+ bar=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, - baz="hello, this is a another value", - ) - --imploding_line = 1 if 1 + 1 == 2 else 0 -+imploding_line = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false - --exploding_line = ( -- "hello this is a slightly long string" -- if some_long_value_name_foo_bar_baz -- else "this one is a little shorter" --) -+exploding_line = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false - - positional_argument_test( -- some_long_value_name_foo_bar_baz -- if some_boolean_variable -- else some_fallback_value_foo_bar_baz -+ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false - ) +@@ -40,11 +36,9 @@ def weird_default_argument( @@ -133,21 +115,15 @@ def something(): - if SOME_CONSTANT - else some_fallback_value_foo_bar_baz - ), -+ x=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false ++ x=some_long_value_name_foo_bar_baz ++ if SOME_CONSTANT ++ else some_fallback_value_foo_bar_baz, ): pass - --nested = ( -- "hello this is a slightly long string" -- if ( -- some_long_value_name_foo_bar_baz -- if nesting_test_expressions -- else some_fallback_value_foo_bar_baz -- ) -- else "this one is a little shorter" --) -+nested = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false +@@ -59,26 +53,14 @@ + else "this one is a little shorter" + ) -generator_expression = ( - ( @@ -174,13 +150,6 @@ def something(): ) - def something(): - clone._iterable_class = ( -- NamedValuesListIterable -- if named -- else FlatValuesListIterable if flat else ValuesListIterable -+ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false - ) ``` ## Ruff Output @@ -188,38 +157,58 @@ def something(): ```py long_kwargs_single_line = my_function( foo="test, this is a sample value", - bar=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, + bar=some_long_value_name_foo_bar_baz + if some_boolean_variable + else some_fallback_value_foo_bar_baz, baz="hello, this is a another value", ) multiline_kwargs_indented = my_function( foo="test, this is a sample value", - bar=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, + bar=some_long_value_name_foo_bar_baz + if some_boolean_variable + else some_fallback_value_foo_bar_baz, baz="hello, this is a another value", ) imploding_kwargs = my_function( foo="test, this is a sample value", - bar=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, + bar=a if foo else b, baz="hello, this is a another value", ) -imploding_line = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false +imploding_line = 1 if 1 + 1 == 2 else 0 -exploding_line = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false +exploding_line = ( + "hello this is a slightly long string" + if some_long_value_name_foo_bar_baz + else "this one is a little shorter" +) positional_argument_test( - NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false + some_long_value_name_foo_bar_baz + if some_boolean_variable + else some_fallback_value_foo_bar_baz ) def weird_default_argument( - x=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false + x=some_long_value_name_foo_bar_baz + if SOME_CONSTANT + else some_fallback_value_foo_bar_baz, ): pass -nested = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false +nested = ( + "hello this is a slightly long string" + if ( + some_long_value_name_foo_bar_baz + if nesting_test_expressions + else some_fallback_value_foo_bar_baz + ) + else "this one is a little shorter" +) generator_expression = (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in []) @@ -234,7 +223,9 @@ def limit_offset_sql(self, low_mark, high_mark): def something(): clone._iterable_class = ( - NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false + NamedValuesListIterable + if named + else FlatValuesListIterable if flat else ValuesListIterable ) ``` 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 729478a04b..b743d9c808 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 @@ -274,7 +274,7 @@ last_call() Name None True -@@ -31,33 +31,39 @@ +@@ -31,18 +31,15 @@ -1 ~int and not v1 ^ 123 + v2 | True (~int) and (not ((v1 ^ (123 + v2)) | True)) @@ -291,16 +291,6 @@ last_call() - "port1": port1_resource, - "port2": port2_resource, -}[port_id] --1 if True else 2 --str or None if True else str or bytes or None --(str or None) if True else (str or bytes or None) --str or None if (1 if True else 2) else str or bytes or None --(str or None) if (1 if True else 2) else (str or bytes or None) --( -- (super_long_variable_name or None) -- if (1 if super_long_test_name else 2) -- else (str or bytes or None) --) +lambda x: True +lambda x: True +lambda x: True @@ -308,25 +298,14 @@ last_call() +lambda x: True +manylambdas = lambda x: True +foo = lambda x: True -+NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -+NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -+NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -+NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -+NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -+(NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false) - {"2.7": dead, "3.7": (long_live or die_hard)} + 1 if True else 2 + str or None if True else str or bytes or None + (str or None) if True else (str or bytes or None) +@@ -57,7 +54,13 @@ {"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}} {**a, **b, **c} --{"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")} + {"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")} -({"a": "b"}, (True or False), (+value), "string", b"bytes") or None -+{ -+ "2.7", -+ "3.6", -+ "3.7", -+ "3.8", -+ "3.9", -+ (NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false), -+} +( + {"a": "b"}, + (True or False), @@ -337,7 +316,7 @@ last_call() () (1,) (1, 2) -@@ -69,40 +75,37 @@ +@@ -69,40 +72,37 @@ 2, 3, ] @@ -396,7 +375,7 @@ last_call() Python3 > Python2 > COBOL Life is Life call() -@@ -115,10 +118,10 @@ +@@ -115,10 +115,10 @@ arg, another, kwarg="hey", @@ -410,7 +389,7 @@ last_call() call(**self.screen_kwargs) call(b, **self.screen_kwargs) lukasz.langa.pl -@@ -131,34 +134,28 @@ +@@ -131,34 +131,28 @@ tuple[str, ...] tuple[str, int, float, dict[str, int]] tuple[ @@ -458,26 +437,16 @@ last_call() numpy[0, :] numpy[:, i] numpy[0, :2] -@@ -172,20 +169,27 @@ +@@ -172,7 +166,7 @@ numpy[1 : c + 1, c] numpy[-(c + 1) :, d] numpy[:, l[-2]] -numpy[:, ::-1] +numpy[:, :: -1] numpy[np.newaxis, :] --(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None) -+NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false + (str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None) {"2.7": dead, "3.7": long_live or die_hard} --{"2.7", "3.6", "3.7", "3.8", "3.9", "4.0" if gilectomy else "3.10"} -+{ -+ "2.7", -+ "3.6", -+ "3.7", -+ "3.8", -+ "3.9", -+ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, -+} - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] +@@ -181,11 +175,11 @@ (SomeName) SomeName (Good, Bad, Ugly) @@ -494,7 +463,7 @@ last_call() { "id": "1", "type": "type", -@@ -200,32 +204,22 @@ +@@ -200,32 +194,22 @@ c = 1 d = (1,) + a + (2,) e = (1,).count(1) @@ -537,7 +506,7 @@ last_call() Ø = set() authors.łukasz.say_thanks() mapping = { -@@ -237,29 +231,29 @@ +@@ -237,29 +221,33 @@ def gen(): @@ -564,7 +533,11 @@ last_call() -), "Short message" -assert parens is TooMany +print(*NOT_YET_IMPLEMENTED_ExprStarred) -+print(**NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false) ++print( ++ **{1: 3} ++ if False ++ else {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} ++) +print(*NOT_YET_IMPLEMENTED_ExprStarred) +NOT_YET_IMPLEMENTED_StmtAssert +NOT_YET_IMPLEMENTED_StmtAssert @@ -580,7 +553,7 @@ last_call() ... for i in call(): ... -@@ -328,13 +322,18 @@ +@@ -328,13 +316,18 @@ ): return True if ( @@ -602,7 +575,7 @@ last_call() ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n ): return True -@@ -342,7 +341,8 @@ +@@ -342,7 +335,8 @@ ~aaaaaaaaaaaaaaaa.a + aaaaaaaaaaaaaaaa.b - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e @@ -659,23 +632,20 @@ lambda x: True lambda x: True manylambdas = lambda x: True foo = lambda x: True -NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -(NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false) +1 if True else 2 +str or None if True else str or bytes or None +(str or None) if True else (str or bytes or None) +str or None if (1 if True else 2) else str or bytes or None +(str or None) if (1 if True else 2) else (str or bytes or None) +( + (super_long_variable_name or None) + if (1 if super_long_test_name else 2) + else (str or bytes or None) +) {"2.7": dead, "3.7": (long_live or die_hard)} {"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}} {**a, **b, **c} -{ - "2.7", - "3.6", - "3.7", - "3.8", - "3.9", - (NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false), -} +{"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")} ( {"a": "b"}, (True or False), @@ -790,16 +760,9 @@ numpy[-(c + 1) :, d] numpy[:, l[-2]] numpy[:, :: -1] numpy[np.newaxis, :] -NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false +(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None) {"2.7": dead, "3.7": long_live or die_hard} -{ - "2.7", - "3.6", - "3.7", - "3.8", - "3.9", - NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, -} +{"2.7", "3.6", "3.7", "3.8", "3.9", "4.0" if gilectomy else "3.10"} [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] (SomeName) SomeName @@ -861,7 +824,11 @@ async def f(): print(*NOT_YET_IMPLEMENTED_ExprStarred) -print(**NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false) +print( + **{1: 3} + if False + else {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value for key, value in NOT_IMPLEMENTED_dict} +) print(*NOT_YET_IMPLEMENTED_ExprStarred) NOT_YET_IMPLEMENTED_StmtAssert NOT_YET_IMPLEMENTED_StmtAssert diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap index 013a97677b..48167498b5 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap @@ -211,7 +211,7 @@ d={'a':1, # Comment 1 # Comment 2 -@@ -17,30 +16,54 @@ +@@ -17,30 +16,44 @@ # fmt: off def func_no_args(): @@ -268,35 +268,15 @@ d={'a':1, + + # fmt: on --def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""): + def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""): - offset = attr.ib(default=attr.Factory(lambda: _r.uniform(1, 2))) - assert task._cancel_stack[: len(old_stack)] == old_stack -+def spaces( -+ a=1, -+ b=(), -+ c=[], -+ d={}, -+ e=True, -+ f=-1, -+ g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, -+ h="", -+ i=r"", -+): + offset = attr.ib(default=attr.Factory(lambda x: True)) + NOT_YET_IMPLEMENTED_StmtAssert def spaces_types( -@@ -50,7 +73,7 @@ - d: dict = {}, - e: bool = True, - f: int = -1, -- g: int = 1 if False else 2, -+ g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, - h: str = "", - i: str = r"", - ): -@@ -63,55 +86,54 @@ +@@ -63,55 +76,54 @@ something = { # fmt: off @@ -371,7 +351,7 @@ d={'a':1, # fmt: on -@@ -132,10 +154,10 @@ +@@ -132,10 +144,10 @@ """Another known limitation.""" # fmt: on # fmt: off @@ -386,7 +366,7 @@ d={'a':1, # fmt: on # fmt: off # ...but comments still get reformatted even though they should not be -@@ -153,9 +175,7 @@ +@@ -153,9 +165,7 @@ ) ) # fmt: off @@ -397,7 +377,7 @@ d={'a':1, # fmt: on _type_comment_re = re.compile( r""" -@@ -178,7 +198,7 @@ +@@ -178,7 +188,7 @@ $ """, # fmt: off @@ -406,7 +386,7 @@ d={'a':1, # fmt: on ) -@@ -216,8 +236,7 @@ +@@ -216,8 +226,7 @@ xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5, ) # fmt: off @@ -476,17 +456,7 @@ def function_signature_stress_test( # fmt: on -def spaces( - a=1, - b=(), - c=[], - d={}, - e=True, - f=-1, - g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, - h="", - i=r"", -): +def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""): offset = attr.ib(default=attr.Factory(lambda x: True)) NOT_YET_IMPLEMENTED_StmtAssert @@ -498,7 +468,7 @@ def spaces_types( d: dict = {}, e: bool = True, f: int = -1, - g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, + g: int = 1 if False else 2, h: str = "", i: str = r"", ): diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap index 3536f09059..2ccef6cead 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap @@ -126,7 +126,7 @@ def __await__(): return (yield) if False: ... for i in range(10): -@@ -41,12 +40,22 @@ +@@ -41,12 +40,12 @@ debug: bool = False, **kwargs, ) -> str: @@ -134,35 +134,15 @@ def __await__(): return (yield) + return text[number : -1] --def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""): + def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""): - offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200000))) - assert task._cancel_stack[: len(old_stack)] == old_stack -+def spaces( -+ a=1, -+ b=(), -+ c=[], -+ d={}, -+ e=True, -+ f=-1, -+ g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, -+ h="", -+ i=r"", -+): + offset = attr.ib(default=attr.Factory(lambda x: True)) + NOT_YET_IMPLEMENTED_StmtAssert def spaces_types( -@@ -56,7 +65,7 @@ - d: dict = {}, - e: bool = True, - f: int = -1, -- g: int = 1 if False else 2, -+ g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, - h: str = "", - i: str = r"", - ): -@@ -64,19 +73,17 @@ +@@ -64,19 +63,17 @@ def spaces2(result=_core.Value(None)): @@ -190,7 +170,7 @@ def __await__(): return (yield) def long_lines(): -@@ -135,14 +142,8 @@ +@@ -135,14 +132,8 @@ a, **kwargs, ) -> A: @@ -257,17 +237,7 @@ def function_signature_stress_test( return text[number : -1] -def spaces( - a=1, - b=(), - c=[], - d={}, - e=True, - f=-1, - g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, - h="", - i=r"", -): +def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""): offset = attr.ib(default=attr.Factory(lambda x: True)) NOT_YET_IMPLEMENTED_StmtAssert @@ -279,7 +249,7 @@ def spaces_types( d: dict = {}, e: bool = True, f: int = -1, - g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, + g: int = 1 if False else 2, h: str = "", i: str = r"", ): diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__if.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__if.py.snap new file mode 100644 index 0000000000..3b563f0454 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__if.py.snap @@ -0,0 +1,84 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/if.py +--- +## Input +```py +a1 = 1 if True else 2 + +a2 = "this is a very long text that will make the group break to check that parentheses are added" if True else 2 + +# These comment should be kept in place +b1 = ( + # We return "a" ... + "a" # that's our True value + # ... if this condition matches ... + if True # that's our test + # ... otherwise we return "b" + else "b" # that's our False value +) + +# These only need to be stable, bonus is we also keep the order +c1 = ( + "a" # 1 + if # 2 + True # 3 + else # 4 + "b" # 5 +) +c2 = ( + "a" # 1 + # 2 + if # 3 + # 4 + True # 5 + # 6 + else # 7 + # 8 + "b" # 9 +) +``` + +## Output +```py +a1 = 1 if True else 2 + +a2 = ( + "this is a very long text that will make the group break to check that parentheses are added" + if True + else 2 +) + +# These comment should be kept in place +b1 = ( + # We return "a" ... + "a" # that's our True value + # ... if this condition matches ... + if True # that's our test + # ... otherwise we return "b" + else "b" # that's our False value +) + +# These only need to be stable, bonus is we also keep the order +c1 = ( + "a" # 1 + # 2 + if True # 3 + # 4 + else "b" # 5 +) +c2 = ( + "a" # 1 + # 2 + # 3 + # 4 + if True # 5 + # 6 + # 7 + # 8 + else "b" # 9 +) +``` + + +