mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 02:13:08 +00:00
![]() ## Summary The motivating code here was: ```python with test as ( # test foo): pass ``` Which we were formatting as: ```python with test as # test (foo): pass ``` `with` statements are oddly difficult. This PR makes a bunch of subtle modifications and adds a more extensive test suite. For example, we now only preserve parentheses if there's more than one `WithItem` _or_ a trailing comma; before, we always preserved. Our formatting is_not_ the same as Black, but here's a diff of our formatted code vs. Black's for the `with.py` test suite. The primary difference is that we tend to break parentheses when they contain comments rather than move them to the end of the life (this is a consistent difference that we make across the codebase): ```diff diff --git a/crates/ruff_python_formatter/foo.py b/crates/ruff_python_formatter/foo.py index 85e761080..31625c876 100644 --- a/crates/ruff_python_formatter/foo.py +++ b/crates/ruff_python_formatter/foo.py @@ -1,6 +1,4 @@ -with ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -), aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: +with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: ... # trailing @@ -16,28 +14,33 @@ with ( # trailing -with a, b: # a # comma # c # colon +with ( + a, # a # comma + b, # c +): # colon ... with ( - a as # a # as - # own line - b, # b # comma + a as ( # a # as + # own line + b + ), # b # comma c, # c ): # colon ... # body # body trailing own -with ( - a as # a # as +with a as ( # a # as # own line - bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # b -): + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +): # b pass -with (a,): # magic trailing comma +with ( + a, +): # magic trailing comma ... @@ -47,6 +50,7 @@ with a: # should remove brackets with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c: ... + with ( # leading comment a @@ -74,8 +78,7 @@ with ( with ( a # trailing same line comment # trailing own line comment - as b -): +) as b: ... with ( @@ -87,7 +90,9 @@ with ( with ( a # trailing own line comment -) as b: # trailing as same line comment # trailing b same line comment +) as ( # trailing as same line comment + b +): # trailing b same line comment ... with ( @@ -124,18 +129,24 @@ with ( # comment ... with ( # outer comment - CtxManager1() as example1, # inner comment + ( # inner comment + CtxManager1() + ) as example1, CtxManager2() as example2, CtxManager3() as example3, ): ... -with CtxManager() as example: # outer comment +with ( # outer comment + CtxManager() +) as example: ... with ( # outer comment CtxManager() -) as example, CtxManager2() as example2: # inner comment +) as example, ( # inner comment + CtxManager2() +) as example2: ... with ( # outer comment @@ -145,7 +156,9 @@ with ( # outer comment ... with ( # outer comment - (CtxManager1()), # inner comment + ( # inner comment + CtxManager1() + ), CtxManager2(), ) as example: ... @@ -179,7 +192,9 @@ with ( ): pass -with a as (b): # foo +with a as ( # foo + b +): pass with f( @@ -209,17 +224,13 @@ with f( ) as b, c as d: pass -with ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -) as b: +with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b: pass with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b: pass -with ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -) as b, c as d: +with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b, c as d: pass with ( @@ -230,6 +241,8 @@ with ( pass with ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -) as b, c as d: + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b, + c as d, +): pass ``` Closes https://github.com/astral-sh/ruff/issues/6600. ## Test Plan Before: | project | similarity index | |--------------|------------------| | cpython | 0.75473 | | django | 0.99804 | | transformers | 0.99618 | | twine | 0.99876 | | typeshed | 0.74292 | | warehouse | 0.99601 | | zulip | 0.99727 | After: | project | similarity index | |--------------|------------------| | cpython | 0.75473 | | django | 0.99804 | | transformers | 0.99618 | | twine | 0.99876 | | typeshed | 0.74292 | | warehouse | 0.99601 | | zulip | 0.99727 | `cargo test` |
||
---|---|---|
.. | ||
black_compatibility@conditional_expression.py.snap | ||
black_compatibility@miscellaneous__blackd_diff.py.snap | ||
black_compatibility@miscellaneous__debug_visitor.py.snap | ||
black_compatibility@miscellaneous__decorators.py.snap | ||
black_compatibility@miscellaneous__docstring_no_string_normalization.py.snap | ||
black_compatibility@miscellaneous__docstring_preview_no_string_normalization.py.snap | ||
black_compatibility@miscellaneous__force_pyi.py.snap | ||
black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap | ||
black_compatibility@miscellaneous__power_op_newline.py.snap | ||
black_compatibility@miscellaneous__string_quotes.py.snap | ||
black_compatibility@py_38__pep_572_remove_parens.py.snap | ||
black_compatibility@py_39__python39.py.snap | ||
black_compatibility@py_310__pattern_matching_complex.py.snap | ||
black_compatibility@py_310__pattern_matching_extras.py.snap | ||
black_compatibility@py_310__pattern_matching_generic.py.snap | ||
black_compatibility@py_310__pattern_matching_simple.py.snap | ||
black_compatibility@py_310__pattern_matching_style.py.snap | ||
black_compatibility@py_310__pep_572_py310.py.snap | ||
black_compatibility@py_310__remove_newline_after_match.py.snap | ||
black_compatibility@simple_cases__attribute_access_on_number_literals.py.snap | ||
black_compatibility@simple_cases__comment_after_escaped_newline.py.snap | ||
black_compatibility@simple_cases__comments2.py.snap | ||
black_compatibility@simple_cases__comments6.py.snap | ||
black_compatibility@simple_cases__comments9.py.snap | ||
black_compatibility@simple_cases__composition.py.snap | ||
black_compatibility@simple_cases__composition_no_trailing_comma.py.snap | ||
black_compatibility@simple_cases__docstring_preview.py.snap | ||
black_compatibility@simple_cases__empty_lines.py.snap | ||
black_compatibility@simple_cases__expression.py.snap | ||
black_compatibility@simple_cases__fmtonoff.py.snap | ||
black_compatibility@simple_cases__fmtonoff4.py.snap | ||
black_compatibility@simple_cases__fmtonoff5.py.snap | ||
black_compatibility@simple_cases__fmtskip5.py.snap | ||
black_compatibility@simple_cases__function.py.snap | ||
black_compatibility@simple_cases__function2.py.snap | ||
black_compatibility@simple_cases__ignore_pyi.py.snap | ||
black_compatibility@simple_cases__multiline_consecutive_open_parentheses_ignore.py.snap | ||
black_compatibility@simple_cases__remove_await_parens.py.snap | ||
black_compatibility@simple_cases__remove_except_parens.py.snap | ||
black_compatibility@simple_cases__remove_for_brackets.py.snap | ||
black_compatibility@simple_cases__remove_parens.py.snap | ||
black_compatibility@simple_cases__return_annotation_brackets.py.snap | ||
black_compatibility@simple_cases__torture.py.snap | ||
black_compatibility@simple_cases__trailing_comma_optional_parens3.py.snap | ||
black_compatibility@simple_cases__trailing_commas_in_leading_parts.py.snap | ||
black_compatibility@simple_cases__tupleassign.py.snap | ||
black_compatibility@simple_cases__whitespace.py.snap | ||
format@carriage_return__string.py.snap | ||
format@docstring.py.snap | ||
format@expression__annotated_assign.py.snap | ||
format@expression__attribute.py.snap | ||
format@expression__binary.py.snap | ||
format@expression__binary_implicit_string.py.snap | ||
format@expression__boolean_operation.py.snap | ||
format@expression__bytes.py.snap | ||
format@expression__call.py.snap | ||
format@expression__compare.py.snap | ||
format@expression__dict.py.snap | ||
format@expression__dict_comp.py.snap | ||
format@expression__fstring.py.snap | ||
format@expression__generator_exp.py.snap | ||
format@expression__if.py.snap | ||
format@expression__lambda.py.snap | ||
format@expression__list.py.snap | ||
format@expression__list_comp.py.snap | ||
format@expression__named_expr.py.snap | ||
format@expression__set_comp.py.snap | ||
format@expression__slice.py.snap | ||
format@expression__split_empty_brackets.py.snap | ||
format@expression__starred.py.snap | ||
format@expression__string.py.snap | ||
format@expression__tuple.py.snap | ||
format@expression__unary.py.snap | ||
format@expression__yield.py.snap | ||
format@expression__yield_from.py.snap | ||
format@fmt_on_off__comments.py.snap | ||
format@fmt_on_off__empty_file.py.snap | ||
format@fmt_on_off__fmt_off_docstring.py.snap | ||
format@fmt_on_off__form_feed.py.snap | ||
format@fmt_on_off__indent.py.snap | ||
format@fmt_on_off__last_statement.py.snap | ||
format@fmt_on_off__mixed_space_and_tab.py.snap | ||
format@fmt_on_off__no_fmt_on.py.snap | ||
format@fmt_on_off__off_on_off_on.py.snap | ||
format@fmt_on_off__simple.py.snap | ||
format@fmt_on_off__trailing_comments.py.snap | ||
format@fmt_on_off__yapf.py.snap | ||
format@fmt_skip__decorators.py.snap | ||
format@fmt_skip__docstrings.py.snap | ||
format@fmt_skip__match.py.snap | ||
format@fmt_skip__or_else.py.snap | ||
format@fmt_skip__parentheses.py.snap | ||
format@fmt_skip__type_params.py.snap | ||
format@parentheses__call_chains.py.snap | ||
format@parentheses__nested.py.snap | ||
format@parentheses__opening_parentheses_comment_empty.py.snap | ||
format@parentheses__opening_parentheses_comment_value.py.snap | ||
format@skip_magic_trailing_comma.py.snap | ||
format@statement__ann_assign.py.snap | ||
format@statement__assert.py.snap | ||
format@statement__assign.py.snap | ||
format@statement__aug_assign.py.snap | ||
format@statement__break.py.snap | ||
format@statement__class_definition.py.snap | ||
format@statement__delete.py.snap | ||
format@statement__for.py.snap | ||
format@statement__function.py.snap | ||
format@statement__global.py.snap | ||
format@statement__if.py.snap | ||
format@statement__import.py.snap | ||
format@statement__import_from.py.snap | ||
format@statement__match.py.snap | ||
format@statement__nonlocal.py.snap | ||
format@statement__raise.py.snap | ||
format@statement__return_annotation.py.snap | ||
format@statement__top_level.py.snap | ||
format@statement__top_level.pyi.snap | ||
format@statement__try.py.snap | ||
format@statement__type_alias.py.snap | ||
format@statement__while.py.snap | ||
format@statement__with.py.snap | ||
format@trivia.py.snap |