From ddca7a712cb8d77eebf4ac906fe6c82dd8cd1299 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 20 Jul 2023 15:41:59 +0200 Subject: [PATCH 01/13] [WIP] Add FormatExprYield and FormatExprYieldFrom --- .../test/fixtures/ruff/expression/yield.py | 31 + .../fixtures/ruff/expression/yield_from.py | 11 + .../src/expression/expr_yield.rs | 42 +- .../src/expression/expr_yield_from.rs | 33 +- .../src/expression/mod.rs | 4 +- ...y@miscellaneous__debug_visitor.py.snap.new | 165 +++ ...us__long_strings_flag_disabled.py.snap.new | 948 +++++++++++++ ...lack_compatibility@py_38__python38.py.snap | 103 -- ...ility@simple_cases__expression.py.snap.new | 1190 +++++++++++++++++ ...ibility@simple_cases__fmtonoff.py.snap.new | 859 ++++++++++++ .../format@expression__yield.py.snap.new | 73 + .../format@expression__yield_from.py.snap.new | 35 + crates/ruff_python_trivia/src/tokenizer.rs | 3 + 13 files changed, 3381 insertions(+), 116 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py create mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap.new create mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap.new delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap create mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap.new create mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap.new create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py new file mode 100644 index 0000000000000..525c658db5f8f --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py @@ -0,0 +1,31 @@ +l = [1,2,3,4] + +def foo(): + yield l + + # some comment + for e in l : yield e # some comment + + for e in l: + + + # some comment + + yield e + + # trail comment + + for e in l: + # comment + yield (((((((e))))))) # Too many parentheses + # comment + + + for ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee in l: + yield ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + + + for x in l: #comment + yield x + (2 * 4) # trailing comment + + diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py new file mode 100644 index 0000000000000..c37539dc2dc20 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py @@ -0,0 +1,11 @@ +l = [1,2,3,4] + + +def foo(): + yield from l # some comment + + # weird indents + yield\ + from\ + l + # indented trailing comment diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index 20fabe7d05d45..7ecc86636880c 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -1,16 +1,46 @@ +use ruff_text_size::{TextRange, TextSize}; use crate::context::PyFormatContext; -use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; -use crate::{not_yet_implemented, FormatNodeRule, PyFormatter}; -use ruff_formatter::{write, Buffer, FormatResult}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize}; +use crate::{not_yet_implemented, FormatNodeRule, PyFormatter, AsFormat}; +use ruff_formatter::{write, Buffer, FormatResult, Format, FormatError, FormatRuleWithOptions}; use ruff_python_ast::node::AnyNodeRef; -use rustpython_parser::ast::ExprYield; +use rustpython_parser::ast::{ExprYield, Ranged}; +use ruff_formatter::prelude::{space, text}; +use ruff_python_whitespace::{SimpleTokenizer, TokenKind}; +use crate::expression::maybe_parenthesize_expression; #[derive(Default)] -pub struct FormatExprYield; +pub struct FormatExprYield{ + parentheses: Option, +} +impl FormatRuleWithOptions> for FormatExprYield { + type Options = Option; + + fn with_options(mut self, options: Self::Options) -> Self { + self.parentheses = options; + self + } +} impl FormatNodeRule for FormatExprYield { fn fmt_fields(&self, item: &ExprYield, f: &mut PyFormatter) -> FormatResult<()> { - write!(f, [not_yet_implemented(item)]) + let ExprYield { + range: _, + value + } = item; + + if let Some(val) = value { + write!( + f, + [&text("yield"), space(), maybe_parenthesize_expression(val, item, Parenthesize::IfRequired)] + )?; + } else { + write!( + f, + [&text("yield")] + )?; + } + Ok(()) } } diff --git a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs index 3f2f6b68422cb..91cbd20cff299 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs @@ -1,16 +1,39 @@ use crate::context::PyFormatContext; -use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize}; use crate::{not_yet_implemented, FormatNodeRule, PyFormatter}; -use ruff_formatter::{write, Buffer, FormatResult}; +use ruff_formatter::{write, Buffer, FormatResult, Format, FormatRuleWithOptions}; use ruff_python_ast::node::AnyNodeRef; -use rustpython_parser::ast::ExprYieldFrom; +use rustpython_parser::ast::{ExprYield, ExprYieldFrom}; +use ruff_formatter::prelude::{space, text}; +use crate::expression::maybe_parenthesize_expression; #[derive(Default)] -pub struct FormatExprYieldFrom; +pub struct FormatExprYieldFrom{ + parentheses: Option, +} +impl FormatRuleWithOptions> for FormatExprYieldFrom { + type Options = Option; + + fn with_options(mut self, options: Self::Options) -> Self { + self.parentheses = options; + self + } +} impl FormatNodeRule for FormatExprYieldFrom { fn fmt_fields(&self, item: &ExprYieldFrom, f: &mut PyFormatter) -> FormatResult<()> { - write!(f, [not_yet_implemented(item)]) + let ExprYieldFrom { + range: _, + value + } = item; + + write!( + f, + [&text("yield from"), space(), maybe_parenthesize_expression(value, item, Parenthesize::IfRequired)] + )?; + + Ok(()) + } } diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 9cc5141e1c340..bb6abbdf647c7 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -78,8 +78,8 @@ impl FormatRule> for FormatExpr { Expr::DictComp(expr) => expr.format().fmt(f), Expr::GeneratorExp(expr) => expr.format().fmt(f), Expr::Await(expr) => expr.format().fmt(f), - Expr::Yield(expr) => expr.format().fmt(f), - Expr::YieldFrom(expr) => expr.format().fmt(f), + Expr::Yield(expr) => expr.format().with_options(Some(parentheses)).fmt(f), + Expr::YieldFrom(expr) => expr.format().with_options(Some(parentheses)).fmt(f), Expr::Compare(expr) => expr.format().with_options(Some(parentheses)).fmt(f), Expr::Call(expr) => expr.format().fmt(f), Expr::FormattedValue(expr) => expr.format().fmt(f), diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap.new new file mode 100644 index 0000000000000..6c174387d81e8 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap.new @@ -0,0 +1,165 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 96 +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/miscellaneous/debug_visitor.py +--- +## Input + +```py +@dataclass +class DebugVisitor(Visitor[T]): + tree_depth: int = 0 + + def visit_default(self, node: LN) -> Iterator[T]: + indent = ' ' * (2 * self.tree_depth) + if isinstance(node, Node): + _type = type_repr(node.type) + out(f'{indent}{_type}', fg='yellow') + self.tree_depth += 1 + for child in node.children: + yield from self.visit(child) + + self.tree_depth -= 1 + out(f'{indent}/{_type}', fg='yellow', bold=False) + else: + _type = token.tok_name.get(node.type, str(node.type)) + out(f'{indent}{_type}', fg='blue', nl=False) + if node.prefix: + # We don't have to handle prefixes for `Node` objects since + # that delegates to the first child anyway. + out(f' {node.prefix!r}', fg='green', bold=False, nl=False) + out(f' {node.value!r}', fg='blue', bold=False) + + @classmethod + def show(cls, code: str) -> None: + """Pretty-prints a given string of `code`. + + Convenience method for debugging. + """ + v: DebugVisitor[None] = DebugVisitor() + list(v.visit(lib2to3_parse(code))) +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -3,24 +3,29 @@ + tree_depth: int = 0 + + def visit_default(self, node: LN) -> Iterator[T]: +- indent = ' ' * (2 * self.tree_depth) ++ indent = " " * (2 * self.tree_depth) + if isinstance(node, Node): + _type = type_repr(node.type) +- out(f'{indent}{_type}', fg='yellow') ++ out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow") + self.tree_depth += 1 + for child in node.children: + yield from self.visit(child) + + self.tree_depth -= 1 +- out(f'{indent}/{_type}', fg='yellow', bold=False) ++ out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow", bold=False) + else: + _type = token.tok_name.get(node.type, str(node.type)) +- out(f'{indent}{_type}', fg='blue', nl=False) ++ out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="blue", nl=False) + if node.prefix: + # We don't have to handle prefixes for `Node` objects since + # that delegates to the first child anyway. +- out(f' {node.prefix!r}', fg='green', bold=False, nl=False) +- out(f' {node.value!r}', fg='blue', bold=False) ++ out( ++ f"NOT_YET_IMPLEMENTED_ExprJoinedStr", ++ fg="green", ++ bold=False, ++ nl=False, ++ ) ++ out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="blue", bold=False) + + @classmethod + def show(cls, code: str) -> None: +``` + +## Ruff Output + +```py +@dataclass +class DebugVisitor(Visitor[T]): + tree_depth: int = 0 + + def visit_default(self, node: LN) -> Iterator[T]: + indent = " " * (2 * self.tree_depth) + if isinstance(node, Node): + _type = type_repr(node.type) + out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow") + self.tree_depth += 1 + for child in node.children: + yield from self.visit(child) + + self.tree_depth -= 1 + out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow", bold=False) + else: + _type = token.tok_name.get(node.type, str(node.type)) + out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="blue", nl=False) + if node.prefix: + # We don't have to handle prefixes for `Node` objects since + # that delegates to the first child anyway. + out( + f"NOT_YET_IMPLEMENTED_ExprJoinedStr", + fg="green", + bold=False, + nl=False, + ) + out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="blue", bold=False) + + @classmethod + def show(cls, code: str) -> None: + """Pretty-prints a given string of `code`. + + Convenience method for debugging. + """ + v: DebugVisitor[None] = DebugVisitor() + list(v.visit(lib2to3_parse(code))) +``` + +## Black Output + +```py +@dataclass +class DebugVisitor(Visitor[T]): + tree_depth: int = 0 + + def visit_default(self, node: LN) -> Iterator[T]: + indent = ' ' * (2 * self.tree_depth) + if isinstance(node, Node): + _type = type_repr(node.type) + out(f'{indent}{_type}', fg='yellow') + self.tree_depth += 1 + for child in node.children: + yield from self.visit(child) + + self.tree_depth -= 1 + out(f'{indent}/{_type}', fg='yellow', bold=False) + else: + _type = token.tok_name.get(node.type, str(node.type)) + out(f'{indent}{_type}', fg='blue', nl=False) + if node.prefix: + # We don't have to handle prefixes for `Node` objects since + # that delegates to the first child anyway. + out(f' {node.prefix!r}', fg='green', bold=False, nl=False) + out(f' {node.value!r}', fg='blue', bold=False) + + @classmethod + def show(cls, code: str) -> None: + """Pretty-prints a given string of `code`. + + Convenience method for debugging. + """ + v: DebugVisitor[None] = DebugVisitor() + list(v.visit(lib2to3_parse(code))) +``` + + diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap.new new file mode 100644 index 0000000000000..37cdc9836c2e8 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap.new @@ -0,0 +1,948 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 96 +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/miscellaneous/long_strings_flag_disabled.py +--- +## Input + +```py +x = "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." + +x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." + +y = "Short string" + +print( + "This is a really long string inside of a print statement with extra arguments attached at the end of it.", + x, + y, + z, +) + +print( + "This is a really long string inside of a print statement with no extra arguments attached at the end of it." +) + +D1 = { + "The First": "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", + "The Second": "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", +} + +D2 = { + 1.0: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", + 2.0: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", +} + +D3 = { + x: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", + y: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", +} + +D4 = { + "A long and ridiculous {}".format( + string_key + ): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", + some_func( + "calling", "some", "stuff" + ): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format( + sooo="soooo", x=2 + ), + "A %s %s" + % ( + "formatted", + "string", + ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." + % ("soooo", 2), +} + +func_with_keywords( + my_arg, + my_kwarg="Long keyword strings also need to be wrapped, but they will probably need to be handled a little bit differently.", +) + +bad_split1 = ( + "But what should happen when code has already been formatted but in the wrong way? Like" + " with a space at the end instead of the beginning. Or what about when it is split too soon?" +) + +bad_split2 = ( + "But what should happen when code has already " + "been formatted but in the wrong way? Like " + "with a space at the end instead of the " + "beginning. Or what about when it is split too " + "soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split." +) + +bad_split3 = ( + "What if we have inline comments on " # First Comment + "each line of a bad split? In that " # Second Comment + "case, we should just leave it alone." # Third Comment +) + +bad_split_func1( + "But what should happen when code has already " + "been formatted but in the wrong way? Like " + "with a space at the end instead of the " + "beginning. Or what about when it is split too " + "soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split.", + xxx, + yyy, + zzz, +) + +bad_split_func2( + xxx, + yyy, + zzz, + long_string_kwarg="But what should happen when code has already been formatted but in the wrong way? Like " + "with a space at the end instead of the beginning. Or what about when it is split too " + "soon?", +) + +bad_split_func3( + ( + "But what should happen when code has already " + r"been formatted but in the wrong way? Like " + "with a space at the end instead of the " + r"beginning. Or what about when it is split too " + r"soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split." + ), + xxx, + yyy, + zzz, +) + +raw_string = r"This is a long raw string. When re-formatting this string, black needs to make sure it prepends the 'r' onto the new string." + +fmt_string1 = "We also need to be sure to preserve any and all {} which may or may not be attached to the string in question.".format( + "method calls" +) + +fmt_string2 = "But what about when the string is {} but {}".format( + "short", + "the method call is really really really really really really really really long?", +) + +old_fmt_string1 = ( + "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it." + % ("formatting", "code") +) + +old_fmt_string2 = "This is a %s %s %s %s" % ( + "really really really really really", + "old", + "way to format strings!", + "Use f-strings instead!", +) + +old_fmt_string3 = ( + "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" + % ( + "really really really really really", + "old", + "way to format strings!", + "Use f-strings instead!", + ) +) + +fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." + +fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." + +comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. + +arg_comment_string = print( + "Long lines with inline comments which are apart of (and not the only member of) an argument list should have their comments appended to the reformatted string's enclosing left parentheses.", # This comment stays on the bottom. + "Arg #2", + "Arg #3", + "Arg #4", + "Arg #5", +) + +pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 + +pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa + +"""This is a really really really long triple quote string and it should not be touched.""" + +triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" + +assert ( + some_type_of_boolean_expression +), "Followed by a really really really long string that is used to provide context to the AssertionError exception." + +assert ( + some_type_of_boolean_expression +), "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( + "formatting" +) + +assert some_type_of_boolean_expression, ( + "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s." + % "formatting" +) + +assert some_type_of_boolean_expression, ( + "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s." + % ("string", "formatting") +) + +some_function_call( + "With a reallly generic name and with a really really long string that is, at some point down the line, " + + added + + " to a variable and then added to another string." +) + +some_function_call( + "With a reallly generic name and with a really really long string that is, at some point down the line, " + + added + + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.", + "and a second argument", + and_a_third, +) + +return "A really really really really really really really really really really really really really long {} {}".format( + "return", "value" +) + +func_with_bad_comma( + "This is a really long string argument to a function that has a trailing comma which should NOT be there.", +) + +func_with_bad_comma( + "This is a really long string argument to a function that has a trailing comma which should NOT be there.", # comment after comma +) + +func_with_bad_comma( + ( + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), +) + +func_with_bad_comma( + ( + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), # comment after comma +) + +func_with_bad_parens_that_wont_fit_in_one_line( + ("short string that should have parens stripped"), x, y, z +) + +func_with_bad_parens_that_wont_fit_in_one_line( + x, y, ("short string that should have parens stripped"), z +) + +func_with_bad_parens( + ("short string that should have parens stripped"), + x, + y, + z, +) + +func_with_bad_parens( + x, + y, + ("short string that should have parens stripped"), + z, +) + +annotated_variable: Final = ( + "This is a large " + + STRING + + " that has been " + + CONCATENATED + + "using the '+' operator." +) +annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." +annotated_variable: Literal[ + "fakse_literal" +] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." + +backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" +backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" +backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" + +short_string = "Hi" " there." + +func_call(short_string=("Hi" " there.")) + +raw_strings = r"Don't" " get" r" merged" " unless they are all raw." + + +def foo(): + yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." + + +x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore + " of it." +) + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa + " of it." +) + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check + " of it." +) +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -143,9 +143,9 @@ + ) + ) + +-fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." ++fstring = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" + +-fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." ++fstring_with_no_fexprs = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" + + comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. + +@@ -165,13 +165,9 @@ + + triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" + +-assert ( +- some_type_of_boolean_expression +-), "Followed by a really really really long string that is used to provide context to the AssertionError exception." ++assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception." + +-assert ( +- some_type_of_boolean_expression +-), "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( ++assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( + "formatting" + ) + +@@ -221,8 +217,8 @@ + func_with_bad_comma( + ( + "This is a really long string argument to a function that has a trailing comma" +- " which should NOT be there." +- ), # comment after comma ++ " which should NOT be there." # comment after comma ++ ), + ) + + func_with_bad_parens_that_wont_fit_in_one_line( +@@ -274,7 +270,7 @@ + yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." + + +-x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." ++x = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" + + long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore +``` + +## Ruff Output + +```py +x = "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." + +x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." + +y = "Short string" + +print( + "This is a really long string inside of a print statement with extra arguments attached at the end of it.", + x, + y, + z, +) + +print( + "This is a really long string inside of a print statement with no extra arguments attached at the end of it." +) + +D1 = { + "The First": "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", + "The Second": "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", +} + +D2 = { + 1.0: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", + 2.0: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", +} + +D3 = { + x: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", + y: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", +} + +D4 = { + "A long and ridiculous {}".format( + string_key + ): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", + some_func( + "calling", "some", "stuff" + ): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format( + sooo="soooo", x=2 + ), + "A %s %s" + % ( + "formatted", + "string", + ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." + % ("soooo", 2), +} + +func_with_keywords( + my_arg, + my_kwarg="Long keyword strings also need to be wrapped, but they will probably need to be handled a little bit differently.", +) + +bad_split1 = ( + "But what should happen when code has already been formatted but in the wrong way? Like" + " with a space at the end instead of the beginning. Or what about when it is split too soon?" +) + +bad_split2 = ( + "But what should happen when code has already " + "been formatted but in the wrong way? Like " + "with a space at the end instead of the " + "beginning. Or what about when it is split too " + "soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split." +) + +bad_split3 = ( + "What if we have inline comments on " # First Comment + "each line of a bad split? In that " # Second Comment + "case, we should just leave it alone." # Third Comment +) + +bad_split_func1( + "But what should happen when code has already " + "been formatted but in the wrong way? Like " + "with a space at the end instead of the " + "beginning. Or what about when it is split too " + "soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split.", + xxx, + yyy, + zzz, +) + +bad_split_func2( + xxx, + yyy, + zzz, + long_string_kwarg="But what should happen when code has already been formatted but in the wrong way? Like " + "with a space at the end instead of the beginning. Or what about when it is split too " + "soon?", +) + +bad_split_func3( + ( + "But what should happen when code has already " + r"been formatted but in the wrong way? Like " + "with a space at the end instead of the " + r"beginning. Or what about when it is split too " + r"soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split." + ), + xxx, + yyy, + zzz, +) + +raw_string = r"This is a long raw string. When re-formatting this string, black needs to make sure it prepends the 'r' onto the new string." + +fmt_string1 = "We also need to be sure to preserve any and all {} which may or may not be attached to the string in question.".format( + "method calls" +) + +fmt_string2 = "But what about when the string is {} but {}".format( + "short", + "the method call is really really really really really really really really long?", +) + +old_fmt_string1 = ( + "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it." + % ("formatting", "code") +) + +old_fmt_string2 = "This is a %s %s %s %s" % ( + "really really really really really", + "old", + "way to format strings!", + "Use f-strings instead!", +) + +old_fmt_string3 = ( + "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" + % ( + "really really really really really", + "old", + "way to format strings!", + "Use f-strings instead!", + ) +) + +fstring = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" + +fstring_with_no_fexprs = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" + +comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. + +arg_comment_string = print( + "Long lines with inline comments which are apart of (and not the only member of) an argument list should have their comments appended to the reformatted string's enclosing left parentheses.", # This comment stays on the bottom. + "Arg #2", + "Arg #3", + "Arg #4", + "Arg #5", +) + +pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 + +pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa + +"""This is a really really really long triple quote string and it should not be touched.""" + +triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" + +assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception." + +assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( + "formatting" +) + +assert some_type_of_boolean_expression, ( + "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s." + % "formatting" +) + +assert some_type_of_boolean_expression, ( + "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s." + % ("string", "formatting") +) + +some_function_call( + "With a reallly generic name and with a really really long string that is, at some point down the line, " + + added + + " to a variable and then added to another string." +) + +some_function_call( + "With a reallly generic name and with a really really long string that is, at some point down the line, " + + added + + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.", + "and a second argument", + and_a_third, +) + +return "A really really really really really really really really really really really really really long {} {}".format( + "return", "value" +) + +func_with_bad_comma( + "This is a really long string argument to a function that has a trailing comma which should NOT be there.", +) + +func_with_bad_comma( + "This is a really long string argument to a function that has a trailing comma which should NOT be there.", # comment after comma +) + +func_with_bad_comma( + ( + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), +) + +func_with_bad_comma( + ( + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." # comment after comma + ), +) + +func_with_bad_parens_that_wont_fit_in_one_line( + ("short string that should have parens stripped"), x, y, z +) + +func_with_bad_parens_that_wont_fit_in_one_line( + x, y, ("short string that should have parens stripped"), z +) + +func_with_bad_parens( + ("short string that should have parens stripped"), + x, + y, + z, +) + +func_with_bad_parens( + x, + y, + ("short string that should have parens stripped"), + z, +) + +annotated_variable: Final = ( + "This is a large " + + STRING + + " that has been " + + CONCATENATED + + "using the '+' operator." +) +annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." +annotated_variable: Literal[ + "fakse_literal" +] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." + +backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" +backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" +backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" + +short_string = "Hi" " there." + +func_call(short_string=("Hi" " there.")) + +raw_strings = r"Don't" " get" r" merged" " unless they are all raw." + + +def foo(): + yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." + + +x = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore + " of it." +) + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa + " of it." +) + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check + " of it." +) +``` + +## Black Output + +```py +x = "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." + +x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." + +y = "Short string" + +print( + "This is a really long string inside of a print statement with extra arguments attached at the end of it.", + x, + y, + z, +) + +print( + "This is a really long string inside of a print statement with no extra arguments attached at the end of it." +) + +D1 = { + "The First": "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", + "The Second": "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", +} + +D2 = { + 1.0: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", + 2.0: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", +} + +D3 = { + x: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", + y: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", +} + +D4 = { + "A long and ridiculous {}".format( + string_key + ): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", + some_func( + "calling", "some", "stuff" + ): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format( + sooo="soooo", x=2 + ), + "A %s %s" + % ( + "formatted", + "string", + ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." + % ("soooo", 2), +} + +func_with_keywords( + my_arg, + my_kwarg="Long keyword strings also need to be wrapped, but they will probably need to be handled a little bit differently.", +) + +bad_split1 = ( + "But what should happen when code has already been formatted but in the wrong way? Like" + " with a space at the end instead of the beginning. Or what about when it is split too soon?" +) + +bad_split2 = ( + "But what should happen when code has already " + "been formatted but in the wrong way? Like " + "with a space at the end instead of the " + "beginning. Or what about when it is split too " + "soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split." +) + +bad_split3 = ( + "What if we have inline comments on " # First Comment + "each line of a bad split? In that " # Second Comment + "case, we should just leave it alone." # Third Comment +) + +bad_split_func1( + "But what should happen when code has already " + "been formatted but in the wrong way? Like " + "with a space at the end instead of the " + "beginning. Or what about when it is split too " + "soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split.", + xxx, + yyy, + zzz, +) + +bad_split_func2( + xxx, + yyy, + zzz, + long_string_kwarg="But what should happen when code has already been formatted but in the wrong way? Like " + "with a space at the end instead of the beginning. Or what about when it is split too " + "soon?", +) + +bad_split_func3( + ( + "But what should happen when code has already " + r"been formatted but in the wrong way? Like " + "with a space at the end instead of the " + r"beginning. Or what about when it is split too " + r"soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split." + ), + xxx, + yyy, + zzz, +) + +raw_string = r"This is a long raw string. When re-formatting this string, black needs to make sure it prepends the 'r' onto the new string." + +fmt_string1 = "We also need to be sure to preserve any and all {} which may or may not be attached to the string in question.".format( + "method calls" +) + +fmt_string2 = "But what about when the string is {} but {}".format( + "short", + "the method call is really really really really really really really really long?", +) + +old_fmt_string1 = ( + "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it." + % ("formatting", "code") +) + +old_fmt_string2 = "This is a %s %s %s %s" % ( + "really really really really really", + "old", + "way to format strings!", + "Use f-strings instead!", +) + +old_fmt_string3 = ( + "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" + % ( + "really really really really really", + "old", + "way to format strings!", + "Use f-strings instead!", + ) +) + +fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." + +fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." + +comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. + +arg_comment_string = print( + "Long lines with inline comments which are apart of (and not the only member of) an argument list should have their comments appended to the reformatted string's enclosing left parentheses.", # This comment stays on the bottom. + "Arg #2", + "Arg #3", + "Arg #4", + "Arg #5", +) + +pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 + +pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa + +"""This is a really really really long triple quote string and it should not be touched.""" + +triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" + +assert ( + some_type_of_boolean_expression +), "Followed by a really really really long string that is used to provide context to the AssertionError exception." + +assert ( + some_type_of_boolean_expression +), "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( + "formatting" +) + +assert some_type_of_boolean_expression, ( + "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s." + % "formatting" +) + +assert some_type_of_boolean_expression, ( + "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s." + % ("string", "formatting") +) + +some_function_call( + "With a reallly generic name and with a really really long string that is, at some point down the line, " + + added + + " to a variable and then added to another string." +) + +some_function_call( + "With a reallly generic name and with a really really long string that is, at some point down the line, " + + added + + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.", + "and a second argument", + and_a_third, +) + +return "A really really really really really really really really really really really really really long {} {}".format( + "return", "value" +) + +func_with_bad_comma( + "This is a really long string argument to a function that has a trailing comma which should NOT be there.", +) + +func_with_bad_comma( + "This is a really long string argument to a function that has a trailing comma which should NOT be there.", # comment after comma +) + +func_with_bad_comma( + ( + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), +) + +func_with_bad_comma( + ( + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), # comment after comma +) + +func_with_bad_parens_that_wont_fit_in_one_line( + ("short string that should have parens stripped"), x, y, z +) + +func_with_bad_parens_that_wont_fit_in_one_line( + x, y, ("short string that should have parens stripped"), z +) + +func_with_bad_parens( + ("short string that should have parens stripped"), + x, + y, + z, +) + +func_with_bad_parens( + x, + y, + ("short string that should have parens stripped"), + z, +) + +annotated_variable: Final = ( + "This is a large " + + STRING + + " that has been " + + CONCATENATED + + "using the '+' operator." +) +annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." +annotated_variable: Literal[ + "fakse_literal" +] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." + +backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" +backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" +backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" + +short_string = "Hi" " there." + +func_call(short_string=("Hi" " there.")) + +raw_strings = r"Don't" " get" r" merged" " unless they are all raw." + + +def foo(): + yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." + + +x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore + " of it." +) + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa + " of it." +) + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check + " of it." +) +``` + + diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap deleted file mode 100644 index f92c9acaf059d..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap +++ /dev/null @@ -1,103 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/py_38/python38.py ---- -## Input - -```py -#!/usr/bin/env python3.8 - - -def starred_return(): - my_list = ["value2", "value3"] - return "value1", *my_list - - -def starred_yield(): - my_list = ["value2", "value3"] - yield "value1", *my_list - - -# all right hand side expressions allowed in regular assignments are now also allowed in -# annotated assignments -a : Tuple[ str, int] = "1", 2 -a: Tuple[int , ... ] = b, *c, d -def t(): - a : str = yield "a" -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -8,7 +8,7 @@ - - def starred_yield(): - my_list = ["value2", "value3"] -- yield "value1", *my_list -+ NOT_YET_IMPLEMENTED_ExprYield - - - # all right hand side expressions allowed in regular assignments are now also allowed in -@@ -18,4 +18,4 @@ - - - def t(): -- a: str = yield "a" -+ a: str = NOT_YET_IMPLEMENTED_ExprYield -``` - -## Ruff Output - -```py -#!/usr/bin/env python3.8 - - -def starred_return(): - my_list = ["value2", "value3"] - return "value1", *my_list - - -def starred_yield(): - my_list = ["value2", "value3"] - NOT_YET_IMPLEMENTED_ExprYield - - -# all right hand side expressions allowed in regular assignments are now also allowed in -# annotated assignments -a: Tuple[str, int] = "1", 2 -a: Tuple[int, ...] = b, *c, d - - -def t(): - a: str = NOT_YET_IMPLEMENTED_ExprYield -``` - -## Black Output - -```py -#!/usr/bin/env python3.8 - - -def starred_return(): - my_list = ["value2", "value3"] - return "value1", *my_list - - -def starred_yield(): - my_list = ["value2", "value3"] - yield "value1", *my_list - - -# all right hand side expressions allowed in regular assignments are now also allowed in -# annotated assignments -a: Tuple[str, int] = "1", 2 -a: Tuple[int, ...] = b, *c, d - - -def t(): - a: str = yield "a" -``` - - diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap.new new file mode 100644 index 0000000000000..310c417865c4d --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap.new @@ -0,0 +1,1190 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 96 +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.py +--- +## Input + +```py +... +'some_string' +b'\\xa3' +Name +None +True +False +1 +1.0 +1j +True or False +True or False or None +True and False +True and False and None +(Name1 and Name2) or Name3 +Name1 and Name2 or Name3 +Name1 or (Name2 and Name3) +Name1 or Name2 and Name3 +(Name1 and Name2) or (Name3 and Name4) +Name1 and Name2 or Name3 and Name4 +Name1 or (Name2 and Name3) or Name4 +Name1 or Name2 and Name3 or Name4 +v1 << 2 +1 >> v2 +1 % finished +1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8 +((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8) +not great +~great ++value +-1 +~int and not v1 ^ 123 + v2 | True +(~int) and (not ((v1 ^ (123 + v2)) | True)) ++really ** -confusing ** ~operator ** -precedence +flags & ~ select.EPOLLIN and waiters.write_task is not None +lambda arg: None +lambda a=True: a +lambda a, b, c=True: a +lambda a, b, c=True, *, d=(1 << v2), e='str': a +lambda a, b, c=True, *vararg, d=(v1 << 2), e='str', **kwargs: a + b +manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() +foo = (lambda port_id, ignore_missing: {"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)) +{'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 +() +(1,) +(1, 2) +(1, 2, 3) +[] +[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)] +[1, 2, 3,] +[*a] +[*range(10)] +[*a, 4, 5,] +[4, *a, 5,] +[this_is_a_very_long_variable_which_will_force_a_delimiter_split, element, another, *more] +{i for i in (1, 2, 3)} +{(i ** 2) for i in (1, 2, 3)} +{(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)} +[i for i in (1, 2, 3)] +[(i ** 2) for i in (1, 2, 3)] +[(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)] +{i: 0 for i in (1, 2, 3)} +{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))} +{a: b * 2 for a, b in dictionary.items()} +{a: b * -2 for a, b in dictionary.items()} +{k: v for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension} +Python3 > Python2 > COBOL +Life is Life +call() +call(arg) +call(kwarg='hey') +call(arg, kwarg='hey') +call(arg, another, kwarg='hey', **kwargs) +call(this_is_a_very_long_variable_which_will_force_a_delimiter_split, arg, another, kwarg='hey', **kwargs) # note: no trailing comma pre-3.6 +call(*gidgets[:2]) +call(a, *gidgets[:2]) +call(**self.screen_kwargs) +call(b, **self.screen_kwargs) +lukasz.langa.pl +call.me(maybe) +1 .real +1.0 .real +....__class__ +list[str] +dict[str, int] +tuple[str, ...] +tuple[ + str, int, float, dict[str, int] +] +tuple[str, int, float, dict[str, int],] +very_long_variable_name_filters: t.List[ + t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], +] +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 + sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) +) +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[ + ..., List[SomeClass] +] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore +slice[0] +slice[0:1] +slice[0:1:2] +slice[:] +slice[:-1] +slice[1:] +slice[::-1] +slice[d :: d + 1] +slice[:c, c - 1] +numpy[:, 0:1] +numpy[:, :-1] +numpy[0, :] +numpy[:, i] +numpy[0, :2] +numpy[:N, 0] +numpy[:2, :4] +numpy[2:4, 1:5] +numpy[4:, 2:] +numpy[:, (0, 1, 2, 5)] +numpy[0, [0]] +numpy[:, [i]] +numpy[1 : c + 1, c] +numpy[-(c + 1) :, d] +numpy[:, l[-2]] +numpy[:, ::-1] +numpy[np.newaxis, :] +(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'} +[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] +(SomeName) +SomeName +(Good, Bad, Ugly) +(i for i in (1, 2, 3)) +((i ** 2) for i in (1, 2, 3)) +((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,) +{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs} +a = (1,) +b = 1, +c = 1 +d = (1,) + a + (2,) +e = (1,).count(1) +f = 1, *range(10) +g = 1, *"ten" +what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set(vars_to_remove) +what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(vars_to_remove) +result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc()).all() +result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc(),).all() +Ø = 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), +} + +def gen(): + yield from outside_of_generator + a = (yield) + b = ((yield)) + c = (((yield))) + +async def f(): + await some.complicated[0].call(with_args=(True or (1 is not 1))) +print(* [] or [1]) +print(**{1: 3} if False else {x: x for x in range(3)}) +print(* lambda x: x) +assert(not Test),("Short message") +assert this is ComplexTest and not requirements.fit_in_a_single_line(force=False), "Short message" +assert(((parens is TooMany))) +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): ... +for addr_family, addr_type, addr_proto, addr_canonname, addr_sockaddr in socket.getaddrinfo('google.com', 'http'): + pass +a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +if ( + threading.current_thread() != threading.main_thread() and + threading.current_thread() != threading.main_thread() or + signal.getsignal(signal.SIGINT) != signal.default_int_handler +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa / + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + ~ aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l ** aaaa.m // aaaa.n +): + return True +if ( + ~ aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l ** aaaaaaaa.m // aaaaaaaa.n +): + return True +if ( + ~ aaaaaaaaaaaaaaaa.a + aaaaaaaaaaaaaaaa.b - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h ^ aaaaaaaaaaaaaaaa.i << aaaaaaaaaaaaaaaa.k >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n +): + return True +aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa - aaaaaaaaaaaaaaaa * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) +aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +bbbb >> bbbb * bbbb +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ^bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +last_call() +# standalone comment at ENDMARKER +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,6 +1,6 @@ + ... + "some_string" +-b"\\xa3" ++b"NOT_YET_IMPLEMENTED_BYTE_STRING" + Name + None + True +@@ -31,7 +31,7 @@ + -1 + ~int and not v1 ^ 123 + v2 | True + (~int) and (not ((v1 ^ (123 + v2)) | True)) +-+(really ** -(confusing ** ~(operator**-precedence))) +++really ** -confusing ** ~operator**-precedence + flags & ~select.EPOLLIN and waiters.write_task is not None + lambda arg: None + lambda a=True: a +@@ -39,10 +39,11 @@ + lambda a, b, c=True, *, d=(1 << v2), e="str": a + lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b + manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() +-foo = lambda port_id, ignore_missing: { +- "port1": port1_resource, +- "port2": port2_resource, +-}[port_id] ++foo = ( ++ lambda port_id, ignore_missing: {"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) +@@ -57,7 +58,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")} +-({"a": "b"}, (True or False), (+value), "string", b"bytes") or None ++( ++ {"a": "b"}, ++ (True or False), ++ (+value), ++ "string", ++ b"NOT_YET_IMPLEMENTED_BYTE_STRING", ++) or None + () + (1,) + (1, 2) +@@ -101,7 +108,10 @@ + {a: b * -2 for a, b in dictionary.items()} + { + k: v +- for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension ++ for ( ++ k, ++ v, ++ ) in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension + } + Python3 > Python2 > COBOL + Life is Life +@@ -115,7 +125,7 @@ + arg, + another, + kwarg="hey", +- **kwargs ++ **kwargs, + ) # note: no trailing comma pre-3.6 + call(*gidgets[:2]) + call(a, *gidgets[:2]) +@@ -152,13 +162,13 @@ + slice[0:1] + slice[0:1:2] + slice[:] +-slice[:-1] ++slice[ : -1] + slice[1:] +-slice[::-1] ++slice[ :: -1] + slice[d :: d + 1] + slice[:c, c - 1] + numpy[:, 0:1] +-numpy[:, :-1] ++numpy[:, : -1] + numpy[0, :] + numpy[:, i] + numpy[0, :2] +@@ -172,7 +182,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) + {"2.7": dead, "3.7": long_live or die_hard} +@@ -208,24 +218,14 @@ + what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( + vars_to_remove + ) +-result = ( +- session.query(models.Customer.id) +- .filter( +- models.Customer.account_id == account_id, models.Customer.email == email_address +- ) +- .order_by(models.Customer.id.asc()) +- .all() +-) +-result = ( +- session.query(models.Customer.id) +- .filter( +- models.Customer.account_id == account_id, models.Customer.email == email_address +- ) +- .order_by( +- models.Customer.id.asc(), +- ) +- .all() +-) ++result = session.query(models.Customer.id).filter( ++ models.Customer.account_id == account_id, models.Customer.email == email_address ++).order_by(models.Customer.id.asc()).all() ++result = session.query(models.Customer.id).filter( ++ models.Customer.account_id == account_id, models.Customer.email == email_address ++).order_by( ++ models.Customer.id.asc(), ++).all() + Ø = set() + authors.łukasz.say_thanks() + mapping = { +@@ -328,13 +328,18 @@ + ): + return True + if ( +- ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e ++ ~aaaa.a ++ + aaaa.b ++ - aaaa.c * aaaa.d / aaaa.e + | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n + ): + return True + if ( +- ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e +- | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h ++ ~aaaaaaaa.a ++ + aaaaaaaa.b ++ - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e ++ | aaaaaaaa.f ++ & aaaaaaaa.g % aaaaaaaa.h + ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n + ): + return True +@@ -342,7 +347,8 @@ + ~aaaaaaaaaaaaaaaa.a + + aaaaaaaaaaaaaaaa.b + - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e +- | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h ++ | aaaaaaaaaaaaaaaa.f ++ & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h + ^ aaaaaaaaaaaaaaaa.i + << aaaaaaaaaaaaaaaa.k + >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n +``` + +## Ruff Output + +```py +... +"some_string" +b"NOT_YET_IMPLEMENTED_BYTE_STRING" +Name +None +True +False +1 +1.0 +1j +True or False +True or False or None +True and False +True and False and None +(Name1 and Name2) or Name3 +Name1 and Name2 or Name3 +Name1 or (Name2 and Name3) +Name1 or Name2 and Name3 +(Name1 and Name2) or (Name3 and Name4) +Name1 and Name2 or Name3 and Name4 +Name1 or (Name2 and Name3) or Name4 +Name1 or Name2 and Name3 or Name4 +v1 << 2 +1 >> v2 +1 % finished +1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8 +((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8) +not great +~great ++value +-1 +~int and not v1 ^ 123 + v2 | True +(~int) and (not ((v1 ^ (123 + v2)) | True)) ++really ** -confusing ** ~operator**-precedence +flags & ~select.EPOLLIN and waiters.write_task is not None +lambda arg: None +lambda a=True: a +lambda a, b, c=True: a +lambda a, b, c=True, *, d=(1 << v2), e="str": a +lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b +manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() +foo = ( + lambda port_id, ignore_missing: {"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) +) +{"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"NOT_YET_IMPLEMENTED_BYTE_STRING", +) or None +() +(1,) +(1, 2) +(1, 2, 3) +[] +[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)] +[ + 1, + 2, + 3, +] +[*a] +[*range(10)] +[ + *a, + 4, + 5, +] +[ + 4, + *a, + 5, +] +[ + this_is_a_very_long_variable_which_will_force_a_delimiter_split, + element, + another, + *more, +] +{i for i in (1, 2, 3)} +{(i**2) for i in (1, 2, 3)} +{(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)} +[i for i in (1, 2, 3)] +[(i**2) for i in (1, 2, 3)] +[(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)] +{i: 0 for i in (1, 2, 3)} +{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))} +{a: b * 2 for a, b in dictionary.items()} +{a: b * -2 for a, b in dictionary.items()} +{ + k: v + for ( + k, + v, + ) in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension +} +Python3 > Python2 > COBOL +Life is Life +call() +call(arg) +call(kwarg="hey") +call(arg, kwarg="hey") +call(arg, another, kwarg="hey", **kwargs) +call( + this_is_a_very_long_variable_which_will_force_a_delimiter_split, + arg, + another, + kwarg="hey", + **kwargs, +) # note: no trailing comma pre-3.6 +call(*gidgets[:2]) +call(a, *gidgets[:2]) +call(**self.screen_kwargs) +call(b, **self.screen_kwargs) +lukasz.langa.pl +call.me(maybe) +(1).real +(1.0).real +....__class__ +list[str] +dict[str, int] +tuple[str, ...] +tuple[str, int, float, dict[str, int]] +tuple[ + str, + int, + float, + dict[str, int], +] +very_long_variable_name_filters: t.List[ + t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], +] +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 + sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) +) +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( + sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) +) # type: ignore +slice[0] +slice[0:1] +slice[0:1:2] +slice[:] +slice[ : -1] +slice[1:] +slice[ :: -1] +slice[d :: d + 1] +slice[:c, c - 1] +numpy[:, 0:1] +numpy[:, : -1] +numpy[0, :] +numpy[:, i] +numpy[0, :2] +numpy[:N, 0] +numpy[:2, :4] +numpy[2:4, 1:5] +numpy[4:, 2:] +numpy[:, (0, 1, 2, 5)] +numpy[0, [0]] +numpy[:, [i]] +numpy[1 : c + 1, c] +numpy[-(c + 1) :, d] +numpy[:, l[-2]] +numpy[:, :: -1] +numpy[np.newaxis, :] +(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"} +[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] +(SomeName) +SomeName +(Good, Bad, Ugly) +(i for i in (1, 2, 3)) +((i**2) for i in (1, 2, 3)) +((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,) +{ + "id": "1", + "type": "type", + "started_at": now(), + "ended_at": now() + timedelta(days=10), + "priority": 1, + "import_session_id": 1, + **kwargs, +} +a = (1,) +b = (1,) +c = 1 +d = (1,) + a + (2,) +e = (1,).count(1) +f = 1, *range(10) +g = 1, *"ten" +what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set( + vars_to_remove +) +what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( + vars_to_remove +) +result = session.query(models.Customer.id).filter( + models.Customer.account_id == account_id, models.Customer.email == email_address +).order_by(models.Customer.id.asc()).all() +result = session.query(models.Customer.id).filter( + models.Customer.account_id == account_id, models.Customer.email == email_address +).order_by( + models.Customer.id.asc(), +).all() +Ø = 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), +} + + +def gen(): + yield from outside_of_generator + a = yield + b = yield + c = yield + + +async def f(): + await some.complicated[0].call(with_args=(True or (1 is not 1))) + + +print(*[] or [1]) +print(**{1: 3} if False else {x: x for x in range(3)}) +print(*lambda x: x) +assert not Test, "Short message" +assert this is ComplexTest and not requirements.fit_in_a_single_line( + force=False +), "Short message" +assert parens is TooMany +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: + ... +for ( + addr_family, + addr_type, + addr_proto, + addr_canonname, + addr_sockaddr, +) in socket.getaddrinfo("google.com", "http"): + pass +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +if ( + threading.current_thread() != threading.main_thread() + and threading.current_thread() != threading.main_thread() + or signal.getsignal(signal.SIGINT) != signal.default_int_handler +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + ~aaaa.a + + aaaa.b + - aaaa.c * aaaa.d / aaaa.e + | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n +): + return True +if ( + ~aaaaaaaa.a + + aaaaaaaa.b + - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e + | aaaaaaaa.f + & aaaaaaaa.g % aaaaaaaa.h + ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n +): + return True +if ( + ~aaaaaaaaaaaaaaaa.a + + aaaaaaaaaaaaaaaa.b + - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e + | aaaaaaaaaaaaaaaa.f + & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h + ^ aaaaaaaaaaaaaaaa.i + << aaaaaaaaaaaaaaaa.k + >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n +): + return True +( + aaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaa + * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) + / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) +) +aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa +( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) +bbbb >> bbbb * bbbb +( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) +last_call() +# standalone comment at ENDMARKER +``` + +## Black Output + +```py +... +"some_string" +b"\\xa3" +Name +None +True +False +1 +1.0 +1j +True or False +True or False or None +True and False +True and False and None +(Name1 and Name2) or Name3 +Name1 and Name2 or Name3 +Name1 or (Name2 and Name3) +Name1 or Name2 and Name3 +(Name1 and Name2) or (Name3 and Name4) +Name1 and Name2 or Name3 and Name4 +Name1 or (Name2 and Name3) or Name4 +Name1 or Name2 and Name3 or Name4 +v1 << 2 +1 >> v2 +1 % finished +1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8 +((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8) +not great +~great ++value +-1 +~int and not v1 ^ 123 + v2 | True +(~int) and (not ((v1 ^ (123 + v2)) | True)) ++(really ** -(confusing ** ~(operator**-precedence))) +flags & ~select.EPOLLIN and waiters.write_task is not None +lambda arg: None +lambda a=True: a +lambda a, b, c=True: a +lambda a, b, c=True, *, d=(1 << v2), e="str": a +lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b +manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() +foo = lambda port_id, ignore_missing: { + "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) +) +{"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 +() +(1,) +(1, 2) +(1, 2, 3) +[] +[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)] +[ + 1, + 2, + 3, +] +[*a] +[*range(10)] +[ + *a, + 4, + 5, +] +[ + 4, + *a, + 5, +] +[ + this_is_a_very_long_variable_which_will_force_a_delimiter_split, + element, + another, + *more, +] +{i for i in (1, 2, 3)} +{(i**2) for i in (1, 2, 3)} +{(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)} +[i for i in (1, 2, 3)] +[(i**2) for i in (1, 2, 3)] +[(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)] +{i: 0 for i in (1, 2, 3)} +{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))} +{a: b * 2 for a, b in dictionary.items()} +{a: b * -2 for a, b in dictionary.items()} +{ + k: v + for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension +} +Python3 > Python2 > COBOL +Life is Life +call() +call(arg) +call(kwarg="hey") +call(arg, kwarg="hey") +call(arg, another, kwarg="hey", **kwargs) +call( + this_is_a_very_long_variable_which_will_force_a_delimiter_split, + arg, + another, + kwarg="hey", + **kwargs +) # note: no trailing comma pre-3.6 +call(*gidgets[:2]) +call(a, *gidgets[:2]) +call(**self.screen_kwargs) +call(b, **self.screen_kwargs) +lukasz.langa.pl +call.me(maybe) +(1).real +(1.0).real +....__class__ +list[str] +dict[str, int] +tuple[str, ...] +tuple[str, int, float, dict[str, int]] +tuple[ + str, + int, + float, + dict[str, int], +] +very_long_variable_name_filters: t.List[ + t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], +] +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 + sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) +) +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( + sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) +) # type: ignore +slice[0] +slice[0:1] +slice[0:1:2] +slice[:] +slice[:-1] +slice[1:] +slice[::-1] +slice[d :: d + 1] +slice[:c, c - 1] +numpy[:, 0:1] +numpy[:, :-1] +numpy[0, :] +numpy[:, i] +numpy[0, :2] +numpy[:N, 0] +numpy[:2, :4] +numpy[2:4, 1:5] +numpy[4:, 2:] +numpy[:, (0, 1, 2, 5)] +numpy[0, [0]] +numpy[:, [i]] +numpy[1 : c + 1, c] +numpy[-(c + 1) :, d] +numpy[:, l[-2]] +numpy[:, ::-1] +numpy[np.newaxis, :] +(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"} +[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] +(SomeName) +SomeName +(Good, Bad, Ugly) +(i for i in (1, 2, 3)) +((i**2) for i in (1, 2, 3)) +((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,) +{ + "id": "1", + "type": "type", + "started_at": now(), + "ended_at": now() + timedelta(days=10), + "priority": 1, + "import_session_id": 1, + **kwargs, +} +a = (1,) +b = (1,) +c = 1 +d = (1,) + a + (2,) +e = (1,).count(1) +f = 1, *range(10) +g = 1, *"ten" +what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set( + vars_to_remove +) +what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( + vars_to_remove +) +result = ( + session.query(models.Customer.id) + .filter( + models.Customer.account_id == account_id, models.Customer.email == email_address + ) + .order_by(models.Customer.id.asc()) + .all() +) +result = ( + session.query(models.Customer.id) + .filter( + models.Customer.account_id == account_id, models.Customer.email == email_address + ) + .order_by( + models.Customer.id.asc(), + ) + .all() +) +Ø = 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), +} + + +def gen(): + yield from outside_of_generator + a = yield + b = yield + c = yield + + +async def f(): + await some.complicated[0].call(with_args=(True or (1 is not 1))) + + +print(*[] or [1]) +print(**{1: 3} if False else {x: x for x in range(3)}) +print(*lambda x: x) +assert not Test, "Short message" +assert this is ComplexTest and not requirements.fit_in_a_single_line( + force=False +), "Short message" +assert parens is TooMany +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: + ... +for ( + addr_family, + addr_type, + addr_proto, + addr_canonname, + addr_sockaddr, +) in socket.getaddrinfo("google.com", "http"): + pass +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +if ( + threading.current_thread() != threading.main_thread() + and threading.current_thread() != threading.main_thread() + or signal.getsignal(signal.SIGINT) != signal.default_int_handler +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True +if ( + ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e + | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n +): + return True +if ( + ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e + | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h + ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n +): + return True +if ( + ~aaaaaaaaaaaaaaaa.a + + aaaaaaaaaaaaaaaa.b + - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e + | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h + ^ aaaaaaaaaaaaaaaa.i + << aaaaaaaaaaaaaaaa.k + >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n +): + return True +( + aaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaa + * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) + / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) +) +aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa +( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) +bbbb >> bbbb * bbbb +( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) +last_call() +# standalone comment at ENDMARKER +``` + + diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap.new new file mode 100644 index 0000000000000..9bc2c4e4ea42f --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap.new @@ -0,0 +1,859 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 96 +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff.py +--- +## Input + +```py +#!/usr/bin/env python3 +import asyncio +import sys + +from third_party import X, Y, Z + +from library import some_connection, \ + some_decorator +# fmt: off +from third_party import (X, + Y, Z) +# fmt: on +f'trigger 3.6 mode' +# Comment 1 + +# Comment 2 + +# fmt: off +def func_no_args(): + a; b; c + if True: raise RuntimeError + if False: ... + for i in range(10): + print(i) + continue + exec('new-style exec', {}, {}) + return None +async def coroutine(arg, exec=False): + 'Single-line docstring. Multiline is harder to reformat.' + async with some_connection() as conn: + await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) + await asyncio.sleep(1) +@asyncio.coroutine +@some_decorator( +with_args=True, +many_args=[1,2,3] +) +def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str: + return text[number:-1] +# fmt: on +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_types(a: int = 1, b: tuple = (), c: list = [], d: dict = {}, e: bool = True, f: int = -1, g: int = 1 if False else 2, h: str = "", i: str = r''): ... +def spaces2(result= _core.Value(None)): + ... +something = { + # fmt: off + key: 'value', +} + +def subscriptlist(): + atom[ + # fmt: off + 'some big and', + 'complex subscript', + # fmt: on + goes + here, andhere, + ] + +def import_as_names(): + # fmt: off + from hello import a, b + 'unformatted' + # fmt: on + +def testlist_star_expr(): + # fmt: off + a , b = *hello + 'unformatted' + # fmt: on + +def yield_expr(): + # fmt: off + yield hello + 'unformatted' + # fmt: on + 'formatted' + # fmt: off + ( yield hello ) + 'unformatted' + # fmt: on + +def example(session): + # fmt: off + result = session\ + .query(models.Customer.id)\ + .filter(models.Customer.account_id == account_id, + models.Customer.email == email_address)\ + .order_by(models.Customer.id.asc())\ + .all() + # fmt: on +def off_and_on_without_data(): + """All comments here are technically on the same prefix. + + The comments between will be formatted. This is a known limitation. + """ + # fmt: off + + + #hey, that won't work + + + # fmt: on + pass +def on_and_off_broken(): + """Another known limitation.""" + # fmt: on + # fmt: off + this=should.not_be.formatted() + and_=indeed . it is not formatted + because . the . handling . inside . generate_ignored_nodes() + now . considers . multiple . fmt . directives . within . one . prefix + # fmt: on + # fmt: off + # ...but comments still get reformatted even though they should not be + # fmt: on +def long_lines(): + if True: + typedargslist.extend( + gen_annotated_params(ast_args.kwonlyargs, ast_args.kw_defaults, parameters, implicit_default=True) + ) + # fmt: off + a = ( + unnecessary_bracket() + ) + # fmt: on + _type_comment_re = re.compile( + r""" + ^ + [\t ]* + \#[ ]type:[ ]* + (?P + [^#\t\n]+? + ) + (? to match + # a trailing space which is why we need the silliness below + (? + (?:\#[^\n]*)? + \n? + ) + $ + """, + # fmt: off + re.MULTILINE|re.VERBOSE + # fmt: on + ) +def single_literal_yapf_disable(): + """Black does not support this.""" + BAZ = { + (1, 2, 3, 4), + (5, 6, 7, 8), + (9, 10, 11, 12) + } # yapf: disable +cfg.rule( + "Default", "address", + xxxx_xxxx=["xxx-xxxxxx-xxxxxxxxxx"], + xxxxxx="xx_xxxxx", xxxxxxx="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + xxxxxxxxx_xxxx=True, xxxxxxxx_xxxxxxxxxx=False, + xxxxxx_xxxxxx=2, xxxxxx_xxxxx_xxxxxxxx=70, xxxxxx_xxxxxx_xxxxx=True, + # fmt: off + xxxxxxx_xxxxxxxxxxxx={ + "xxxxxxxx": { + "xxxxxx": False, + "xxxxxxx": False, + "xxxx_xxxxxx": "xxxxx", + }, + "xxxxxxxx-xxxxx": { + "xxxxxx": False, + "xxxxxxx": True, + "xxxx_xxxxxx": "xxxxxx", + }, + }, + # fmt: on + xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5 +) +# fmt: off +yield 'hello' +# No formatting to the end of the file +l=[1,2,3] +d={'a':1, + 'b':2} +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -6,10 +6,9 @@ + + from library import some_connection, some_decorator + # fmt: off +-from third_party import (X, +- Y, Z) ++from third_party import X, Y, Z + # fmt: on +-f"trigger 3.6 mode" ++f"NOT_YET_IMPLEMENTED_ExprJoinedStr" + # Comment 1 + + # Comment 2 +@@ -17,30 +16,44 @@ + + # fmt: off + def func_no_args(): +- a; b; c +- if True: raise RuntimeError +- if False: ... +- for i in range(10): +- print(i) +- continue +- exec('new-style exec', {}, {}) +- return None ++ a ++ b ++ c ++ if True: ++ raise RuntimeError ++ if False: ++ ... ++ for i in range(10): ++ print(i) ++ continue ++ exec("new-style exec", {}, {}) ++ return None ++ ++ + async def coroutine(arg, exec=False): +- 'Single-line docstring. Multiline is harder to reformat.' +- async with some_connection() as conn: +- await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) +- await asyncio.sleep(1) ++ "Single-line docstring. Multiline is harder to reformat." ++ async with some_connection() as conn: ++ await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2) ++ await asyncio.sleep(1) ++ ++ + @asyncio.coroutine +-@some_decorator( +-with_args=True, +-many_args=[1,2,3] +-) +-def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str: +- return text[number:-1] ++@some_decorator(with_args=True, many_args=[1, 2, 3]) ++def function_signature_stress_test( ++ number: int, ++ no_annotation=None, ++ text: str = "default", ++ *, ++ debug: bool = False, ++ **kwargs, ++) -> str: ++ return text[number : -1] ++ ++ + # fmt: on + 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 ++ assert task._cancel_stack[ : len(old_stack)] == old_stack + + + def spaces_types( +@@ -63,15 +76,15 @@ + + something = { + # fmt: off +- key: 'value', ++ key: "value", + } + + + def subscriptlist(): + atom[ + # fmt: off +- 'some big and', +- 'complex subscript', ++ "some big and", ++ "complex subscript", + # fmt: on + goes + here, + andhere, +@@ -80,38 +93,35 @@ + + def import_as_names(): + # fmt: off +- from hello import a, b +- 'unformatted' ++ from hello import a, b ++ "unformatted" + # fmt: on + + + def testlist_star_expr(): + # fmt: off +- a , b = *hello +- 'unformatted' ++ a, b = *hello ++ "unformatted" + # fmt: on + + + def yield_expr(): + # fmt: off + yield hello +- 'unformatted' ++ "unformatted" + # fmt: on + "formatted" + # fmt: off +- ( yield hello ) +- 'unformatted' ++ (yield hello) ++ "unformatted" + # fmt: on + + + def example(session): + # fmt: off +- result = session\ +- .query(models.Customer.id)\ +- .filter(models.Customer.account_id == account_id, +- models.Customer.email == email_address)\ +- .order_by(models.Customer.id.asc())\ +- .all() ++ result = session.query(models.Customer.id).filter( ++ models.Customer.account_id == account_id, models.Customer.email == email_address ++ ).order_by(models.Customer.id.asc()).all() + # fmt: on + + +@@ -132,10 +142,10 @@ + """Another known limitation.""" + # fmt: on + # fmt: off +- this=should.not_be.formatted() +- and_=indeed . it is not formatted +- because . the . handling . inside . generate_ignored_nodes() +- now . considers . multiple . fmt . directives . within . one . prefix ++ this = should.not_be.formatted() ++ and_ = indeed.it is not formatted ++ because.the.handling.inside.generate_ignored_nodes() ++ now.considers.multiple.fmt.directives.within.one.prefix + # fmt: on + # fmt: off + # ...but comments still get reformatted even though they should not be +@@ -153,9 +163,7 @@ + ) + ) + # fmt: off +- a = ( +- unnecessary_bracket() +- ) ++ a = unnecessary_bracket() + # fmt: on + _type_comment_re = re.compile( + r""" +@@ -178,7 +186,7 @@ + $ + """, + # fmt: off +- re.MULTILINE|re.VERBOSE ++ re.MULTILINE | re.VERBOSE, + # fmt: on + ) + +@@ -216,8 +224,7 @@ + xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5, + ) + # fmt: off +-yield 'hello' ++yield "hello" + # No formatting to the end of the file +-l=[1,2,3] +-d={'a':1, +- 'b':2} ++l = [1, 2, 3] ++d = {"a": 1, "b": 2} +``` + +## Ruff Output + +```py +#!/usr/bin/env python3 +import asyncio +import sys + +from third_party import X, Y, Z + +from library import some_connection, some_decorator +# fmt: off +from third_party import X, Y, Z +# fmt: on +f"NOT_YET_IMPLEMENTED_ExprJoinedStr" +# Comment 1 + +# Comment 2 + + +# fmt: off +def func_no_args(): + a + b + c + if True: + raise RuntimeError + if False: + ... + for i in range(10): + print(i) + continue + exec("new-style exec", {}, {}) + return None + + +async def coroutine(arg, exec=False): + "Single-line docstring. Multiline is harder to reformat." + async with some_connection() as conn: + await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2) + await asyncio.sleep(1) + + +@asyncio.coroutine +@some_decorator(with_args=True, many_args=[1, 2, 3]) +def function_signature_stress_test( + number: int, + no_annotation=None, + text: str = "default", + *, + debug: bool = False, + **kwargs, +) -> str: + return text[number : -1] + + +# fmt: on +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_types( + a: int = 1, + b: tuple = (), + c: list = [], + d: dict = {}, + e: bool = True, + f: int = -1, + g: int = 1 if False else 2, + h: str = "", + i: str = r"", +): + ... + + +def spaces2(result=_core.Value(None)): + ... + + +something = { + # fmt: off + key: "value", +} + + +def subscriptlist(): + atom[ + # fmt: off + "some big and", + "complex subscript", + # fmt: on + goes + here, + andhere, + ] + + +def import_as_names(): + # fmt: off + from hello import a, b + "unformatted" + # fmt: on + + +def testlist_star_expr(): + # fmt: off + a, b = *hello + "unformatted" + # fmt: on + + +def yield_expr(): + # fmt: off + yield hello + "unformatted" + # fmt: on + "formatted" + # fmt: off + (yield hello) + "unformatted" + # fmt: on + + +def example(session): + # fmt: off + result = session.query(models.Customer.id).filter( + models.Customer.account_id == account_id, models.Customer.email == email_address + ).order_by(models.Customer.id.asc()).all() + # fmt: on + + +def off_and_on_without_data(): + """All comments here are technically on the same prefix. + + The comments between will be formatted. This is a known limitation. + """ + # fmt: off + + # hey, that won't work + + # fmt: on + pass + + +def on_and_off_broken(): + """Another known limitation.""" + # fmt: on + # fmt: off + this = should.not_be.formatted() + and_ = indeed.it is not formatted + because.the.handling.inside.generate_ignored_nodes() + now.considers.multiple.fmt.directives.within.one.prefix + # fmt: on + # fmt: off + # ...but comments still get reformatted even though they should not be + # fmt: on + + +def long_lines(): + if True: + typedargslist.extend( + gen_annotated_params( + ast_args.kwonlyargs, + ast_args.kw_defaults, + parameters, + implicit_default=True, + ) + ) + # fmt: off + a = unnecessary_bracket() + # fmt: on + _type_comment_re = re.compile( + r""" + ^ + [\t ]* + \#[ ]type:[ ]* + (?P + [^#\t\n]+? + ) + (? to match + # a trailing space which is why we need the silliness below + (? + (?:\#[^\n]*)? + \n? + ) + $ + """, + # fmt: off + re.MULTILINE | re.VERBOSE, + # fmt: on + ) + + +def single_literal_yapf_disable(): + """Black does not support this.""" + BAZ = {(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)} # yapf: disable + + +cfg.rule( + "Default", + "address", + xxxx_xxxx=["xxx-xxxxxx-xxxxxxxxxx"], + xxxxxx="xx_xxxxx", + xxxxxxx="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + xxxxxxxxx_xxxx=True, + xxxxxxxx_xxxxxxxxxx=False, + xxxxxx_xxxxxx=2, + xxxxxx_xxxxx_xxxxxxxx=70, + xxxxxx_xxxxxx_xxxxx=True, + # fmt: off + xxxxxxx_xxxxxxxxxxxx={ + "xxxxxxxx": { + "xxxxxx": False, + "xxxxxxx": False, + "xxxx_xxxxxx": "xxxxx", + }, + "xxxxxxxx-xxxxx": { + "xxxxxx": False, + "xxxxxxx": True, + "xxxx_xxxxxx": "xxxxxx", + }, + }, + # fmt: on + xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5, +) +# fmt: off +yield "hello" +# No formatting to the end of the file +l = [1, 2, 3] +d = {"a": 1, "b": 2} +``` + +## Black Output + +```py +#!/usr/bin/env python3 +import asyncio +import sys + +from third_party import X, Y, Z + +from library import some_connection, some_decorator +# fmt: off +from third_party import (X, + Y, Z) +# fmt: on +f"trigger 3.6 mode" +# Comment 1 + +# Comment 2 + + +# fmt: off +def func_no_args(): + a; b; c + if True: raise RuntimeError + if False: ... + for i in range(10): + print(i) + continue + exec('new-style exec', {}, {}) + return None +async def coroutine(arg, exec=False): + 'Single-line docstring. Multiline is harder to reformat.' + async with some_connection() as conn: + await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) + await asyncio.sleep(1) +@asyncio.coroutine +@some_decorator( +with_args=True, +many_args=[1,2,3] +) +def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str: + return text[number:-1] +# fmt: on +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_types( + a: int = 1, + b: tuple = (), + c: list = [], + d: dict = {}, + e: bool = True, + f: int = -1, + g: int = 1 if False else 2, + h: str = "", + i: str = r"", +): + ... + + +def spaces2(result=_core.Value(None)): + ... + + +something = { + # fmt: off + key: 'value', +} + + +def subscriptlist(): + atom[ + # fmt: off + 'some big and', + 'complex subscript', + # fmt: on + goes + here, + andhere, + ] + + +def import_as_names(): + # fmt: off + from hello import a, b + 'unformatted' + # fmt: on + + +def testlist_star_expr(): + # fmt: off + a , b = *hello + 'unformatted' + # fmt: on + + +def yield_expr(): + # fmt: off + yield hello + 'unformatted' + # fmt: on + "formatted" + # fmt: off + ( yield hello ) + 'unformatted' + # fmt: on + + +def example(session): + # fmt: off + result = session\ + .query(models.Customer.id)\ + .filter(models.Customer.account_id == account_id, + models.Customer.email == email_address)\ + .order_by(models.Customer.id.asc())\ + .all() + # fmt: on + + +def off_and_on_without_data(): + """All comments here are technically on the same prefix. + + The comments between will be formatted. This is a known limitation. + """ + # fmt: off + + # hey, that won't work + + # fmt: on + pass + + +def on_and_off_broken(): + """Another known limitation.""" + # fmt: on + # fmt: off + this=should.not_be.formatted() + and_=indeed . it is not formatted + because . the . handling . inside . generate_ignored_nodes() + now . considers . multiple . fmt . directives . within . one . prefix + # fmt: on + # fmt: off + # ...but comments still get reformatted even though they should not be + # fmt: on + + +def long_lines(): + if True: + typedargslist.extend( + gen_annotated_params( + ast_args.kwonlyargs, + ast_args.kw_defaults, + parameters, + implicit_default=True, + ) + ) + # fmt: off + a = ( + unnecessary_bracket() + ) + # fmt: on + _type_comment_re = re.compile( + r""" + ^ + [\t ]* + \#[ ]type:[ ]* + (?P + [^#\t\n]+? + ) + (? to match + # a trailing space which is why we need the silliness below + (? + (?:\#[^\n]*)? + \n? + ) + $ + """, + # fmt: off + re.MULTILINE|re.VERBOSE + # fmt: on + ) + + +def single_literal_yapf_disable(): + """Black does not support this.""" + BAZ = {(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)} # yapf: disable + + +cfg.rule( + "Default", + "address", + xxxx_xxxx=["xxx-xxxxxx-xxxxxxxxxx"], + xxxxxx="xx_xxxxx", + xxxxxxx="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + xxxxxxxxx_xxxx=True, + xxxxxxxx_xxxxxxxxxx=False, + xxxxxx_xxxxxx=2, + xxxxxx_xxxxx_xxxxxxxx=70, + xxxxxx_xxxxxx_xxxxx=True, + # fmt: off + xxxxxxx_xxxxxxxxxxxx={ + "xxxxxxxx": { + "xxxxxx": False, + "xxxxxxx": False, + "xxxx_xxxxxx": "xxxxx", + }, + "xxxxxxxx-xxxxx": { + "xxxxxx": False, + "xxxxxxx": True, + "xxxx_xxxxxx": "xxxxxx", + }, + }, + # fmt: on + xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5, +) +# fmt: off +yield 'hello' +# No formatting to the end of the file +l=[1,2,3] +d={'a':1, + 'b':2} +``` + + diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new new file mode 100644 index 0000000000000..a8c6d29a4d2ee --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new @@ -0,0 +1,73 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 161 +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py +--- +## Input +```py +l = [1,2,3,4] + +def foo(): + yield l + + # some comment + for e in l : yield e # some comment + + for e in l: + + + # some comment + + yield e + + # trail comment + + for e in l: + # comment + yield (((((((e))))))) # Too many parentheses + # comment + + + for ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee in l: + yield ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + + + for x in l: #comment + yield x + (2 * 4) # trailing comment + + +``` + +## Output +```py +l = [1, 2, 3, 4] + + +def foo(): + yield l + + # some comment + for e in l: + yield e # some comment + + for e in l: + # some comment + + yield e + + # trail comment + + for e in l: + # comment + yield e # Too many parentheses + # comment + + for ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee in l: + yield ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + + for x in l: # comment + yield x + (2 * 4) # trailing comment +``` + + + diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new new file mode 100644 index 0000000000000..e2fc72d21f22d --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new @@ -0,0 +1,35 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 161 +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py +--- +## Input +```py +l = [1,2,3,4] + + +def foo(): + yield from l # some comment + + # weird indents + yield\ + from\ + l + # indented trailing comment +``` + +## Output +```py +l = [1, 2, 3, 4] + + +def foo(): + yield from l # some comment + + # weird indents + yield from l + # indented trailing comment +``` + + + diff --git a/crates/ruff_python_trivia/src/tokenizer.rs b/crates/ruff_python_trivia/src/tokenizer.rs index 40b4df599db93..30381a772b474 100644 --- a/crates/ruff_python_trivia/src/tokenizer.rs +++ b/crates/ruff_python_trivia/src/tokenizer.rs @@ -204,6 +204,9 @@ pub enum SimpleTokenKind { /// `async` Async, + /// `yield` + Yield, + /// Any other non trivia token. Other, From 91f07ed805d25870cc7dc405de6cf18e9bae7c19 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 20 Jul 2023 16:21:19 +0200 Subject: [PATCH 02/13] Expand NeedsParentheses if parent is `await` or `return` --- .../src/expression/expr_yield.rs | 6 +- .../src/expression/expr_yield_from.rs | 6 +- ...ibility@simple_cases__function.py.snap.new | 463 ++++++++++++++++++ ...ple_cases__remove_await_parens.py.snap.new | 310 ++++++++++++ 4 files changed, 783 insertions(+), 2 deletions(-) create mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap.new create mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap.new diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index 7ecc86636880c..376d50feb3b4b 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -50,6 +50,10 @@ impl NeedsParentheses for ExprYield { _parent: AnyNodeRef, _context: &PyFormatContext, ) -> OptionalParentheses { - OptionalParentheses::Multiline + if _parent.is_stmt_return() || _parent.is_expr_await() { + OptionalParentheses::Always + } else { + OptionalParentheses::Multiline + } } } diff --git a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs index 91cbd20cff299..81d4eea61a253 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs @@ -43,6 +43,10 @@ impl NeedsParentheses for ExprYieldFrom { _parent: AnyNodeRef, _context: &PyFormatContext, ) -> OptionalParentheses { - OptionalParentheses::Multiline + if _parent.is_stmt_return() || _parent.is_expr_await() { + OptionalParentheses::Always + } else { + OptionalParentheses::Multiline + } } } diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap.new new file mode 100644 index 0000000000000..2cb3414cc8808 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap.new @@ -0,0 +1,463 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 96 +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function.py +--- +## Input + +```py +#!/usr/bin/env python3 +import asyncio +import sys + +from third_party import X, Y, Z + +from library import some_connection, \ + some_decorator +f'trigger 3.6 mode' +def func_no_args(): + a; b; c + if True: raise RuntimeError + if False: ... + for i in range(10): + print(i) + continue + exec("new-style exec", {}, {}) + return None +async def coroutine(arg, exec=False): + "Single-line docstring. Multiline is harder to reformat." + async with some_connection() as conn: + await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) + await asyncio.sleep(1) +@asyncio.coroutine +@some_decorator( +with_args=True, +many_args=[1,2,3] +) +def function_signature_stress_test(number:int,no_annotation=None,text:str="default",* ,debug:bool=False,**kwargs) -> str: + return text[number:-1] +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_types(a: int = 1, b: tuple = (), c: list = [], d: dict = {}, e: bool = True, f: int = -1, g: int = 1 if False else 2, h: str = "", i: str = r''): ... +def spaces2(result= _core.Value(None)): + assert fut is self._read_fut, (fut, self._read_fut) + # EMPTY LINE WITH WHITESPACE (this comment will be removed) +def example(session): + result = session.query(models.Customer.id).filter( + models.Customer.account_id == account_id, + models.Customer.email == email_address, + ).order_by( + models.Customer.id.asc() + ).all() +def long_lines(): + if True: + typedargslist.extend( + gen_annotated_params(ast_args.kwonlyargs, ast_args.kw_defaults, parameters, implicit_default=True) + ) + typedargslist.extend( + gen_annotated_params( + ast_args.kwonlyargs, ast_args.kw_defaults, parameters, implicit_default=True, + # trailing standalone comment + ) + ) + _type_comment_re = re.compile( + r""" + ^ + [\t ]* + \#[ ]type:[ ]* + (?P + [^#\t\n]+? + ) + (? to match + # a trailing space which is why we need the silliness below + (? + (?:\#[^\n]*)? + \n? + ) + $ + """, re.MULTILINE | re.VERBOSE + ) +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), +} +def f( + a, + **kwargs, +) -> A: + return ( + yield from A( + very_long_argument_name1=very_long_value_for_the_argument, + very_long_argument_name2=very_long_value_for_the_argument, + **kwargs, + ) + ) +def __await__(): return (yield) +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -5,8 +5,7 @@ + from third_party import X, Y, Z + + from library import some_connection, some_decorator +- +-f"trigger 3.6 mode" ++f"NOT_YET_IMPLEMENTED_ExprJoinedStr" + + + def func_no_args(): +@@ -41,12 +40,12 @@ + debug: bool = False, + **kwargs, + ) -> str: +- return text[number:-1] ++ return text[number : -1] + + + 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 ++ assert task._cancel_stack[ : len(old_stack)] == old_stack + + + def spaces_types( +@@ -65,18 +64,14 @@ + + def spaces2(result=_core.Value(None)): + assert fut is self._read_fut, (fut, self._read_fut) ++ # EMPTY LINE WITH WHITESPACE (this comment will be removed) + + + def example(session): +- result = ( +- session.query(models.Customer.id) +- .filter( +- models.Customer.account_id == account_id, +- models.Customer.email == email_address, +- ) +- .order_by(models.Customer.id.asc()) +- .all() +- ) ++ result = session.query(models.Customer.id).filter( ++ models.Customer.account_id == account_id, ++ models.Customer.email == email_address, ++ ).order_by(models.Customer.id.asc()).all() + + + def long_lines(): +``` + +## Ruff Output + +```py +#!/usr/bin/env python3 +import asyncio +import sys + +from third_party import X, Y, Z + +from library import some_connection, some_decorator +f"NOT_YET_IMPLEMENTED_ExprJoinedStr" + + +def func_no_args(): + a + b + c + if True: + raise RuntimeError + if False: + ... + for i in range(10): + print(i) + continue + exec("new-style exec", {}, {}) + return None + + +async def coroutine(arg, exec=False): + "Single-line docstring. Multiline is harder to reformat." + async with some_connection() as conn: + await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2) + await asyncio.sleep(1) + + +@asyncio.coroutine +@some_decorator(with_args=True, many_args=[1, 2, 3]) +def function_signature_stress_test( + number: int, + no_annotation=None, + text: str = "default", + *, + debug: bool = False, + **kwargs, +) -> str: + return text[number : -1] + + +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_types( + a: int = 1, + b: tuple = (), + c: list = [], + d: dict = {}, + e: bool = True, + f: int = -1, + g: int = 1 if False else 2, + h: str = "", + i: str = r"", +): + ... + + +def spaces2(result=_core.Value(None)): + assert fut is self._read_fut, (fut, self._read_fut) + # EMPTY LINE WITH WHITESPACE (this comment will be removed) + + +def example(session): + result = session.query(models.Customer.id).filter( + models.Customer.account_id == account_id, + models.Customer.email == email_address, + ).order_by(models.Customer.id.asc()).all() + + +def long_lines(): + if True: + typedargslist.extend( + gen_annotated_params( + ast_args.kwonlyargs, + ast_args.kw_defaults, + parameters, + implicit_default=True, + ) + ) + typedargslist.extend( + gen_annotated_params( + ast_args.kwonlyargs, + ast_args.kw_defaults, + parameters, + implicit_default=True, + # trailing standalone comment + ) + ) + _type_comment_re = re.compile( + r""" + ^ + [\t ]* + \#[ ]type:[ ]* + (?P + [^#\t\n]+? + ) + (? to match + # a trailing space which is why we need the silliness below + (? + (?:\#[^\n]*)? + \n? + ) + $ + """, + re.MULTILINE | re.VERBOSE, + ) + + +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), + } + + +def f( + a, + **kwargs, +) -> A: + return ( + yield from A( + very_long_argument_name1=very_long_value_for_the_argument, + very_long_argument_name2=very_long_value_for_the_argument, + **kwargs, + ) + ) + + +def __await__(): + return (yield) +``` + +## Black Output + +```py +#!/usr/bin/env python3 +import asyncio +import sys + +from third_party import X, Y, Z + +from library import some_connection, some_decorator + +f"trigger 3.6 mode" + + +def func_no_args(): + a + b + c + if True: + raise RuntimeError + if False: + ... + for i in range(10): + print(i) + continue + exec("new-style exec", {}, {}) + return None + + +async def coroutine(arg, exec=False): + "Single-line docstring. Multiline is harder to reformat." + async with some_connection() as conn: + await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2) + await asyncio.sleep(1) + + +@asyncio.coroutine +@some_decorator(with_args=True, many_args=[1, 2, 3]) +def function_signature_stress_test( + number: int, + no_annotation=None, + text: str = "default", + *, + debug: bool = False, + **kwargs, +) -> str: + return text[number:-1] + + +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_types( + a: int = 1, + b: tuple = (), + c: list = [], + d: dict = {}, + e: bool = True, + f: int = -1, + g: int = 1 if False else 2, + h: str = "", + i: str = r"", +): + ... + + +def spaces2(result=_core.Value(None)): + assert fut is self._read_fut, (fut, self._read_fut) + + +def example(session): + result = ( + session.query(models.Customer.id) + .filter( + models.Customer.account_id == account_id, + models.Customer.email == email_address, + ) + .order_by(models.Customer.id.asc()) + .all() + ) + + +def long_lines(): + if True: + typedargslist.extend( + gen_annotated_params( + ast_args.kwonlyargs, + ast_args.kw_defaults, + parameters, + implicit_default=True, + ) + ) + typedargslist.extend( + gen_annotated_params( + ast_args.kwonlyargs, + ast_args.kw_defaults, + parameters, + implicit_default=True, + # trailing standalone comment + ) + ) + _type_comment_re = re.compile( + r""" + ^ + [\t ]* + \#[ ]type:[ ]* + (?P + [^#\t\n]+? + ) + (? to match + # a trailing space which is why we need the silliness below + (? + (?:\#[^\n]*)? + \n? + ) + $ + """, + re.MULTILINE | re.VERBOSE, + ) + + +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), + } + + +def f( + a, + **kwargs, +) -> A: + return ( + yield from A( + very_long_argument_name1=very_long_value_for_the_argument, + very_long_argument_name2=very_long_value_for_the_argument, + **kwargs, + ) + ) + + +def __await__(): + return (yield) +``` + + diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap.new new file mode 100644 index 0000000000000..7b3d73adb9fe7 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap.new @@ -0,0 +1,310 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 96 +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_await_parens.py +--- +## Input + +```py +import asyncio + +# Control example +async def main(): + await asyncio.sleep(1) + +# Remove brackets for short coroutine/task +async def main(): + await (asyncio.sleep(1)) + +async def main(): + await ( + asyncio.sleep(1) + ) + +async def main(): + await (asyncio.sleep(1) + ) + +# Check comments +async def main(): + await ( # Hello + asyncio.sleep(1) + ) + +async def main(): + await ( + asyncio.sleep(1) # Hello + ) + +async def main(): + await ( + asyncio.sleep(1) + ) # Hello + +# Long lines +async def main(): + await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1)) + +# Same as above but with magic trailing comma in function +async def main(): + await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1),) + +# Cr@zY Br@ck3Tz +async def main(): + await ( + ((((((((((((( + ((( ((( + ((( ((( + ((( ((( + ((( ((( + ((black(1))) + ))) ))) + ))) ))) + ))) ))) + ))) ))) + ))))))))))))) + ) + +# Keep brackets around non power operations and nested awaits +async def main(): + await (set_of_tasks | other_set) + +async def main(): + await (await asyncio.sleep(1)) + +# It's awaits all the way down... +async def main(): + await (await x) + +async def main(): + await (yield x) + +async def main(): + await (await (asyncio.sleep(1))) + +async def main(): + await (await (await (await (await (asyncio.sleep(1)))))) + +async def main(): + await (yield) +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -21,7 +21,10 @@ + + # Check comments + async def main(): +- await asyncio.sleep(1) # Hello ++ await ( ++ # Hello ++ asyncio.sleep(1) ++ ) + + + async def main(): +``` + +## Ruff Output + +```py +import asyncio + + +# Control example +async def main(): + await asyncio.sleep(1) + + +# Remove brackets for short coroutine/task +async def main(): + await asyncio.sleep(1) + + +async def main(): + await asyncio.sleep(1) + + +async def main(): + await asyncio.sleep(1) + + +# Check comments +async def main(): + await ( + # Hello + asyncio.sleep(1) + ) + + +async def main(): + await asyncio.sleep(1) # Hello + + +async def main(): + await asyncio.sleep(1) # Hello + + +# Long lines +async def main(): + await asyncio.gather( + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + ) + + +# Same as above but with magic trailing comma in function +async def main(): + await asyncio.gather( + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + ) + + +# Cr@zY Br@ck3Tz +async def main(): + await black(1) + + +# Keep brackets around non power operations and nested awaits +async def main(): + await (set_of_tasks | other_set) + + +async def main(): + await (await asyncio.sleep(1)) + + +# It's awaits all the way down... +async def main(): + await (await x) + + +async def main(): + await (yield x) + + +async def main(): + await (await asyncio.sleep(1)) + + +async def main(): + await (await (await (await (await asyncio.sleep(1))))) + + +async def main(): + await (yield) +``` + +## Black Output + +```py +import asyncio + + +# Control example +async def main(): + await asyncio.sleep(1) + + +# Remove brackets for short coroutine/task +async def main(): + await asyncio.sleep(1) + + +async def main(): + await asyncio.sleep(1) + + +async def main(): + await asyncio.sleep(1) + + +# Check comments +async def main(): + await asyncio.sleep(1) # Hello + + +async def main(): + await asyncio.sleep(1) # Hello + + +async def main(): + await asyncio.sleep(1) # Hello + + +# Long lines +async def main(): + await asyncio.gather( + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + ) + + +# Same as above but with magic trailing comma in function +async def main(): + await asyncio.gather( + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + ) + + +# Cr@zY Br@ck3Tz +async def main(): + await black(1) + + +# Keep brackets around non power operations and nested awaits +async def main(): + await (set_of_tasks | other_set) + + +async def main(): + await (await asyncio.sleep(1)) + + +# It's awaits all the way down... +async def main(): + await (await x) + + +async def main(): + await (yield x) + + +async def main(): + await (await asyncio.sleep(1)) + + +async def main(): + await (await (await (await (await asyncio.sleep(1))))) + + +async def main(): + await (yield) +``` + + From 0f047c07c17a94b2cf7e97be7eeab4f4b4360aa9 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 20 Jul 2023 16:24:56 +0200 Subject: [PATCH 03/13] Remove unnecessary `FormatRuleWithOptions` review --- .../src/expression/expr_yield.rs | 12 +- .../src/expression/expr_yield_from.rs | 12 +- .../src/expression/mod.rs | 4 +- ...ility@miscellaneous__debug_visitor.py.snap | 5 +- ...y@miscellaneous__debug_visitor.py.snap.new | 165 --- ...aneous__long_strings_flag_disabled.py.snap | 10 +- ...us__long_strings_flag_disabled.py.snap.new | 948 ------------- ...atibility@simple_cases__expression.py.snap | 25 +- ...ility@simple_cases__expression.py.snap.new | 1190 ----------------- ...mpatibility@simple_cases__fmtonoff.py.snap | 13 +- ...ibility@simple_cases__fmtonoff.py.snap.new | 859 ------------ ...mpatibility@simple_cases__function.py.snap | 27 +- ...ibility@simple_cases__function.py.snap.new | 463 ------- ...@simple_cases__remove_await_parens.py.snap | 19 +- ...ple_cases__remove_await_parens.py.snap.new | 310 ----- ...p.new => format@expression__yield.py.snap} | 1 - ... => format@expression__yield_from.py.snap} | 1 - 17 files changed, 30 insertions(+), 4034 deletions(-) delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap.new delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap.new delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap.new delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap.new delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap.new delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap.new rename crates/ruff_python_formatter/tests/snapshots/{format@expression__yield.py.snap.new => format@expression__yield.py.snap} (98%) rename crates/ruff_python_formatter/tests/snapshots/{format@expression__yield_from.py.snap.new => format@expression__yield_from.py.snap} (96%) diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index 376d50feb3b4b..572860736ea04 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -10,17 +10,7 @@ use ruff_python_whitespace::{SimpleTokenizer, TokenKind}; use crate::expression::maybe_parenthesize_expression; #[derive(Default)] -pub struct FormatExprYield{ - parentheses: Option, -} -impl FormatRuleWithOptions> for FormatExprYield { - type Options = Option; - - fn with_options(mut self, options: Self::Options) -> Self { - self.parentheses = options; - self - } -} +pub struct FormatExprYield; impl FormatNodeRule for FormatExprYield { fn fmt_fields(&self, item: &ExprYield, f: &mut PyFormatter) -> FormatResult<()> { diff --git a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs index 81d4eea61a253..b56a33826fb3e 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs @@ -8,17 +8,7 @@ use ruff_formatter::prelude::{space, text}; use crate::expression::maybe_parenthesize_expression; #[derive(Default)] -pub struct FormatExprYieldFrom{ - parentheses: Option, -} -impl FormatRuleWithOptions> for FormatExprYieldFrom { - type Options = Option; - - fn with_options(mut self, options: Self::Options) -> Self { - self.parentheses = options; - self - } -} +pub struct FormatExprYieldFrom; impl FormatNodeRule for FormatExprYieldFrom { fn fmt_fields(&self, item: &ExprYieldFrom, f: &mut PyFormatter) -> FormatResult<()> { diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index bb6abbdf647c7..9cc5141e1c340 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -78,8 +78,8 @@ impl FormatRule> for FormatExpr { Expr::DictComp(expr) => expr.format().fmt(f), Expr::GeneratorExp(expr) => expr.format().fmt(f), Expr::Await(expr) => expr.format().fmt(f), - Expr::Yield(expr) => expr.format().with_options(Some(parentheses)).fmt(f), - Expr::YieldFrom(expr) => expr.format().with_options(Some(parentheses)).fmt(f), + Expr::Yield(expr) => expr.format().fmt(f), + Expr::YieldFrom(expr) => expr.format().fmt(f), Expr::Compare(expr) => expr.format().with_options(Some(parentheses)).fmt(f), Expr::Call(expr) => expr.format().fmt(f), Expr::FormattedValue(expr) => expr.format().fmt(f), diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap index 3dba6a699c169..2773e587be52a 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap @@ -56,8 +56,7 @@ class DebugVisitor(Visitor[T]): + out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow") self.tree_depth += 1 for child in node.children: -- yield from self.visit(child) -+ NOT_YET_IMPLEMENTED_ExprYieldFrom + yield from self.visit(child) self.tree_depth -= 1 - out(f'{indent}/{_type}', fg='yellow', bold=False) @@ -97,7 +96,7 @@ class DebugVisitor(Visitor[T]): out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow") self.tree_depth += 1 for child in node.children: - NOT_YET_IMPLEMENTED_ExprYieldFrom + yield from self.visit(child) self.tree_depth -= 1 out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow", bold=False) diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap.new deleted file mode 100644 index 6c174387d81e8..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap.new +++ /dev/null @@ -1,165 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 96 -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/miscellaneous/debug_visitor.py ---- -## Input - -```py -@dataclass -class DebugVisitor(Visitor[T]): - tree_depth: int = 0 - - def visit_default(self, node: LN) -> Iterator[T]: - indent = ' ' * (2 * self.tree_depth) - if isinstance(node, Node): - _type = type_repr(node.type) - out(f'{indent}{_type}', fg='yellow') - self.tree_depth += 1 - for child in node.children: - yield from self.visit(child) - - self.tree_depth -= 1 - out(f'{indent}/{_type}', fg='yellow', bold=False) - else: - _type = token.tok_name.get(node.type, str(node.type)) - out(f'{indent}{_type}', fg='blue', nl=False) - if node.prefix: - # We don't have to handle prefixes for `Node` objects since - # that delegates to the first child anyway. - out(f' {node.prefix!r}', fg='green', bold=False, nl=False) - out(f' {node.value!r}', fg='blue', bold=False) - - @classmethod - def show(cls, code: str) -> None: - """Pretty-prints a given string of `code`. - - Convenience method for debugging. - """ - v: DebugVisitor[None] = DebugVisitor() - list(v.visit(lib2to3_parse(code))) -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -3,24 +3,29 @@ - tree_depth: int = 0 - - def visit_default(self, node: LN) -> Iterator[T]: -- indent = ' ' * (2 * self.tree_depth) -+ indent = " " * (2 * self.tree_depth) - if isinstance(node, Node): - _type = type_repr(node.type) -- out(f'{indent}{_type}', fg='yellow') -+ out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow") - self.tree_depth += 1 - for child in node.children: - yield from self.visit(child) - - self.tree_depth -= 1 -- out(f'{indent}/{_type}', fg='yellow', bold=False) -+ out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow", bold=False) - else: - _type = token.tok_name.get(node.type, str(node.type)) -- out(f'{indent}{_type}', fg='blue', nl=False) -+ out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="blue", nl=False) - if node.prefix: - # We don't have to handle prefixes for `Node` objects since - # that delegates to the first child anyway. -- out(f' {node.prefix!r}', fg='green', bold=False, nl=False) -- out(f' {node.value!r}', fg='blue', bold=False) -+ out( -+ f"NOT_YET_IMPLEMENTED_ExprJoinedStr", -+ fg="green", -+ bold=False, -+ nl=False, -+ ) -+ out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="blue", bold=False) - - @classmethod - def show(cls, code: str) -> None: -``` - -## Ruff Output - -```py -@dataclass -class DebugVisitor(Visitor[T]): - tree_depth: int = 0 - - def visit_default(self, node: LN) -> Iterator[T]: - indent = " " * (2 * self.tree_depth) - if isinstance(node, Node): - _type = type_repr(node.type) - out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow") - self.tree_depth += 1 - for child in node.children: - yield from self.visit(child) - - self.tree_depth -= 1 - out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="yellow", bold=False) - else: - _type = token.tok_name.get(node.type, str(node.type)) - out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="blue", nl=False) - if node.prefix: - # We don't have to handle prefixes for `Node` objects since - # that delegates to the first child anyway. - out( - f"NOT_YET_IMPLEMENTED_ExprJoinedStr", - fg="green", - bold=False, - nl=False, - ) - out(f"NOT_YET_IMPLEMENTED_ExprJoinedStr", fg="blue", bold=False) - - @classmethod - def show(cls, code: str) -> None: - """Pretty-prints a given string of `code`. - - Convenience method for debugging. - """ - v: DebugVisitor[None] = DebugVisitor() - list(v.visit(lib2to3_parse(code))) -``` - -## Black Output - -```py -@dataclass -class DebugVisitor(Visitor[T]): - tree_depth: int = 0 - - def visit_default(self, node: LN) -> Iterator[T]: - indent = ' ' * (2 * self.tree_depth) - if isinstance(node, Node): - _type = type_repr(node.type) - out(f'{indent}{_type}', fg='yellow') - self.tree_depth += 1 - for child in node.children: - yield from self.visit(child) - - self.tree_depth -= 1 - out(f'{indent}/{_type}', fg='yellow', bold=False) - else: - _type = token.tok_name.get(node.type, str(node.type)) - out(f'{indent}{_type}', fg='blue', nl=False) - if node.prefix: - # We don't have to handle prefixes for `Node` objects since - # that delegates to the first child anyway. - out(f' {node.prefix!r}', fg='green', bold=False, nl=False) - out(f' {node.value!r}', fg='blue', bold=False) - - @classmethod - def show(cls, code: str) -> None: - """Pretty-prints a given string of `code`. - - Convenience method for debugging. - """ - v: DebugVisitor[None] = DebugVisitor() - list(v.visit(lib2to3_parse(code))) -``` - - diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap index f65f0a835bb3a..2a5343cdea2c0 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap @@ -343,12 +343,8 @@ long_unmergable_string_with_pragma = ( ) func_with_bad_parens_that_wont_fit_in_one_line( -@@ -271,10 +267,10 @@ - - - def foo(): -- yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." -+ NOT_YET_IMPLEMENTED_ExprYield +@@ -274,7 +270,7 @@ + yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." -x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." @@ -630,7 +626,7 @@ raw_strings = r"Don't" " get" r" merged" " unless they are all raw." def foo(): - NOT_YET_IMPLEMENTED_ExprYield + yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." x = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap.new deleted file mode 100644 index 37cdc9836c2e8..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap.new +++ /dev/null @@ -1,948 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 96 -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/miscellaneous/long_strings_flag_disabled.py ---- -## Input - -```py -x = "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -y = "Short string" - -print( - "This is a really long string inside of a print statement with extra arguments attached at the end of it.", - x, - y, - z, -) - -print( - "This is a really long string inside of a print statement with no extra arguments attached at the end of it." -) - -D1 = { - "The First": "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - "The Second": "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D2 = { - 1.0: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - 2.0: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D3 = { - x: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - y: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D4 = { - "A long and ridiculous {}".format( - string_key - ): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", - some_func( - "calling", "some", "stuff" - ): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format( - sooo="soooo", x=2 - ), - "A %s %s" - % ( - "formatted", - "string", - ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." - % ("soooo", 2), -} - -func_with_keywords( - my_arg, - my_kwarg="Long keyword strings also need to be wrapped, but they will probably need to be handled a little bit differently.", -) - -bad_split1 = ( - "But what should happen when code has already been formatted but in the wrong way? Like" - " with a space at the end instead of the beginning. Or what about when it is split too soon?" -) - -bad_split2 = ( - "But what should happen when code has already " - "been formatted but in the wrong way? Like " - "with a space at the end instead of the " - "beginning. Or what about when it is split too " - "soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split." -) - -bad_split3 = ( - "What if we have inline comments on " # First Comment - "each line of a bad split? In that " # Second Comment - "case, we should just leave it alone." # Third Comment -) - -bad_split_func1( - "But what should happen when code has already " - "been formatted but in the wrong way? Like " - "with a space at the end instead of the " - "beginning. Or what about when it is split too " - "soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split.", - xxx, - yyy, - zzz, -) - -bad_split_func2( - xxx, - yyy, - zzz, - long_string_kwarg="But what should happen when code has already been formatted but in the wrong way? Like " - "with a space at the end instead of the beginning. Or what about when it is split too " - "soon?", -) - -bad_split_func3( - ( - "But what should happen when code has already " - r"been formatted but in the wrong way? Like " - "with a space at the end instead of the " - r"beginning. Or what about when it is split too " - r"soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split." - ), - xxx, - yyy, - zzz, -) - -raw_string = r"This is a long raw string. When re-formatting this string, black needs to make sure it prepends the 'r' onto the new string." - -fmt_string1 = "We also need to be sure to preserve any and all {} which may or may not be attached to the string in question.".format( - "method calls" -) - -fmt_string2 = "But what about when the string is {} but {}".format( - "short", - "the method call is really really really really really really really really long?", -) - -old_fmt_string1 = ( - "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it." - % ("formatting", "code") -) - -old_fmt_string2 = "This is a %s %s %s %s" % ( - "really really really really really", - "old", - "way to format strings!", - "Use f-strings instead!", -) - -old_fmt_string3 = ( - "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" - % ( - "really really really really really", - "old", - "way to format strings!", - "Use f-strings instead!", - ) -) - -fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." - -fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." - -comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. - -arg_comment_string = print( - "Long lines with inline comments which are apart of (and not the only member of) an argument list should have their comments appended to the reformatted string's enclosing left parentheses.", # This comment stays on the bottom. - "Arg #2", - "Arg #3", - "Arg #4", - "Arg #5", -) - -pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 - -pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa - -"""This is a really really really long triple quote string and it should not be touched.""" - -triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" - -assert ( - some_type_of_boolean_expression -), "Followed by a really really really long string that is used to provide context to the AssertionError exception." - -assert ( - some_type_of_boolean_expression -), "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( - "formatting" -) - -assert some_type_of_boolean_expression, ( - "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s." - % "formatting" -) - -assert some_type_of_boolean_expression, ( - "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s." - % ("string", "formatting") -) - -some_function_call( - "With a reallly generic name and with a really really long string that is, at some point down the line, " - + added - + " to a variable and then added to another string." -) - -some_function_call( - "With a reallly generic name and with a really really long string that is, at some point down the line, " - + added - + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.", - "and a second argument", - and_a_third, -) - -return "A really really really really really really really really really really really really really long {} {}".format( - "return", "value" -) - -func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma which should NOT be there.", -) - -func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma which should NOT be there.", # comment after comma -) - -func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there." - ), -) - -func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there." - ), # comment after comma -) - -func_with_bad_parens_that_wont_fit_in_one_line( - ("short string that should have parens stripped"), x, y, z -) - -func_with_bad_parens_that_wont_fit_in_one_line( - x, y, ("short string that should have parens stripped"), z -) - -func_with_bad_parens( - ("short string that should have parens stripped"), - x, - y, - z, -) - -func_with_bad_parens( - x, - y, - ("short string that should have parens stripped"), - z, -) - -annotated_variable: Final = ( - "This is a large " - + STRING - + " that has been " - + CONCATENATED - + "using the '+' operator." -) -annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." -annotated_variable: Literal[ - "fakse_literal" -] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." - -backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" -backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" -backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" - -short_string = "Hi" " there." - -func_call(short_string=("Hi" " there.")) - -raw_strings = r"Don't" " get" r" merged" " unless they are all raw." - - -def foo(): - yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - - -x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore - " of it." -) - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa - " of it." -) - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check - " of it." -) -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -143,9 +143,9 @@ - ) - ) - --fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." -+fstring = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" - --fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." -+fstring_with_no_fexprs = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" - - comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. - -@@ -165,13 +165,9 @@ - - triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" - --assert ( -- some_type_of_boolean_expression --), "Followed by a really really really long string that is used to provide context to the AssertionError exception." -+assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception." - --assert ( -- some_type_of_boolean_expression --), "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( -+assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( - "formatting" - ) - -@@ -221,8 +217,8 @@ - func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" -- " which should NOT be there." -- ), # comment after comma -+ " which should NOT be there." # comment after comma -+ ), - ) - - func_with_bad_parens_that_wont_fit_in_one_line( -@@ -274,7 +270,7 @@ - yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - - --x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." -+x = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" - - long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore -``` - -## Ruff Output - -```py -x = "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -y = "Short string" - -print( - "This is a really long string inside of a print statement with extra arguments attached at the end of it.", - x, - y, - z, -) - -print( - "This is a really long string inside of a print statement with no extra arguments attached at the end of it." -) - -D1 = { - "The First": "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - "The Second": "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D2 = { - 1.0: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - 2.0: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D3 = { - x: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - y: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D4 = { - "A long and ridiculous {}".format( - string_key - ): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", - some_func( - "calling", "some", "stuff" - ): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format( - sooo="soooo", x=2 - ), - "A %s %s" - % ( - "formatted", - "string", - ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." - % ("soooo", 2), -} - -func_with_keywords( - my_arg, - my_kwarg="Long keyword strings also need to be wrapped, but they will probably need to be handled a little bit differently.", -) - -bad_split1 = ( - "But what should happen when code has already been formatted but in the wrong way? Like" - " with a space at the end instead of the beginning. Or what about when it is split too soon?" -) - -bad_split2 = ( - "But what should happen when code has already " - "been formatted but in the wrong way? Like " - "with a space at the end instead of the " - "beginning. Or what about when it is split too " - "soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split." -) - -bad_split3 = ( - "What if we have inline comments on " # First Comment - "each line of a bad split? In that " # Second Comment - "case, we should just leave it alone." # Third Comment -) - -bad_split_func1( - "But what should happen when code has already " - "been formatted but in the wrong way? Like " - "with a space at the end instead of the " - "beginning. Or what about when it is split too " - "soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split.", - xxx, - yyy, - zzz, -) - -bad_split_func2( - xxx, - yyy, - zzz, - long_string_kwarg="But what should happen when code has already been formatted but in the wrong way? Like " - "with a space at the end instead of the beginning. Or what about when it is split too " - "soon?", -) - -bad_split_func3( - ( - "But what should happen when code has already " - r"been formatted but in the wrong way? Like " - "with a space at the end instead of the " - r"beginning. Or what about when it is split too " - r"soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split." - ), - xxx, - yyy, - zzz, -) - -raw_string = r"This is a long raw string. When re-formatting this string, black needs to make sure it prepends the 'r' onto the new string." - -fmt_string1 = "We also need to be sure to preserve any and all {} which may or may not be attached to the string in question.".format( - "method calls" -) - -fmt_string2 = "But what about when the string is {} but {}".format( - "short", - "the method call is really really really really really really really really long?", -) - -old_fmt_string1 = ( - "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it." - % ("formatting", "code") -) - -old_fmt_string2 = "This is a %s %s %s %s" % ( - "really really really really really", - "old", - "way to format strings!", - "Use f-strings instead!", -) - -old_fmt_string3 = ( - "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" - % ( - "really really really really really", - "old", - "way to format strings!", - "Use f-strings instead!", - ) -) - -fstring = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" - -fstring_with_no_fexprs = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" - -comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. - -arg_comment_string = print( - "Long lines with inline comments which are apart of (and not the only member of) an argument list should have their comments appended to the reformatted string's enclosing left parentheses.", # This comment stays on the bottom. - "Arg #2", - "Arg #3", - "Arg #4", - "Arg #5", -) - -pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 - -pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa - -"""This is a really really really long triple quote string and it should not be touched.""" - -triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" - -assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception." - -assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( - "formatting" -) - -assert some_type_of_boolean_expression, ( - "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s." - % "formatting" -) - -assert some_type_of_boolean_expression, ( - "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s." - % ("string", "formatting") -) - -some_function_call( - "With a reallly generic name and with a really really long string that is, at some point down the line, " - + added - + " to a variable and then added to another string." -) - -some_function_call( - "With a reallly generic name and with a really really long string that is, at some point down the line, " - + added - + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.", - "and a second argument", - and_a_third, -) - -return "A really really really really really really really really really really really really really long {} {}".format( - "return", "value" -) - -func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma which should NOT be there.", -) - -func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma which should NOT be there.", # comment after comma -) - -func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there." - ), -) - -func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there." # comment after comma - ), -) - -func_with_bad_parens_that_wont_fit_in_one_line( - ("short string that should have parens stripped"), x, y, z -) - -func_with_bad_parens_that_wont_fit_in_one_line( - x, y, ("short string that should have parens stripped"), z -) - -func_with_bad_parens( - ("short string that should have parens stripped"), - x, - y, - z, -) - -func_with_bad_parens( - x, - y, - ("short string that should have parens stripped"), - z, -) - -annotated_variable: Final = ( - "This is a large " - + STRING - + " that has been " - + CONCATENATED - + "using the '+' operator." -) -annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." -annotated_variable: Literal[ - "fakse_literal" -] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." - -backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" -backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" -backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" - -short_string = "Hi" " there." - -func_call(short_string=("Hi" " there.")) - -raw_strings = r"Don't" " get" r" merged" " unless they are all raw." - - -def foo(): - yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - - -x = f"NOT_YET_IMPLEMENTED_ExprJoinedStr" - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore - " of it." -) - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa - " of it." -) - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check - " of it." -) -``` - -## Black Output - -```py -x = "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -y = "Short string" - -print( - "This is a really long string inside of a print statement with extra arguments attached at the end of it.", - x, - y, - z, -) - -print( - "This is a really long string inside of a print statement with no extra arguments attached at the end of it." -) - -D1 = { - "The First": "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - "The Second": "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D2 = { - 1.0: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - 2.0: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D3 = { - x: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - y: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D4 = { - "A long and ridiculous {}".format( - string_key - ): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", - some_func( - "calling", "some", "stuff" - ): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format( - sooo="soooo", x=2 - ), - "A %s %s" - % ( - "formatted", - "string", - ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." - % ("soooo", 2), -} - -func_with_keywords( - my_arg, - my_kwarg="Long keyword strings also need to be wrapped, but they will probably need to be handled a little bit differently.", -) - -bad_split1 = ( - "But what should happen when code has already been formatted but in the wrong way? Like" - " with a space at the end instead of the beginning. Or what about when it is split too soon?" -) - -bad_split2 = ( - "But what should happen when code has already " - "been formatted but in the wrong way? Like " - "with a space at the end instead of the " - "beginning. Or what about when it is split too " - "soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split." -) - -bad_split3 = ( - "What if we have inline comments on " # First Comment - "each line of a bad split? In that " # Second Comment - "case, we should just leave it alone." # Third Comment -) - -bad_split_func1( - "But what should happen when code has already " - "been formatted but in the wrong way? Like " - "with a space at the end instead of the " - "beginning. Or what about when it is split too " - "soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split.", - xxx, - yyy, - zzz, -) - -bad_split_func2( - xxx, - yyy, - zzz, - long_string_kwarg="But what should happen when code has already been formatted but in the wrong way? Like " - "with a space at the end instead of the beginning. Or what about when it is split too " - "soon?", -) - -bad_split_func3( - ( - "But what should happen when code has already " - r"been formatted but in the wrong way? Like " - "with a space at the end instead of the " - r"beginning. Or what about when it is split too " - r"soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split." - ), - xxx, - yyy, - zzz, -) - -raw_string = r"This is a long raw string. When re-formatting this string, black needs to make sure it prepends the 'r' onto the new string." - -fmt_string1 = "We also need to be sure to preserve any and all {} which may or may not be attached to the string in question.".format( - "method calls" -) - -fmt_string2 = "But what about when the string is {} but {}".format( - "short", - "the method call is really really really really really really really really long?", -) - -old_fmt_string1 = ( - "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it." - % ("formatting", "code") -) - -old_fmt_string2 = "This is a %s %s %s %s" % ( - "really really really really really", - "old", - "way to format strings!", - "Use f-strings instead!", -) - -old_fmt_string3 = ( - "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" - % ( - "really really really really really", - "old", - "way to format strings!", - "Use f-strings instead!", - ) -) - -fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." - -fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." - -comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. - -arg_comment_string = print( - "Long lines with inline comments which are apart of (and not the only member of) an argument list should have their comments appended to the reformatted string's enclosing left parentheses.", # This comment stays on the bottom. - "Arg #2", - "Arg #3", - "Arg #4", - "Arg #5", -) - -pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 - -pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa - -"""This is a really really really long triple quote string and it should not be touched.""" - -triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" - -assert ( - some_type_of_boolean_expression -), "Followed by a really really really long string that is used to provide context to the AssertionError exception." - -assert ( - some_type_of_boolean_expression -), "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( - "formatting" -) - -assert some_type_of_boolean_expression, ( - "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s." - % "formatting" -) - -assert some_type_of_boolean_expression, ( - "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s." - % ("string", "formatting") -) - -some_function_call( - "With a reallly generic name and with a really really long string that is, at some point down the line, " - + added - + " to a variable and then added to another string." -) - -some_function_call( - "With a reallly generic name and with a really really long string that is, at some point down the line, " - + added - + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.", - "and a second argument", - and_a_third, -) - -return "A really really really really really really really really really really really really really long {} {}".format( - "return", "value" -) - -func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma which should NOT be there.", -) - -func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma which should NOT be there.", # comment after comma -) - -func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there." - ), -) - -func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there." - ), # comment after comma -) - -func_with_bad_parens_that_wont_fit_in_one_line( - ("short string that should have parens stripped"), x, y, z -) - -func_with_bad_parens_that_wont_fit_in_one_line( - x, y, ("short string that should have parens stripped"), z -) - -func_with_bad_parens( - ("short string that should have parens stripped"), - x, - y, - z, -) - -func_with_bad_parens( - x, - y, - ("short string that should have parens stripped"), - z, -) - -annotated_variable: Final = ( - "This is a large " - + STRING - + " that has been " - + CONCATENATED - + "using the '+' operator." -) -annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." -annotated_variable: Literal[ - "fakse_literal" -] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." - -backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" -backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" -backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" - -short_string = "Hi" " there." - -func_call(short_string=("Hi" " there.")) - -raw_strings = r"Don't" " get" r" merged" " unless they are all raw." - - -def foo(): - yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - - -x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore - " of it." -) - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa - " of it." -) - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check - " of it." -) -``` - - 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 86b8c8ddbe95a..98a72f9520cfd 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 @@ -357,22 +357,7 @@ last_call() Ø = set() authors.łukasz.say_thanks() mapping = { -@@ -237,10 +234,10 @@ - - - def gen(): -- yield from outside_of_generator -- a = yield -- b = yield -- c = yield -+ NOT_YET_IMPLEMENTED_ExprYieldFrom -+ a = NOT_YET_IMPLEMENTED_ExprYield -+ b = NOT_YET_IMPLEMENTED_ExprYield -+ c = NOT_YET_IMPLEMENTED_ExprYield - - - async def f(): -@@ -328,13 +325,18 @@ +@@ -328,13 +328,18 @@ ): return True if ( @@ -645,10 +630,10 @@ mapping = { def gen(): - NOT_YET_IMPLEMENTED_ExprYieldFrom - a = NOT_YET_IMPLEMENTED_ExprYield - b = NOT_YET_IMPLEMENTED_ExprYield - c = NOT_YET_IMPLEMENTED_ExprYield + yield from outside_of_generator + a = yield + b = yield + c = yield async def f(): diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap.new deleted file mode 100644 index 310c417865c4d..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap.new +++ /dev/null @@ -1,1190 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 96 -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.py ---- -## Input - -```py -... -'some_string' -b'\\xa3' -Name -None -True -False -1 -1.0 -1j -True or False -True or False or None -True and False -True and False and None -(Name1 and Name2) or Name3 -Name1 and Name2 or Name3 -Name1 or (Name2 and Name3) -Name1 or Name2 and Name3 -(Name1 and Name2) or (Name3 and Name4) -Name1 and Name2 or Name3 and Name4 -Name1 or (Name2 and Name3) or Name4 -Name1 or Name2 and Name3 or Name4 -v1 << 2 -1 >> v2 -1 % finished -1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8 -((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8) -not great -~great -+value --1 -~int and not v1 ^ 123 + v2 | True -(~int) and (not ((v1 ^ (123 + v2)) | True)) -+really ** -confusing ** ~operator ** -precedence -flags & ~ select.EPOLLIN and waiters.write_task is not None -lambda arg: None -lambda a=True: a -lambda a, b, c=True: a -lambda a, b, c=True, *, d=(1 << v2), e='str': a -lambda a, b, c=True, *vararg, d=(v1 << 2), e='str', **kwargs: a + b -manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() -foo = (lambda port_id, ignore_missing: {"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)) -{'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 -() -(1,) -(1, 2) -(1, 2, 3) -[] -[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)] -[1, 2, 3,] -[*a] -[*range(10)] -[*a, 4, 5,] -[4, *a, 5,] -[this_is_a_very_long_variable_which_will_force_a_delimiter_split, element, another, *more] -{i for i in (1, 2, 3)} -{(i ** 2) for i in (1, 2, 3)} -{(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)} -[i for i in (1, 2, 3)] -[(i ** 2) for i in (1, 2, 3)] -[(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)] -{i: 0 for i in (1, 2, 3)} -{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))} -{a: b * 2 for a, b in dictionary.items()} -{a: b * -2 for a, b in dictionary.items()} -{k: v for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension} -Python3 > Python2 > COBOL -Life is Life -call() -call(arg) -call(kwarg='hey') -call(arg, kwarg='hey') -call(arg, another, kwarg='hey', **kwargs) -call(this_is_a_very_long_variable_which_will_force_a_delimiter_split, arg, another, kwarg='hey', **kwargs) # note: no trailing comma pre-3.6 -call(*gidgets[:2]) -call(a, *gidgets[:2]) -call(**self.screen_kwargs) -call(b, **self.screen_kwargs) -lukasz.langa.pl -call.me(maybe) -1 .real -1.0 .real -....__class__ -list[str] -dict[str, int] -tuple[str, ...] -tuple[ - str, int, float, dict[str, int] -] -tuple[str, int, float, dict[str, int],] -very_long_variable_name_filters: t.List[ - t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], -] -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 - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) -) -xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[ - ..., List[SomeClass] -] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore -slice[0] -slice[0:1] -slice[0:1:2] -slice[:] -slice[:-1] -slice[1:] -slice[::-1] -slice[d :: d + 1] -slice[:c, c - 1] -numpy[:, 0:1] -numpy[:, :-1] -numpy[0, :] -numpy[:, i] -numpy[0, :2] -numpy[:N, 0] -numpy[:2, :4] -numpy[2:4, 1:5] -numpy[4:, 2:] -numpy[:, (0, 1, 2, 5)] -numpy[0, [0]] -numpy[:, [i]] -numpy[1 : c + 1, c] -numpy[-(c + 1) :, d] -numpy[:, l[-2]] -numpy[:, ::-1] -numpy[np.newaxis, :] -(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'} -[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] -(SomeName) -SomeName -(Good, Bad, Ugly) -(i for i in (1, 2, 3)) -((i ** 2) for i in (1, 2, 3)) -((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,) -{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs} -a = (1,) -b = 1, -c = 1 -d = (1,) + a + (2,) -e = (1,).count(1) -f = 1, *range(10) -g = 1, *"ten" -what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set(vars_to_remove) -what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(vars_to_remove) -result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc()).all() -result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc(),).all() -Ø = 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), -} - -def gen(): - yield from outside_of_generator - a = (yield) - b = ((yield)) - c = (((yield))) - -async def f(): - await some.complicated[0].call(with_args=(True or (1 is not 1))) -print(* [] or [1]) -print(**{1: 3} if False else {x: x for x in range(3)}) -print(* lambda x: x) -assert(not Test),("Short message") -assert this is ComplexTest and not requirements.fit_in_a_single_line(force=False), "Short message" -assert(((parens is TooMany))) -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): ... -for addr_family, addr_type, addr_proto, addr_canonname, addr_sockaddr in socket.getaddrinfo('google.com', 'http'): - pass -a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -if ( - threading.current_thread() != threading.main_thread() and - threading.current_thread() != threading.main_thread() or - signal.getsignal(signal.SIGINT) != signal.default_int_handler -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa / - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - ~ aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l ** aaaa.m // aaaa.n -): - return True -if ( - ~ aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l ** aaaaaaaa.m // aaaaaaaa.n -): - return True -if ( - ~ aaaaaaaaaaaaaaaa.a + aaaaaaaaaaaaaaaa.b - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h ^ aaaaaaaaaaaaaaaa.i << aaaaaaaaaaaaaaaa.k >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n -): - return True -aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa - aaaaaaaaaaaaaaaa * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) -aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa -aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -bbbb >> bbbb * bbbb -aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ^bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -last_call() -# standalone comment at ENDMARKER -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -1,6 +1,6 @@ - ... - "some_string" --b"\\xa3" -+b"NOT_YET_IMPLEMENTED_BYTE_STRING" - Name - None - True -@@ -31,7 +31,7 @@ - -1 - ~int and not v1 ^ 123 + v2 | True - (~int) and (not ((v1 ^ (123 + v2)) | True)) --+(really ** -(confusing ** ~(operator**-precedence))) -++really ** -confusing ** ~operator**-precedence - flags & ~select.EPOLLIN and waiters.write_task is not None - lambda arg: None - lambda a=True: a -@@ -39,10 +39,11 @@ - lambda a, b, c=True, *, d=(1 << v2), e="str": a - lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b - manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() --foo = lambda port_id, ignore_missing: { -- "port1": port1_resource, -- "port2": port2_resource, --}[port_id] -+foo = ( -+ lambda port_id, ignore_missing: {"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) -@@ -57,7 +58,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")} --({"a": "b"}, (True or False), (+value), "string", b"bytes") or None -+( -+ {"a": "b"}, -+ (True or False), -+ (+value), -+ "string", -+ b"NOT_YET_IMPLEMENTED_BYTE_STRING", -+) or None - () - (1,) - (1, 2) -@@ -101,7 +108,10 @@ - {a: b * -2 for a, b in dictionary.items()} - { - k: v -- for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension -+ for ( -+ k, -+ v, -+ ) in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension - } - Python3 > Python2 > COBOL - Life is Life -@@ -115,7 +125,7 @@ - arg, - another, - kwarg="hey", -- **kwargs -+ **kwargs, - ) # note: no trailing comma pre-3.6 - call(*gidgets[:2]) - call(a, *gidgets[:2]) -@@ -152,13 +162,13 @@ - slice[0:1] - slice[0:1:2] - slice[:] --slice[:-1] -+slice[ : -1] - slice[1:] --slice[::-1] -+slice[ :: -1] - slice[d :: d + 1] - slice[:c, c - 1] - numpy[:, 0:1] --numpy[:, :-1] -+numpy[:, : -1] - numpy[0, :] - numpy[:, i] - numpy[0, :2] -@@ -172,7 +182,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) - {"2.7": dead, "3.7": long_live or die_hard} -@@ -208,24 +218,14 @@ - what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( - vars_to_remove - ) --result = ( -- session.query(models.Customer.id) -- .filter( -- models.Customer.account_id == account_id, models.Customer.email == email_address -- ) -- .order_by(models.Customer.id.asc()) -- .all() --) --result = ( -- session.query(models.Customer.id) -- .filter( -- models.Customer.account_id == account_id, models.Customer.email == email_address -- ) -- .order_by( -- models.Customer.id.asc(), -- ) -- .all() --) -+result = session.query(models.Customer.id).filter( -+ models.Customer.account_id == account_id, models.Customer.email == email_address -+).order_by(models.Customer.id.asc()).all() -+result = session.query(models.Customer.id).filter( -+ models.Customer.account_id == account_id, models.Customer.email == email_address -+).order_by( -+ models.Customer.id.asc(), -+).all() - Ø = set() - authors.łukasz.say_thanks() - mapping = { -@@ -328,13 +328,18 @@ - ): - return True - if ( -- ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e -+ ~aaaa.a -+ + aaaa.b -+ - aaaa.c * aaaa.d / aaaa.e - | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n - ): - return True - if ( -- ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e -- | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h -+ ~aaaaaaaa.a -+ + aaaaaaaa.b -+ - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e -+ | aaaaaaaa.f -+ & aaaaaaaa.g % aaaaaaaa.h - ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n - ): - return True -@@ -342,7 +347,8 @@ - ~aaaaaaaaaaaaaaaa.a - + aaaaaaaaaaaaaaaa.b - - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e -- | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h -+ | aaaaaaaaaaaaaaaa.f -+ & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h - ^ aaaaaaaaaaaaaaaa.i - << aaaaaaaaaaaaaaaa.k - >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n -``` - -## Ruff Output - -```py -... -"some_string" -b"NOT_YET_IMPLEMENTED_BYTE_STRING" -Name -None -True -False -1 -1.0 -1j -True or False -True or False or None -True and False -True and False and None -(Name1 and Name2) or Name3 -Name1 and Name2 or Name3 -Name1 or (Name2 and Name3) -Name1 or Name2 and Name3 -(Name1 and Name2) or (Name3 and Name4) -Name1 and Name2 or Name3 and Name4 -Name1 or (Name2 and Name3) or Name4 -Name1 or Name2 and Name3 or Name4 -v1 << 2 -1 >> v2 -1 % finished -1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8 -((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8) -not great -~great -+value --1 -~int and not v1 ^ 123 + v2 | True -(~int) and (not ((v1 ^ (123 + v2)) | True)) -+really ** -confusing ** ~operator**-precedence -flags & ~select.EPOLLIN and waiters.write_task is not None -lambda arg: None -lambda a=True: a -lambda a, b, c=True: a -lambda a, b, c=True, *, d=(1 << v2), e="str": a -lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b -manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() -foo = ( - lambda port_id, ignore_missing: {"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) -) -{"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"NOT_YET_IMPLEMENTED_BYTE_STRING", -) or None -() -(1,) -(1, 2) -(1, 2, 3) -[] -[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)] -[ - 1, - 2, - 3, -] -[*a] -[*range(10)] -[ - *a, - 4, - 5, -] -[ - 4, - *a, - 5, -] -[ - this_is_a_very_long_variable_which_will_force_a_delimiter_split, - element, - another, - *more, -] -{i for i in (1, 2, 3)} -{(i**2) for i in (1, 2, 3)} -{(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)} -[i for i in (1, 2, 3)] -[(i**2) for i in (1, 2, 3)] -[(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)] -{i: 0 for i in (1, 2, 3)} -{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))} -{a: b * 2 for a, b in dictionary.items()} -{a: b * -2 for a, b in dictionary.items()} -{ - k: v - for ( - k, - v, - ) in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension -} -Python3 > Python2 > COBOL -Life is Life -call() -call(arg) -call(kwarg="hey") -call(arg, kwarg="hey") -call(arg, another, kwarg="hey", **kwargs) -call( - this_is_a_very_long_variable_which_will_force_a_delimiter_split, - arg, - another, - kwarg="hey", - **kwargs, -) # note: no trailing comma pre-3.6 -call(*gidgets[:2]) -call(a, *gidgets[:2]) -call(**self.screen_kwargs) -call(b, **self.screen_kwargs) -lukasz.langa.pl -call.me(maybe) -(1).real -(1.0).real -....__class__ -list[str] -dict[str, int] -tuple[str, ...] -tuple[str, int, float, dict[str, int]] -tuple[ - str, - int, - float, - dict[str, int], -] -very_long_variable_name_filters: t.List[ - t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], -] -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 - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) -) -xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) -) # type: ignore -slice[0] -slice[0:1] -slice[0:1:2] -slice[:] -slice[ : -1] -slice[1:] -slice[ :: -1] -slice[d :: d + 1] -slice[:c, c - 1] -numpy[:, 0:1] -numpy[:, : -1] -numpy[0, :] -numpy[:, i] -numpy[0, :2] -numpy[:N, 0] -numpy[:2, :4] -numpy[2:4, 1:5] -numpy[4:, 2:] -numpy[:, (0, 1, 2, 5)] -numpy[0, [0]] -numpy[:, [i]] -numpy[1 : c + 1, c] -numpy[-(c + 1) :, d] -numpy[:, l[-2]] -numpy[:, :: -1] -numpy[np.newaxis, :] -(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"} -[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] -(SomeName) -SomeName -(Good, Bad, Ugly) -(i for i in (1, 2, 3)) -((i**2) for i in (1, 2, 3)) -((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,) -{ - "id": "1", - "type": "type", - "started_at": now(), - "ended_at": now() + timedelta(days=10), - "priority": 1, - "import_session_id": 1, - **kwargs, -} -a = (1,) -b = (1,) -c = 1 -d = (1,) + a + (2,) -e = (1,).count(1) -f = 1, *range(10) -g = 1, *"ten" -what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set( - vars_to_remove -) -what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( - vars_to_remove -) -result = session.query(models.Customer.id).filter( - models.Customer.account_id == account_id, models.Customer.email == email_address -).order_by(models.Customer.id.asc()).all() -result = session.query(models.Customer.id).filter( - models.Customer.account_id == account_id, models.Customer.email == email_address -).order_by( - models.Customer.id.asc(), -).all() -Ø = 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), -} - - -def gen(): - yield from outside_of_generator - a = yield - b = yield - c = yield - - -async def f(): - await some.complicated[0].call(with_args=(True or (1 is not 1))) - - -print(*[] or [1]) -print(**{1: 3} if False else {x: x for x in range(3)}) -print(*lambda x: x) -assert not Test, "Short message" -assert this is ComplexTest and not requirements.fit_in_a_single_line( - force=False -), "Short message" -assert parens is TooMany -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: - ... -for ( - addr_family, - addr_type, - addr_proto, - addr_canonname, - addr_sockaddr, -) in socket.getaddrinfo("google.com", "http"): - pass -a = ( - aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp - in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -) -a = ( - aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp - not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -) -a = ( - aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp - is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -) -a = ( - aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp - is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -) -if ( - threading.current_thread() != threading.main_thread() - and threading.current_thread() != threading.main_thread() - or signal.getsignal(signal.SIGINT) != signal.default_int_handler -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - ~aaaa.a - + aaaa.b - - aaaa.c * aaaa.d / aaaa.e - | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n -): - return True -if ( - ~aaaaaaaa.a - + aaaaaaaa.b - - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e - | aaaaaaaa.f - & aaaaaaaa.g % aaaaaaaa.h - ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n -): - return True -if ( - ~aaaaaaaaaaaaaaaa.a - + aaaaaaaaaaaaaaaa.b - - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e - | aaaaaaaaaaaaaaaa.f - & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h - ^ aaaaaaaaaaaaaaaa.i - << aaaaaaaaaaaaaaaa.k - >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n -): - return True -( - aaaaaaaaaaaaaaaa - + aaaaaaaaaaaaaaaa - - aaaaaaaaaaaaaaaa - * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) - / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) -) -aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa -( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -) -bbbb >> bbbb * bbbb -( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -) -last_call() -# standalone comment at ENDMARKER -``` - -## Black Output - -```py -... -"some_string" -b"\\xa3" -Name -None -True -False -1 -1.0 -1j -True or False -True or False or None -True and False -True and False and None -(Name1 and Name2) or Name3 -Name1 and Name2 or Name3 -Name1 or (Name2 and Name3) -Name1 or Name2 and Name3 -(Name1 and Name2) or (Name3 and Name4) -Name1 and Name2 or Name3 and Name4 -Name1 or (Name2 and Name3) or Name4 -Name1 or Name2 and Name3 or Name4 -v1 << 2 -1 >> v2 -1 % finished -1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8 -((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8) -not great -~great -+value --1 -~int and not v1 ^ 123 + v2 | True -(~int) and (not ((v1 ^ (123 + v2)) | True)) -+(really ** -(confusing ** ~(operator**-precedence))) -flags & ~select.EPOLLIN and waiters.write_task is not None -lambda arg: None -lambda a=True: a -lambda a, b, c=True: a -lambda a, b, c=True, *, d=(1 << v2), e="str": a -lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b -manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() -foo = lambda port_id, ignore_missing: { - "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) -) -{"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 -() -(1,) -(1, 2) -(1, 2, 3) -[] -[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)] -[ - 1, - 2, - 3, -] -[*a] -[*range(10)] -[ - *a, - 4, - 5, -] -[ - 4, - *a, - 5, -] -[ - this_is_a_very_long_variable_which_will_force_a_delimiter_split, - element, - another, - *more, -] -{i for i in (1, 2, 3)} -{(i**2) for i in (1, 2, 3)} -{(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)} -[i for i in (1, 2, 3)] -[(i**2) for i in (1, 2, 3)] -[(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)] -{i: 0 for i in (1, 2, 3)} -{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))} -{a: b * 2 for a, b in dictionary.items()} -{a: b * -2 for a, b in dictionary.items()} -{ - k: v - for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension -} -Python3 > Python2 > COBOL -Life is Life -call() -call(arg) -call(kwarg="hey") -call(arg, kwarg="hey") -call(arg, another, kwarg="hey", **kwargs) -call( - this_is_a_very_long_variable_which_will_force_a_delimiter_split, - arg, - another, - kwarg="hey", - **kwargs -) # note: no trailing comma pre-3.6 -call(*gidgets[:2]) -call(a, *gidgets[:2]) -call(**self.screen_kwargs) -call(b, **self.screen_kwargs) -lukasz.langa.pl -call.me(maybe) -(1).real -(1.0).real -....__class__ -list[str] -dict[str, int] -tuple[str, ...] -tuple[str, int, float, dict[str, int]] -tuple[ - str, - int, - float, - dict[str, int], -] -very_long_variable_name_filters: t.List[ - t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], -] -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 - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) -) -xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( - sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) -) # type: ignore -slice[0] -slice[0:1] -slice[0:1:2] -slice[:] -slice[:-1] -slice[1:] -slice[::-1] -slice[d :: d + 1] -slice[:c, c - 1] -numpy[:, 0:1] -numpy[:, :-1] -numpy[0, :] -numpy[:, i] -numpy[0, :2] -numpy[:N, 0] -numpy[:2, :4] -numpy[2:4, 1:5] -numpy[4:, 2:] -numpy[:, (0, 1, 2, 5)] -numpy[0, [0]] -numpy[:, [i]] -numpy[1 : c + 1, c] -numpy[-(c + 1) :, d] -numpy[:, l[-2]] -numpy[:, ::-1] -numpy[np.newaxis, :] -(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"} -[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] -(SomeName) -SomeName -(Good, Bad, Ugly) -(i for i in (1, 2, 3)) -((i**2) for i in (1, 2, 3)) -((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,) -{ - "id": "1", - "type": "type", - "started_at": now(), - "ended_at": now() + timedelta(days=10), - "priority": 1, - "import_session_id": 1, - **kwargs, -} -a = (1,) -b = (1,) -c = 1 -d = (1,) + a + (2,) -e = (1,).count(1) -f = 1, *range(10) -g = 1, *"ten" -what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set( - vars_to_remove -) -what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( - vars_to_remove -) -result = ( - session.query(models.Customer.id) - .filter( - models.Customer.account_id == account_id, models.Customer.email == email_address - ) - .order_by(models.Customer.id.asc()) - .all() -) -result = ( - session.query(models.Customer.id) - .filter( - models.Customer.account_id == account_id, models.Customer.email == email_address - ) - .order_by( - models.Customer.id.asc(), - ) - .all() -) -Ø = 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), -} - - -def gen(): - yield from outside_of_generator - a = yield - b = yield - c = yield - - -async def f(): - await some.complicated[0].call(with_args=(True or (1 is not 1))) - - -print(*[] or [1]) -print(**{1: 3} if False else {x: x for x in range(3)}) -print(*lambda x: x) -assert not Test, "Short message" -assert this is ComplexTest and not requirements.fit_in_a_single_line( - force=False -), "Short message" -assert parens is TooMany -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: - ... -for ( - addr_family, - addr_type, - addr_proto, - addr_canonname, - addr_sockaddr, -) in socket.getaddrinfo("google.com", "http"): - pass -a = ( - aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp - in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -) -a = ( - aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp - not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -) -a = ( - aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp - is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -) -a = ( - aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp - is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -) -if ( - threading.current_thread() != threading.main_thread() - and threading.current_thread() != threading.main_thread() - or signal.getsignal(signal.SIGINT) != signal.default_int_handler -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -): - return True -if ( - ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e - | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n -): - return True -if ( - ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e - | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h - ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n -): - return True -if ( - ~aaaaaaaaaaaaaaaa.a - + aaaaaaaaaaaaaaaa.b - - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e - | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h - ^ aaaaaaaaaaaaaaaa.i - << aaaaaaaaaaaaaaaa.k - >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n -): - return True -( - aaaaaaaaaaaaaaaa - + aaaaaaaaaaaaaaaa - - aaaaaaaaaaaaaaaa - * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) - / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) -) -aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa -( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -) -bbbb >> bbbb * bbbb -( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -) -last_call() -# standalone comment at ENDMARKER -``` - - 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 c291a6d4e0ea1..1f83b05cc2962 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 @@ -311,16 +311,15 @@ d={'a':1, def yield_expr(): # fmt: off -- yield hello + yield hello - 'unformatted' -+ NOT_YET_IMPLEMENTED_ExprYield + "unformatted" # fmt: on "formatted" # fmt: off - ( yield hello ) - 'unformatted' -+ (NOT_YET_IMPLEMENTED_ExprYield) ++ (yield hello) + "unformatted" # fmt: on @@ -379,7 +378,7 @@ d={'a':1, ) # fmt: off -yield 'hello' -+NOT_YET_IMPLEMENTED_ExprYield ++yield "hello" # No formatting to the end of the file -l=[1,2,3] -d={'a':1, @@ -500,12 +499,12 @@ def testlist_star_expr(): def yield_expr(): # fmt: off - NOT_YET_IMPLEMENTED_ExprYield + yield hello "unformatted" # fmt: on "formatted" # fmt: off - (NOT_YET_IMPLEMENTED_ExprYield) + (yield hello) "unformatted" # fmt: on @@ -617,7 +616,7 @@ cfg.rule( xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5, ) # fmt: off -NOT_YET_IMPLEMENTED_ExprYield +yield "hello" # No formatting to the end of the file l = [1, 2, 3] d = {"a": 1, "b": 2} diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap.new deleted file mode 100644 index 9bc2c4e4ea42f..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap.new +++ /dev/null @@ -1,859 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 96 -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff.py ---- -## Input - -```py -#!/usr/bin/env python3 -import asyncio -import sys - -from third_party import X, Y, Z - -from library import some_connection, \ - some_decorator -# fmt: off -from third_party import (X, - Y, Z) -# fmt: on -f'trigger 3.6 mode' -# Comment 1 - -# Comment 2 - -# fmt: off -def func_no_args(): - a; b; c - if True: raise RuntimeError - if False: ... - for i in range(10): - print(i) - continue - exec('new-style exec', {}, {}) - return None -async def coroutine(arg, exec=False): - 'Single-line docstring. Multiline is harder to reformat.' - async with some_connection() as conn: - await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) - await asyncio.sleep(1) -@asyncio.coroutine -@some_decorator( -with_args=True, -many_args=[1,2,3] -) -def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str: - return text[number:-1] -# fmt: on -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_types(a: int = 1, b: tuple = (), c: list = [], d: dict = {}, e: bool = True, f: int = -1, g: int = 1 if False else 2, h: str = "", i: str = r''): ... -def spaces2(result= _core.Value(None)): - ... -something = { - # fmt: off - key: 'value', -} - -def subscriptlist(): - atom[ - # fmt: off - 'some big and', - 'complex subscript', - # fmt: on - goes + here, andhere, - ] - -def import_as_names(): - # fmt: off - from hello import a, b - 'unformatted' - # fmt: on - -def testlist_star_expr(): - # fmt: off - a , b = *hello - 'unformatted' - # fmt: on - -def yield_expr(): - # fmt: off - yield hello - 'unformatted' - # fmt: on - 'formatted' - # fmt: off - ( yield hello ) - 'unformatted' - # fmt: on - -def example(session): - # fmt: off - result = session\ - .query(models.Customer.id)\ - .filter(models.Customer.account_id == account_id, - models.Customer.email == email_address)\ - .order_by(models.Customer.id.asc())\ - .all() - # fmt: on -def off_and_on_without_data(): - """All comments here are technically on the same prefix. - - The comments between will be formatted. This is a known limitation. - """ - # fmt: off - - - #hey, that won't work - - - # fmt: on - pass -def on_and_off_broken(): - """Another known limitation.""" - # fmt: on - # fmt: off - this=should.not_be.formatted() - and_=indeed . it is not formatted - because . the . handling . inside . generate_ignored_nodes() - now . considers . multiple . fmt . directives . within . one . prefix - # fmt: on - # fmt: off - # ...but comments still get reformatted even though they should not be - # fmt: on -def long_lines(): - if True: - typedargslist.extend( - gen_annotated_params(ast_args.kwonlyargs, ast_args.kw_defaults, parameters, implicit_default=True) - ) - # fmt: off - a = ( - unnecessary_bracket() - ) - # fmt: on - _type_comment_re = re.compile( - r""" - ^ - [\t ]* - \#[ ]type:[ ]* - (?P - [^#\t\n]+? - ) - (? to match - # a trailing space which is why we need the silliness below - (? - (?:\#[^\n]*)? - \n? - ) - $ - """, - # fmt: off - re.MULTILINE|re.VERBOSE - # fmt: on - ) -def single_literal_yapf_disable(): - """Black does not support this.""" - BAZ = { - (1, 2, 3, 4), - (5, 6, 7, 8), - (9, 10, 11, 12) - } # yapf: disable -cfg.rule( - "Default", "address", - xxxx_xxxx=["xxx-xxxxxx-xxxxxxxxxx"], - xxxxxx="xx_xxxxx", xxxxxxx="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - xxxxxxxxx_xxxx=True, xxxxxxxx_xxxxxxxxxx=False, - xxxxxx_xxxxxx=2, xxxxxx_xxxxx_xxxxxxxx=70, xxxxxx_xxxxxx_xxxxx=True, - # fmt: off - xxxxxxx_xxxxxxxxxxxx={ - "xxxxxxxx": { - "xxxxxx": False, - "xxxxxxx": False, - "xxxx_xxxxxx": "xxxxx", - }, - "xxxxxxxx-xxxxx": { - "xxxxxx": False, - "xxxxxxx": True, - "xxxx_xxxxxx": "xxxxxx", - }, - }, - # fmt: on - xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5 -) -# fmt: off -yield 'hello' -# No formatting to the end of the file -l=[1,2,3] -d={'a':1, - 'b':2} -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -6,10 +6,9 @@ - - from library import some_connection, some_decorator - # fmt: off --from third_party import (X, -- Y, Z) -+from third_party import X, Y, Z - # fmt: on --f"trigger 3.6 mode" -+f"NOT_YET_IMPLEMENTED_ExprJoinedStr" - # Comment 1 - - # Comment 2 -@@ -17,30 +16,44 @@ - - # fmt: off - def func_no_args(): -- a; b; c -- if True: raise RuntimeError -- if False: ... -- for i in range(10): -- print(i) -- continue -- exec('new-style exec', {}, {}) -- return None -+ a -+ b -+ c -+ if True: -+ raise RuntimeError -+ if False: -+ ... -+ for i in range(10): -+ print(i) -+ continue -+ exec("new-style exec", {}, {}) -+ return None -+ -+ - async def coroutine(arg, exec=False): -- 'Single-line docstring. Multiline is harder to reformat.' -- async with some_connection() as conn: -- await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) -- await asyncio.sleep(1) -+ "Single-line docstring. Multiline is harder to reformat." -+ async with some_connection() as conn: -+ await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2) -+ await asyncio.sleep(1) -+ -+ - @asyncio.coroutine --@some_decorator( --with_args=True, --many_args=[1,2,3] --) --def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str: -- return text[number:-1] -+@some_decorator(with_args=True, many_args=[1, 2, 3]) -+def function_signature_stress_test( -+ number: int, -+ no_annotation=None, -+ text: str = "default", -+ *, -+ debug: bool = False, -+ **kwargs, -+) -> str: -+ return text[number : -1] -+ -+ - # fmt: on - 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 -+ assert task._cancel_stack[ : len(old_stack)] == old_stack - - - def spaces_types( -@@ -63,15 +76,15 @@ - - something = { - # fmt: off -- key: 'value', -+ key: "value", - } - - - def subscriptlist(): - atom[ - # fmt: off -- 'some big and', -- 'complex subscript', -+ "some big and", -+ "complex subscript", - # fmt: on - goes + here, - andhere, -@@ -80,38 +93,35 @@ - - def import_as_names(): - # fmt: off -- from hello import a, b -- 'unformatted' -+ from hello import a, b -+ "unformatted" - # fmt: on - - - def testlist_star_expr(): - # fmt: off -- a , b = *hello -- 'unformatted' -+ a, b = *hello -+ "unformatted" - # fmt: on - - - def yield_expr(): - # fmt: off - yield hello -- 'unformatted' -+ "unformatted" - # fmt: on - "formatted" - # fmt: off -- ( yield hello ) -- 'unformatted' -+ (yield hello) -+ "unformatted" - # fmt: on - - - def example(session): - # fmt: off -- result = session\ -- .query(models.Customer.id)\ -- .filter(models.Customer.account_id == account_id, -- models.Customer.email == email_address)\ -- .order_by(models.Customer.id.asc())\ -- .all() -+ result = session.query(models.Customer.id).filter( -+ models.Customer.account_id == account_id, models.Customer.email == email_address -+ ).order_by(models.Customer.id.asc()).all() - # fmt: on - - -@@ -132,10 +142,10 @@ - """Another known limitation.""" - # fmt: on - # fmt: off -- this=should.not_be.formatted() -- and_=indeed . it is not formatted -- because . the . handling . inside . generate_ignored_nodes() -- now . considers . multiple . fmt . directives . within . one . prefix -+ this = should.not_be.formatted() -+ and_ = indeed.it is not formatted -+ because.the.handling.inside.generate_ignored_nodes() -+ now.considers.multiple.fmt.directives.within.one.prefix - # fmt: on - # fmt: off - # ...but comments still get reformatted even though they should not be -@@ -153,9 +163,7 @@ - ) - ) - # fmt: off -- a = ( -- unnecessary_bracket() -- ) -+ a = unnecessary_bracket() - # fmt: on - _type_comment_re = re.compile( - r""" -@@ -178,7 +186,7 @@ - $ - """, - # fmt: off -- re.MULTILINE|re.VERBOSE -+ re.MULTILINE | re.VERBOSE, - # fmt: on - ) - -@@ -216,8 +224,7 @@ - xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5, - ) - # fmt: off --yield 'hello' -+yield "hello" - # No formatting to the end of the file --l=[1,2,3] --d={'a':1, -- 'b':2} -+l = [1, 2, 3] -+d = {"a": 1, "b": 2} -``` - -## Ruff Output - -```py -#!/usr/bin/env python3 -import asyncio -import sys - -from third_party import X, Y, Z - -from library import some_connection, some_decorator -# fmt: off -from third_party import X, Y, Z -# fmt: on -f"NOT_YET_IMPLEMENTED_ExprJoinedStr" -# Comment 1 - -# Comment 2 - - -# fmt: off -def func_no_args(): - a - b - c - if True: - raise RuntimeError - if False: - ... - for i in range(10): - print(i) - continue - exec("new-style exec", {}, {}) - return None - - -async def coroutine(arg, exec=False): - "Single-line docstring. Multiline is harder to reformat." - async with some_connection() as conn: - await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2) - await asyncio.sleep(1) - - -@asyncio.coroutine -@some_decorator(with_args=True, many_args=[1, 2, 3]) -def function_signature_stress_test( - number: int, - no_annotation=None, - text: str = "default", - *, - debug: bool = False, - **kwargs, -) -> str: - return text[number : -1] - - -# fmt: on -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_types( - a: int = 1, - b: tuple = (), - c: list = [], - d: dict = {}, - e: bool = True, - f: int = -1, - g: int = 1 if False else 2, - h: str = "", - i: str = r"", -): - ... - - -def spaces2(result=_core.Value(None)): - ... - - -something = { - # fmt: off - key: "value", -} - - -def subscriptlist(): - atom[ - # fmt: off - "some big and", - "complex subscript", - # fmt: on - goes + here, - andhere, - ] - - -def import_as_names(): - # fmt: off - from hello import a, b - "unformatted" - # fmt: on - - -def testlist_star_expr(): - # fmt: off - a, b = *hello - "unformatted" - # fmt: on - - -def yield_expr(): - # fmt: off - yield hello - "unformatted" - # fmt: on - "formatted" - # fmt: off - (yield hello) - "unformatted" - # fmt: on - - -def example(session): - # fmt: off - result = session.query(models.Customer.id).filter( - models.Customer.account_id == account_id, models.Customer.email == email_address - ).order_by(models.Customer.id.asc()).all() - # fmt: on - - -def off_and_on_without_data(): - """All comments here are technically on the same prefix. - - The comments between will be formatted. This is a known limitation. - """ - # fmt: off - - # hey, that won't work - - # fmt: on - pass - - -def on_and_off_broken(): - """Another known limitation.""" - # fmt: on - # fmt: off - this = should.not_be.formatted() - and_ = indeed.it is not formatted - because.the.handling.inside.generate_ignored_nodes() - now.considers.multiple.fmt.directives.within.one.prefix - # fmt: on - # fmt: off - # ...but comments still get reformatted even though they should not be - # fmt: on - - -def long_lines(): - if True: - typedargslist.extend( - gen_annotated_params( - ast_args.kwonlyargs, - ast_args.kw_defaults, - parameters, - implicit_default=True, - ) - ) - # fmt: off - a = unnecessary_bracket() - # fmt: on - _type_comment_re = re.compile( - r""" - ^ - [\t ]* - \#[ ]type:[ ]* - (?P - [^#\t\n]+? - ) - (? to match - # a trailing space which is why we need the silliness below - (? - (?:\#[^\n]*)? - \n? - ) - $ - """, - # fmt: off - re.MULTILINE | re.VERBOSE, - # fmt: on - ) - - -def single_literal_yapf_disable(): - """Black does not support this.""" - BAZ = {(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)} # yapf: disable - - -cfg.rule( - "Default", - "address", - xxxx_xxxx=["xxx-xxxxxx-xxxxxxxxxx"], - xxxxxx="xx_xxxxx", - xxxxxxx="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - xxxxxxxxx_xxxx=True, - xxxxxxxx_xxxxxxxxxx=False, - xxxxxx_xxxxxx=2, - xxxxxx_xxxxx_xxxxxxxx=70, - xxxxxx_xxxxxx_xxxxx=True, - # fmt: off - xxxxxxx_xxxxxxxxxxxx={ - "xxxxxxxx": { - "xxxxxx": False, - "xxxxxxx": False, - "xxxx_xxxxxx": "xxxxx", - }, - "xxxxxxxx-xxxxx": { - "xxxxxx": False, - "xxxxxxx": True, - "xxxx_xxxxxx": "xxxxxx", - }, - }, - # fmt: on - xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5, -) -# fmt: off -yield "hello" -# No formatting to the end of the file -l = [1, 2, 3] -d = {"a": 1, "b": 2} -``` - -## Black Output - -```py -#!/usr/bin/env python3 -import asyncio -import sys - -from third_party import X, Y, Z - -from library import some_connection, some_decorator -# fmt: off -from third_party import (X, - Y, Z) -# fmt: on -f"trigger 3.6 mode" -# Comment 1 - -# Comment 2 - - -# fmt: off -def func_no_args(): - a; b; c - if True: raise RuntimeError - if False: ... - for i in range(10): - print(i) - continue - exec('new-style exec', {}, {}) - return None -async def coroutine(arg, exec=False): - 'Single-line docstring. Multiline is harder to reformat.' - async with some_connection() as conn: - await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) - await asyncio.sleep(1) -@asyncio.coroutine -@some_decorator( -with_args=True, -many_args=[1,2,3] -) -def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str: - return text[number:-1] -# fmt: on -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_types( - a: int = 1, - b: tuple = (), - c: list = [], - d: dict = {}, - e: bool = True, - f: int = -1, - g: int = 1 if False else 2, - h: str = "", - i: str = r"", -): - ... - - -def spaces2(result=_core.Value(None)): - ... - - -something = { - # fmt: off - key: 'value', -} - - -def subscriptlist(): - atom[ - # fmt: off - 'some big and', - 'complex subscript', - # fmt: on - goes + here, - andhere, - ] - - -def import_as_names(): - # fmt: off - from hello import a, b - 'unformatted' - # fmt: on - - -def testlist_star_expr(): - # fmt: off - a , b = *hello - 'unformatted' - # fmt: on - - -def yield_expr(): - # fmt: off - yield hello - 'unformatted' - # fmt: on - "formatted" - # fmt: off - ( yield hello ) - 'unformatted' - # fmt: on - - -def example(session): - # fmt: off - result = session\ - .query(models.Customer.id)\ - .filter(models.Customer.account_id == account_id, - models.Customer.email == email_address)\ - .order_by(models.Customer.id.asc())\ - .all() - # fmt: on - - -def off_and_on_without_data(): - """All comments here are technically on the same prefix. - - The comments between will be formatted. This is a known limitation. - """ - # fmt: off - - # hey, that won't work - - # fmt: on - pass - - -def on_and_off_broken(): - """Another known limitation.""" - # fmt: on - # fmt: off - this=should.not_be.formatted() - and_=indeed . it is not formatted - because . the . handling . inside . generate_ignored_nodes() - now . considers . multiple . fmt . directives . within . one . prefix - # fmt: on - # fmt: off - # ...but comments still get reformatted even though they should not be - # fmt: on - - -def long_lines(): - if True: - typedargslist.extend( - gen_annotated_params( - ast_args.kwonlyargs, - ast_args.kw_defaults, - parameters, - implicit_default=True, - ) - ) - # fmt: off - a = ( - unnecessary_bracket() - ) - # fmt: on - _type_comment_re = re.compile( - r""" - ^ - [\t ]* - \#[ ]type:[ ]* - (?P - [^#\t\n]+? - ) - (? to match - # a trailing space which is why we need the silliness below - (? - (?:\#[^\n]*)? - \n? - ) - $ - """, - # fmt: off - re.MULTILINE|re.VERBOSE - # fmt: on - ) - - -def single_literal_yapf_disable(): - """Black does not support this.""" - BAZ = {(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)} # yapf: disable - - -cfg.rule( - "Default", - "address", - xxxx_xxxx=["xxx-xxxxxx-xxxxxxxxxx"], - xxxxxx="xx_xxxxx", - xxxxxxx="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - xxxxxxxxx_xxxx=True, - xxxxxxxx_xxxxxxxxxx=False, - xxxxxx_xxxxxx=2, - xxxxxx_xxxxx_xxxxxxxx=70, - xxxxxx_xxxxxx_xxxxx=True, - # fmt: off - xxxxxxx_xxxxxxxxxxxx={ - "xxxxxxxx": { - "xxxxxx": False, - "xxxxxxx": False, - "xxxx_xxxxxx": "xxxxx", - }, - "xxxxxxxx-xxxxx": { - "xxxxxx": False, - "xxxxxxx": True, - "xxxx_xxxxxx": "xxxxxx", - }, - }, - # fmt: on - xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5, -) -# fmt: off -yield 'hello' -# No formatting to the end of the file -l=[1,2,3] -d={'a':1, - 'b':2} -``` - - 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 b5fdcc6e94e73..d300b64696055 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 @@ -141,23 +141,6 @@ def __await__(): return (yield) def long_lines(): -@@ -135,14 +130,8 @@ - a, - **kwargs, - ) -> A: -- return ( -- yield from A( -- very_long_argument_name1=very_long_value_for_the_argument, -- very_long_argument_name2=very_long_value_for_the_argument, -- **kwargs, -- ) -- ) -+ return NOT_YET_IMPLEMENTED_ExprYieldFrom - - - def __await__(): -- return (yield) -+ return NOT_YET_IMPLEMENTED_ExprYield ``` ## Ruff Output @@ -295,11 +278,17 @@ def f( a, **kwargs, ) -> A: - return NOT_YET_IMPLEMENTED_ExprYieldFrom + return ( + yield from A( + very_long_argument_name1=very_long_value_for_the_argument, + very_long_argument_name2=very_long_value_for_the_argument, + **kwargs, + ) + ) def __await__(): - return NOT_YET_IMPLEMENTED_ExprYield + return (yield) ``` ## Black Output diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap.new deleted file mode 100644 index 2cb3414cc8808..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function.py.snap.new +++ /dev/null @@ -1,463 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 96 -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function.py ---- -## Input - -```py -#!/usr/bin/env python3 -import asyncio -import sys - -from third_party import X, Y, Z - -from library import some_connection, \ - some_decorator -f'trigger 3.6 mode' -def func_no_args(): - a; b; c - if True: raise RuntimeError - if False: ... - for i in range(10): - print(i) - continue - exec("new-style exec", {}, {}) - return None -async def coroutine(arg, exec=False): - "Single-line docstring. Multiline is harder to reformat." - async with some_connection() as conn: - await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) - await asyncio.sleep(1) -@asyncio.coroutine -@some_decorator( -with_args=True, -many_args=[1,2,3] -) -def function_signature_stress_test(number:int,no_annotation=None,text:str="default",* ,debug:bool=False,**kwargs) -> str: - return text[number:-1] -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_types(a: int = 1, b: tuple = (), c: list = [], d: dict = {}, e: bool = True, f: int = -1, g: int = 1 if False else 2, h: str = "", i: str = r''): ... -def spaces2(result= _core.Value(None)): - assert fut is self._read_fut, (fut, self._read_fut) - # EMPTY LINE WITH WHITESPACE (this comment will be removed) -def example(session): - result = session.query(models.Customer.id).filter( - models.Customer.account_id == account_id, - models.Customer.email == email_address, - ).order_by( - models.Customer.id.asc() - ).all() -def long_lines(): - if True: - typedargslist.extend( - gen_annotated_params(ast_args.kwonlyargs, ast_args.kw_defaults, parameters, implicit_default=True) - ) - typedargslist.extend( - gen_annotated_params( - ast_args.kwonlyargs, ast_args.kw_defaults, parameters, implicit_default=True, - # trailing standalone comment - ) - ) - _type_comment_re = re.compile( - r""" - ^ - [\t ]* - \#[ ]type:[ ]* - (?P - [^#\t\n]+? - ) - (? to match - # a trailing space which is why we need the silliness below - (? - (?:\#[^\n]*)? - \n? - ) - $ - """, re.MULTILINE | re.VERBOSE - ) -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), -} -def f( - a, - **kwargs, -) -> A: - return ( - yield from A( - very_long_argument_name1=very_long_value_for_the_argument, - very_long_argument_name2=very_long_value_for_the_argument, - **kwargs, - ) - ) -def __await__(): return (yield) -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -5,8 +5,7 @@ - from third_party import X, Y, Z - - from library import some_connection, some_decorator -- --f"trigger 3.6 mode" -+f"NOT_YET_IMPLEMENTED_ExprJoinedStr" - - - def func_no_args(): -@@ -41,12 +40,12 @@ - debug: bool = False, - **kwargs, - ) -> str: -- return text[number:-1] -+ return text[number : -1] - - - 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 -+ assert task._cancel_stack[ : len(old_stack)] == old_stack - - - def spaces_types( -@@ -65,18 +64,14 @@ - - def spaces2(result=_core.Value(None)): - assert fut is self._read_fut, (fut, self._read_fut) -+ # EMPTY LINE WITH WHITESPACE (this comment will be removed) - - - def example(session): -- result = ( -- session.query(models.Customer.id) -- .filter( -- models.Customer.account_id == account_id, -- models.Customer.email == email_address, -- ) -- .order_by(models.Customer.id.asc()) -- .all() -- ) -+ result = session.query(models.Customer.id).filter( -+ models.Customer.account_id == account_id, -+ models.Customer.email == email_address, -+ ).order_by(models.Customer.id.asc()).all() - - - def long_lines(): -``` - -## Ruff Output - -```py -#!/usr/bin/env python3 -import asyncio -import sys - -from third_party import X, Y, Z - -from library import some_connection, some_decorator -f"NOT_YET_IMPLEMENTED_ExprJoinedStr" - - -def func_no_args(): - a - b - c - if True: - raise RuntimeError - if False: - ... - for i in range(10): - print(i) - continue - exec("new-style exec", {}, {}) - return None - - -async def coroutine(arg, exec=False): - "Single-line docstring. Multiline is harder to reformat." - async with some_connection() as conn: - await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2) - await asyncio.sleep(1) - - -@asyncio.coroutine -@some_decorator(with_args=True, many_args=[1, 2, 3]) -def function_signature_stress_test( - number: int, - no_annotation=None, - text: str = "default", - *, - debug: bool = False, - **kwargs, -) -> str: - return text[number : -1] - - -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_types( - a: int = 1, - b: tuple = (), - c: list = [], - d: dict = {}, - e: bool = True, - f: int = -1, - g: int = 1 if False else 2, - h: str = "", - i: str = r"", -): - ... - - -def spaces2(result=_core.Value(None)): - assert fut is self._read_fut, (fut, self._read_fut) - # EMPTY LINE WITH WHITESPACE (this comment will be removed) - - -def example(session): - result = session.query(models.Customer.id).filter( - models.Customer.account_id == account_id, - models.Customer.email == email_address, - ).order_by(models.Customer.id.asc()).all() - - -def long_lines(): - if True: - typedargslist.extend( - gen_annotated_params( - ast_args.kwonlyargs, - ast_args.kw_defaults, - parameters, - implicit_default=True, - ) - ) - typedargslist.extend( - gen_annotated_params( - ast_args.kwonlyargs, - ast_args.kw_defaults, - parameters, - implicit_default=True, - # trailing standalone comment - ) - ) - _type_comment_re = re.compile( - r""" - ^ - [\t ]* - \#[ ]type:[ ]* - (?P - [^#\t\n]+? - ) - (? to match - # a trailing space which is why we need the silliness below - (? - (?:\#[^\n]*)? - \n? - ) - $ - """, - re.MULTILINE | re.VERBOSE, - ) - - -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), - } - - -def f( - a, - **kwargs, -) -> A: - return ( - yield from A( - very_long_argument_name1=very_long_value_for_the_argument, - very_long_argument_name2=very_long_value_for_the_argument, - **kwargs, - ) - ) - - -def __await__(): - return (yield) -``` - -## Black Output - -```py -#!/usr/bin/env python3 -import asyncio -import sys - -from third_party import X, Y, Z - -from library import some_connection, some_decorator - -f"trigger 3.6 mode" - - -def func_no_args(): - a - b - c - if True: - raise RuntimeError - if False: - ... - for i in range(10): - print(i) - continue - exec("new-style exec", {}, {}) - return None - - -async def coroutine(arg, exec=False): - "Single-line docstring. Multiline is harder to reformat." - async with some_connection() as conn: - await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2) - await asyncio.sleep(1) - - -@asyncio.coroutine -@some_decorator(with_args=True, many_args=[1, 2, 3]) -def function_signature_stress_test( - number: int, - no_annotation=None, - text: str = "default", - *, - debug: bool = False, - **kwargs, -) -> str: - return text[number:-1] - - -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_types( - a: int = 1, - b: tuple = (), - c: list = [], - d: dict = {}, - e: bool = True, - f: int = -1, - g: int = 1 if False else 2, - h: str = "", - i: str = r"", -): - ... - - -def spaces2(result=_core.Value(None)): - assert fut is self._read_fut, (fut, self._read_fut) - - -def example(session): - result = ( - session.query(models.Customer.id) - .filter( - models.Customer.account_id == account_id, - models.Customer.email == email_address, - ) - .order_by(models.Customer.id.asc()) - .all() - ) - - -def long_lines(): - if True: - typedargslist.extend( - gen_annotated_params( - ast_args.kwonlyargs, - ast_args.kw_defaults, - parameters, - implicit_default=True, - ) - ) - typedargslist.extend( - gen_annotated_params( - ast_args.kwonlyargs, - ast_args.kw_defaults, - parameters, - implicit_default=True, - # trailing standalone comment - ) - ) - _type_comment_re = re.compile( - r""" - ^ - [\t ]* - \#[ ]type:[ ]* - (?P - [^#\t\n]+? - ) - (? to match - # a trailing space which is why we need the silliness below - (? - (?:\#[^\n]*)? - \n? - ) - $ - """, - re.MULTILINE | re.VERBOSE, - ) - - -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), - } - - -def f( - a, - **kwargs, -) -> A: - return ( - yield from A( - very_long_argument_name1=very_long_value_for_the_argument, - very_long_argument_name2=very_long_value_for_the_argument, - **kwargs, - ) - ) - - -def __await__(): - return (yield) -``` - - 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 390baf45de86d..26fc695ecbb9f 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 @@ -105,21 +105,6 @@ async def main(): async def main(): -@@ -78,7 +81,7 @@ - - - async def main(): -- await (yield x) -+ await NOT_YET_IMPLEMENTED_ExprYield - - - async def main(): -@@ -90,4 +93,4 @@ - - - async def main(): -- await (yield) -+ await NOT_YET_IMPLEMENTED_ExprYield ``` ## Ruff Output @@ -208,7 +193,7 @@ async def main(): async def main(): - await NOT_YET_IMPLEMENTED_ExprYield + await (yield x) async def main(): @@ -220,7 +205,7 @@ async def main(): async def main(): - await NOT_YET_IMPLEMENTED_ExprYield + await (yield) ``` ## Black Output diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap.new deleted file mode 100644 index 7b3d73adb9fe7..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__remove_await_parens.py.snap.new +++ /dev/null @@ -1,310 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 96 -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_await_parens.py ---- -## Input - -```py -import asyncio - -# Control example -async def main(): - await asyncio.sleep(1) - -# Remove brackets for short coroutine/task -async def main(): - await (asyncio.sleep(1)) - -async def main(): - await ( - asyncio.sleep(1) - ) - -async def main(): - await (asyncio.sleep(1) - ) - -# Check comments -async def main(): - await ( # Hello - asyncio.sleep(1) - ) - -async def main(): - await ( - asyncio.sleep(1) # Hello - ) - -async def main(): - await ( - asyncio.sleep(1) - ) # Hello - -# Long lines -async def main(): - await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1)) - -# Same as above but with magic trailing comma in function -async def main(): - await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1),) - -# Cr@zY Br@ck3Tz -async def main(): - await ( - ((((((((((((( - ((( ((( - ((( ((( - ((( ((( - ((( ((( - ((black(1))) - ))) ))) - ))) ))) - ))) ))) - ))) ))) - ))))))))))))) - ) - -# Keep brackets around non power operations and nested awaits -async def main(): - await (set_of_tasks | other_set) - -async def main(): - await (await asyncio.sleep(1)) - -# It's awaits all the way down... -async def main(): - await (await x) - -async def main(): - await (yield x) - -async def main(): - await (await (asyncio.sleep(1))) - -async def main(): - await (await (await (await (await (asyncio.sleep(1)))))) - -async def main(): - await (yield) -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -21,7 +21,10 @@ - - # Check comments - async def main(): -- await asyncio.sleep(1) # Hello -+ await ( -+ # Hello -+ asyncio.sleep(1) -+ ) - - - async def main(): -``` - -## Ruff Output - -```py -import asyncio - - -# Control example -async def main(): - await asyncio.sleep(1) - - -# Remove brackets for short coroutine/task -async def main(): - await asyncio.sleep(1) - - -async def main(): - await asyncio.sleep(1) - - -async def main(): - await asyncio.sleep(1) - - -# Check comments -async def main(): - await ( - # Hello - asyncio.sleep(1) - ) - - -async def main(): - await asyncio.sleep(1) # Hello - - -async def main(): - await asyncio.sleep(1) # Hello - - -# Long lines -async def main(): - await asyncio.gather( - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - ) - - -# Same as above but with magic trailing comma in function -async def main(): - await asyncio.gather( - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - ) - - -# Cr@zY Br@ck3Tz -async def main(): - await black(1) - - -# Keep brackets around non power operations and nested awaits -async def main(): - await (set_of_tasks | other_set) - - -async def main(): - await (await asyncio.sleep(1)) - - -# It's awaits all the way down... -async def main(): - await (await x) - - -async def main(): - await (yield x) - - -async def main(): - await (await asyncio.sleep(1)) - - -async def main(): - await (await (await (await (await asyncio.sleep(1))))) - - -async def main(): - await (yield) -``` - -## Black Output - -```py -import asyncio - - -# Control example -async def main(): - await asyncio.sleep(1) - - -# Remove brackets for short coroutine/task -async def main(): - await asyncio.sleep(1) - - -async def main(): - await asyncio.sleep(1) - - -async def main(): - await asyncio.sleep(1) - - -# Check comments -async def main(): - await asyncio.sleep(1) # Hello - - -async def main(): - await asyncio.sleep(1) # Hello - - -async def main(): - await asyncio.sleep(1) # Hello - - -# Long lines -async def main(): - await asyncio.gather( - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - ) - - -# Same as above but with magic trailing comma in function -async def main(): - await asyncio.gather( - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), - ) - - -# Cr@zY Br@ck3Tz -async def main(): - await black(1) - - -# Keep brackets around non power operations and nested awaits -async def main(): - await (set_of_tasks | other_set) - - -async def main(): - await (await asyncio.sleep(1)) - - -# It's awaits all the way down... -async def main(): - await (await x) - - -async def main(): - await (yield x) - - -async def main(): - await (await asyncio.sleep(1)) - - -async def main(): - await (await (await (await (await asyncio.sleep(1))))) - - -async def main(): - await (yield) -``` - - diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap similarity index 98% rename from crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new rename to crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap index a8c6d29a4d2ee..53a43aa97989f 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 161 input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py --- ## Input diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap similarity index 96% rename from crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new rename to crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap index e2fc72d21f22d..334f68aed7f27 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 161 input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py --- ## Input From e4653caaf6c9b2fe73f5f073cef010e07adc4da0 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 20 Jul 2023 16:27:19 +0200 Subject: [PATCH 04/13] cargo fmt && cargo clippy --- .../src/expression/expr_yield.rs | 36 +++++++++---------- .../src/expression/expr_yield_from.rs | 30 ++++++++-------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index 572860736ea04..838cb235c1503 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -1,34 +1,32 @@ -use ruff_text_size::{TextRange, TextSize}; use crate::context::PyFormatContext; -use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize}; -use crate::{not_yet_implemented, FormatNodeRule, PyFormatter, AsFormat}; -use ruff_formatter::{write, Buffer, FormatResult, Format, FormatError, FormatRuleWithOptions}; -use ruff_python_ast::node::AnyNodeRef; -use rustpython_parser::ast::{ExprYield, Ranged}; -use ruff_formatter::prelude::{space, text}; -use ruff_python_whitespace::{SimpleTokenizer, TokenKind}; use crate::expression::maybe_parenthesize_expression; +use crate::expression::parentheses::{ + NeedsParentheses, OptionalParentheses, Parenthesize, +}; +use crate::{FormatNodeRule, PyFormatter}; +use ruff_formatter::prelude::{space, text}; +use ruff_formatter::{write, Buffer, FormatResult}; +use ruff_python_ast::node::AnyNodeRef; +use rustpython_parser::ast::{ExprYield}; #[derive(Default)] pub struct FormatExprYield; impl FormatNodeRule for FormatExprYield { fn fmt_fields(&self, item: &ExprYield, f: &mut PyFormatter) -> FormatResult<()> { - let ExprYield { - range: _, - value - } = item; + let ExprYield { range: _, value } = item; if let Some(val) = value { write!( f, - [&text("yield"), space(), maybe_parenthesize_expression(val, item, Parenthesize::IfRequired)] + [ + &text("yield"), + space(), + maybe_parenthesize_expression(val, item, Parenthesize::IfRequired) + ] )?; } else { - write!( - f, - [&text("yield")] - )?; + write!(f, [&text("yield")])?; } Ok(()) } @@ -37,10 +35,10 @@ impl FormatNodeRule for FormatExprYield { impl NeedsParentheses for ExprYield { fn needs_parentheses( &self, - _parent: AnyNodeRef, + parent: AnyNodeRef, _context: &PyFormatContext, ) -> OptionalParentheses { - if _parent.is_stmt_return() || _parent.is_expr_await() { + if parent.is_stmt_return() || parent.is_expr_await() { OptionalParentheses::Always } else { OptionalParentheses::Multiline diff --git a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs index b56a33826fb3e..0c13ad9da922a 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs @@ -1,39 +1,41 @@ use crate::context::PyFormatContext; -use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize}; -use crate::{not_yet_implemented, FormatNodeRule, PyFormatter}; -use ruff_formatter::{write, Buffer, FormatResult, Format, FormatRuleWithOptions}; -use ruff_python_ast::node::AnyNodeRef; -use rustpython_parser::ast::{ExprYield, ExprYieldFrom}; -use ruff_formatter::prelude::{space, text}; use crate::expression::maybe_parenthesize_expression; +use crate::expression::parentheses::{ + NeedsParentheses, OptionalParentheses, Parenthesize, +}; +use crate::{FormatNodeRule, PyFormatter}; +use ruff_formatter::prelude::{space, text}; +use ruff_formatter::{write, Buffer, FormatResult}; +use ruff_python_ast::node::AnyNodeRef; +use rustpython_parser::ast::ExprYieldFrom; #[derive(Default)] pub struct FormatExprYieldFrom; impl FormatNodeRule for FormatExprYieldFrom { fn fmt_fields(&self, item: &ExprYieldFrom, f: &mut PyFormatter) -> FormatResult<()> { - let ExprYieldFrom { - range: _, - value - } = item; + let ExprYieldFrom { range: _, value } = item; write!( f, - [&text("yield from"), space(), maybe_parenthesize_expression(value, item, Parenthesize::IfRequired)] + [ + &text("yield from"), + space(), + maybe_parenthesize_expression(value, item, Parenthesize::IfRequired) + ] )?; Ok(()) - } } impl NeedsParentheses for ExprYieldFrom { fn needs_parentheses( &self, - _parent: AnyNodeRef, + parent: AnyNodeRef, _context: &PyFormatContext, ) -> OptionalParentheses { - if _parent.is_stmt_return() || _parent.is_expr_await() { + if parent.is_stmt_return() || parent.is_expr_await() { OptionalParentheses::Always } else { OptionalParentheses::Multiline From 17cf058ead0f2fd71799338474fb11d8e7bd6ab9 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 20 Jul 2023 16:28:27 +0200 Subject: [PATCH 05/13] rebase --- .../black_compatibility@simple_cases__expression.py.snap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 98a72f9520cfd..3a47e37f8791d 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 @@ -327,7 +327,7 @@ last_call() ) what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( vars_to_remove --) + ) -result = ( - session.query(models.Customer.id) - .filter( @@ -335,7 +335,7 @@ last_call() - ) - .order_by(models.Customer.id.asc()) - .all() - ) +-) -result = ( - session.query(models.Customer.id) - .filter( @@ -357,7 +357,7 @@ last_call() Ø = set() authors.łukasz.say_thanks() mapping = { -@@ -328,13 +328,18 @@ +@@ -328,13 +325,18 @@ ): return True if ( From 1ad7ff44b4e568a8f163473d48f5dd834e9d4310 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 20 Jul 2023 16:29:01 +0200 Subject: [PATCH 06/13] cargo fmt --- crates/ruff_python_formatter/src/expression/expr_yield.rs | 6 ++---- .../ruff_python_formatter/src/expression/expr_yield_from.rs | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index 838cb235c1503..e401f9d952400 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -1,13 +1,11 @@ use crate::context::PyFormatContext; use crate::expression::maybe_parenthesize_expression; -use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parenthesize, -}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parenthesize}; use crate::{FormatNodeRule, PyFormatter}; use ruff_formatter::prelude::{space, text}; use ruff_formatter::{write, Buffer, FormatResult}; use ruff_python_ast::node::AnyNodeRef; -use rustpython_parser::ast::{ExprYield}; +use rustpython_parser::ast::ExprYield; #[derive(Default)] pub struct FormatExprYield; diff --git a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs index 0c13ad9da922a..a68f209bcfdbb 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs @@ -1,8 +1,6 @@ use crate::context::PyFormatContext; use crate::expression::maybe_parenthesize_expression; -use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parenthesize, -}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parenthesize}; use crate::{FormatNodeRule, PyFormatter}; use ruff_formatter::prelude::{space, text}; use ruff_formatter::{write, Buffer, FormatResult}; From 207007f91d36672711f5f8b420de8e354ae35820 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 20 Jul 2023 16:36:53 +0200 Subject: [PATCH 07/13] Remove Yield token --- crates/ruff_python_trivia/src/tokenizer.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/ruff_python_trivia/src/tokenizer.rs b/crates/ruff_python_trivia/src/tokenizer.rs index 30381a772b474..40b4df599db93 100644 --- a/crates/ruff_python_trivia/src/tokenizer.rs +++ b/crates/ruff_python_trivia/src/tokenizer.rs @@ -204,9 +204,6 @@ pub enum SimpleTokenKind { /// `async` Async, - /// `yield` - Yield, - /// Any other non trivia token. Other, From 07454cb623254ab664670f72160b2e4372f31c3e Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 20 Jul 2023 23:56:32 +0200 Subject: [PATCH 08/13] Invert parenthesis logic, expand test cases --- .../test/fixtures/ruff/expression/yield.py | 27 ++++ .../fixtures/ruff/expression/yield_from.py | 24 ++++ .../src/expression/expr_yield.rs | 6 +- .../src/expression/expr_yield_from.rs | 6 +- ..._compatibility@py_38__python38.py.snap.new | 95 ++++++++++++++ .../format@expression__yield.py.snap.new | 123 ++++++++++++++++++ .../format@expression__yield_from.py.snap.new | 75 +++++++++++ 7 files changed, 350 insertions(+), 6 deletions(-) create mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap.new create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py index 525c658db5f8f..50d93856d9316 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py @@ -3,6 +3,27 @@ def foo(): yield l + b = yield l + + c = [ + (yield l) , ( + yield l + + )] + + with ( + # Some comment + yield + ): + pass + + if (yield): + # comment + pass + + + (yield a, b) = (1, 2) + # some comment for e in l : yield e # some comment @@ -28,4 +49,10 @@ def foo(): for x in l: #comment yield x + (2 * 4) # trailing comment + while ( + yield l + ): + pass + + yield from (yield l) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py index c37539dc2dc20..46877be69c864 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py @@ -9,3 +9,27 @@ def foo(): from\ l # indented trailing comment + + a = yield from l + + with ( + # Comment + yield from l + # Comment + ): + pass + + c = [(yield from l) , ( + yield from l + + )] + + while ( + yield from l + ): + pass + + yield ( + yield from l + ) + diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index e401f9d952400..8de65220fc33e 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -36,10 +36,10 @@ impl NeedsParentheses for ExprYield { parent: AnyNodeRef, _context: &PyFormatContext, ) -> OptionalParentheses { - if parent.is_stmt_return() || parent.is_expr_await() { - OptionalParentheses::Always - } else { + if parent.is_stmt_assign() { OptionalParentheses::Multiline + } else { + OptionalParentheses::Always } } } diff --git a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs index a68f209bcfdbb..9a8c0eb4099a0 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs @@ -33,10 +33,10 @@ impl NeedsParentheses for ExprYieldFrom { parent: AnyNodeRef, _context: &PyFormatContext, ) -> OptionalParentheses { - if parent.is_stmt_return() || parent.is_expr_await() { - OptionalParentheses::Always - } else { + if parent.is_stmt_assign() { OptionalParentheses::Multiline + } else { + OptionalParentheses::Always } } } diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap.new new file mode 100644 index 0000000000000..fa2c77262fabc --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap.new @@ -0,0 +1,95 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 96 +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/py_38/python38.py +--- +## Input + +```py +#!/usr/bin/env python3.8 + + +def starred_return(): + my_list = ["value2", "value3"] + return "value1", *my_list + + +def starred_yield(): + my_list = ["value2", "value3"] + yield "value1", *my_list + + +# all right hand side expressions allowed in regular assignments are now also allowed in +# annotated assignments +a : Tuple[ str, int] = "1", 2 +a: Tuple[int , ... ] = b, *c, d +def t(): + a : str = yield "a" +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -18,4 +18,4 @@ + + + def t(): +- a: str = yield "a" ++ a: str = (yield "a") +``` + +## Ruff Output + +```py +#!/usr/bin/env python3.8 + + +def starred_return(): + my_list = ["value2", "value3"] + return "value1", *my_list + + +def starred_yield(): + my_list = ["value2", "value3"] + yield "value1", *my_list + + +# all right hand side expressions allowed in regular assignments are now also allowed in +# annotated assignments +a: Tuple[str, int] = "1", 2 +a: Tuple[int, ...] = b, *c, d + + +def t(): + a: str = (yield "a") +``` + +## Black Output + +```py +#!/usr/bin/env python3.8 + + +def starred_return(): + my_list = ["value2", "value3"] + return "value1", *my_list + + +def starred_yield(): + my_list = ["value2", "value3"] + yield "value1", *my_list + + +# all right hand side expressions allowed in regular assignments are now also allowed in +# annotated assignments +a: Tuple[str, int] = "1", 2 +a: Tuple[int, ...] = b, *c, d + + +def t(): + a: str = yield "a" +``` + + diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new new file mode 100644 index 0000000000000..7f423f67cdff3 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new @@ -0,0 +1,123 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 161 +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py +--- +## Input +```py +l = [1,2,3,4] + +def foo(): + yield l + + b = yield l + + c = [ + (yield l) , ( + yield l + + )] + + with ( + # Some comment + yield + ): + pass + + if (yield): + # comment + pass + + + (yield a, b) = (1, 2) + + # some comment + for e in l : yield e # some comment + + for e in l: + + + # some comment + + yield e + + # trail comment + + for e in l: + # comment + yield (((((((e))))))) # Too many parentheses + # comment + + + for ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee in l: + yield ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + + + for x in l: #comment + yield x + (2 * 4) # trailing comment + + while ( + + + yield l + ): + pass + + yield from (yield l) + +``` + +## Output +```py +l = [1, 2, 3, 4] + + +def foo(): + yield l + + b = yield l + + c = [(yield l), (yield l)] + + with ( + # Some comment + yield + ): + pass + + if (yield): + # comment + pass + + (yield a, b) = (1, 2) + + # some comment + for e in l: + yield e # some comment + + for e in l: + # some comment + + yield e + + # trail comment + + for e in l: + # comment + yield e # Too many parentheses + # comment + + for ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee in l: + yield ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + + for x in l: # comment + yield x + (2 * 4) # trailing comment + + while (yield l): + pass + + yield from (yield l) +``` + + + diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new new file mode 100644 index 0000000000000..3cc0778e5b4f1 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new @@ -0,0 +1,75 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +assertion_line: 161 +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py +--- +## Input +```py +l = [1,2,3,4] + + +def foo(): + yield from l # some comment + + # weird indents + yield\ + from\ + l + # indented trailing comment + + a = yield from l + + with ( + # Comment + yield from l + # Comment + ): + pass + + c = [(yield from l) , ( + yield from l + + )] + + while ( + yield from l + ): + pass + + yield ( + yield from l + ) + +``` + +## Output +```py +l = [1, 2, 3, 4] + + +def foo(): + yield from l # some comment + + # weird indents + yield from l + # indented trailing comment + + a = yield from l + + with ( + # Comment + yield from l + # Comment + ): + pass + + c = [(yield from l), (yield from l)] + + while (yield from l): + pass + + yield (yield from l) +``` + + + From 8ed8a31073470094abc2d509bd1e24de04001332 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Fri, 21 Jul 2023 00:01:57 +0200 Subject: [PATCH 09/13] Expand assignment checks --- .../test/fixtures/ruff/expression/yield.py | 1 + .../src/expression/expr_yield.rs | 2 +- .../src/expression/expr_yield_from.rs | 2 +- ..._compatibility@py_38__python38.py.snap.new | 95 -------------- .../format@expression__yield.py.snap | 49 +++++++ .../format@expression__yield.py.snap.new | 123 ------------------ .../format@expression__yield_from.py.snap | 40 ++++++ .../format@expression__yield_from.py.snap.new | 75 ----------- 8 files changed, 92 insertions(+), 295 deletions(-) delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap.new delete mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new delete mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py index 50d93856d9316..592cebdaa1c57 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py @@ -50,6 +50,7 @@ def foo(): yield x + (2 * 4) # trailing comment while ( + yield l ): pass diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index 8de65220fc33e..ae08fa33fffeb 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -36,7 +36,7 @@ impl NeedsParentheses for ExprYield { parent: AnyNodeRef, _context: &PyFormatContext, ) -> OptionalParentheses { - if parent.is_stmt_assign() { + if parent.is_stmt_assign() || parent.is_stmt_ann_assign() || parent.is_stmt_aug_assign() { OptionalParentheses::Multiline } else { OptionalParentheses::Always diff --git a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs index 9a8c0eb4099a0..3c75c9289603c 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs @@ -33,7 +33,7 @@ impl NeedsParentheses for ExprYieldFrom { parent: AnyNodeRef, _context: &PyFormatContext, ) -> OptionalParentheses { - if parent.is_stmt_assign() { + if parent.is_stmt_assign() || parent.is_stmt_ann_assign() || parent.is_stmt_aug_assign() { OptionalParentheses::Multiline } else { OptionalParentheses::Always diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap.new deleted file mode 100644 index fa2c77262fabc..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap.new +++ /dev/null @@ -1,95 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 96 -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/py_38/python38.py ---- -## Input - -```py -#!/usr/bin/env python3.8 - - -def starred_return(): - my_list = ["value2", "value3"] - return "value1", *my_list - - -def starred_yield(): - my_list = ["value2", "value3"] - yield "value1", *my_list - - -# all right hand side expressions allowed in regular assignments are now also allowed in -# annotated assignments -a : Tuple[ str, int] = "1", 2 -a: Tuple[int , ... ] = b, *c, d -def t(): - a : str = yield "a" -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -18,4 +18,4 @@ - - - def t(): -- a: str = yield "a" -+ a: str = (yield "a") -``` - -## Ruff Output - -```py -#!/usr/bin/env python3.8 - - -def starred_return(): - my_list = ["value2", "value3"] - return "value1", *my_list - - -def starred_yield(): - my_list = ["value2", "value3"] - yield "value1", *my_list - - -# all right hand side expressions allowed in regular assignments are now also allowed in -# annotated assignments -a: Tuple[str, int] = "1", 2 -a: Tuple[int, ...] = b, *c, d - - -def t(): - a: str = (yield "a") -``` - -## Black Output - -```py -#!/usr/bin/env python3.8 - - -def starred_return(): - my_list = ["value2", "value3"] - return "value1", *my_list - - -def starred_yield(): - my_list = ["value2", "value3"] - yield "value1", *my_list - - -# all right hand side expressions allowed in regular assignments are now also allowed in -# annotated assignments -a: Tuple[str, int] = "1", 2 -a: Tuple[int, ...] = b, *c, d - - -def t(): - a: str = yield "a" -``` - - diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap index 53a43aa97989f..b5d464ec81c09 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap @@ -9,6 +9,27 @@ l = [1,2,3,4] def foo(): yield l + b = yield l + + c = [ + (yield l) , ( + yield l + + )] + + with ( + # Some comment + yield + ): + pass + + if (yield): + # comment + pass + + + (yield a, b) = (1, 2) + # some comment for e in l : yield e # some comment @@ -34,6 +55,13 @@ def foo(): for x in l: #comment yield x + (2 * 4) # trailing comment + while ( + + yield l + ): + pass + + yield from (yield l) ``` @@ -45,6 +73,22 @@ l = [1, 2, 3, 4] def foo(): yield l + b = yield l + + c = [(yield l), (yield l)] + + with ( + # Some comment + yield + ): + pass + + if (yield): + # comment + pass + + (yield a, b) = (1, 2) + # some comment for e in l: yield e # some comment @@ -66,6 +110,11 @@ def foo(): for x in l: # comment yield x + (2 * 4) # trailing comment + + while (yield l): + pass + + yield from (yield l) ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new deleted file mode 100644 index 7f423f67cdff3..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap.new +++ /dev/null @@ -1,123 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 161 -input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py ---- -## Input -```py -l = [1,2,3,4] - -def foo(): - yield l - - b = yield l - - c = [ - (yield l) , ( - yield l - - )] - - with ( - # Some comment - yield - ): - pass - - if (yield): - # comment - pass - - - (yield a, b) = (1, 2) - - # some comment - for e in l : yield e # some comment - - for e in l: - - - # some comment - - yield e - - # trail comment - - for e in l: - # comment - yield (((((((e))))))) # Too many parentheses - # comment - - - for ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee in l: - yield ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee - - - for x in l: #comment - yield x + (2 * 4) # trailing comment - - while ( - - - yield l - ): - pass - - yield from (yield l) - -``` - -## Output -```py -l = [1, 2, 3, 4] - - -def foo(): - yield l - - b = yield l - - c = [(yield l), (yield l)] - - with ( - # Some comment - yield - ): - pass - - if (yield): - # comment - pass - - (yield a, b) = (1, 2) - - # some comment - for e in l: - yield e # some comment - - for e in l: - # some comment - - yield e - - # trail comment - - for e in l: - # comment - yield e # Too many parentheses - # comment - - for ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee in l: - yield ridiculouslylongelementnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee - - for x in l: # comment - yield x + (2 * 4) # trailing comment - - while (yield l): - pass - - yield from (yield l) -``` - - - diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap index 334f68aed7f27..3439eafe704cb 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap @@ -15,6 +15,30 @@ def foo(): from\ l # indented trailing comment + + a = yield from l + + with ( + # Comment + yield from l + # Comment + ): + pass + + c = [(yield from l) , ( + yield from l + + )] + + while ( + yield from l + ): + pass + + yield ( + yield from l + ) + ``` ## Output @@ -28,6 +52,22 @@ def foo(): # weird indents yield from l # indented trailing comment + + a = yield from l + + with ( + # Comment + yield from l + # Comment + ): + pass + + c = [(yield from l), (yield from l)] + + while (yield from l): + pass + + yield (yield from l) ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new deleted file mode 100644 index 3cc0778e5b4f1..0000000000000 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield_from.py.snap.new +++ /dev/null @@ -1,75 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -assertion_line: 161 -input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield_from.py ---- -## Input -```py -l = [1,2,3,4] - - -def foo(): - yield from l # some comment - - # weird indents - yield\ - from\ - l - # indented trailing comment - - a = yield from l - - with ( - # Comment - yield from l - # Comment - ): - pass - - c = [(yield from l) , ( - yield from l - - )] - - while ( - yield from l - ): - pass - - yield ( - yield from l - ) - -``` - -## Output -```py -l = [1, 2, 3, 4] - - -def foo(): - yield from l # some comment - - # weird indents - yield from l - # indented trailing comment - - a = yield from l - - with ( - # Comment - yield from l - # Comment - ): - pass - - c = [(yield from l), (yield from l)] - - while (yield from l): - pass - - yield (yield from l) -``` - - - From 76bc00f4545265605e2237f95c3523038553963a Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Fri, 21 Jul 2023 12:51:21 +0200 Subject: [PATCH 10/13] Add comments to `needs_parentheses()` funcs --- crates/ruff_python_formatter/src/expression/expr_yield.rs | 6 ++++++ .../ruff_python_formatter/src/expression/expr_yield_from.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index ae08fa33fffeb..7340497a8456f 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -36,6 +36,12 @@ impl NeedsParentheses for ExprYield { parent: AnyNodeRef, _context: &PyFormatContext, ) -> OptionalParentheses { + // According to https://docs.python.org/3/reference/grammar.html There are two situations + // where we do not want to always parenthesize a yield expression: + // 1. Right hand side of an assignment, e.g. `x = yield y` + // 2. Yield statement, e.g. `def foo(): yield y` + // We catch situation 1 below. Situation 2 does not need to be handled here as + // FormatStmtExpr, does not add parenthesis if parent.is_stmt_assign() || parent.is_stmt_ann_assign() || parent.is_stmt_aug_assign() { OptionalParentheses::Multiline } else { diff --git a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs index 3c75c9289603c..66f99f2792c52 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs @@ -33,6 +33,12 @@ impl NeedsParentheses for ExprYieldFrom { parent: AnyNodeRef, _context: &PyFormatContext, ) -> OptionalParentheses { + // According to https://docs.python.org/3/reference/grammar.html There are two situations + // where we do not want to always parenthesize a yield expression: + // 1. Right hand side of an assignment, e.g. `x = yield y` + // 2. Yield statement, e.g. `def foo(): yield y` + // We catch situation 1 below. Situation 2 does not need to be handled here as + // FormatStmtExpr, does not add parenthesis if parent.is_stmt_assign() || parent.is_stmt_ann_assign() || parent.is_stmt_aug_assign() { OptionalParentheses::Multiline } else { From 1e2d8adb7df0aae30ef307d378f2490ec76db6ac Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Fri, 21 Jul 2023 12:58:58 +0200 Subject: [PATCH 11/13] rebase --- .../black_compatibility@simple_cases__expression.py.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3a47e37f8791d..af4a17bd1522e 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 @@ -327,7 +327,7 @@ last_call() ) what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( vars_to_remove - ) +-) -result = ( - session.query(models.Customer.id) - .filter( @@ -335,7 +335,7 @@ last_call() - ) - .order_by(models.Customer.id.asc()) - .all() --) + ) -result = ( - session.query(models.Customer.id) - .filter( From 2546875d4cc1f26a8ef84f61f2aa2d4ab93372d2 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 21 Jul 2023 13:58:12 +0200 Subject: [PATCH 12/13] Update crates/ruff_python_formatter/src/expression/expr_yield.rs --- crates/ruff_python_formatter/src/expression/expr_yield.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_python_formatter/src/expression/expr_yield.rs b/crates/ruff_python_formatter/src/expression/expr_yield.rs index 7340497a8456f..106e3489e7a18 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield.rs @@ -18,7 +18,7 @@ impl FormatNodeRule for FormatExprYield { write!( f, [ - &text("yield"), + text("yield"), space(), maybe_parenthesize_expression(val, item, Parenthesize::IfRequired) ] From de94c9eb31d547ad17e41819ac0851735fe32fa4 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 21 Jul 2023 13:58:18 +0200 Subject: [PATCH 13/13] Update crates/ruff_python_formatter/src/expression/expr_yield_from.rs --- crates/ruff_python_formatter/src/expression/expr_yield_from.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs index 66f99f2792c52..ffab491cc91a4 100644 --- a/crates/ruff_python_formatter/src/expression/expr_yield_from.rs +++ b/crates/ruff_python_formatter/src/expression/expr_yield_from.rs @@ -17,7 +17,7 @@ impl FormatNodeRule for FormatExprYieldFrom { write!( f, [ - &text("yield from"), + text("yield from"), space(), maybe_parenthesize_expression(value, item, Parenthesize::IfRequired) ]