From 773e79b481278ae74f225db1ae2927017716bd15 Mon Sep 17 00:00:00 2001 From: David Szotten Date: Tue, 20 Jun 2023 10:25:08 +0100 Subject: [PATCH] basic formatting for ExprDict (#5167) --- .../test/fixtures/ruff/expression/dict.py | 50 ++++++ .../src/comments/placement.rs | 73 ++++++++- .../src/expression/expr_dict.rs | 101 +++++++++++- ...er__tests__black_test__collections_py.snap | 32 ++-- ...atter__tests__black_test__comments_py.snap | 36 +++-- ...ter__tests__black_test__expression_py.snap | 144 +++++++++++------- ...atter__tests__black_test__fmtonoff_py.snap | 47 +++--- ...atter__tests__black_test__function_py.snap | 38 ++--- ...lack_test__function_trailing_comma_py.snap | 51 ++++--- ...ests__black_test__power_op_spacing_py.snap | 10 +- ...t__trailing_comma_optional_parens3_py.snap | 16 +- ...sts__ruff_test__expression__binary_py.snap | 7 +- ...tests__ruff_test__expression__dict_py.snap | 117 ++++++++++++++ crates/ruff_python_formatter/src/trivia.rs | 4 + 14 files changed, 556 insertions(+), 170 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/dict.py create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__dict_py.snap diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/dict.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/dict.py new file mode 100644 index 0000000000000..f8cf5a93512ed --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/dict.py @@ -0,0 +1,50 @@ +# before +{ # open + key# key + : # colon + value# value +} # close +# after + + +{**d} + +{**a, # leading +** # middle +b # trailing +} + +{ +** # middle with single item +b +} + +{ + # before + ** # between + b, +} + +{ + **a # comment before preceeding node's comma + , + # before + ** # between + b, +} + +{} + +{1:2,} + +{1:2, + 3:4,} + +{asdfsadfalsdkjfhalsdkjfhalskdjfhlaksjdfhlaskjdfhlaskjdfhlaksdjfh: 1, adsfadsflasdflasdfasdfasdasdf: 2} + +mapping = { + A: 0.25 * (10.0 / 12), + B: 0.1 * (10.0 / 12), + C: 0.1 * (10.0 / 12), + D: 0.1 * (10.0 / 12), +} diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 2bfe71690a5c6..3c6742508fcd4 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -1,6 +1,6 @@ use crate::comments::visitor::{CommentPlacement, DecoratedComment}; use crate::comments::CommentTextPosition; -use crate::trivia::{SimpleTokenizer, TokenKind}; +use crate::trivia::{SimpleTokenizer, Token, TokenKind}; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::source_code::Locator; use ruff_python_ast::whitespace; @@ -29,6 +29,7 @@ pub(super) fn place_comment<'a>( .or_else(|comment| handle_positional_only_arguments_separator_comment(comment, locator)) .or_else(|comment| handle_trailing_binary_expression_left_or_operator_comment(comment, locator)) .or_else(handle_leading_function_with_decorators_comment) + .or_else(|comment| handle_dict_unpacking_comment(comment, locator)) } /// Handles leading comments in front of a match case or a trailing comment of the `match` statement. @@ -885,6 +886,76 @@ fn handle_leading_function_with_decorators_comment(comment: DecoratedComment) -> } } +/// Handles comments between `**` and the variable name in dict unpacking +/// It attaches these to the appropriate value node +/// +/// ```python +/// { +/// ** # comment between `**` and the variable name +/// value +/// ... +/// } +/// ``` +fn handle_dict_unpacking_comment<'a>( + comment: DecoratedComment<'a>, + locator: &Locator, +) -> CommentPlacement<'a> { + match comment.enclosing_node() { + // TODO: can maybe also add AnyNodeRef::Arguments here, but tricky to test due to + // https://github.com/astral-sh/ruff/issues/5176 + AnyNodeRef::ExprDict(_) => {} + _ => { + return CommentPlacement::Default(comment); + } + }; + + // no node after our comment so we can't be between `**` and the name (node) + let Some(following) = comment.following_node() else { + return CommentPlacement::Default(comment); + }; + + // we look at tokens between the previous node (or the start of the dict) + // and the comment + let preceding_end = match comment.preceding_node() { + Some(preceding) => preceding.end(), + None => comment.enclosing_node().start(), + }; + if preceding_end > comment.slice().start() { + return CommentPlacement::Default(comment); + } + let mut tokens = SimpleTokenizer::new( + locator.contents(), + TextRange::new(preceding_end, comment.slice().start()), + ) + .skip_trivia(); + + // we start from the preceding node but we skip its token + if let Some(first) = tokens.next() { + debug_assert!(matches!( + first, + Token { + kind: TokenKind::LBrace | TokenKind::Comma | TokenKind::Colon, + .. + } + )); + } + + // if the remaining tokens from the previous node is exactly `**`, + // re-assign the comment to the one that follows the stars + let mut count = 0; + for token in tokens { + if token.kind != TokenKind::Star { + return CommentPlacement::Default(comment); + } + count += 1; + } + if count == 2 { + return CommentPlacement::trailing(following, comment); + } + + CommentPlacement::Default(comment) +} + /// 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_dict.rs b/crates/ruff_python_formatter/src/expression/expr_dict.rs index 3ccc6a4dc8e8f..11d1a3ca85c9e 100644 --- a/crates/ruff_python_formatter/src/expression/expr_dict.rs +++ b/crates/ruff_python_formatter/src/expression/expr_dict.rs @@ -1,21 +1,108 @@ -use crate::comments::Comments; +use crate::comments::{dangling_node_comments, leading_comments, Comments}; +use crate::context::PyFormatContext; use crate::expression::parentheses::{ default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize, }; -use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter}; +use crate::prelude::*; +use crate::trivia::Token; +use crate::trivia::{first_non_trivia_token, TokenKind}; +use crate::USE_MAGIC_TRAILING_COMMA; +use crate::{FormatNodeRule, PyFormatter}; +use ruff_formatter::format_args; use ruff_formatter::{write, Buffer, FormatResult}; -use rustpython_parser::ast::ExprDict; +use ruff_python_ast::prelude::Ranged; +use rustpython_parser::ast::{Expr, ExprDict}; #[derive(Default)] pub struct FormatExprDict; +struct KeyValuePair<'a> { + key: &'a Option, + value: &'a Expr, +} + +impl Format> for KeyValuePair<'_> { + fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { + if let Some(key) = self.key { + write!( + f, + [group(&format_args![ + key.format(), + text(":"), + space(), + self.value.format() + ])] + ) + } else { + let comments = f.context().comments().clone(); + let leading_value_comments = comments.leading_comments(self.value.into()); + write!( + f, + [ + // make sure the leading comments are hoisted past the `**` + leading_comments(leading_value_comments), + group(&format_args![text("**"), self.value.format()]) + ] + ) + } + } +} + impl FormatNodeRule for FormatExprDict { - fn fmt_fields(&self, _item: &ExprDict, f: &mut PyFormatter) -> FormatResult<()> { + fn fmt_fields(&self, item: &ExprDict, f: &mut PyFormatter) -> FormatResult<()> { + let ExprDict { + range: _, + keys, + values, + } = item; + + let last = match &values[..] { + [] => { + return write!( + f, + [ + &text("{"), + block_indent(&dangling_node_comments(item)), + &text("}"), + ] + ); + } + [.., last] => last, + }; + let magic_trailing_comma = USE_MAGIC_TRAILING_COMMA + && matches!( + first_non_trivia_token(last.range().end(), f.context().contents()), + Some(Token { + kind: TokenKind::Comma, + .. + }) + ); + + debug_assert_eq!(keys.len(), values.len()); + + let joined = format_with(|f| { + f.join_with(format_args!(text(","), soft_line_break_or_space())) + .entries( + keys.iter() + .zip(values) + .map(|(key, value)| KeyValuePair { key, value }), + ) + .finish() + }); + + let block = if magic_trailing_comma { + block_indent + } else { + soft_block_indent + }; + write!( f, - [not_yet_implemented_custom_text( - "{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value}" - )] + [group(&format_args![ + text("{"), + block(&format_args![joined, if_group_breaks(&text(",")),]), + text("}") + ])] ) } diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap index 6a6c2179d6a13..0b9b054e8aa75 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap @@ -84,7 +84,7 @@ if True: ```diff --- Black +++ Ruff -@@ -1,99 +1,52 @@ +@@ -1,99 +1,56 @@ -import core, time, a +NOT_YET_IMPLEMENTED_StmtImport @@ -143,24 +143,24 @@ if True: - "dddddddddddddddddddddddddddddddddddddddd", + "NOT_YET_IMPLEMENTED_STRING", ] --{ + { - "oneple": (1,), --} ++ "NOT_YET_IMPLEMENTED_STRING": (1,), + } -{"oneple": (1,)} -["ls", "lsoneple/%s" % (foo,)] -x = {"oneple": (1,)} --y = { ++{"NOT_YET_IMPLEMENTED_STRING": (1,)} ++["NOT_YET_IMPLEMENTED_STRING", "NOT_YET_IMPLEMENTED_STRING" % (foo,)] ++x = {"NOT_YET_IMPLEMENTED_STRING": (1,)} + y = { - "oneple": (1,), --} ++ "NOT_YET_IMPLEMENTED_STRING": (1,), + } -assert False, ( - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s" - % bar -) -+{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} -+{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} -+["NOT_YET_IMPLEMENTED_STRING", "NOT_YET_IMPLEMENTED_STRING" % (foo,)] -+x = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} -+y = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +NOT_YET_IMPLEMENTED_StmtAssert # looping over a 1-tuple should also not get wrapped @@ -245,11 +245,15 @@ nested_long_lines = [ (1, 2, 3), "NOT_YET_IMPLEMENTED_STRING", ] -{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} -{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +{ + "NOT_YET_IMPLEMENTED_STRING": (1,), +} +{"NOT_YET_IMPLEMENTED_STRING": (1,)} ["NOT_YET_IMPLEMENTED_STRING", "NOT_YET_IMPLEMENTED_STRING" % (foo,)] -x = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} -y = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +x = {"NOT_YET_IMPLEMENTED_STRING": (1,)} +y = { + "NOT_YET_IMPLEMENTED_STRING": (1,), +} NOT_YET_IMPLEMENTED_StmtAssert # looping over a 1-tuple should also not get wrapped diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_py.snap index ec61d998465af..5a2dcb790f1c7 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_py.snap @@ -114,24 +114,24 @@ async def wat(): # Has many lines. Many, many lines. # Many, many, many lines. -"""Module docstring. -- ++"NOT_YET_IMPLEMENTED_STRING" + -Possibly also many, many lines. -""" -- ++NOT_YET_IMPLEMENTED_StmtImport ++NOT_YET_IMPLEMENTED_StmtImport + -import os.path -import sys - -import a -from b.c import X # some noqa comment -+"NOT_YET_IMPLEMENTED_STRING" - +- -try: - import fast -except ImportError: - import slow as fast -+NOT_YET_IMPLEMENTED_StmtImport -+NOT_YET_IMPLEMENTED_StmtImport - +- +NOT_YET_IMPLEMENTED_StmtImport +NOT_YET_IMPLEMENTED_StmtImportFrom # some noqa comment @@ -140,7 +140,7 @@ async def wat(): y = 1 ( # some strings -@@ -30,67 +21,46 @@ +@@ -30,67 +21,50 @@ def function(default=None): @@ -168,22 +168,26 @@ async def wat(): # Explains why we use global state. -GLOBAL_STATE = {"a": a(1), "b": a(2), "c": a(3)} -+GLOBAL_STATE = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} ++GLOBAL_STATE = { ++ "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_call(), ++ "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_call(), ++ "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_call(), ++} # Another comment! # This time two lines. - - +- +- -class Foo: - """Docstring for class Foo. Example from Sphinx docs.""" -- + - #: Doc comment for class attribute Foo.bar. - #: It can have multiple lines. - bar = 1 - - flox = 1.5 #: Doc comment for Foo.flox. One line only. -- + - baz = 2 - """Docstring for class attribute Foo.baz.""" - @@ -264,7 +268,11 @@ def function(default=None): # Explains why we use global state. -GLOBAL_STATE = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +GLOBAL_STATE = { + "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_call(), + "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_call(), + "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_call(), +} # Another comment! diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap index b2cc26d4f2969..bb84670f91294 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap @@ -276,7 +276,7 @@ last_call() Name None True -@@ -7,294 +8,236 @@ +@@ -7,226 +8,225 @@ 1 1.0 1j @@ -339,9 +339,6 @@ last_call() -) -{"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", ("4.0" if gilectomy else "3.10")} --({"a": "b"}, (True or False), (+value), "string", b"bytes") or None +NOT_YET_IMPLEMENTED_ExprUnaryOp +NOT_YET_IMPLEMENTED_ExprUnaryOp +NOT_YET_IMPLEMENTED_ExprUnaryOp @@ -363,9 +360,22 @@ last_call() +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_dict_key: NOT_IMPLEMENTED_dict_value} -+{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} -+{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} ++{ ++ "NOT_YET_IMPLEMENTED_STRING": dead, ++ "NOT_YET_IMPLEMENTED_STRING": ( ++ NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2 ++ ), ++} ++{ ++ "NOT_YET_IMPLEMENTED_STRING": dead, ++ "NOT_YET_IMPLEMENTED_STRING": ( ++ NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2 ++ ), ++ **{"NOT_YET_IMPLEMENTED_STRING": verygood}, ++} + {**a, **b, **c} +-{"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 +{ + "NOT_YET_IMPLEMENTED_STRING", + "NOT_YET_IMPLEMENTED_STRING", @@ -392,6 +402,11 @@ last_call() - *a, 4, 5, +-] +-[ +- 4, +- *a, +- 5, + 6, + 7, + 8, @@ -400,11 +415,6 @@ last_call() + (NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2), + (NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2), ] --[ -- 4, -- *a, -- 5, --] +[1, 2, 3] +[NOT_YET_IMPLEMENTED_ExprStarred] +[NOT_YET_IMPLEMENTED_ExprStarred] @@ -495,16 +505,11 @@ last_call() +NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key] +NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key] +NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -+{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} { - k: v - for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension -+ "NOT_YET_IMPLEMENTED_STRING", -+ "NOT_YET_IMPLEMENTED_STRING", -+ "NOT_YET_IMPLEMENTED_STRING", -+ "NOT_YET_IMPLEMENTED_STRING", -+ "NOT_YET_IMPLEMENTED_STRING", -+ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, ++ "NOT_YET_IMPLEMENTED_STRING": dead, ++ "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2, } -Python3 > Python2 > COBOL -Life is Life @@ -541,6 +546,14 @@ last_call() -] -very_long_variable_name_filters: t.List[ - t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], ++{ ++ "NOT_YET_IMPLEMENTED_STRING", ++ "NOT_YET_IMPLEMENTED_STRING", ++ "NOT_YET_IMPLEMENTED_STRING", ++ "NOT_YET_IMPLEMENTED_STRING", ++ "NOT_YET_IMPLEMENTED_STRING", ++ NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, ++} +[ + 1, + 2, @@ -602,21 +615,26 @@ last_call() -((i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))) -(((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)) -(*starred,) --{ ++(i for i in []) ++(i for i in []) ++(i for i in []) ++(i for i in []) ++(NOT_YET_IMPLEMENTED_ExprStarred,) + { - "id": "1", - "type": "type", - "started_at": now(), - "ended_at": now() + timedelta(days=10), - "priority": 1, - "import_session_id": 1, -- **kwargs, --} -+(i for i in []) -+(i for i in []) -+(i for i in []) -+(i for i in []) -+(NOT_YET_IMPLEMENTED_ExprStarred,) -+{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} ++ "NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING", ++ "NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING", ++ "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_call(), ++ "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_call() + NOT_IMPLEMENTED_call(), ++ "NOT_YET_IMPLEMENTED_STRING": 1, ++ "NOT_YET_IMPLEMENTED_STRING": 1, + **kwargs, + } a = (1,) b = (1,) c = 1 @@ -659,17 +677,14 @@ last_call() ) -Ø = set() -authors.łukasz.say_thanks() --mapping = { -- A: 0.25 * (10.0 / 12), -- B: 0.1 * (10.0 / 12), -- C: 0.1 * (10.0 / 12), -- D: 0.1 * (10.0 / 12), --} +result = NOT_IMPLEMENTED_call() +result = NOT_IMPLEMENTED_call() +Ø = NOT_IMPLEMENTED_call() +NOT_IMPLEMENTED_call() -+mapping = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} + mapping = { + A: 0.25 * (10.0 / 12), + B: 0.1 * (10.0 / 12), +@@ -236,65 +236,35 @@ def gen(): @@ -699,14 +714,6 @@ last_call() -for (x,) in (1,), (2,), (3,): - ... -for y in (): -- ... --for z in (i for i in (1, 2, 3)): -- ... --for i in call(): -- ... --for j in 1 + (2 + 3): -- ... --while this and that: +NOT_IMPLEMENTED_call() +NOT_IMPLEMENTED_call() +NOT_IMPLEMENTED_call() @@ -720,6 +727,14 @@ last_call() +NOT_YET_IMPLEMENTED_StmtFor +while NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2: ... +-for z in (i for i in (1, 2, 3)): +- ... +-for i in call(): +- ... +-for j in 1 + (2 + 3): +- ... +-while this and that: +- ... -for ( - addr_family, - addr_type, @@ -758,7 +773,7 @@ last_call() return True if ( aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -@@ -327,24 +270,44 @@ +@@ -327,24 +297,44 @@ ): return True if ( @@ -815,7 +830,7 @@ last_call() ): return True ( -@@ -363,8 +326,9 @@ +@@ -363,8 +353,9 @@ bbbb >> bbbb * bbbb ( aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa @@ -880,9 +895,20 @@ 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_dict_key: NOT_IMPLEMENTED_dict_value} -{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} -{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +{ + "NOT_YET_IMPLEMENTED_STRING": dead, + "NOT_YET_IMPLEMENTED_STRING": ( + NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2 + ), +} +{ + "NOT_YET_IMPLEMENTED_STRING": dead, + "NOT_YET_IMPLEMENTED_STRING": ( + NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2 + ), + **{"NOT_YET_IMPLEMENTED_STRING": verygood}, +} +{**a, **b, **c} { "NOT_YET_IMPLEMENTED_STRING", "NOT_YET_IMPLEMENTED_STRING", @@ -988,7 +1014,10 @@ NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key] NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key] NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key] NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false -{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +{ + "NOT_YET_IMPLEMENTED_STRING": dead, + "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_bool_op1 and NOT_IMPLEMENTED_bool_op2, +} { "NOT_YET_IMPLEMENTED_STRING", "NOT_YET_IMPLEMENTED_STRING", @@ -1019,7 +1048,15 @@ SomeName (i for i in []) (i for i in []) (NOT_YET_IMPLEMENTED_ExprStarred,) -{NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +{ + "NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING", + "NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING", + "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_call(), + "NOT_YET_IMPLEMENTED_STRING": NOT_IMPLEMENTED_call() + NOT_IMPLEMENTED_call(), + "NOT_YET_IMPLEMENTED_STRING": 1, + "NOT_YET_IMPLEMENTED_STRING": 1, + **kwargs, +} a = (1,) b = (1,) c = 1 @@ -1039,7 +1076,12 @@ result = NOT_IMPLEMENTED_call() result = NOT_IMPLEMENTED_call() Ø = NOT_IMPLEMENTED_call() NOT_IMPLEMENTED_call() -mapping = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +mapping = { + A: 0.25 * (10.0 / 12), + B: 0.1 * (10.0 / 12), + C: 0.1 * (10.0 / 12), + D: 0.1 * (10.0 / 12), +} def gen(): diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap index 183ed478e1bae..2dcad260b630c 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap @@ -222,7 +222,7 @@ d={'a':1, # Comment 1 # Comment 2 -@@ -18,109 +16,112 @@ +@@ -18,30 +16,51 @@ # fmt: off def func_no_args(): @@ -284,7 +284,7 @@ d={'a':1, + a=1, + b=(), + c=[], -+ d={NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value}, ++ d={}, + e=True, + f=NOT_YET_IMPLEMENTED_ExprUnaryOp, + g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, @@ -296,11 +296,9 @@ d={'a':1, def spaces_types( - a: int = 1, - b: tuple = (), +@@ -50,77 +69,62 @@ c: list = [], -- d: dict = {}, -+ d: dict = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value}, + d: dict = {}, e: bool = True, - f: int = -1, - g: int = 1 if False else 2, @@ -319,11 +317,11 @@ d={'a':1, ... --something = { -- # fmt: off + something = { + # fmt: off - key: 'value', --} -+something = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} ++ key: "NOT_YET_IMPLEMENTED_STRING", + } def subscriptlist(): @@ -394,7 +392,7 @@ d={'a':1, # fmt: off # hey, that won't work -@@ -130,13 +131,15 @@ +@@ -130,13 +134,15 @@ def on_and_off_broken(): @@ -415,7 +413,7 @@ d={'a':1, # fmt: on # fmt: off # ...but comments still get reformatted even though they should not be -@@ -145,80 +148,21 @@ +@@ -145,80 +151,21 @@ def long_lines(): if True: @@ -427,11 +425,13 @@ d={'a':1, - implicit_default=True, - ) - ) -- # fmt: off ++ NOT_IMPLEMENTED_call() + # fmt: off - a = ( - unnecessary_bracket() - ) -- # fmt: on ++ a = NOT_IMPLEMENTED_call() + # fmt: on - _type_comment_re = re.compile( - r""" - ^ @@ -452,11 +452,9 @@ d={'a':1, - ) - $ - """, -+ NOT_IMPLEMENTED_call() - # fmt: off +- # fmt: off - re.MULTILINE|re.VERBOSE -+ a = NOT_IMPLEMENTED_call() - # fmt: on +- # fmt: on - ) + _type_comment_re = NOT_IMPLEMENTED_call() @@ -503,7 +501,7 @@ d={'a':1, -d={'a':1, - 'b':2} +l = [1, 2, 3] -+d = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} ++d = {"NOT_YET_IMPLEMENTED_STRING": 1, "NOT_YET_IMPLEMENTED_STRING": 2} ``` ## Ruff Output @@ -563,7 +561,7 @@ def spaces( a=1, b=(), c=[], - d={NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value}, + d={}, e=True, f=NOT_YET_IMPLEMENTED_ExprUnaryOp, g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, @@ -578,7 +576,7 @@ def spaces_types( a: int = 1, b: tuple = (), c: list = [], - d: dict = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value}, + d: dict = {}, e: bool = True, f: int = NOT_YET_IMPLEMENTED_ExprUnaryOp, g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, @@ -592,7 +590,10 @@ def spaces2(result=NOT_IMPLEMENTED_call()): ... -something = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +something = { + # fmt: off + key: "NOT_YET_IMPLEMENTED_STRING", +} def subscriptlist(): @@ -676,7 +677,7 @@ NOT_IMPLEMENTED_call() NOT_YET_IMPLEMENTED_ExprYield # No formatting to the end of the file l = [1, 2, 3] -d = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +d = {"NOT_YET_IMPLEMENTED_STRING": 1, "NOT_YET_IMPLEMENTED_STRING": 2} ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap index 9f5f58ae760ba..a82537bcef15d 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap @@ -112,21 +112,21 @@ def __await__(): return (yield) #!/usr/bin/env python3 -import asyncio -import sys -- --from third_party import X, Y, Z +NOT_YET_IMPLEMENTED_StmtImport +NOT_YET_IMPLEMENTED_StmtImport --from library import some_connection, some_decorator +-from third_party import X, Y, Z +NOT_YET_IMPLEMENTED_StmtImportFrom +-from library import some_connection, some_decorator +- -f"trigger 3.6 mode" +NOT_YET_IMPLEMENTED_StmtImportFrom +NOT_YET_IMPLEMENTED_ExprJoinedStr def func_no_args(): -@@ -14,135 +13,87 @@ +@@ -14,39 +13,46 @@ b c if True: @@ -177,7 +177,7 @@ def __await__(): return (yield) + a=1, + b=(), + c=[], -+ d={NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value}, ++ d={}, + e=True, + f=NOT_YET_IMPLEMENTED_ExprUnaryOp, + g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, @@ -189,11 +189,9 @@ def __await__(): return (yield) def spaces_types( - a: int = 1, - b: tuple = (), +@@ -55,71 +61,27 @@ c: list = [], -- d: dict = {}, -+ d: dict = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value}, + d: dict = {}, e: bool = True, - f: int = -1, - g: int = 1 if False else 2, @@ -273,16 +271,7 @@ def __await__(): return (yield) def trailing_comma(): -- mapping = { -- A: 0.25 * (10.0 / 12), -- B: 0.1 * (10.0 / 12), -- C: 0.1 * (10.0 / 12), -- D: 0.1 * (10.0 / 12), -- } -+ mapping = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} - - - def f( +@@ -135,14 +97,8 @@ a, **kwargs, ) -> A: @@ -350,7 +339,7 @@ def spaces( a=1, b=(), c=[], - d={NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value}, + d={}, e=True, f=NOT_YET_IMPLEMENTED_ExprUnaryOp, g=NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, @@ -365,7 +354,7 @@ def spaces_types( a: int = 1, b: tuple = (), c: list = [], - d: dict = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value}, + d: dict = {}, e: bool = True, f: int = NOT_YET_IMPLEMENTED_ExprUnaryOp, g: int = NOT_IMPLEMENTED_true if NOT_IMPLEMENTED_cond else NOT_IMPLEMENTED_false, @@ -391,7 +380,12 @@ def long_lines(): def trailing_comma(): - mapping = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} + mapping = { + A: 0.25 * (10.0 / 12), + B: 0.1 * (10.0 / 12), + C: 0.1 * (10.0 / 12), + D: 0.1 * (10.0 / 12), + } def f( diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap index 3e472e8729298..89e72cdcc0bd4 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap @@ -74,30 +74,27 @@ some_module.some_function( ```diff --- Black +++ Ruff -@@ -1,9 +1,7 @@ - def f( +@@ -2,7 +2,7 @@ a, ): -- d = { + d = { - "key": "value", -- } -+ d = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} ++ "NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING", + } tup = (1,) - -@@ -11,10 +9,7 @@ - a, +@@ -12,8 +12,8 @@ b, ): -- d = { + d = { - "key": "value", - "key2": "value2", -- } -+ d = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} ++ "NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING", ++ "NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING", + } tup = ( 1, - 2, -@@ -24,46 +19,15 @@ +@@ -24,45 +24,18 @@ def f( a: int = 1, ): @@ -136,7 +133,8 @@ some_module.some_function( -def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( - Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] -): -- json = { ++def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]: + json = { - "k": { - "k2": { - "k3": [ @@ -144,13 +142,13 @@ some_module.some_function( - ] - } - } -- } -+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]: -+ json = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} ++ "NOT_YET_IMPLEMENTED_STRING": { ++ "NOT_YET_IMPLEMENTED_STRING": {"NOT_YET_IMPLEMENTED_STRING": [1]}, ++ }, + } - # The type annotation shouldn't get a trailing comma since that would change its type. -@@ -80,35 +44,16 @@ +@@ -80,35 +53,16 @@ pass @@ -198,7 +196,9 @@ some_module.some_function( def f( a, ): - d = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} + d = { + "NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING", + } tup = (1,) @@ -206,7 +206,10 @@ def f2( a, b, ): - d = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} + d = { + "NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING", + "NOT_YET_IMPLEMENTED_STRING": "NOT_YET_IMPLEMENTED_STRING", + } tup = ( 1, 2, @@ -224,7 +227,11 @@ def f( def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]: - json = {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} + json = { + "NOT_YET_IMPLEMENTED_STRING": { + "NOT_YET_IMPLEMENTED_STRING": {"NOT_YET_IMPLEMENTED_STRING": [1]}, + }, + } # The type annotation shouldn't get a trailing comma since that would change its type. diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__power_op_spacing_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__power_op_spacing_py.snap index 4a41269a87376..6358e8d7e3b59 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__power_op_spacing_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__power_op_spacing_py.snap @@ -76,12 +76,8 @@ return np.divide( ```diff --- Black +++ Ruff -@@ -8,56 +8,49 @@ - - - def function_dont_replace_spaces(): -- {**a, **b, **c} -+ {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +@@ -11,53 +11,46 @@ + {**a, **b, **c} -a = 5**~4 @@ -184,7 +180,7 @@ def function_replace_spaces(**kwargs): def function_dont_replace_spaces(): - {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} + {**a, **b, **c} a = 5**NOT_YET_IMPLEMENTED_ExprUnaryOp diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens3_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens3_py.snap index 889aaf9c58941..ea49d311ae749 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens3_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens3_py.snap @@ -30,10 +30,10 @@ if True: - + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.", - "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe", - ) % {"reported_username": reported_username, "report_reason": report_reason} -+ return ( -+ NOT_IMPLEMENTED_call() -+ % {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} -+ ) ++ return NOT_IMPLEMENTED_call() % { ++ "NOT_YET_IMPLEMENTED_STRING": reported_username, ++ "NOT_YET_IMPLEMENTED_STRING": report_reason, ++ } ``` ## Ruff Output @@ -42,10 +42,10 @@ if True: if True: if True: if True: - return ( - NOT_IMPLEMENTED_call() - % {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} - ) + return NOT_IMPLEMENTED_call() % { + "NOT_YET_IMPLEMENTED_STRING": reported_username, + "NOT_YET_IMPLEMENTED_STRING": report_reason, + } ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__binary_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__binary_py.snap index 47374f6ad8a97..6f7abbf1a6e1f 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__binary_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__binary_py.snap @@ -92,7 +92,12 @@ aaaaaaaaaaaaaa + ( dddddddddddddddd, eeeeeee, ) -aaaaaaaaaaaaaa + {NOT_IMPLEMENTED_dict_key: NOT_IMPLEMENTED_dict_value} +aaaaaaaaaaaaaa + { + key1: bbbbbbbbbbbbbbbbbbbbbb, + key2: ccccccccccccccccccccc, + key3: dddddddddddddddd, + key4: eeeeeee, +} aaaaaaaaaaaaaa + { bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__dict_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__dict_py.snap new file mode 100644 index 0000000000000..2c0cba1259e23 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__dict_py.snap @@ -0,0 +1,117 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +--- +## Input +```py +# before +{ # open + key# key + : # colon + value# value +} # close +# after + + +{**d} + +{**a, # leading +** # middle +b # trailing +} + +{ +** # middle with single item +b +} + +{ + # before + ** # between + b, +} + +{ + **a # comment before preceeding node's comma + , + # before + ** # between + b, +} + +{} + +{1:2,} + +{1:2, + 3:4,} + +{asdfsadfalsdkjfhalsdkjfhalskdjfhlaksjdfhlaskjdfhlaskjdfhlaksdjfh: 1, adsfadsflasdflasdfasdfasdasdf: 2} + +mapping = { + A: 0.25 * (10.0 / 12), + B: 0.1 * (10.0 / 12), + C: 0.1 * (10.0 / 12), + D: 0.1 * (10.0 / 12), +} +``` + + + +## Output +```py +# before +{ + # open + key: value, # key # colon # value +} # close +# after + + +{**d} + +{ + **a, # leading + **b, # middle # trailing +} + +{ + **b, # middle with single item +} + +{ + # before + **b, # between +} + +{ + **a, # comment before preceeding node's comma + # before + **b, # between +} + +{} + +{ + 1: 2, +} + +{ + 1: 2, + 3: 4, +} + +{ + asdfsadfalsdkjfhalsdkjfhalskdjfhlaksjdfhlaskjdfhlaskjdfhlaksdjfh: 1, + adsfadsflasdflasdfasdfasdasdf: 2, +} + +mapping = { + A: 0.25 * (10.0 / 12), + B: 0.1 * (10.0 / 12), + C: 0.1 * (10.0 / 12), + D: 0.1 * (10.0 / 12), +} +``` + + diff --git a/crates/ruff_python_formatter/src/trivia.rs b/crates/ruff_python_formatter/src/trivia.rs index 01415e89b18dc..04a06dd404514 100644 --- a/crates/ruff_python_formatter/src/trivia.rs +++ b/crates/ruff_python_formatter/src/trivia.rs @@ -162,6 +162,9 @@ pub(crate) enum TokenKind { /// '/' Slash, + /// '*' + Star, + /// Any other non trivia token. Always has a length of 1 Other, @@ -181,6 +184,7 @@ impl TokenKind { ',' => TokenKind::Comma, ':' => TokenKind::Colon, '/' => TokenKind::Slash, + '*' => TokenKind::Star, _ => TokenKind::Other, } }