From ed33b75badb4d8c4076e13515551a753b43a0d1b Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 22 Feb 2023 15:25:06 +0100 Subject: [PATCH] test(ruff_python_formatter): Run all Black tests (#2993) This PR changes the testing infrastructure to run all black tests and: * Pass if Ruff and Black generate the same formatting * Fail and write a markdown snapshot that shows the input code, the differences between Black and Ruff, Ruffs output, and Blacks output This is achieved by introducing a new `fixture` macro (open to better name suggestions) that "duplicates" the attributed test for every file that matches the specified glob pattern. Creating a new test for each file over having a test that iterates over all files has the advantage that you can run a single test, and that test failures indicate which case is failing. The `fixture` macro also makes it straightforward to e.g. setup our own spec tests that test very specific formatting by creating a new folder and use insta to assert the formatted output. --- Cargo.lock | 13 + crates/ruff_python_formatter/Cargo.toml | 2 + .../test/fixtures/black/.editorconfig | 6 + ...ibute_access_on_number_literals.py.expect} | 8 +- .../beginning_backslash.py.expect | 1 + .../black/simple_cases/bracketmatch.py.expect | 4 + .../class_blank_parentheses.py.expect} | 8 +- .../class_methods_new_line.py.expect} | 8 +- .../black/simple_cases/collections.py.expect} | 8 +- .../comment_after_escaped_newline.py.expect | 6 + .../black/simple_cases/comments.py.expect} | 8 +- .../black/simple_cases/comments2.py.expect} | 8 +- .../black/simple_cases/comments3.py.expect} | 8 +- .../black/simple_cases/comments4.py.expect} | 8 +- .../black/simple_cases/comments5.py.expect} | 8 +- .../black/simple_cases/comments6.py.expect} | 8 +- .../black/simple_cases/comments8.py.expect} | 8 +- .../black/simple_cases/comments9.py.expect} | 8 +- .../comments_non_breaking_space.py.expect} | 8 +- .../black/simple_cases/composition.py.expect} | 8 +- .../composition_no_trailing_comma.py.expect} | 8 +- .../black/simple_cases/docstring.py.expect} | 8 +- ..._no_extra_empty_line_before_eof.py.expect} | 8 +- .../simple_cases/docstring_preview.py.expect} | 8 +- .../black/simple_cases/empty_lines.py.expect} | 8 +- .../black/simple_cases/expression.diff | 466 ------ .../black/simple_cases/expression.py.expect} | 9 +- .../black/simple_cases/fmtonoff.py.expect} | 8 +- .../black/simple_cases/fmtonoff2.py.expect} | 8 +- .../black/simple_cases/fmtonoff3.py.expect} | 8 +- .../black/simple_cases/fmtonoff4.py.expect} | 8 +- .../black/simple_cases/fmtonoff5.py.expect} | 8 +- .../black/simple_cases/fmtskip.py.expect | 3 + .../black/simple_cases/fmtskip2.py.expect} | 8 +- .../black/simple_cases/fmtskip3.py.expect} | 8 +- .../black/simple_cases/fmtskip4.py.expect | 7 + .../black/simple_cases/fmtskip5.py.expect} | 8 +- .../black/simple_cases/fmtskip6.py.expect | 5 + .../black/simple_cases/fmtskip7.py.expect} | 8 +- .../black/simple_cases/fmtskip8.py.expect} | 8 +- .../black/simple_cases/fstring.py.expect} | 8 +- .../black/simple_cases/function.py.expect} | 8 +- .../black/simple_cases/function2.py.expect} | 7 +- .../function_trailing_comma.py.expect} | 8 +- .../simple_cases/import_spacing.py.expect} | 8 +- .../one_element_subscript.py.expect} | 8 +- .../simple_cases/power_op_spacing.py.expect} | 8 +- .../prefer_rhs_split_reformatted.py.expect} | 8 +- .../remove_await_parens.py.expect} | 8 +- .../remove_except_parens.py.expect} | 8 +- .../remove_for_brackets.py.expect} | 8 +- ...e_newline_after_code_block_open.py.expect} | 8 +- .../simple_cases/remove_parens.py.expect} | 8 +- .../return_annotation_brackets.py.expect} | 8 +- .../skip_magic_trailing_comma.py.expect} | 8 +- .../black/simple_cases/slices.py.expect} | 7 +- .../simple_cases/string_prefixes.py.expect} | 8 +- .../black/simple_cases/torture.py.expect} | 8 +- ...trailing_comma_optional_parens1.py.expect} | 8 +- ...trailing_comma_optional_parens2.py.expect} | 8 +- ...trailing_comma_optional_parens3.py.expect} | 8 +- ...railing_commas_in_leading_parts.py.expect} | 8 +- .../tricky_unicode_symbols.py.expect | 9 + .../black/simple_cases/tupleassign.py.expect} | 6 +- .../black/simple_cases/whitespace.py.expect | 0 crates/ruff_python_formatter/src/lib.rs | 175 ++- ...mment_after_escaped_newline.py.snap.expect | 12 - ...ests__simple_cases__fmtskip.py.snap.expect | 9 - ...sts__simple_cases__fmtskip4.py.snap.expect | 13 - ...sts__simple_cases__fmtskip6.py.snap.expect | 11 - ...s__simple_cases__whitespace.py.snap.expect | 7 - ...ttribute_access_on_number_literals_py.snap | 138 ++ ...r__tests__black_test__bracketmatch_py.snap | 53 + ...lack_test__class_blank_parentheses_py.snap | 121 ++ ...black_test__class_methods_new_line_py.snap | 468 ++++++ ...er__tests__black_test__collections_py.snap | 329 +++++ ...est__comment_after_escaped_newline_py.snap | 67 + ...tter__tests__black_test__comments2_py.snap | 734 ++++++++++ ...tter__tests__black_test__comments4_py.snap | 450 ++++++ ...tter__tests__black_test__comments6_py.snap | 554 +++++++ ...tter__tests__black_test__comments9_py.snap | 629 ++++++++ ...est__composition_no_trailing_comma_py.snap | 882 +++++++++++ ...er__tests__black_test__composition_py.snap | 882 +++++++++++ ...ing_no_extra_empty_line_before_eof_py.snap | 50 + ...er__tests__black_test__empty_lines_py.snap | 419 ++++++ ...ter__tests__black_test__expression_py.snap | 1302 +++++++++++++++++ ...tter__tests__black_test__fmtonoff2_py.snap | 210 +++ ...tter__tests__black_test__fmtonoff3_py.snap | 104 ++ ...tter__tests__black_test__fmtonoff4_py.snap | 115 ++ ...tter__tests__black_test__fmtonoff5_py.snap | 378 +++++ ...atter__tests__black_test__fmtskip2_py.snap | 72 + ...atter__tests__black_test__fmtskip3_py.snap | 67 + ...atter__tests__black_test__fmtskip5_py.snap | 67 + ...atter__tests__black_test__fmtskip6_py.snap | 53 + ...atter__tests__black_test__fmtskip7_py.snap | 52 + ...matter__tests__black_test__fmtskip_py.snap | 45 + ...matter__tests__black_test__fstring_py.snap | 65 + ...tter__tests__black_test__function2_py.snap | 223 +++ ...atter__tests__black_test__function_py.snap | 493 +++++++ ...lack_test__function_trailing_comma_py.snap | 419 ++++++ ...s__black_test__remove_await_parens_py.snap | 407 ++++++ ...__black_test__remove_except_parens_py.snap | 175 +++ ...s__black_test__remove_for_brackets_py.snap | 126 ++ ...move_newline_after_code_block_open_py.snap | 302 ++++ ...__tests__black_test__remove_parens_py.snap | 294 ++++ ...k_test__return_annotation_brackets_py.snap | 497 +++++++ ...ck_test__skip_magic_trailing_comma_py.snap | 239 +++ ...tests__black_test__string_prefixes_py.snap | 117 ++ ...matter__tests__black_test__torture_py.snap | 267 ++++ ...t__trailing_comma_optional_parens1_py.snap | 179 +++ ...t__trailing_comma_optional_parens2_py.snap | 58 + ...t__trailing_comma_optional_parens3_py.snap | 64 + ...__trailing_commas_in_leading_parts_py.snap | 181 +++ ..._simple_cases__beginning_backslash.py.snap | 7 - ..._tests__simple_cases__bracketmatch.py.snap | 10 - ...mple_cases__tricky_unicode_symbols.py.snap | 15 - crates/ruff_python_formatter/src/test.rs | 7 - crates/ruff_testing_macros/Cargo.toml | 17 + crates/ruff_testing_macros/src/lib.rs | 397 +++++ 119 files changed, 12989 insertions(+), 981 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/.editorconfig rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__attribute_access_on_number_literals.py.snap.expect => resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py.expect} (82%) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/beginning_backslash.py.expect create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/bracketmatch.py.expect rename crates/ruff_python_formatter/{src/snapshots/ruff_python_formatter__tests__simple_cases__class_blank_parentheses.py.snap => resources/test/fixtures/black/simple_cases/class_blank_parentheses.py.expect} (83%) rename crates/ruff_python_formatter/{src/snapshots/ruff_python_formatter__tests__simple_cases__class_methods_new_line.py.snap => resources/test/fixtures/black/simple_cases/class_methods_new_line.py.expect} (95%) rename crates/ruff_python_formatter/{src/snapshots/ruff_python_formatter__tests__simple_cases__collections.py.snap => resources/test/fixtures/black/simple_cases/collections.py.expect} (95%) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comment_after_escaped_newline.py.expect rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments.py.snap.expect => resources/test/fixtures/black/simple_cases/comments.py.expect} (95%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments2.py.snap.expect => resources/test/fixtures/black/simple_cases/comments2.py.expect} (97%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments3.py.snap.expect => resources/test/fixtures/black/simple_cases/comments3.py.expect} (94%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments4.py.snap.expect => resources/test/fixtures/black/simple_cases/comments4.py.expect} (97%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments5.py.snap.expect => resources/test/fixtures/black/simple_cases/comments5.py.expect} (92%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments6.py.snap.expect => resources/test/fixtures/black/simple_cases/comments6.py.expect} (94%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments8.py.snap.expect => resources/test/fixtures/black/simple_cases/comments8.py.expect} (62%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments9.py.snap.expect => resources/test/fixtures/black/simple_cases/comments9.py.expect} (96%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments_non_breaking_space.py.snap.expect => resources/test/fixtures/black/simple_cases/comments_non_breaking_space.py.expect} (84%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__composition.py.snap.expect => resources/test/fixtures/black/simple_cases/composition.py.expect} (98%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__composition_no_trailing_comma.py.snap.expect => resources/test/fixtures/black/simple_cases/composition_no_trailing_comma.py.expect} (98%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring.py.snap.expect => resources/test/fixtures/black/simple_cases/docstring.py.expect} (97%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_no_extra_empty_line_before_eof.py.snap.expect => resources/test/fixtures/black/simple_cases/docstring_no_extra_empty_line_before_eof.py.expect} (53%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_preview.py.snap.expect => resources/test/fixtures/black/simple_cases/docstring_preview.py.expect} (93%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__empty_lines.py.snap.expect => resources/test/fixtures/black/simple_cases/empty_lines.py.expect} (95%) delete mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.diff rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__expression.py.snap.expect => resources/test/fixtures/black/simple_cases/expression.py.expect} (98%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff.py.snap.expect => resources/test/fixtures/black/simple_cases/fmtonoff.py.expect} (98%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff2.py.snap.expect => resources/test/fixtures/black/simple_cases/fmtonoff2.py.expect} (86%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff3.py.snap.expect => resources/test/fixtures/black/simple_cases/fmtonoff3.py.expect} (50%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff4.py.snap.expect => resources/test/fixtures/black/simple_cases/fmtonoff4.py.expect} (62%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff5.py.snap.expect => resources/test/fixtures/black/simple_cases/fmtonoff5.py.expect} (94%) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip.py.expect rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip2.py.snap.expect => resources/test/fixtures/black/simple_cases/fmtskip2.py.expect} (77%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip3.py.snap.expect => resources/test/fixtures/black/simple_cases/fmtskip3.py.expect} (68%) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip4.py.expect rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip5.py.snap.expect => resources/test/fixtures/black/simple_cases/fmtskip5.py.expect} (54%) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip6.py.expect rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip7.py.snap.expect => resources/test/fixtures/black/simple_cases/fmtskip7.py.expect} (51%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip8.py.snap.expect => resources/test/fixtures/black/simple_cases/fmtskip8.py.expect} (93%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fstring.py.snap.expect => resources/test/fixtures/black/simple_cases/fstring.py.expect} (76%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function.py.snap.expect => resources/test/fixtures/black/simple_cases/function.py.expect} (97%) rename crates/ruff_python_formatter/{src/snapshots/ruff_python_formatter__tests__simple_cases__function2.py.snap => resources/test/fixtures/black/simple_cases/function2.py.expect} (90%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function_trailing_comma.py.snap.expect => resources/test/fixtures/black/simple_cases/function_trailing_comma.py.expect} (96%) rename crates/ruff_python_formatter/{src/snapshots/ruff_python_formatter__tests__simple_cases__import_spacing.py.snap => resources/test/fixtures/black/simple_cases/import_spacing.py.expect} (94%) rename crates/ruff_python_formatter/{src/snapshots/ruff_python_formatter__tests__simple_cases__one_element_subscript.py.snap => resources/test/fixtures/black/simple_cases/one_element_subscript.py.expect} (80%) rename crates/ruff_python_formatter/{src/snapshots/ruff_python_formatter__tests__simple_cases__power_op_spacing.py.snap => resources/test/fixtures/black/simple_cases/power_op_spacing.py.expect} (95%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__prefer_rhs_split_reformatted.py.snap.expect => resources/test/fixtures/black/simple_cases/prefer_rhs_split_reformatted.py.expect} (86%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_await_parens.py.snap.expect => resources/test/fixtures/black/simple_cases/remove_await_parens.py.expect} (93%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_except_parens.py.snap.expect => resources/test/fixtures/black/simple_cases/remove_except_parens.py.expect} (91%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_for_brackets.py.snap.expect => resources/test/fixtures/black/simple_cases/remove_for_brackets.py.expect} (88%) rename crates/ruff_python_formatter/{src/snapshots/ruff_python_formatter__tests__simple_cases__remove_newline_after_code_block_open.py.snap => resources/test/fixtures/black/simple_cases/remove_newline_after_code_block_open.py.expect} (92%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_parens.py.snap.expect => resources/test/fixtures/black/simple_cases/remove_parens.py.expect} (94%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__return_annotation_brackets.py.snap.expect => resources/test/fixtures/black/simple_cases/return_annotation_brackets.py.expect} (96%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__skip_magic_trailing_comma.py.snap.expect => resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.py.expect} (80%) rename crates/ruff_python_formatter/{src/snapshots/ruff_python_formatter__tests__simple_cases__slices.py.snap => resources/test/fixtures/black/simple_cases/slices.py.expect} (86%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__string_prefixes.py.snap.expect => resources/test/fixtures/black/simple_cases/string_prefixes.py.expect} (82%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__torture.py.snap.expect => resources/test/fixtures/black/simple_cases/torture.py.expect} (92%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens1.py.snap.expect => resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens1.py.expect} (89%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens2.py.snap.expect => resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens2.py.expect} (56%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens3.py.snap.expect => resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens3.py.expect} (81%) rename crates/ruff_python_formatter/{src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_commas_in_leading_parts.py.snap.expect => resources/test/fixtures/black/simple_cases/trailing_commas_in_leading_parts.py.expect} (92%) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/tricky_unicode_symbols.py.expect rename crates/ruff_python_formatter/{src/snapshots/ruff_python_formatter__tests__simple_cases__tupleassign.py.snap => resources/test/fixtures/black/simple_cases/tupleassign.py.expect} (68%) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/whitespace.py.expect delete mode 100644 crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comment_after_escaped_newline.py.snap.expect delete mode 100644 crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip.py.snap.expect delete mode 100644 crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip4.py.snap.expect delete mode 100644 crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip6.py.snap.expect delete mode 100644 crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__whitespace.py.snap.expect create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__attribute_access_on_number_literals_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__bracketmatch_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__class_blank_parentheses_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__class_methods_new_line_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comment_after_escaped_newline_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments4_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments6_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments9_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__composition_no_trailing_comma_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__composition_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_no_extra_empty_line_before_eof_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff2_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff3_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff4_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff5_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip2_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip3_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip5_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip6_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip7_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fstring_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function2_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_await_parens_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_except_parens_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_for_brackets_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_newline_after_code_block_open_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_parens_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__return_annotation_brackets_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__skip_magic_trailing_comma_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__torture_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens1_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens2_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens3_py.snap create mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_commas_in_leading_parts_py.snap delete mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__beginning_backslash.py.snap delete mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__bracketmatch.py.snap delete mode 100644 crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__tricky_unicode_symbols.py.snap delete mode 100644 crates/ruff_python_formatter/src/test.rs create mode 100644 crates/ruff_testing_macros/Cargo.toml create mode 100644 crates/ruff_testing_macros/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 20644e20be..7c401aa7c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2075,13 +2075,26 @@ dependencies = [ "insta", "once_cell", "ruff_formatter", + "ruff_testing_macros", "ruff_text_size", "rustc-hash", "rustpython-common", "rustpython-parser", + "similar", "test-case", ] +[[package]] +name = "ruff_testing_macros" +version = "0.0.0" +dependencies = [ + "glob", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ruff_text_size" version = "0.0.0" diff --git a/crates/ruff_python_formatter/Cargo.toml b/crates/ruff_python_formatter/Cargo.toml index 96fd6b5c97..a7e76f31f9 100644 --- a/crates/ruff_python_formatter/Cargo.toml +++ b/crates/ruff_python_formatter/Cargo.toml @@ -18,3 +18,5 @@ rustpython-parser = { workspace = true } [dev-dependencies] insta = { version = "1.19.0", features = [] } test-case = { version = "2.2.2" } +ruff_testing_macros = { path = "../ruff_testing_macros" } +similar = "2.2.1" diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/.editorconfig b/crates/ruff_python_formatter/resources/test/fixtures/black/.editorconfig new file mode 100644 index 0000000000..4e8f7bd60b --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/.editorconfig @@ -0,0 +1,6 @@ +# Check http://editorconfig.org for more information +# This is the main config file for this project: +root = true + +[*.py.expect] +insert_final_newline = false diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__attribute_access_on_number_literals.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py.expect similarity index 82% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__attribute_access_on_number_literals.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py.expect index b4ae9ee696..3ada815d85 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__attribute_access_on_number_literals.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- x = (123456789).bit_count() x = (123456).__abs__() x = (0.1).is_integer() @@ -24,5 +19,4 @@ if (10).real: ... y = 100[no] -y = 100(no) - +y = 100(no) \ No newline at end of file diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/beginning_backslash.py.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/beginning_backslash.py.expect new file mode 100644 index 0000000000..bc63dc6882 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/beginning_backslash.py.expect @@ -0,0 +1 @@ +print("hello, world") \ No newline at end of file diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/bracketmatch.py.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/bracketmatch.py.expect new file mode 100644 index 0000000000..c0cac519f3 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/bracketmatch.py.expect @@ -0,0 +1,4 @@ +for ((x in {}) or {})["a"] in x: + pass +pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip()) +lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__class_blank_parentheses.py.snap b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/class_blank_parentheses.py.expect similarity index 83% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__class_blank_parentheses.py.snap rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/class_blank_parentheses.py.expect index 7f86af3f7f..1adfe6d16a 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__class_blank_parentheses.py.snap +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/class_blank_parentheses.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- class SimpleClassWithBlankParentheses: pass @@ -32,5 +27,4 @@ def class_under_the_func_with_blank_parentheses(): class NormalClass: def func_for_testing(self, first, second): sum = first + second - return sum - + return sum \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__class_methods_new_line.py.snap b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/class_methods_new_line.py.expect similarity index 95% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__class_methods_new_line.py.snap rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/class_methods_new_line.py.expect index 1566f40af4..b39ac3f13e 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__class_methods_new_line.py.snap +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/class_methods_new_line.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- class ClassSimplest: pass @@ -167,5 +162,4 @@ class ClassWithDecoInitAndVarsAndDocstringWithInner2: @deco def __init__(self): - pass - + pass \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__collections.py.snap b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/collections.py.expect similarity index 95% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__collections.py.snap rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/collections.py.expect index 4ec9c12e68..3a895ca55d 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__collections.py.snap +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/collections.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- import core, time, a from . import A, B, C @@ -101,5 +96,4 @@ if True: WaiterConfig={ "Delay": 5, }, - ) - + ) \ No newline at end of file diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comment_after_escaped_newline.py.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comment_after_escaped_newline.py.expect new file mode 100644 index 0000000000..80f4ae11fd --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comment_after_escaped_newline.py.expect @@ -0,0 +1,6 @@ +def bob(): # pylint: disable=W9016 + pass + + +def bobtwo(): # some comment here + pass \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments.py.expect similarity index 95% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments.py.expect index 4fb3442f12..4a94e7ad93 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- #!/usr/bin/env python3 # fmt: on # Some license here. @@ -98,5 +93,4 @@ async def wat(): # Some closing comments. # Maybe Vim or Emacs directives for formatting. -# Who knows. - +# Who knows. \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments2.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments2.py.expect similarity index 97% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments2.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments2.py.expect index 5d2a804ac1..d4bd0e0f13 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments2.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments2.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( MyLovelyCompanyTeamProjectComponent, # NOT DRY ) @@ -175,5 +170,4 @@ class Test: instruction() # comment with bad spacing # END COMMENTS -# MORE END COMMENTS - +# MORE END COMMENTS \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments3.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments3.py.expect similarity index 94% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments3.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments3.py.expect index 4aa052ff52..f964bee665 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments3.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments3.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # The percent-percent comments are Spyder IDE cells. @@ -50,5 +45,4 @@ def func(): ) -# %% - +# %% \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments4.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments4.py.expect similarity index 97% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments4.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments4.py.expect index ddb844f33c..073d1e36f9 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments4.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments4.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( MyLovelyCompanyTeamProjectComponent, # NOT DRY ) @@ -96,5 +91,4 @@ def foo3(list_a, list_b): db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) ) .filter(User.xyz.is_(None)) - ) - + ) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments5.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments5.py.expect similarity index 92% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments5.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments5.py.expect index 9ed281954d..23a56e5679 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments5.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments5.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- while True: if something.changed: do.stuff() # trailing comment @@ -75,5 +70,4 @@ def g(): if __name__ == "__main__": - main() - + main() \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments6.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments6.py.expect similarity index 94% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments6.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments6.py.expect index 527609fa48..2b9b6eda6d 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments6.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments6.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- from typing import Any, Tuple @@ -120,5 +115,4 @@ call_to_some_function_asdf( [AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, BBBBBBBBBBBB], # type: ignore ) -aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] - +aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments8.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments8.py.expect similarity index 62% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments8.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments8.py.expect index 1c6a7ae745..672dca0c03 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments8.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments8.py.expect @@ -1,12 +1,6 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # The percent-percent comments are Spyder IDE cells. # Both `#%%`` and `# %%` are accepted, so `black` standardises # to the latter. # %% -# %% - +# %% \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments9.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments9.py.expect similarity index 96% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments9.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments9.py.expect index 918f7355bc..1071bc2a23 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments9.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments9.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # Test for https://github.com/psf/black/issues/246. some = statement @@ -163,5 +158,4 @@ def foo(): @decorator1 # A standalone comment def bar(): - pass - + pass \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments_non_breaking_space.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments_non_breaking_space.py.expect similarity index 84% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments_non_breaking_space.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments_non_breaking_space.py.expect index 737aa0a47b..c3b90247f8 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments_non_breaking_space.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments_non_breaking_space.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- from .config import ( ConfigTypeAttributes, Int, @@ -25,5 +20,4 @@ def function(a: int = 42): """ # There's a NBSP + 3 spaces before # And 4 spaces on the next line - pass - + pass \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__composition.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/composition.py.expect similarity index 98% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__composition.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/composition.py.expect index 4cc21eddcb..0f590039a8 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__composition.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/composition.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- class C: def test(self) -> None: with patch("black.out", print): @@ -183,5 +178,4 @@ class C: key8: value8, key9: value9, } - ) - + ) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__composition_no_trailing_comma.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/composition_no_trailing_comma.py.expect similarity index 98% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__composition_no_trailing_comma.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/composition_no_trailing_comma.py.expect index 4cc21eddcb..0f590039a8 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__composition_no_trailing_comma.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/composition_no_trailing_comma.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- class C: def test(self) -> None: with patch("black.out", print): @@ -183,5 +178,4 @@ class C: key8: value8, key9: value9, } - ) - + ) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring.py.expect similarity index 97% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring.py.expect index b774287760..be9808041e 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- class MyClass: """Multiline class docstring @@ -221,5 +216,4 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): """' - """ - + """ \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_no_extra_empty_line_before_eof.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_no_extra_empty_line_before_eof.py.expect similarity index 53% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_no_extra_empty_line_before_eof.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_no_extra_empty_line_before_eof.py.expect index df6aa95f9a..0afda15012 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_no_extra_empty_line_before_eof.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_no_extra_empty_line_before_eof.py.expect @@ -1,10 +1,4 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # Make sure when the file ends with class's docstring, # It doesn't add extra blank lines. class ClassWithDocstring: - """A docstring.""" - + """A docstring.""" \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_preview.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_preview.py.expect similarity index 93% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_preview.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_preview.py.expect index a245e99a82..94efd7b60d 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_preview.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_preview.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- def docstring_almost_at_line_limit(): """long docstring.................................................................""" @@ -50,5 +45,4 @@ def single_quote_docstring_over_line_limit(): def single_quote_docstring_over_line_limit2(): - "We do not want to put the closing quote on a new line as that is invalid (see GH-3141)." - + "We do not want to put the closing quote on a new line as that is invalid (see GH-3141)." \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__empty_lines.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/empty_lines.py.expect similarity index 95% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__empty_lines.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/empty_lines.py.expect index 8a672658f6..a4f759b379 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__empty_lines.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/empty_lines.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- """Docstring.""" @@ -91,5 +86,4 @@ def g(): syms.arglist, syms.argument, }: - return NO - + return NO \ No newline at end of file diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.diff b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.diff deleted file mode 100644 index 2eaaeb479f..0000000000 --- a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.diff +++ /dev/null @@ -1,466 +0,0 @@ ---- [Deterministic header] -+++ [Deterministic header] -@@ -1,8 +1,8 @@ - ... --'some_string' --b'\\xa3' -+"some_string" -+b"\\xa3" - Name - None - True - False - 1 -@@ -21,99 +21,135 @@ - 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) -+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 -++(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 -+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) - 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}} -+( -+ (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 -+{"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,] -+[ -+ 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] -+[ -+ *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**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**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'))} -+{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} -+{ -+ 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(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 -+(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] --] --tuple[str, int, float, dict[str, int],] -+ 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 -+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] -@@ -137,118 +173,199 @@ - 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'} -+{"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)) -+((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} -+{ -+ "id": "1", -+ "type": "type", -+ "started_at": now(), -+ "ended_at": now() + timedelta(days=10), -+ "priority": 1, -+ "import_session_id": 1, -+ **kwargs, -+} - a = (1,) --b = 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() -+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))) -+ 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(*[] 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'): -+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) -+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 -+( -+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -+ >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -+ << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -+) - bbbb >> bbbb * bbbb --aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ^bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -+( -+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -+ ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -+ ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -+) - last_call() - # standalone comment at ENDMARKER diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__expression.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.py.expect similarity index 98% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__expression.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.py.expect index ff1b33cc9b..ac24fdc41a 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__expression.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.py.expect @@ -1,9 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- -... "some_string" b"\\xa3" Name @@ -373,5 +367,4 @@ bbbb >> bbbb * bbbb ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ) last_call() -# standalone comment at ENDMARKER - +# standalone comment at ENDMARKER \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff.py.expect similarity index 98% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff.py.expect index 8278fc83cc..a0ccf5d2b7 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- #!/usr/bin/env python3 import asyncio import sys @@ -226,5 +221,4 @@ yield 'hello' # No formatting to the end of the file l=[1,2,3] d={'a':1, - 'b':2} - + 'b':2} \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff2.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff2.py.expect similarity index 86% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff2.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff2.py.expect index 2ab9cbe536..aa116f636a 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff2.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff2.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- import pytest TmSt = 1 @@ -42,5 +37,4 @@ def test_calculate_fades(): (None, 4, 0, 0, 10, 0, 0, 6, 10), ] -# fmt: on - +# fmt: on \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff3.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff3.py.expect similarity index 50% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff3.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff3.py.expect index c5b1e4506e..3cbffdbc3c 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff3.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff3.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # fmt: off x = [ 1, 2, @@ -17,5 +12,4 @@ x = [ ] # fmt: on -x = [1, 2, 3, 4] - +x = [1, 2, 3, 4] \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff4.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff4.py.expect similarity index 62% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff4.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff4.py.expect index b2f1528d5f..4ec8bf850d 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff4.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff4.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # fmt: off @test([ 1, 2, @@ -22,5 +17,4 @@ def f(): ] ) def f(): - pass - + pass \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff5.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff5.py.expect similarity index 94% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff5.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff5.py.expect index 1d8c230ba2..4a26d3b8ee 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff5.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff5.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # Regression test for https://github.com/psf/black/issues/3129. setup( entry_points={ @@ -89,5 +84,4 @@ if x: # fmt: off elif unformatted: # fmt: on - will_be_formatted() - + will_be_formatted() \ No newline at end of file diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip.py.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip.py.expect new file mode 100644 index 0000000000..cd89a7d48a --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip.py.expect @@ -0,0 +1,3 @@ +a, b = 1, 2 +c = 6 # fmt: skip +d = 5 \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip2.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip2.py.expect similarity index 77% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip2.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip2.py.expect index 2794958a2d..1b3b0956f8 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip2.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip2.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- l1 = [ "This list should be broken up", "into multiple lines", @@ -13,5 +8,4 @@ l3 = [ "I have", "trailing comma", "so I should be braked", -] - +] \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip3.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip3.py.expect similarity index 68% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip3.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip3.py.expect index a8b8fcc946..e6591cd8c0 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip3.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip3.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- a = 3 # fmt: off b, c = 1, 2 @@ -12,5 +7,4 @@ e = 5 f = [ "This is a very long line that should be formatted into a clearer line ", "by rearranging.", -] - +] \ No newline at end of file diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip4.py.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip4.py.expect new file mode 100644 index 0000000000..d70353026c --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip4.py.expect @@ -0,0 +1,7 @@ +a = 2 +# fmt: skip +l = [ + 1, + 2, + 3, +] \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip5.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip5.py.expect similarity index 54% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip5.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip5.py.expect index 4ad5157bef..b4e502a68c 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip5.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip5.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- a, b, c = 3, 4, 5 if ( a == 3 @@ -11,5 +6,4 @@ if ( ): print("I'm good!") else: - print("I'm bad") - + print("I'm bad") \ No newline at end of file diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip6.py.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip6.py.expect new file mode 100644 index 0000000000..c7e65f5378 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip6.py.expect @@ -0,0 +1,5 @@ +class A: + def f(self): + for line in range(10): + if True: + pass # fmt: skip \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip7.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip7.py.expect similarity index 51% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip7.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip7.py.expect index 76dbf12310..72ded7d7dc 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip7.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip7.py.expect @@ -1,10 +1,4 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- a = "this is some code" b = 5 # fmt:skip c = 9 # fmt: skip -d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip - +d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip8.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip8.py.expect similarity index 93% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip8.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip8.py.expect index b249067ea9..e2b0a87ce0 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip8.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip8.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # Make sure a leading comment is not removed. def some_func( unformatted, args ): # fmt: skip print("I am some_func") @@ -64,5 +59,4 @@ with give_me_context( unformatted, args ): # fmt: skip async def test_async_with(): async with give_me_async_context( unformatted, args ): # fmt: skip - print("Do something") - + print("Do something") \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fstring.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fstring.py.expect similarity index 76% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fstring.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fstring.py.expect index 571a1683d4..6ad85cf259 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fstring.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fstring.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- f"f-string without formatted values is just a string" f"{{NOT a formatted value}}" f'{{NOT \'a\' "formatted" "value"}}' @@ -11,5 +6,4 @@ f'some f-string with {a} {few(""):.2f} {formatted.values!r}' f"{f'''{'nested'} inner'''} outer" f"\"{f'{nested} inner'}\" outer" f"space between opening braces: { {a for a in (1, 2, 3)}}" -f'Hello \'{tricky + "example"}\'' - +f'Hello \'{tricky + "example"}\'' \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function.py.expect similarity index 97% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function.py.expect index 20b3f4f84f..51c20d8c28 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- #!/usr/bin/env python3 import asyncio import sys @@ -150,5 +145,4 @@ def f( def __await__(): - return (yield) - + return (yield) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__function2.py.snap b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function2.py.expect similarity index 90% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__function2.py.snap rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function2.py.expect index 559659fc4f..9e53dd323b 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__function2.py.snap +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function2.py.expect @@ -1,7 +1,3 @@ ---- -source: crates/ruff_python_formatter/src/lib.rs -expression: adjust_quotes(formatted.print()?.as_code()) ---- def f( a, **kwargs, @@ -67,5 +63,4 @@ else: with hmm_but_this_should_get_two_preceding_newlines(): - pass - + pass \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function_trailing_comma.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function_trailing_comma.py.expect similarity index 96% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function_trailing_comma.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function_trailing_comma.py.expect index 47edd1f2d7..e6324cc78b 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function_trailing_comma.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function_trailing_comma.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- def f( a, ): @@ -116,5 +111,4 @@ some_module.some_function( argument4, argument5, argument6, -) - +) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__import_spacing.py.snap b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/import_spacing.py.expect similarity index 94% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__import_spacing.py.snap rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/import_spacing.py.expect index 233baa1868..aa82662d27 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__import_spacing.py.snap +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/import_spacing.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- """The asyncio package, tracking PEP 3156.""" # flake8: noqa @@ -66,5 +61,4 @@ __all__ = ( + queues.__all__ + streams.__all__ + tasks.__all__ -) - +) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__one_element_subscript.py.snap b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/one_element_subscript.py.expect similarity index 80% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__one_element_subscript.py.snap rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/one_element_subscript.py.expect index b93a2f09cd..2ba01ee747 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__one_element_subscript.py.snap +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/one_element_subscript.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # We should not treat the trailing comma # in a single-element subscript. a: tuple[int,] @@ -24,5 +19,4 @@ small_list = [ ] list_of_types = [ tuple[int,], -] - +] \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__power_op_spacing.py.snap b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/power_op_spacing.py.expect similarity index 95% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__power_op_spacing.py.snap rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/power_op_spacing.py.expect index 7a8512a70c..72fa4d14b6 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__power_op_spacing.py.snap +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/power_op_spacing.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- def function(**kwargs): t = a**2 + b**3 return t**2 @@ -65,5 +60,4 @@ if hasattr(view, "sum_of_weights"): return np.divide( where=view.sum_of_weights_of_weight_long**2 > view.sum_of_weights_squared, # type: ignore -) - +) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__prefer_rhs_split_reformatted.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/prefer_rhs_split_reformatted.py.expect similarity index 86% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__prefer_rhs_split_reformatted.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/prefer_rhs_split_reformatted.py.expect index 6730e959d4..d60abb5ff3 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__prefer_rhs_split_reformatted.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/prefer_rhs_split_reformatted.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # Test cases separate from `prefer_rhs_split.py` that contains unformatted source. # Left hand side fits in a single line but will still be exploded by the @@ -23,5 +18,4 @@ expression: formatted # exactly line length limit + 1, it won't be split like that. xxxxxxxxx_yyy_zzzzzzzz[ xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1) -] = 1 - +] = 1 \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_await_parens.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_await_parens.py.expect similarity index 93% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_await_parens.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_await_parens.py.expect index fabcb9ed80..bb4e3f1a61 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_await_parens.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_await_parens.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- import asyncio @@ -95,5 +90,4 @@ async def main(): async def main(): - await (yield) - + await (yield) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_except_parens.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_except_parens.py.expect similarity index 91% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_except_parens.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_except_parens.py.expect index ab395b583e..3faee3dc72 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_except_parens.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_except_parens.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # These brackets are redundant, therefore remove. try: a.something @@ -44,5 +39,4 @@ except ( some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, ) as err: - raise err - + raise err \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_for_brackets.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_for_brackets.py.expect similarity index 88% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_for_brackets.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_for_brackets.py.expect index 43077b0550..c30502a90c 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_for_brackets.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_for_brackets.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # Only remove tuple brackets after `for` for k, v in d.items(): print(k, v) @@ -29,5 +24,4 @@ for ( # Test deeply nested brackets for k, v in d.items(): - print(k, v) - + print(k, v) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__remove_newline_after_code_block_open.py.snap b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_newline_after_code_block_open.py.expect similarity index 92% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__remove_newline_after_code_block_open.py.snap rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_newline_after_code_block_open.py.expect index 96d7e17ceb..47134235e6 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__remove_newline_after_code_block_open.py.snap +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_newline_after_code_block_open.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- import random @@ -80,5 +75,4 @@ with open("/path/to/file.txt", mode="w") as file: with open("/path/to/file.txt", mode="r") as read_file: with open("/path/to/output_file.txt", mode="w") as write_file: - write_file.writelines(read_file.readlines()) - + write_file.writelines(read_file.readlines()) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_parens.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_parens.py.expect similarity index 94% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_parens.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_parens.py.expect index f49f5db22d..e63581778b 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_parens.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_parens.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- x = 1 x = 1.2 @@ -87,5 +82,4 @@ def example7(): def example8(): - return None - + return None \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__return_annotation_brackets.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/return_annotation_brackets.py.expect similarity index 96% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__return_annotation_brackets.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/return_annotation_brackets.py.expect index b1744fd7d9..b78d1ed37f 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__return_annotation_brackets.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/return_annotation_brackets.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # Control def double(a: int) -> int: return 2 * a @@ -122,5 +117,4 @@ def foo() -> ( int, ] ): - return 2 - + return 2 \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__skip_magic_trailing_comma.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.py.expect similarity index 80% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__skip_magic_trailing_comma.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.py.expect index 508ed6e633..855b0d5b3b 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__skip_magic_trailing_comma.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- # We should not remove the trailing comma in a single-element subscript. a: tuple[int,] b = tuple[int,] @@ -27,5 +22,4 @@ func1(arg1).func2(arg2).func3(arg3).func4(arg4).func5(arg5) (a, b, c, d) = func1(arg1) and func2(arg2) -func(argument1, (one, two), argument4, argument5, argument6) - +func(argument1, (one, two), argument4, argument5, argument6) \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__slices.py.snap b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/slices.py.expect similarity index 86% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__slices.py.snap rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/slices.py.expect index b75e97b6a4..8bdfd398d6 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__slices.py.snap +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/slices.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- slice[a.b : c.d] slice[d :: d + 1] slice[d + 1 :: d] @@ -33,4 +28,4 @@ ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:] ham[lower:upper], ham[lower:upper:], ham[lower::step] # ham[lower+offset : upper+offset] ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)] -ham[lower + offset : upper + offset] +ham[lower + offset : upper + offset] \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__string_prefixes.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/string_prefixes.py.expect similarity index 82% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__string_prefixes.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/string_prefixes.py.expect index 96fe06ec84..83e95c5d76 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__string_prefixes.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/string_prefixes.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- #!/usr/bin/env python3 name = "Łukasz" @@ -22,5 +17,4 @@ def docstring_singleline(): def docstring_multiline(): R""" clear out all of the issues opened in that time :p - """ - + """ \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__torture.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/torture.py.expect similarity index 92% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__torture.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/torture.py.expect index 0bf010f59a..36e9ea3515 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__torture.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/torture.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- importA ( () @@ -60,5 +55,4 @@ def test(self, othr): assert a_function( very_long_arguments_that_surpass_the_limit, which_is_eighty_eight_in_this_case_plus_a_bit_more, -) == {"x": "this need to pass the line limit as well", "b": "but only by a little bit"} - +) == {"x": "this need to pass the line limit as well", "b": "but only by a little bit"} \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens1.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens1.py.expect similarity index 89% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens1.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens1.py.expect index f76c0c61c4..dd945eb330 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens1.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens1.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- if e1234123412341234.winerror not in ( _winapi.ERROR_SEM_TIMEOUT, _winapi.ERROR_PIPE_BUSY, @@ -36,5 +31,4 @@ class A: 4, 3, ) < self.connection.mysql_version < (10, 5, 2): - pass - + pass \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens2.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens2.py.expect similarity index 56% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens2.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens2.py.expect index 538fa6b013..eefadeb59b 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens2.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens2.py.expect @@ -1,12 +1,6 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- if e123456.get_tk_patchlevel() >= (8, 6, 0, "final") or ( 8, 5, 8, ) <= get_tk_patchlevel() < (8, 6): - pass - + pass \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens3.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens3.py.expect similarity index 81% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens3.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens3.py.expect index c26109904a..e6a673ec53 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_comma_optional_parens3.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens3.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- if True: if True: if True: @@ -10,5 +5,4 @@ if True: "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas " + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.", "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe", - ) % {"reported_username": reported_username, "report_reason": report_reason} - + ) % {"reported_username": reported_username, "report_reason": report_reason} \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_commas_in_leading_parts.py.snap.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_commas_in_leading_parts.py.expect similarity index 92% rename from crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_commas_in_leading_parts.py.snap.expect rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_commas_in_leading_parts.py.expect index 233dde6f9d..a34cef7a69 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_commas_in_leading_parts.py.snap.expect +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_commas_in_leading_parts.py.expect @@ -1,8 +1,3 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- zero( one, ).two( @@ -52,5 +47,4 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( xxxxxxxxx ).xxxxxxxxxxxxxxxxxx(), ( "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -) - +) \ No newline at end of file diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/tricky_unicode_symbols.py.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/tricky_unicode_symbols.py.expect new file mode 100644 index 0000000000..1225df1a2b --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/tricky_unicode_symbols.py.expect @@ -0,0 +1,9 @@ +ä = 1 +µ = 2 +蟒 = 3 +x󠄀 = 4 +មុ = 1 +Q̇_per_meter = 4 + +A᧚ = 3 +A፩ = 8 \ No newline at end of file diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__tupleassign.py.snap b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/tupleassign.py.expect similarity index 68% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__tupleassign.py.snap rename to crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/tupleassign.py.expect index 863532ace1..16505cf05e 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__tupleassign.py.snap +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/tupleassign.py.expect @@ -1,7 +1,3 @@ ---- -source: crates/ruff_python_formatter/src/lib.rs -expression: formatted.print()?.as_code() ---- # This is a standalone comment. ( sdfjklsdfsjldkflkjsf, @@ -13,4 +9,4 @@ expression: formatted.print()?.as_code() # This is as well. (this_will_be_wrapped_in_parens,) = struct.unpack(b"12345678901234567890") -(a,) = call() +(a,) = call() \ No newline at end of file diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/whitespace.py.expect b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/whitespace.py.expect new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index 2b416a45cd..b84ad15d9d 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -20,8 +20,6 @@ mod format; mod newlines; mod parentheses; pub mod shared_traits; -#[cfg(test)] -mod test; pub mod trivia; pub fn fmt(contents: &str) -> Result> { @@ -57,70 +55,98 @@ pub fn fmt(contents: &str) -> Result> { #[cfg(test)] mod tests { + use std::fs; use std::path::Path; use anyhow::Result; - use test_case::test_case; use crate::fmt; - use crate::test::test_resource_path; + use ruff_testing_macros::fixture; + use similar::TextDiff; + use std::fmt::{Formatter, Write}; + + #[fixture( + pattern = "resources/test/fixtures/black/**/*.py", + // Excluded tests because they reach unreachable when attaching tokens + exclude = [ + "*comments.py", + "*comments[3,5,8].py", + "*comments_non_breaking_space.py", + "*docstring_preview.py", + "*docstring.py", + "*fmtonoff.py", + "*fmtskip8.py", + ]) + ] + #[test] + fn black_test(input_path: &Path) -> Result<()> { + let content = fs::read_to_string(input_path)?; - #[test_case(Path::new("simple_cases/beginning_backslash.py"); "beginning_backslash")] - #[test_case(Path::new("simple_cases/class_blank_parentheses.py"); "class_blank_parentheses")] - #[test_case(Path::new("simple_cases/class_methods_new_line.py"); "class_methods_new_line")] - #[test_case(Path::new("simple_cases/import_spacing.py"); "import_spacing")] - #[test_case(Path::new("simple_cases/one_element_subscript.py"); "one_element_subscript")] - #[test_case(Path::new("simple_cases/power_op_spacing.py"); "power_op_spacing")] - #[test_case(Path::new("simple_cases/remove_newline_after_code_block_open.py"); "remove_newline_after_code_block_open")] - #[test_case(Path::new("simple_cases/slices.py"); "slices")] - #[test_case(Path::new("simple_cases/tricky_unicode_symbols.py"); "tricky_unicode_symbols")] - // Passing except that `1, 2, 3,` should be `(1, 2, 3)`. - #[test_case(Path::new("simple_cases/tupleassign.py"); "tupleassign")] - // Passing except that `CliRunner().invoke(...)` arguments are improperly wrapped. - #[test_case(Path::new("simple_cases/function2.py"); "function2")] - fn passing(path: &Path) -> Result<()> { - let snapshot = format!("{}", path.display()); - let content = std::fs::read_to_string(test_resource_path( - Path::new("fixtures/black").join(path).as_path(), - ))?; let formatted = fmt(&content)?; - insta::assert_display_snapshot!(snapshot, formatted.print()?.as_code()); - Ok(()) - } - #[test_case(Path::new("simple_cases/collections.py"); "collections")] - #[test_case(Path::new("simple_cases/bracketmatch.py"); "bracketmatch")] - fn passing_modulo_string_normalization(path: &Path) -> Result<()> { - fn adjust_quotes(contents: &str) -> String { - // Replace all single quotes with double quotes. - contents.replace('\'', "\"") + let expected_path = input_path.with_extension("py.expect"); + let expected_output = fs::read_to_string(&expected_path) + .unwrap_or_else(|_| panic!("Expected Black output file '{expected_path:?}' to exist")); + + let printed = formatted.print()?; + let formatted_code = printed.as_code(); + + if formatted_code == expected_output { + // Black and Ruff formatting matches. Delete any existing snapshot files because the Black output + // already perfectly captures the expected output. + // The following code mimics insta's logic generating the snapshot name for a test. + let workspace_path = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let snapshot_name = insta::_function_name!() + .strip_prefix(&format!("{}::", module_path!())) + .unwrap(); + let module_path = module_path!().replace("::", "__"); + + let snapshot_path = Path::new(&workspace_path) + .join("src/snapshots") + .join(&format!( + "{module_path}__{}.snap", + snapshot_name.replace(&['/', '\\'][..], "__") + )); + + if snapshot_path.exists() && snapshot_path.is_file() { + // SAFETY: This is a convenience feature. That's why we don't want to abort + // when deleting a no longer needed snapshot fails. + fs::remove_file(&snapshot_path).ok(); + } + + let new_snapshot_path = snapshot_path.with_extension("snap.new"); + if new_snapshot_path.exists() && new_snapshot_path.is_file() { + // SAFETY: This is a convenience feature. That's why we don't want to abort + // when deleting a no longer needed snapshot fails. + fs::remove_file(&new_snapshot_path).ok(); + } + } else { + // Black and Ruff have different formatting. Write out a snapshot that covers the differences + // today. + let mut snapshot = String::new(); + write!(snapshot, "{}", Header::new("Input"))?; + write!(snapshot, "{}", CodeFrame::new("py", &content))?; + + write!(snapshot, "{}", Header::new("Black Differences"))?; + + let diff = TextDiff::from_lines(expected_output.as_str(), formatted_code) + .unified_diff() + .header("Black", "Ruff") + .to_string(); + + write!(snapshot, "{}", CodeFrame::new("diff", &diff))?; + + write!(snapshot, "{}", Header::new("Ruff Output"))?; + write!(snapshot, "{}", CodeFrame::new("py", formatted_code))?; + + write!(snapshot, "{}", Header::new("Black Output"))?; + write!(snapshot, "{}", CodeFrame::new("py", &expected_output))?; + + insta::with_settings!({ omit_expression => false, input_file => input_path }, { + insta::assert_snapshot!(snapshot); + }); } - let snapshot = format!("{}", path.display()); - let content = std::fs::read_to_string(test_resource_path( - Path::new("fixtures/black").join(path).as_path(), - ))?; - let formatted = fmt(&content)?; - insta::assert_display_snapshot!(snapshot, adjust_quotes(formatted.print()?.as_code())); - Ok(()) - } - - #[ignore] - // Lots of deviations, _mostly_ related to string normalization and wrapping. - #[test_case(Path::new("simple_cases/expression.py"); "expression")] - // Passing apart from a trailing end-of-line comment within an if statement, which is being - // inappropriately associated with the if statement rather than the line it's on. - #[test_case(Path::new("simple_cases/comments.py"); "comments")] - #[test_case(Path::new("simple_cases/function.py"); "function")] - #[test_case(Path::new("simple_cases/function_trailing_comma.py"); "function_trailing_comma")] - #[test_case(Path::new("simple_cases/composition.py"); "composition")] - fn failing(path: &Path) -> Result<()> { - let snapshot = format!("{}", path.display()); - let content = std::fs::read_to_string(test_resource_path( - Path::new("fixtures/black").join(path).as_path(), - ))?; - let formatted = fmt(&content)?; - insta::assert_display_snapshot!(snapshot, formatted.print()?.as_code()); Ok(()) } @@ -150,4 +176,41 @@ mod tests { }"# ); } + + struct Header<'a> { + title: &'a str, + } + + impl<'a> Header<'a> { + fn new(title: &'a str) -> Self { + Self { title } + } + } + + impl std::fmt::Display for Header<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "## {}", self.title)?; + writeln!(f) + } + } + + struct CodeFrame<'a> { + language: &'a str, + code: &'a str, + } + + impl<'a> CodeFrame<'a> { + fn new(language: &'a str, code: &'a str) -> Self { + Self { language, code } + } + } + + impl std::fmt::Display for CodeFrame<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "```{}", self.language)?; + writeln!(f, "{}", self.code)?; + writeln!(f, "```")?; + writeln!(f) + } + } } diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comment_after_escaped_newline.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comment_after_escaped_newline.py.snap.expect deleted file mode 100644 index e2d0719e53..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comment_after_escaped_newline.py.snap.expect +++ /dev/null @@ -1,12 +0,0 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- -def bob(): # pylint: disable=W9016 - pass - - -def bobtwo(): # some comment here - pass - diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip.py.snap.expect deleted file mode 100644 index d618908662..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip.py.snap.expect +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- -a, b = 1, 2 -c = 6 # fmt: skip -d = 5 - diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip4.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip4.py.snap.expect deleted file mode 100644 index 0c81a009b9..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip4.py.snap.expect +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- -a = 2 -# fmt: skip -l = [ - 1, - 2, - 3, -] - diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip6.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip6.py.snap.expect deleted file mode 100644 index 5690143bb8..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtskip6.py.snap.expect +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- -class A: - def f(self): - for line in range(10): - if True: - pass # fmt: skip - diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__whitespace.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__whitespace.py.snap.expect deleted file mode 100644 index 6093b15f4e..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__whitespace.py.snap.expect +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- - - diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__attribute_access_on_number_literals_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__attribute_access_on_number_literals_py.snap new file mode 100644 index 0000000000..699222ef9d --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__attribute_access_on_number_literals_py.snap @@ -0,0 +1,138 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py +--- +## Input + +```py +x = 123456789 .bit_count() +x = (123456).__abs__() +x = .1.is_integer() +x = 1. .imag +x = 1E+1.imag +x = 1E-1.real +x = 123456789.123456789.hex() +x = 123456789.123456789E123456789 .real +x = 123456789E123456789 .conjugate() +x = 123456789J.real +x = 123456789.123456789J.__add__(0b1011.bit_length()) +x = 0XB1ACC.conjugate() +x = 0B1011 .conjugate() +x = 0O777 .real +x = 0.000000006 .hex() +x = -100.0000J + +if 10 .real: + ... + +y = 100[no] +y = 100(no) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,22 +1,22 @@ +-x = (123456789).bit_count() ++x = 123456789.bit_count() + x = (123456).__abs__() +-x = (0.1).is_integer() +-x = (1.0).imag +-x = (1e1).imag +-x = (1e-1).real +-x = (123456789.123456789).hex() +-x = (123456789.123456789e123456789).real +-x = (123456789e123456789).conjugate() +-x = 123456789j.real +-x = 123456789.123456789j.__add__(0b1011.bit_length()) +-x = 0xB1ACC.conjugate() +-x = 0b1011.conjugate() +-x = 0o777.real +-x = (0.000000006).hex() +-x = -100.0000j ++x = .1.is_integer() ++x = 1..imag ++x = 1E+1.imag ++x = 1E-1.real ++x = 123456789.123456789.hex() ++x = 123456789.123456789E123456789.real ++x = 123456789E123456789.conjugate() ++x = 123456789J.real ++x = 123456789.123456789J.__add__(0b1011.bit_length()) ++x = 0XB1ACC.conjugate() ++x = 0B1011.conjugate() ++x = 0O777.real ++x = 0.000000006.hex() ++x = -100.0000J + +-if (10).real: ++if 10.real: + ... + + y = 100[no] +-y = 100(no) +\ No newline at end of file ++y = 100((no)) +\ No newline at end of file + +``` + +## Ruff Output + +```py +x = 123456789.bit_count() +x = (123456).__abs__() +x = .1.is_integer() +x = 1..imag +x = 1E+1.imag +x = 1E-1.real +x = 123456789.123456789.hex() +x = 123456789.123456789E123456789.real +x = 123456789E123456789.conjugate() +x = 123456789J.real +x = 123456789.123456789J.__add__(0b1011.bit_length()) +x = 0XB1ACC.conjugate() +x = 0B1011.conjugate() +x = 0O777.real +x = 0.000000006.hex() +x = -100.0000J + +if 10.real: + ... + +y = 100[no] +y = 100((no)) +``` + +## Black Output + +```py +x = (123456789).bit_count() +x = (123456).__abs__() +x = (0.1).is_integer() +x = (1.0).imag +x = (1e1).imag +x = (1e-1).real +x = (123456789.123456789).hex() +x = (123456789.123456789e123456789).real +x = (123456789e123456789).conjugate() +x = 123456789j.real +x = 123456789.123456789j.__add__(0b1011.bit_length()) +x = 0xB1ACC.conjugate() +x = 0b1011.conjugate() +x = 0o777.real +x = (0.000000006).hex() +x = -100.0000j + +if (10).real: + ... + +y = 100[no] +y = 100(no) +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__bracketmatch_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__bracketmatch_py.snap new file mode 100644 index 0000000000..8e94a9725a --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__bracketmatch_py.snap @@ -0,0 +1,53 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/bracketmatch.py +--- +## Input + +```py +for ((x in {}) or {})['a'] in x: + pass +pem_spam = lambda l, spam = { + "x": 3 +}: not spam.get(l.strip()) +lambda x=lambda y={1: 3}: y['x':lambda y: {1: 2}]: x + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,4 +1,4 @@ +-for ((x in {}) or {})["a"] in x: ++for ((x in {}) or {})['a'] in x: + pass + pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip()) +-lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x +\ No newline at end of file ++lambda x=lambda y={1: 3}: y['x' : lambda y: {1: 2}]: x +\ No newline at end of file + +``` + +## Ruff Output + +```py +for ((x in {}) or {})['a'] in x: + pass +pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip()) +lambda x=lambda y={1: 3}: y['x' : lambda y: {1: 2}]: x +``` + +## Black Output + +```py +for ((x in {}) or {})["a"] in x: + pass +pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip()) +lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__class_blank_parentheses_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__class_blank_parentheses_py.snap new file mode 100644 index 0000000000..9891c18a81 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__class_blank_parentheses_py.snap @@ -0,0 +1,121 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/class_blank_parentheses.py +--- +## Input + +```py +class SimpleClassWithBlankParentheses(): + pass +class ClassWithSpaceParentheses ( ): + first_test_data = 90 + second_test_data = 100 + def test_func(self): + return None +class ClassWithEmptyFunc(object): + + def func_with_blank_parentheses(): + return 5 + + +def public_func_with_blank_parentheses(): + return None +def class_under_the_func_with_blank_parentheses(): + class InsideFunc(): + pass +class NormalClass ( +): + def func_for_testing(self, first, second): + sum = first + second + return sum + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -27,4 +27,4 @@ + class NormalClass: + def func_for_testing(self, first, second): + sum = first + second +- return sum +\ No newline at end of file ++ return sum + +``` + +## Ruff Output + +```py +class SimpleClassWithBlankParentheses: + pass + + +class ClassWithSpaceParentheses: + first_test_data = 90 + second_test_data = 100 + + def test_func(self): + return None + + +class ClassWithEmptyFunc(object): + def func_with_blank_parentheses(): + return 5 + + +def public_func_with_blank_parentheses(): + return None + + +def class_under_the_func_with_blank_parentheses(): + class InsideFunc: + pass + + +class NormalClass: + def func_for_testing(self, first, second): + sum = first + second + return sum + +``` + +## Black Output + +```py +class SimpleClassWithBlankParentheses: + pass + + +class ClassWithSpaceParentheses: + first_test_data = 90 + second_test_data = 100 + + def test_func(self): + return None + + +class ClassWithEmptyFunc(object): + def func_with_blank_parentheses(): + return 5 + + +def public_func_with_blank_parentheses(): + return None + + +def class_under_the_func_with_blank_parentheses(): + class InsideFunc: + pass + + +class NormalClass: + def func_for_testing(self, first, second): + sum = first + second + return sum +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__class_methods_new_line_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__class_methods_new_line_py.snap new file mode 100644 index 0000000000..74c6800373 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__class_methods_new_line_py.snap @@ -0,0 +1,468 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/class_methods_new_line.py +--- +## Input + +```py +class ClassSimplest: + pass +class ClassWithSingleField: + a = 1 +class ClassWithJustTheDocstring: + """Just a docstring.""" +class ClassWithInit: + def __init__(self): + pass +class ClassWithTheDocstringAndInit: + """Just a docstring.""" + def __init__(self): + pass +class ClassWithInitAndVars: + cls_var = 100 + def __init__(self): + pass +class ClassWithInitAndVarsAndDocstring: + """Test class""" + cls_var = 100 + def __init__(self): + pass +class ClassWithDecoInit: + @deco + def __init__(self): + pass +class ClassWithDecoInitAndVars: + cls_var = 100 + @deco + def __init__(self): + pass +class ClassWithDecoInitAndVarsAndDocstring: + """Test class""" + cls_var = 100 + @deco + def __init__(self): + pass +class ClassSimplestWithInner: + class Inner: + pass +class ClassSimplestWithInnerWithDocstring: + class Inner: + """Just a docstring.""" + def __init__(self): + pass +class ClassWithSingleFieldWithInner: + a = 1 + class Inner: + pass +class ClassWithJustTheDocstringWithInner: + """Just a docstring.""" + class Inner: + pass +class ClassWithInitWithInner: + class Inner: + pass + def __init__(self): + pass +class ClassWithInitAndVarsWithInner: + cls_var = 100 + class Inner: + pass + def __init__(self): + pass +class ClassWithInitAndVarsAndDocstringWithInner: + """Test class""" + cls_var = 100 + class Inner: + pass + def __init__(self): + pass +class ClassWithDecoInitWithInner: + class Inner: + pass + @deco + def __init__(self): + pass +class ClassWithDecoInitAndVarsWithInner: + cls_var = 100 + class Inner: + pass + @deco + def __init__(self): + pass +class ClassWithDecoInitAndVarsAndDocstringWithInner: + """Test class""" + cls_var = 100 + class Inner: + pass + @deco + def __init__(self): + pass +class ClassWithDecoInitAndVarsAndDocstringWithInner2: + """Test class""" + class Inner: + pass + cls_var = 100 + @deco + def __init__(self): + pass + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -162,4 +162,4 @@ + + @deco + def __init__(self): +- pass +\ No newline at end of file ++ pass + +``` + +## Ruff Output + +```py +class ClassSimplest: + pass + + +class ClassWithSingleField: + a = 1 + + +class ClassWithJustTheDocstring: + """Just a docstring.""" + + +class ClassWithInit: + def __init__(self): + pass + + +class ClassWithTheDocstringAndInit: + """Just a docstring.""" + + def __init__(self): + pass + + +class ClassWithInitAndVars: + cls_var = 100 + + def __init__(self): + pass + + +class ClassWithInitAndVarsAndDocstring: + """Test class""" + + cls_var = 100 + + def __init__(self): + pass + + +class ClassWithDecoInit: + @deco + def __init__(self): + pass + + +class ClassWithDecoInitAndVars: + cls_var = 100 + + @deco + def __init__(self): + pass + + +class ClassWithDecoInitAndVarsAndDocstring: + """Test class""" + + cls_var = 100 + + @deco + def __init__(self): + pass + + +class ClassSimplestWithInner: + class Inner: + pass + + +class ClassSimplestWithInnerWithDocstring: + class Inner: + """Just a docstring.""" + + def __init__(self): + pass + + +class ClassWithSingleFieldWithInner: + a = 1 + + class Inner: + pass + + +class ClassWithJustTheDocstringWithInner: + """Just a docstring.""" + + class Inner: + pass + + +class ClassWithInitWithInner: + class Inner: + pass + + def __init__(self): + pass + + +class ClassWithInitAndVarsWithInner: + cls_var = 100 + + class Inner: + pass + + def __init__(self): + pass + + +class ClassWithInitAndVarsAndDocstringWithInner: + """Test class""" + + cls_var = 100 + + class Inner: + pass + + def __init__(self): + pass + + +class ClassWithDecoInitWithInner: + class Inner: + pass + + @deco + def __init__(self): + pass + + +class ClassWithDecoInitAndVarsWithInner: + cls_var = 100 + + class Inner: + pass + + @deco + def __init__(self): + pass + + +class ClassWithDecoInitAndVarsAndDocstringWithInner: + """Test class""" + + cls_var = 100 + + class Inner: + pass + + @deco + def __init__(self): + pass + + +class ClassWithDecoInitAndVarsAndDocstringWithInner2: + """Test class""" + + class Inner: + pass + + cls_var = 100 + + @deco + def __init__(self): + pass + +``` + +## Black Output + +```py +class ClassSimplest: + pass + + +class ClassWithSingleField: + a = 1 + + +class ClassWithJustTheDocstring: + """Just a docstring.""" + + +class ClassWithInit: + def __init__(self): + pass + + +class ClassWithTheDocstringAndInit: + """Just a docstring.""" + + def __init__(self): + pass + + +class ClassWithInitAndVars: + cls_var = 100 + + def __init__(self): + pass + + +class ClassWithInitAndVarsAndDocstring: + """Test class""" + + cls_var = 100 + + def __init__(self): + pass + + +class ClassWithDecoInit: + @deco + def __init__(self): + pass + + +class ClassWithDecoInitAndVars: + cls_var = 100 + + @deco + def __init__(self): + pass + + +class ClassWithDecoInitAndVarsAndDocstring: + """Test class""" + + cls_var = 100 + + @deco + def __init__(self): + pass + + +class ClassSimplestWithInner: + class Inner: + pass + + +class ClassSimplestWithInnerWithDocstring: + class Inner: + """Just a docstring.""" + + def __init__(self): + pass + + +class ClassWithSingleFieldWithInner: + a = 1 + + class Inner: + pass + + +class ClassWithJustTheDocstringWithInner: + """Just a docstring.""" + + class Inner: + pass + + +class ClassWithInitWithInner: + class Inner: + pass + + def __init__(self): + pass + + +class ClassWithInitAndVarsWithInner: + cls_var = 100 + + class Inner: + pass + + def __init__(self): + pass + + +class ClassWithInitAndVarsAndDocstringWithInner: + """Test class""" + + cls_var = 100 + + class Inner: + pass + + def __init__(self): + pass + + +class ClassWithDecoInitWithInner: + class Inner: + pass + + @deco + def __init__(self): + pass + + +class ClassWithDecoInitAndVarsWithInner: + cls_var = 100 + + class Inner: + pass + + @deco + def __init__(self): + pass + + +class ClassWithDecoInitAndVarsAndDocstringWithInner: + """Test class""" + + cls_var = 100 + + class Inner: + pass + + @deco + def __init__(self): + pass + + +class ClassWithDecoInitAndVarsAndDocstringWithInner2: + """Test class""" + + class Inner: + pass + + cls_var = 100 + + @deco + def __init__(self): + pass +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap new file mode 100644 index 0000000000..6039fa6b91 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap @@ -0,0 +1,329 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/collections.py +--- +## Input + +```py +import core, time, a + +from . import A, B, C + +# keeps existing trailing comma +from foo import ( + bar, +) + +# also keeps existing structure +from foo import ( + baz, + qux, +) + +# `as` works as well +from foo import ( + xyzzy as magic, +) + +a = {1,2,3,} +b = { +1,2, + 3} +c = { + 1, + 2, + 3, +} +x = 1, +y = narf(), +nested = {(1,2,3),(4,5,6),} +nested_no_trailing_comma = {(1,2,3),(4,5,6)} +nested_long_lines = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccc", (1, 2, 3), "dddddddddddddddddddddddddddddddddddddddd"] +{"oneple": (1,),} +{"oneple": (1,)} +['ls', 'lsoneple/%s' % (foo,)] +x = {"oneple": (1,)} +y = {"oneple": (1,),} +assert False, ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s" % bar) + +# looping over a 1-tuple should also not get wrapped +for x in (1,): + pass +for (x,) in (1,), (2,), (3,): + pass + +[1, 2, 3,] + +division_result_tuple = (6/2,) +print("foo %r", (foo.bar,)) + +if True: + IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = ( + Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING + | {pylons.controllers.WSGIController} + ) + +if True: + ec2client.get_waiter('instance_stopped').wait( + InstanceIds=[instance.id], + WaiterConfig={ + 'Delay': 5, + }) + ec2client.get_waiter("instance_stopped").wait( + InstanceIds=[instance.id], + WaiterConfig={"Delay": 5,}, + ) + ec2client.get_waiter("instance_stopped").wait( + InstanceIds=[instance.id], WaiterConfig={"Delay": 5,}, + ) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -47,7 +47,7 @@ + "oneple": (1,), + } + {"oneple": (1,)} +-["ls", "lsoneple/%s" % (foo,)] ++['ls', 'lsoneple/%s' % (foo,)] + x = {"oneple": (1,)} + y = { + "oneple": (1,), +@@ -79,10 +79,10 @@ + ) + + if True: +- ec2client.get_waiter("instance_stopped").wait( ++ ec2client.get_waiter('instance_stopped').wait( + InstanceIds=[instance.id], + WaiterConfig={ +- "Delay": 5, ++ 'Delay': 5, + }, + ) + ec2client.get_waiter("instance_stopped").wait( +@@ -96,4 +96,4 @@ + WaiterConfig={ + "Delay": 5, + }, +- ) +\ No newline at end of file ++ ) + +``` + +## Ruff Output + +```py +import core, time, a + +from . import A, B, C + +# keeps existing trailing comma +from foo import ( + bar, +) + +# also keeps existing structure +from foo import ( + baz, + qux, +) + +# `as` works as well +from foo import ( + xyzzy as magic, +) + +a = { + 1, + 2, + 3, +} +b = {1, 2, 3} +c = { + 1, + 2, + 3, +} +x = (1,) +y = (narf(),) +nested = { + (1, 2, 3), + (4, 5, 6), +} +nested_no_trailing_comma = {(1, 2, 3), (4, 5, 6)} +nested_long_lines = [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "cccccccccccccccccccccccccccccccccccccccc", + (1, 2, 3), + "dddddddddddddddddddddddddddddddddddddddd", +] +{ + "oneple": (1,), +} +{"oneple": (1,)} +['ls', 'lsoneple/%s' % (foo,)] +x = {"oneple": (1,)} +y = { + "oneple": (1,), +} +assert False, ( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s" + % bar +) + +# looping over a 1-tuple should also not get wrapped +for x in (1,): + pass +for (x,) in (1,), (2,), (3,): + pass + +[ + 1, + 2, + 3, +] + +division_result_tuple = (6 / 2,) +print("foo %r", (foo.bar,)) + +if True: + IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = ( + Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING + | {pylons.controllers.WSGIController} + ) + +if True: + ec2client.get_waiter('instance_stopped').wait( + InstanceIds=[instance.id], + WaiterConfig={ + 'Delay': 5, + }, + ) + ec2client.get_waiter("instance_stopped").wait( + InstanceIds=[instance.id], + WaiterConfig={ + "Delay": 5, + }, + ) + ec2client.get_waiter("instance_stopped").wait( + InstanceIds=[instance.id], + WaiterConfig={ + "Delay": 5, + }, + ) + +``` + +## Black Output + +```py +import core, time, a + +from . import A, B, C + +# keeps existing trailing comma +from foo import ( + bar, +) + +# also keeps existing structure +from foo import ( + baz, + qux, +) + +# `as` works as well +from foo import ( + xyzzy as magic, +) + +a = { + 1, + 2, + 3, +} +b = {1, 2, 3} +c = { + 1, + 2, + 3, +} +x = (1,) +y = (narf(),) +nested = { + (1, 2, 3), + (4, 5, 6), +} +nested_no_trailing_comma = {(1, 2, 3), (4, 5, 6)} +nested_long_lines = [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "cccccccccccccccccccccccccccccccccccccccc", + (1, 2, 3), + "dddddddddddddddddddddddddddddddddddddddd", +] +{ + "oneple": (1,), +} +{"oneple": (1,)} +["ls", "lsoneple/%s" % (foo,)] +x = {"oneple": (1,)} +y = { + "oneple": (1,), +} +assert False, ( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s" + % bar +) + +# looping over a 1-tuple should also not get wrapped +for x in (1,): + pass +for (x,) in (1,), (2,), (3,): + pass + +[ + 1, + 2, + 3, +] + +division_result_tuple = (6 / 2,) +print("foo %r", (foo.bar,)) + +if True: + IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING = ( + Config.IGNORED_TYPES_FOR_ATTRIBUTE_CHECKING + | {pylons.controllers.WSGIController} + ) + +if True: + ec2client.get_waiter("instance_stopped").wait( + InstanceIds=[instance.id], + WaiterConfig={ + "Delay": 5, + }, + ) + ec2client.get_waiter("instance_stopped").wait( + InstanceIds=[instance.id], + WaiterConfig={ + "Delay": 5, + }, + ) + ec2client.get_waiter("instance_stopped").wait( + InstanceIds=[instance.id], + WaiterConfig={ + "Delay": 5, + }, + ) +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comment_after_escaped_newline_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comment_after_escaped_newline_py.snap new file mode 100644 index 0000000000..5971e9cdb4 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comment_after_escaped_newline_py.snap @@ -0,0 +1,67 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comment_after_escaped_newline.py +--- +## Input + +```py +def bob(): \ + # pylint: disable=W9016 + pass + + +def bobtwo(): \ + \ + # some comment here + pass + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,6 +1,8 @@ +-def bob(): # pylint: disable=W9016 ++def bob(): ++ # pylint: disable=W9016 + pass + + +-def bobtwo(): # some comment here +- pass +\ No newline at end of file ++def bobtwo(): ++ # some comment here ++ pass + +``` + +## Ruff Output + +```py +def bob(): + # pylint: disable=W9016 + pass + + +def bobtwo(): + # some comment here + pass + +``` + +## Black Output + +```py +def bob(): # pylint: disable=W9016 + pass + + +def bobtwo(): # some comment here + pass +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap new file mode 100644 index 0000000000..66361d7926 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap @@ -0,0 +1,734 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments2.py +--- +## Input + +```py +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent # NOT DRY +) +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent as component # DRY +) + +# Please keep __all__ alphabetized within each category. + +__all__ = [ + # Super-special typing primitives. + 'Any', + 'Callable', + 'ClassVar', + + # ABCs (from collections.abc). + 'AbstractSet', # collections.abc.Set. + 'ByteString', + 'Container', + + # Concrete collection types. + 'Counter', + 'Deque', + 'Dict', + 'DefaultDict', + 'List', + 'Set', + 'FrozenSet', + 'NamedTuple', # Not really a type. + 'Generator', +] + +not_shareables = [ + # singletons + True, + False, + NotImplemented, ..., + # builtin types and objects + type, + object, + object(), + Exception(), + 42, + 100.0, + "spam", + # user-defined types and objects + Cheese, + Cheese("Wensleydale"), + SubBytes(b"spam"), +] + +if 'PYTHON' in os.environ: + add_compiler(compiler_from_env()) +else: + # for compiler in compilers.values(): + # add_compiler(compiler) + add_compiler(compilers[(7.0, 32)]) + # add_compiler(compilers[(7.1, 64)]) + +# Comment before function. +def inline_comments_in_brackets_ruin_everything(): + if typedargslist: + parameters.children = [ + children[0], # (1 + body, + children[-1] # )1 + ] + parameters.children = [ + children[0], + body, + children[-1], # type: ignore + ] + else: + parameters.children = [ + parameters.children[0], # (2 what if this was actually long + body, + parameters.children[-1], # )2 + ] + parameters.children = [parameters.what_if_this_was_actually_long.children[0], body, parameters.children[-1]] # type: ignore + if (self._proc is not None + # has the child process finished? + and self._returncode is None + # the child process has finished, but the + # transport hasn't been notified yet? + and self._proc.poll() is None): + pass + # no newline before or after + short = [ + # one + 1, + # two + 2] + + # no newline after + call(arg1, arg2, """ +short +""", arg3=True) + + ############################################################################ + + call2( + #short + arg1, + #but + arg2, + #multiline + """ +short +""", + # yup + arg3=True) + lcomp = [ + element # yup + for element in collection # yup + if element is not None # right + ] + lcomp2 = [ + # hello + element + # yup + for element in collection + # right + if element is not None + ] + lcomp3 = [ + # This one is actually too long to fit in a single line. + element.split('\n', 1)[0] + # yup + for element in collection.select_elements() + # right + if element is not None + ] + while True: + if False: + continue + + # and round and round we go + # and round and round we go + + # let's return + return Node( + syms.simple_stmt, + [ + Node(statement, result), + Leaf(token.NEWLINE, '\n') # FIXME: \r\n? + ], + ) + +CONFIG_FILES = [CONFIG_FILE, ] + SHARED_CONFIG_FILES + USER_CONFIG_FILES # type: Final + +class Test: + def _init_host(self, parsed) -> None: + if (parsed.hostname is None or # type: ignore + not parsed.hostname.strip()): + pass + +####################### +### SECTION COMMENT ### +####################### + + +instruction()#comment with bad spacing + +# END COMMENTS +# MORE END COMMENTS + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,31 +1,31 @@ + from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( +- MyLovelyCompanyTeamProjectComponent, # NOT DRY ++ MyLovelyCompanyTeamProjectComponent, + ) + from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( +- MyLovelyCompanyTeamProjectComponent as component, # DRY ++ MyLovelyCompanyTeamProjectComponent as component, + ) + + # Please keep __all__ alphabetized within each category. + + __all__ = [ + # Super-special typing primitives. +- "Any", +- "Callable", +- "ClassVar", ++ 'Any', ++ 'Callable', ++ 'ClassVar', + # ABCs (from collections.abc). +- "AbstractSet", # collections.abc.Set. +- "ByteString", +- "Container", ++ 'AbstractSet', ++ 'ByteString', ++ 'Container', + # Concrete collection types. +- "Counter", +- "Deque", +- "Dict", +- "DefaultDict", +- "List", +- "Set", +- "FrozenSet", +- "NamedTuple", # Not really a type. +- "Generator", ++ 'Counter', ++ 'Deque', ++ 'Dict', ++ 'DefaultDict', ++ 'List', ++ 'Set', ++ 'FrozenSet', ++ 'NamedTuple', ++ 'Generator', + ] + + not_shareables = [ +@@ -48,38 +48,45 @@ + SubBytes(b"spam"), + ] + +-if "PYTHON" in os.environ: ++if 'PYTHON' in os.environ: + add_compiler(compiler_from_env()) + else: + # for compiler in compilers.values(): + # add_compiler(compiler) + add_compiler(compilers[(7.0, 32)]) +- # add_compiler(compilers[(7.1, 64)]) + + ++# add_compiler(compilers[(7.1, 64)]) ++ + # Comment before function. + def inline_comments_in_brackets_ruin_everything(): + if typedargslist: +- parameters.children = [children[0], body, children[-1]] # (1 # )1 ++ parameters.children = [children[0], body, children[-1]] + parameters.children = [ + children[0], + body, +- children[-1], # type: ignore ++ children[-1], + ] + else: + parameters.children = [ +- parameters.children[0], # (2 what if this was actually long ++ parameters.children[0], + body, +- parameters.children[-1], # )2 ++ parameters.children[-1], + ] +- parameters.children = [parameters.what_if_this_was_actually_long.children[0], body, parameters.children[-1]] # type: ignore ++ parameters.children = [ ++ parameters.what_if_this_was_actually_long.children[0], ++ body, ++ parameters.children[-1], ++ ] + if ( + self._proc is not None +- # has the child process finished? +- and self._returncode is None +- # the child process has finished, but the ++ and # has the child process finished? ++ self._returncode ++ is None ++ and # the child process has finished, but the + # transport hasn't been notified yet? +- and self._proc.poll() is None ++ self._proc.poll() ++ is None + ): + pass + # no newline before or after +@@ -103,47 +110,47 @@ + ############################################################################ + + call2( +- # short ++ #short + arg1, +- # but ++ #but + arg2, +- # multiline ++ #multiline + """ + short + """, +- # yup +- arg3=True, ++ arg3=# yup ++ True, + ) +- lcomp = [ +- element for element in collection if element is not None # yup # yup # right +- ] ++ lcomp = [element for element in collection if element is not None] # yup # yup # right + lcomp2 = [ + # hello + element +- # yup +- for element in collection +- # right +- if element is not None ++ for # yup ++ element in collection ++ if # right ++ element ++ is not None + ] + lcomp3 = [ + # This one is actually too long to fit in a single line. +- element.split("\n", 1)[0] +- # yup +- for element in collection.select_elements() +- # right +- if element is not None ++ element.split('\n', 1)[0] ++ for # yup ++ element in collection.select_elements() ++ if # right ++ element ++ is not None + ] + while True: + if False: + continue + +- # and round and round we go +- # and round and round we go ++ # and round and round we go ++ # and round and round we go + + # let's return + return Node( + syms.simple_stmt, +- [Node(statement, result), Leaf(token.NEWLINE, "\n")], # FIXME: \r\n? ++ [Node(statement, result), Leaf(token.NEWLINE, '\n')], # FIXME: \r\n? + ) + + +@@ -167,7 +174,7 @@ + ####################### + + +-instruction() # comment with bad spacing ++instruction() #comment with bad spacing + + # END COMMENTS +-# MORE END COMMENTS +\ No newline at end of file ++# MORE END COMMENTS + +``` + +## Ruff Output + +```py +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent, +) +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent as component, +) + +# Please keep __all__ alphabetized within each category. + +__all__ = [ + # Super-special typing primitives. + 'Any', + 'Callable', + 'ClassVar', + # ABCs (from collections.abc). + 'AbstractSet', + 'ByteString', + 'Container', + # Concrete collection types. + 'Counter', + 'Deque', + 'Dict', + 'DefaultDict', + 'List', + 'Set', + 'FrozenSet', + 'NamedTuple', + 'Generator', +] + +not_shareables = [ + # singletons + True, + False, + NotImplemented, + ..., + # builtin types and objects + type, + object, + object(), + Exception(), + 42, + 100.0, + "spam", + # user-defined types and objects + Cheese, + Cheese("Wensleydale"), + SubBytes(b"spam"), +] + +if 'PYTHON' in os.environ: + add_compiler(compiler_from_env()) +else: + # for compiler in compilers.values(): + # add_compiler(compiler) + add_compiler(compilers[(7.0, 32)]) + + +# add_compiler(compilers[(7.1, 64)]) + +# Comment before function. +def inline_comments_in_brackets_ruin_everything(): + if typedargslist: + parameters.children = [children[0], body, children[-1]] + parameters.children = [ + children[0], + body, + children[-1], + ] + else: + parameters.children = [ + parameters.children[0], + body, + parameters.children[-1], + ] + parameters.children = [ + parameters.what_if_this_was_actually_long.children[0], + body, + parameters.children[-1], + ] + if ( + self._proc is not None + and # has the child process finished? + self._returncode + is None + and # the child process has finished, but the + # transport hasn't been notified yet? + self._proc.poll() + is None + ): + pass + # no newline before or after + short = [ + # one + 1, + # two + 2, + ] + + # no newline after + call( + arg1, + arg2, + """ +short +""", + arg3=True, + ) + + ############################################################################ + + call2( + #short + arg1, + #but + arg2, + #multiline + """ +short +""", + arg3=# yup + True, + ) + lcomp = [element for element in collection if element is not None] # yup # yup # right + lcomp2 = [ + # hello + element + for # yup + element in collection + if # right + element + is not None + ] + lcomp3 = [ + # This one is actually too long to fit in a single line. + element.split('\n', 1)[0] + for # yup + element in collection.select_elements() + if # right + element + is not None + ] + while True: + if False: + continue + + # and round and round we go + # and round and round we go + + # let's return + return Node( + syms.simple_stmt, + [Node(statement, result), Leaf(token.NEWLINE, '\n')], # FIXME: \r\n? + ) + + +CONFIG_FILES = ( + [ + CONFIG_FILE, + ] + + SHARED_CONFIG_FILES + + USER_CONFIG_FILES +) # type: Final + + +class Test: + def _init_host(self, parsed) -> None: + if parsed.hostname is None or not parsed.hostname.strip(): # type: ignore + pass + + +####################### +### SECTION COMMENT ### +####################### + + +instruction() #comment with bad spacing + +# END COMMENTS +# MORE END COMMENTS + +``` + +## Black Output + +```py +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent, # NOT DRY +) +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent as component, # DRY +) + +# Please keep __all__ alphabetized within each category. + +__all__ = [ + # Super-special typing primitives. + "Any", + "Callable", + "ClassVar", + # ABCs (from collections.abc). + "AbstractSet", # collections.abc.Set. + "ByteString", + "Container", + # Concrete collection types. + "Counter", + "Deque", + "Dict", + "DefaultDict", + "List", + "Set", + "FrozenSet", + "NamedTuple", # Not really a type. + "Generator", +] + +not_shareables = [ + # singletons + True, + False, + NotImplemented, + ..., + # builtin types and objects + type, + object, + object(), + Exception(), + 42, + 100.0, + "spam", + # user-defined types and objects + Cheese, + Cheese("Wensleydale"), + SubBytes(b"spam"), +] + +if "PYTHON" in os.environ: + add_compiler(compiler_from_env()) +else: + # for compiler in compilers.values(): + # add_compiler(compiler) + add_compiler(compilers[(7.0, 32)]) + # add_compiler(compilers[(7.1, 64)]) + + +# Comment before function. +def inline_comments_in_brackets_ruin_everything(): + if typedargslist: + parameters.children = [children[0], body, children[-1]] # (1 # )1 + parameters.children = [ + children[0], + body, + children[-1], # type: ignore + ] + else: + parameters.children = [ + parameters.children[0], # (2 what if this was actually long + body, + parameters.children[-1], # )2 + ] + parameters.children = [parameters.what_if_this_was_actually_long.children[0], body, parameters.children[-1]] # type: ignore + if ( + self._proc is not None + # has the child process finished? + and self._returncode is None + # the child process has finished, but the + # transport hasn't been notified yet? + and self._proc.poll() is None + ): + pass + # no newline before or after + short = [ + # one + 1, + # two + 2, + ] + + # no newline after + call( + arg1, + arg2, + """ +short +""", + arg3=True, + ) + + ############################################################################ + + call2( + # short + arg1, + # but + arg2, + # multiline + """ +short +""", + # yup + arg3=True, + ) + lcomp = [ + element for element in collection if element is not None # yup # yup # right + ] + lcomp2 = [ + # hello + element + # yup + for element in collection + # right + if element is not None + ] + lcomp3 = [ + # This one is actually too long to fit in a single line. + element.split("\n", 1)[0] + # yup + for element in collection.select_elements() + # right + if element is not None + ] + while True: + if False: + continue + + # and round and round we go + # and round and round we go + + # let's return + return Node( + syms.simple_stmt, + [Node(statement, result), Leaf(token.NEWLINE, "\n")], # FIXME: \r\n? + ) + + +CONFIG_FILES = ( + [ + CONFIG_FILE, + ] + + SHARED_CONFIG_FILES + + USER_CONFIG_FILES +) # type: Final + + +class Test: + def _init_host(self, parsed) -> None: + if parsed.hostname is None or not parsed.hostname.strip(): # type: ignore + pass + + +####################### +### SECTION COMMENT ### +####################### + + +instruction() # comment with bad spacing + +# END COMMENTS +# MORE END COMMENTS +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments4_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments4_py.snap new file mode 100644 index 0000000000..04ec4febde --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments4_py.snap @@ -0,0 +1,450 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments4.py +--- +## Input + +```py +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent, # NOT DRY +) +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent as component, # DRY +) + + +class C: + @pytest.mark.parametrize( + ("post_data", "message"), + [ + # metadata_version errors. + ( + {}, + "None is an invalid value for Metadata-Version. Error: This field is" + " required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( + {"metadata_version": "-1"}, + "'-1' is an invalid value for Metadata-Version. Error: Unknown Metadata" + " Version see" + " https://packaging.python.org/specifications/core-metadata", + ), + # name errors. + ( + {"metadata_version": "1.2"}, + "'' is an invalid value for Name. Error: This field is required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( + {"metadata_version": "1.2", "name": "foo-"}, + "'foo-' is an invalid value for Name. Error: Must start and end with a" + " letter or numeral and contain only ascii numeric and '.', '_' and" + " '-'. see https://packaging.python.org/specifications/core-metadata", + ), + # version errors. + ( + {"metadata_version": "1.2", "name": "example"}, + "'' is an invalid value for Version. Error: This field is required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( + {"metadata_version": "1.2", "name": "example", "version": "dog"}, + "'dog' is an invalid value for Version. Error: Must start and end with" + " a letter or numeral and contain only ascii numeric and '.', '_' and" + " '-'. see https://packaging.python.org/specifications/core-metadata", + ), + ], + ) + def test_fails_invalid_post_data( + self, pyramid_config, db_request, post_data, message + ): + pyramid_config.testing_securitypolicy(userid=1) + db_request.POST = MultiDict(post_data) + + +def foo(list_a, list_b): + results = ( + User.query.filter(User.foo == "bar") + .filter( # Because foo. + db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) + ) + .filter(User.xyz.is_(None)) + # Another comment about the filtering on is_quux goes here. + .filter(db.not_(User.is_pending.astext.cast(db.Boolean).is_(True))) + .order_by(User.created_at.desc()) + .with_for_update(key_share=True) + .all() + ) + return results + + +def foo2(list_a, list_b): + # Standalone comment reasonably placed. + return ( + User.query.filter(User.foo == "bar") + .filter( + db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) + ) + .filter(User.xyz.is_(None)) + ) + + +def foo3(list_a, list_b): + return ( + # Standlone comment but weirdly placed. + User.query.filter(User.foo == "bar") + .filter( + db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) + ) + .filter(User.xyz.is_(None)) + ) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,8 +1,8 @@ + from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( +- MyLovelyCompanyTeamProjectComponent, # NOT DRY ++ MyLovelyCompanyTeamProjectComponent, + ) + from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( +- MyLovelyCompanyTeamProjectComponent as component, # DRY ++ MyLovelyCompanyTeamProjectComponent as component, + ) + + +@@ -10,39 +10,50 @@ + @pytest.mark.parametrize( + ("post_data", "message"), + [ +- # metadata_version errors. +- ( ++ (# metadata_version errors. + {}, + "None is an invalid value for Metadata-Version. Error: This field is" + " required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( +- {"metadata_version": "-1"}, ++ { ++ "metadata_version": "-1", ++ }, + "'-1' is an invalid value for Metadata-Version. Error: Unknown Metadata" + " Version see" + " https://packaging.python.org/specifications/core-metadata", + ), +- # name errors. +- ( +- {"metadata_version": "1.2"}, ++ (# name errors. ++ { ++ "metadata_version": "1.2", ++ }, + "'' is an invalid value for Name. Error: This field is required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( +- {"metadata_version": "1.2", "name": "foo-"}, ++ { ++ "metadata_version": "1.2", ++ "name": "foo-", ++ }, + "'foo-' is an invalid value for Name. Error: Must start and end with a" + " letter or numeral and contain only ascii numeric and '.', '_' and" + " '-'. see https://packaging.python.org/specifications/core-metadata", + ), +- # version errors. +- ( +- {"metadata_version": "1.2", "name": "example"}, ++ (# version errors. ++ { ++ "metadata_version": "1.2", ++ "name": "example", ++ }, + "'' is an invalid value for Version. Error: This field is required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( +- {"metadata_version": "1.2", "name": "example", "version": "dog"}, ++ { ++ "metadata_version": "1.2", ++ "name": "example", ++ "version": "dog", ++ }, + "'dog' is an invalid value for Version. Error: Must start and end with" + " a letter or numeral and contain only ascii numeric and '.', '_' and" + " '-'. see https://packaging.python.org/specifications/core-metadata", +@@ -50,45 +61,34 @@ + ], + ) + def test_fails_invalid_post_data( +- self, pyramid_config, db_request, post_data, message ++ self, ++ pyramid_config, ++ db_request, ++ post_data, ++ message, + ): + pyramid_config.testing_securitypolicy(userid=1) + db_request.POST = MultiDict(post_data) + + + def foo(list_a, list_b): +- results = ( +- User.query.filter(User.foo == "bar") +- .filter( # Because foo. +- db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) +- ) +- .filter(User.xyz.is_(None)) +- # Another comment about the filtering on is_quux goes here. +- .filter(db.not_(User.is_pending.astext.cast(db.Boolean).is_(True))) +- .order_by(User.created_at.desc()) +- .with_for_update(key_share=True) +- .all() +- ) ++ results = User.query.filter(User.foo == "bar").filter( # Because foo. ++ db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) ++ ).filter(User.xyz.is_(None)).filter( ++ db.not_(User.is_pending.astext.cast(db.Boolean).is_(True)) ++ ).order_by(User.created_at.desc()).with_for_update(key_share=True).all() + return results + + + def foo2(list_a, list_b): + # Standalone comment reasonably placed. +- return ( +- User.query.filter(User.foo == "bar") +- .filter( +- db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) +- ) +- .filter(User.xyz.is_(None)) +- ) ++ return User.query.filter(User.foo == "bar").filter( ++ db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) ++ ).filter(User.xyz.is_(None)) + + + def foo3(list_a, list_b): +- return ( +- # Standlone comment but weirdly placed. +- User.query.filter(User.foo == "bar") +- .filter( +- db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) +- ) +- .filter(User.xyz.is_(None)) +- ) +\ No newline at end of file ++ return # Standlone comment but weirdly placed. ++ User.query.filter(User.foo == "bar").filter( ++ db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) ++ ).filter(User.xyz.is_(None)) + +``` + +## Ruff Output + +```py +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent, +) +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent as component, +) + + +class C: + @pytest.mark.parametrize( + ("post_data", "message"), + [ + (# metadata_version errors. + {}, + "None is an invalid value for Metadata-Version. Error: This field is" + " required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( + { + "metadata_version": "-1", + }, + "'-1' is an invalid value for Metadata-Version. Error: Unknown Metadata" + " Version see" + " https://packaging.python.org/specifications/core-metadata", + ), + (# name errors. + { + "metadata_version": "1.2", + }, + "'' is an invalid value for Name. Error: This field is required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( + { + "metadata_version": "1.2", + "name": "foo-", + }, + "'foo-' is an invalid value for Name. Error: Must start and end with a" + " letter or numeral and contain only ascii numeric and '.', '_' and" + " '-'. see https://packaging.python.org/specifications/core-metadata", + ), + (# version errors. + { + "metadata_version": "1.2", + "name": "example", + }, + "'' is an invalid value for Version. Error: This field is required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( + { + "metadata_version": "1.2", + "name": "example", + "version": "dog", + }, + "'dog' is an invalid value for Version. Error: Must start and end with" + " a letter or numeral and contain only ascii numeric and '.', '_' and" + " '-'. see https://packaging.python.org/specifications/core-metadata", + ), + ], + ) + def test_fails_invalid_post_data( + self, + pyramid_config, + db_request, + post_data, + message, + ): + pyramid_config.testing_securitypolicy(userid=1) + db_request.POST = MultiDict(post_data) + + +def foo(list_a, list_b): + results = User.query.filter(User.foo == "bar").filter( # Because foo. + db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) + ).filter(User.xyz.is_(None)).filter( + db.not_(User.is_pending.astext.cast(db.Boolean).is_(True)) + ).order_by(User.created_at.desc()).with_for_update(key_share=True).all() + return results + + +def foo2(list_a, list_b): + # Standalone comment reasonably placed. + return User.query.filter(User.foo == "bar").filter( + db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) + ).filter(User.xyz.is_(None)) + + +def foo3(list_a, list_b): + return # Standlone comment but weirdly placed. + User.query.filter(User.foo == "bar").filter( + db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) + ).filter(User.xyz.is_(None)) + +``` + +## Black Output + +```py +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent, # NOT DRY +) +from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( + MyLovelyCompanyTeamProjectComponent as component, # DRY +) + + +class C: + @pytest.mark.parametrize( + ("post_data", "message"), + [ + # metadata_version errors. + ( + {}, + "None is an invalid value for Metadata-Version. Error: This field is" + " required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( + {"metadata_version": "-1"}, + "'-1' is an invalid value for Metadata-Version. Error: Unknown Metadata" + " Version see" + " https://packaging.python.org/specifications/core-metadata", + ), + # name errors. + ( + {"metadata_version": "1.2"}, + "'' is an invalid value for Name. Error: This field is required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( + {"metadata_version": "1.2", "name": "foo-"}, + "'foo-' is an invalid value for Name. Error: Must start and end with a" + " letter or numeral and contain only ascii numeric and '.', '_' and" + " '-'. see https://packaging.python.org/specifications/core-metadata", + ), + # version errors. + ( + {"metadata_version": "1.2", "name": "example"}, + "'' is an invalid value for Version. Error: This field is required. see" + " https://packaging.python.org/specifications/core-metadata", + ), + ( + {"metadata_version": "1.2", "name": "example", "version": "dog"}, + "'dog' is an invalid value for Version. Error: Must start and end with" + " a letter or numeral and contain only ascii numeric and '.', '_' and" + " '-'. see https://packaging.python.org/specifications/core-metadata", + ), + ], + ) + def test_fails_invalid_post_data( + self, pyramid_config, db_request, post_data, message + ): + pyramid_config.testing_securitypolicy(userid=1) + db_request.POST = MultiDict(post_data) + + +def foo(list_a, list_b): + results = ( + User.query.filter(User.foo == "bar") + .filter( # Because foo. + db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) + ) + .filter(User.xyz.is_(None)) + # Another comment about the filtering on is_quux goes here. + .filter(db.not_(User.is_pending.astext.cast(db.Boolean).is_(True))) + .order_by(User.created_at.desc()) + .with_for_update(key_share=True) + .all() + ) + return results + + +def foo2(list_a, list_b): + # Standalone comment reasonably placed. + return ( + User.query.filter(User.foo == "bar") + .filter( + db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) + ) + .filter(User.xyz.is_(None)) + ) + + +def foo3(list_a, list_b): + return ( + # Standlone comment but weirdly placed. + User.query.filter(User.foo == "bar") + .filter( + db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b)) + ) + .filter(User.xyz.is_(None)) + ) +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments6_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments6_py.snap new file mode 100644 index 0000000000..575a740eb1 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments6_py.snap @@ -0,0 +1,554 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments6.py +--- +## Input + +```py +from typing import Any, Tuple + + +def f( + a, # type: int +): + pass + + +# test type comments +def f(a, b, c, d, e, f, g, h, i): + # type: (int, int, int, int, int, int, int, int, int) -> None + pass + + +def f( + a, # type: int + b, # type: int + c, # type: int + d, # type: int + e, # type: int + f, # type: int + g, # type: int + h, # type: int + i, # type: int +): + # type: (...) -> None + pass + + +def f( + arg, # type: int + *args, # type: *Any + default=False, # type: bool + **kwargs, # type: **Any +): + # type: (...) -> None + pass + + +def f( + a, # type: int + b, # type: int + c, # type: int + d, # type: int +): + # type: (...) -> None + + element = 0 # type: int + another_element = 1 # type: float + another_element_with_long_name = 2 # type: int + another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style = ( + 3 + ) # type: int + an_element_with_a_long_value = calls() or more_calls() and more() # type: bool + + tup = ( + another_element, + another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style, + ) # type: Tuple[int, int] + + a = ( + element + + another_element + + another_element_with_long_name + + element + + another_element + + another_element_with_long_name + ) # type: int + + +def f( + x, # not a type comment + y, # type: int +): + # type: (...) -> None + pass + + +def f( + x, # not a type comment +): # type: (int) -> None + pass + + +def func( + a=some_list[0], # type: int +): # type: () -> int + c = call( + 0.0123, + 0.0456, + 0.0789, + 0.0123, + 0.0456, + 0.0789, + 0.0123, + 0.0456, + 0.0789, + a[-1], # type: ignore + ) + + c = call( + "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa" # type: ignore + ) + + +result = ( # aaa + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +) + +AAAAAAAAAAAAA = [AAAAAAAAAAAAA] + SHARED_AAAAAAAAAAAAA + USER_AAAAAAAAAAAAA + AAAAAAAAAAAAA # type: ignore + +call_to_some_function_asdf( + foo, + [AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, BBBBBBBBBBBB], # type: ignore +) + +aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -2,8 +2,8 @@ + + + def f( +- a, # type: int +-): ++ a, ++): # type: int + pass + + +@@ -14,44 +14,42 @@ + + + def f( +- a, # type: int +- b, # type: int +- c, # type: int +- d, # type: int +- e, # type: int +- f, # type: int +- g, # type: int +- h, # type: int +- i, # type: int +-): ++ a, ++ b, ++ c, ++ d, ++ e, ++ f, ++ g, ++ h, ++ i, ++): # type: int# type: int# type: int# type: int# type: int# type: int# type: int# type: int# type: int + # type: (...) -> None + pass + + + def f( +- arg, # type: int +- *args, # type: *Any +- default=False, # type: bool +- **kwargs, # type: **Any +-): ++ arg, ++ *args, ++ default=False, ++ **kwargs, ++): # type: int# type: *Any + # type: (...) -> None + pass + + + def f( +- a, # type: int +- b, # type: int +- c, # type: int +- d, # type: int +-): ++ a, ++ b, ++ c, ++ d, ++): # type: int# type: int# type: int# type: int# type: int + # type: (...) -> None + + element = 0 # type: int + another_element = 1 # type: float + another_element_with_long_name = 2 # type: int +- another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style = ( +- 3 +- ) # type: int ++ another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style = 3 # type: int + an_element_with_a_long_value = calls() or more_calls() and more() # type: bool + + tup = ( +@@ -66,26 +64,26 @@ + + element + + another_element + + another_element_with_long_name +- ) # type: int ++ ) + + + def f( +- x, # not a type comment +- y, # type: int +-): ++ x, ++ y, ++): # not a type comment# type: int + # type: (...) -> None + pass + + + def f( +- x, # not a type comment +-): # type: (int) -> None ++ x, ++): # not a type comment# type: (int) -> None + pass + + + def func( +- a=some_list[0], # type: int +-): # type: () -> int ++ a=some_list[0], ++): + c = call( + 0.0123, + 0.0456, +@@ -96,23 +94,37 @@ + 0.0123, + 0.0456, + 0.0789, +- a[-1], # type: ignore ++ a[-1], + ) + + c = call( +- "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa" # type: ignore ++ "aaaaaaaa", ++ "aaaaaaaa", ++ "aaaaaaaa", ++ "aaaaaaaa", ++ "aaaaaaaa", ++ "aaaaaaaa", ++ "aaaaaaaa", + ) + + +-result = ( # aaa +- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +-) ++result = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # aaa + +-AAAAAAAAAAAAA = [AAAAAAAAAAAAA] + SHARED_AAAAAAAAAAAAA + USER_AAAAAAAAAAAAA + AAAAAAAAAAAAA # type: ignore ++AAAAAAAAAAAAA = ( ++ [AAAAAAAAAAAAA] ++ + SHARED_AAAAAAAAAAAAA ++ + USER_AAAAAAAAAAAAA ++ + AAAAAAAAAAAAA ++) # type: ignore + + call_to_some_function_asdf( + foo, +- [AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, BBBBBBBBBBBB], # type: ignore ++ [ ++ AAAAAAAAAAAAAAAAAAAAAAA, ++ AAAAAAAAAAAAAAAAAAAAAAA, ++ AAAAAAAAAAAAAAAAAAAAAAA, ++ BBBBBBBBBBBB, ++ ], + ) + + aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] +\ No newline at end of file + +``` + +## Ruff Output + +```py +from typing import Any, Tuple + + +def f( + a, +): # type: int + pass + + +# test type comments +def f(a, b, c, d, e, f, g, h, i): + # type: (int, int, int, int, int, int, int, int, int) -> None + pass + + +def f( + a, + b, + c, + d, + e, + f, + g, + h, + i, +): # type: int# type: int# type: int# type: int# type: int# type: int# type: int# type: int# type: int + # type: (...) -> None + pass + + +def f( + arg, + *args, + default=False, + **kwargs, +): # type: int# type: *Any + # type: (...) -> None + pass + + +def f( + a, + b, + c, + d, +): # type: int# type: int# type: int# type: int# type: int + # type: (...) -> None + + element = 0 # type: int + another_element = 1 # type: float + another_element_with_long_name = 2 # type: int + another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style = 3 # type: int + an_element_with_a_long_value = calls() or more_calls() and more() # type: bool + + tup = ( + another_element, + another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style, + ) # type: Tuple[int, int] + + a = ( + element + + another_element + + another_element_with_long_name + + element + + another_element + + another_element_with_long_name + ) + + +def f( + x, + y, +): # not a type comment# type: int + # type: (...) -> None + pass + + +def f( + x, +): # not a type comment# type: (int) -> None + pass + + +def func( + a=some_list[0], +): + c = call( + 0.0123, + 0.0456, + 0.0789, + 0.0123, + 0.0456, + 0.0789, + 0.0123, + 0.0456, + 0.0789, + a[-1], + ) + + c = call( + "aaaaaaaa", + "aaaaaaaa", + "aaaaaaaa", + "aaaaaaaa", + "aaaaaaaa", + "aaaaaaaa", + "aaaaaaaa", + ) + + +result = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # aaa + +AAAAAAAAAAAAA = ( + [AAAAAAAAAAAAA] + + SHARED_AAAAAAAAAAAAA + + USER_AAAAAAAAAAAAA + + AAAAAAAAAAAAA +) # type: ignore + +call_to_some_function_asdf( + foo, + [ + AAAAAAAAAAAAAAAAAAAAAAA, + AAAAAAAAAAAAAAAAAAAAAAA, + AAAAAAAAAAAAAAAAAAAAAAA, + BBBBBBBBBBBB, + ], +) + +aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] +``` + +## Black Output + +```py +from typing import Any, Tuple + + +def f( + a, # type: int +): + pass + + +# test type comments +def f(a, b, c, d, e, f, g, h, i): + # type: (int, int, int, int, int, int, int, int, int) -> None + pass + + +def f( + a, # type: int + b, # type: int + c, # type: int + d, # type: int + e, # type: int + f, # type: int + g, # type: int + h, # type: int + i, # type: int +): + # type: (...) -> None + pass + + +def f( + arg, # type: int + *args, # type: *Any + default=False, # type: bool + **kwargs, # type: **Any +): + # type: (...) -> None + pass + + +def f( + a, # type: int + b, # type: int + c, # type: int + d, # type: int +): + # type: (...) -> None + + element = 0 # type: int + another_element = 1 # type: float + another_element_with_long_name = 2 # type: int + another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style = ( + 3 + ) # type: int + an_element_with_a_long_value = calls() or more_calls() and more() # type: bool + + tup = ( + another_element, + another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style, + ) # type: Tuple[int, int] + + a = ( + element + + another_element + + another_element_with_long_name + + element + + another_element + + another_element_with_long_name + ) # type: int + + +def f( + x, # not a type comment + y, # type: int +): + # type: (...) -> None + pass + + +def f( + x, # not a type comment +): # type: (int) -> None + pass + + +def func( + a=some_list[0], # type: int +): # type: () -> int + c = call( + 0.0123, + 0.0456, + 0.0789, + 0.0123, + 0.0456, + 0.0789, + 0.0123, + 0.0456, + 0.0789, + a[-1], # type: ignore + ) + + c = call( + "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa" # type: ignore + ) + + +result = ( # aaa + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +) + +AAAAAAAAAAAAA = [AAAAAAAAAAAAA] + SHARED_AAAAAAAAAAAAA + USER_AAAAAAAAAAAAA + AAAAAAAAAAAAA # type: ignore + +call_to_some_function_asdf( + foo, + [AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, BBBBBBBBBBBB], # type: ignore +) + +aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments9_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments9_py.snap new file mode 100644 index 0000000000..eb50a64000 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments9_py.snap @@ -0,0 +1,629 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments9.py +--- +## Input + +```py +# Test for https://github.com/psf/black/issues/246. + +some = statement +# This comment should be split from the statement above by two lines. +def function(): + pass + + +some = statement +# This multiline comments section +# should be split from the statement +# above by two lines. +def function(): + pass + + +some = statement +# This comment should be split from the statement above by two lines. +async def async_function(): + pass + + +some = statement +# This comment should be split from the statement above by two lines. +class MyClass: + pass + + +some = statement +# This should be stick to the statement above + +# This should be split from the above by two lines +class MyClassWithComplexLeadingComments: + pass + + +class ClassWithDocstring: + """A docstring.""" +# Leading comment after a class with just a docstring +class MyClassAfterAnotherClassWithDocstring: + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +# leading 2 extra +@deco2(with_args=True) +# leading 3 +@deco3 +# leading 4 +def decorated(): + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) + +# leading 3 that already has an empty line +@deco3 +# leading 4 +def decorated_with_split_leading_comments(): + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 + +# leading 4 that already has an empty line +def decorated_with_split_leading_comments(): + pass + + +def main(): + if a: + # Leading comment before inline function + def inline(): + pass + # Another leading comment + def another_inline(): + pass + else: + # More leading comments + def inline_after_else(): + pass + + +if a: + # Leading comment before "top-level inline" function + def top_level_quote_inline(): + pass + # Another leading comment + def another_top_level_quote_inline_inline(): + pass +else: + # More leading comments + def top_level_quote_inline_after_else(): + pass + + +class MyClass: + # First method has no empty lines between bare class def. + # More comments. + def first_method(self): + pass + + +# Regression test for https://github.com/psf/black/issues/3454. +def foo(): + pass + # Trailing comment that belongs to this function + + +@decorator1 +@decorator2 # fmt: skip +def bar(): + pass + + +# Regression test for https://github.com/psf/black/issues/3454. +def foo(): + pass + # Trailing comment that belongs to this function. + # NOTE this comment only has one empty line below, and the formatter + # should enforce two blank lines. + +@decorator1 +# A standalone comment +def bar(): + pass + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -35,9 +35,10 @@ + + + some = statement ++ ++ + # This should be stick to the statement above + +- + # This should be split from the above by two lines + class MyClassWithComplexLeadingComments: + pass +@@ -57,13 +58,13 @@ + + # leading 1 + @deco1 +-# leading 2 ++@# leading 2 + # leading 2 extra +-@deco2(with_args=True) +-# leading 3 +-@deco3 +-# leading 4 ++deco2(with_args=True) ++@# leading 3 ++deco3 + def decorated(): ++ # leading 4 + pass + + +@@ -72,13 +73,12 @@ + + # leading 1 + @deco1 +-# leading 2 +-@deco2(with_args=True) +- +-# leading 3 that already has an empty line +-@deco3 +-# leading 4 ++@# leading 2 ++deco2(with_args=True) ++@# leading 3 that already has an empty line ++deco3 + def decorated_with_split_leading_comments(): ++ # leading 4 + pass + + +@@ -87,18 +87,18 @@ + + # leading 1 + @deco1 +-# leading 2 +-@deco2(with_args=True) +-# leading 3 +-@deco3 +- +-# leading 4 that already has an empty line ++@# leading 2 ++deco2(with_args=True) ++@# leading 3 ++deco3 + def decorated_with_split_leading_comments(): ++ # leading 4 that already has an empty line + pass + + + def main(): + if a: ++ + # Leading comment before inline function + def inline(): + pass +@@ -108,12 +108,14 @@ + pass + + else: ++ + # More leading comments + def inline_after_else(): + pass + + + if a: ++ + # Leading comment before "top-level inline" function + def top_level_quote_inline(): + pass +@@ -123,6 +125,7 @@ + pass + + else: ++ + # More leading comments + def top_level_quote_inline_after_else(): + pass +@@ -138,9 +141,11 @@ + # Regression test for https://github.com/psf/black/issues/3454. + def foo(): + pass +- # Trailing comment that belongs to this function + + ++# Trailing comment that belongs to this function ++ ++ + @decorator1 + @decorator2 # fmt: skip + def bar(): +@@ -150,12 +155,13 @@ + # Regression test for https://github.com/psf/black/issues/3454. + def foo(): + pass +- # Trailing comment that belongs to this function. +- # NOTE this comment only has one empty line below, and the formatter +- # should enforce two blank lines. + + ++# Trailing comment that belongs to this function. ++# NOTE this comment only has one empty line below, and the formatter ++# should enforce two blank lines. ++ + @decorator1 +-# A standalone comment + def bar(): +- pass +\ No newline at end of file ++ # A standalone comment ++ pass + +``` + +## Ruff Output + +```py +# Test for https://github.com/psf/black/issues/246. + +some = statement + + +# This comment should be split from the statement above by two lines. +def function(): + pass + + +some = statement + + +# This multiline comments section +# should be split from the statement +# above by two lines. +def function(): + pass + + +some = statement + + +# This comment should be split from the statement above by two lines. +async def async_function(): + pass + + +some = statement + + +# This comment should be split from the statement above by two lines. +class MyClass: + pass + + +some = statement + + +# This should be stick to the statement above + +# This should be split from the above by two lines +class MyClassWithComplexLeadingComments: + pass + + +class ClassWithDocstring: + """A docstring.""" + + +# Leading comment after a class with just a docstring +class MyClassAfterAnotherClassWithDocstring: + pass + + +some = statement + + +# leading 1 +@deco1 +@# leading 2 +# leading 2 extra +deco2(with_args=True) +@# leading 3 +deco3 +def decorated(): + # leading 4 + pass + + +some = statement + + +# leading 1 +@deco1 +@# leading 2 +deco2(with_args=True) +@# leading 3 that already has an empty line +deco3 +def decorated_with_split_leading_comments(): + # leading 4 + pass + + +some = statement + + +# leading 1 +@deco1 +@# leading 2 +deco2(with_args=True) +@# leading 3 +deco3 +def decorated_with_split_leading_comments(): + # leading 4 that already has an empty line + pass + + +def main(): + if a: + + # Leading comment before inline function + def inline(): + pass + + # Another leading comment + def another_inline(): + pass + + else: + + # More leading comments + def inline_after_else(): + pass + + +if a: + + # Leading comment before "top-level inline" function + def top_level_quote_inline(): + pass + + # Another leading comment + def another_top_level_quote_inline_inline(): + pass + +else: + + # More leading comments + def top_level_quote_inline_after_else(): + pass + + +class MyClass: + # First method has no empty lines between bare class def. + # More comments. + def first_method(self): + pass + + +# Regression test for https://github.com/psf/black/issues/3454. +def foo(): + pass + + +# Trailing comment that belongs to this function + + +@decorator1 +@decorator2 # fmt: skip +def bar(): + pass + + +# Regression test for https://github.com/psf/black/issues/3454. +def foo(): + pass + + +# Trailing comment that belongs to this function. +# NOTE this comment only has one empty line below, and the formatter +# should enforce two blank lines. + +@decorator1 +def bar(): + # A standalone comment + pass + +``` + +## Black Output + +```py +# Test for https://github.com/psf/black/issues/246. + +some = statement + + +# This comment should be split from the statement above by two lines. +def function(): + pass + + +some = statement + + +# This multiline comments section +# should be split from the statement +# above by two lines. +def function(): + pass + + +some = statement + + +# This comment should be split from the statement above by two lines. +async def async_function(): + pass + + +some = statement + + +# This comment should be split from the statement above by two lines. +class MyClass: + pass + + +some = statement +# This should be stick to the statement above + + +# This should be split from the above by two lines +class MyClassWithComplexLeadingComments: + pass + + +class ClassWithDocstring: + """A docstring.""" + + +# Leading comment after a class with just a docstring +class MyClassAfterAnotherClassWithDocstring: + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +# leading 2 extra +@deco2(with_args=True) +# leading 3 +@deco3 +# leading 4 +def decorated(): + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) + +# leading 3 that already has an empty line +@deco3 +# leading 4 +def decorated_with_split_leading_comments(): + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 + +# leading 4 that already has an empty line +def decorated_with_split_leading_comments(): + pass + + +def main(): + if a: + # Leading comment before inline function + def inline(): + pass + + # Another leading comment + def another_inline(): + pass + + else: + # More leading comments + def inline_after_else(): + pass + + +if a: + # Leading comment before "top-level inline" function + def top_level_quote_inline(): + pass + + # Another leading comment + def another_top_level_quote_inline_inline(): + pass + +else: + # More leading comments + def top_level_quote_inline_after_else(): + pass + + +class MyClass: + # First method has no empty lines between bare class def. + # More comments. + def first_method(self): + pass + + +# Regression test for https://github.com/psf/black/issues/3454. +def foo(): + pass + # Trailing comment that belongs to this function + + +@decorator1 +@decorator2 # fmt: skip +def bar(): + pass + + +# Regression test for https://github.com/psf/black/issues/3454. +def foo(): + pass + # Trailing comment that belongs to this function. + # NOTE this comment only has one empty line below, and the formatter + # should enforce two blank lines. + + +@decorator1 +# A standalone comment +def bar(): + pass +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__composition_no_trailing_comma_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__composition_no_trailing_comma_py.snap new file mode 100644 index 0000000000..8c690d7bba --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__composition_no_trailing_comma_py.snap @@ -0,0 +1,882 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/composition_no_trailing_comma.py +--- +## Input + +```py +class C: + def test(self) -> None: + with patch("black.out", print): + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 1 file failed to reformat." + ) + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 1 file left unchanged, 1 file failed to reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 1 file left unchanged, 1 file failed to" + " reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, 2 files failed to" + " reformat.", + ) + for i in (a,): + if ( + # Rule 1 + i % 2 == 0 + # Rule 2 + and i % 3 == 0 + ): + while ( + # Just a comment + call() + # Another + ): + print(i) + xxxxxxxxxxxxxxxx = Yyyy2YyyyyYyyyyy( + push_manager=context.request.resource_manager, + max_items_to_push=num_items, + batch_size=Yyyy2YyyyYyyyyYyyy.FULL_SIZE + ).push( + # Only send the first n items. + items=items[:num_items] + ) + return ( + 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' + % (test.name, test.filename, lineno, lname, err) + ) + + def omitting_trailers(self) -> None: + get_collection( + hey_this_is_a_very_long_call, it_has_funny_attributes, really=True + )[OneLevelIndex] + get_collection( + hey_this_is_a_very_long_call, it_has_funny_attributes, really=True + )[OneLevelIndex][TwoLevelIndex][ThreeLevelIndex][FourLevelIndex] + d[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][ + 22 + ] + assignment = ( + some.rather.elaborate.rule() and another.rule.ending_with.index[123] + ) + + def easy_asserts(self) -> None: + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9 + } == expected, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9 + }, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9 + } + + def tricky_asserts(self) -> None: + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9 + } == expected( + value, is_going_to_be="too long to fit in a single line", srsly=True + ), "Not what we expected" + + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9 + } == expected, ( + "Not what we expected and the message is too long to fit in one line" + ) + + assert expected( + value, is_going_to_be="too long to fit in a single line", srsly=True + ) == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9 + }, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9 + }, ( + "Not what we expected and the message is too long to fit in one line" + " because it's too long" + ) + + dis_c_instance_method = """\ + %3d 0 LOAD_FAST 1 (x) + 2 LOAD_CONST 1 (1) + 4 COMPARE_OP 2 (==) + 6 LOAD_FAST 0 (self) + 8 STORE_ATTR 0 (x) + 10 LOAD_CONST 0 (None) + 12 RETURN_VALUE + """ % ( + _C.__init__.__code__.co_firstlineno + 1, + ) + + assert ( + expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9 + } + ) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -2,7 +2,8 @@ + def test(self) -> None: + with patch("black.out", print): + self.assertEqual( +- unstyle(str(report)), "1 file reformatted, 1 file failed to reformat." ++ unstyle(str(report)), ++ "1 file reformatted, 1 file failed to reformat.", + ) + self.assertEqual( + unstyle(str(report)), +@@ -22,133 +23,156 @@ + if ( + # Rule 1 + i % 2 == 0 +- # Rule 2 +- and i % 3 == 0 ++ and # Rule 2 ++ i % 3 ++ == 0 + ): +- while ( +- # Just a comment +- call() ++ while # Just a comment ++ call(): + # Another +- ): + print(i) + xxxxxxxxxxxxxxxx = Yyyy2YyyyyYyyyyy( + push_manager=context.request.resource_manager, + max_items_to_push=num_items, + batch_size=Yyyy2YyyyYyyyyYyyy.FULL_SIZE, + ).push( +- # Only send the first n items. +- items=items[:num_items] ++ items=# Only send the first n items. ++ items[:num_items] + ) +- return ( +- 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' +- % (test.name, test.filename, lineno, lname, err) +- ) ++ return 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' ++ % (test.name, test.filename, lineno, lname, err) + + def omitting_trailers(self) -> None: + get_collection( +- hey_this_is_a_very_long_call, it_has_funny_attributes, really=True ++ hey_this_is_a_very_long_call, ++ it_has_funny_attributes, ++ really=True, + )[OneLevelIndex] + get_collection( +- hey_this_is_a_very_long_call, it_has_funny_attributes, really=True ++ hey_this_is_a_very_long_call, ++ it_has_funny_attributes, ++ really=True, + )[OneLevelIndex][TwoLevelIndex][ThreeLevelIndex][FourLevelIndex] + d[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][ + 22 + ] +- assignment = ( +- some.rather.elaborate.rule() and another.rule.ending_with.index[123] +- ) ++ assignment = some.rather.elaborate.rule() ++ and another.rule.ending_with.index[123] + + def easy_asserts(self) -> None: +- assert { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- } == expected, "Not what we expected" ++ assert ( ++ { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ == expected ++ ), "Not what we expected" + +- assert expected == { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- }, "Not what we expected" ++ assert ( ++ expected ++ == { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ ), "Not what we expected" + +- assert expected == { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- } ++ assert ( ++ expected ++ == { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ ) + + def tricky_asserts(self) -> None: +- assert { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- } == expected( +- value, is_going_to_be="too long to fit in a single line", srsly=True ++ assert ( ++ { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ == expected( ++ value, ++ is_going_to_be="too long to fit in a single line", ++ srsly=True, ++ ) + ), "Not what we expected" + +- assert { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- } == expected, ( +- "Not what we expected and the message is too long to fit in one line" +- ) ++ assert ( ++ { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ == expected ++ ), "Not what we expected and the message is too long to fit in one line" + +- assert expected( +- value, is_going_to_be="too long to fit in a single line", srsly=True +- ) == { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- }, "Not what we expected" ++ assert ( ++ expected( ++ value, ++ is_going_to_be="too long to fit in a single line", ++ srsly=True, ++ ) ++ == { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ ), "Not what we expected" + +- assert expected == { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- }, ( ++ assert ( ++ expected ++ == { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ ), ( + "Not what we expected and the message is too long to fit in one line" + " because it's too long" + ) +@@ -161,9 +185,8 @@ + 8 STORE_ATTR 0 (x) + 10 LOAD_CONST 0 (None) + 12 RETURN_VALUE +- """ % ( +- _C.__init__.__code__.co_firstlineno + 1, +- ) ++ """ ++ % (_C.__init__.__code__.co_firstlineno + 1,) + + assert ( + expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect +@@ -178,4 +201,4 @@ + key8: value8, + key9: value9, + } +- ) +\ No newline at end of file ++ ) + +``` + +## Ruff Output + +```py +class C: + def test(self) -> None: + with patch("black.out", print): + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 1 file failed to reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 1 file left unchanged, 1 file failed to reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 1 file left unchanged, 1 file failed to" + " reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, 2 files failed to" + " reformat.", + ) + for i in (a,): + if ( + # Rule 1 + i % 2 == 0 + and # Rule 2 + i % 3 + == 0 + ): + while # Just a comment + call(): + # Another + print(i) + xxxxxxxxxxxxxxxx = Yyyy2YyyyyYyyyyy( + push_manager=context.request.resource_manager, + max_items_to_push=num_items, + batch_size=Yyyy2YyyyYyyyyYyyy.FULL_SIZE, + ).push( + items=# Only send the first n items. + items[:num_items] + ) + return 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' + % (test.name, test.filename, lineno, lname, err) + + def omitting_trailers(self) -> None: + get_collection( + hey_this_is_a_very_long_call, + it_has_funny_attributes, + really=True, + )[OneLevelIndex] + get_collection( + hey_this_is_a_very_long_call, + it_has_funny_attributes, + really=True, + )[OneLevelIndex][TwoLevelIndex][ThreeLevelIndex][FourLevelIndex] + d[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][ + 22 + ] + assignment = some.rather.elaborate.rule() + and another.rule.ending_with.index[123] + + def easy_asserts(self) -> None: + assert ( + { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + == expected + ), "Not what we expected" + + assert ( + expected + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ), "Not what we expected" + + assert ( + expected + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ) + + def tricky_asserts(self) -> None: + assert ( + { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + == expected( + value, + is_going_to_be="too long to fit in a single line", + srsly=True, + ) + ), "Not what we expected" + + assert ( + { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + == expected + ), "Not what we expected and the message is too long to fit in one line" + + assert ( + expected( + value, + is_going_to_be="too long to fit in a single line", + srsly=True, + ) + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ), "Not what we expected" + + assert ( + expected + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ), ( + "Not what we expected and the message is too long to fit in one line" + " because it's too long" + ) + + dis_c_instance_method = """\ + %3d 0 LOAD_FAST 1 (x) + 2 LOAD_CONST 1 (1) + 4 COMPARE_OP 2 (==) + 6 LOAD_FAST 0 (self) + 8 STORE_ATTR 0 (x) + 10 LOAD_CONST 0 (None) + 12 RETURN_VALUE + """ + % (_C.__init__.__code__.co_firstlineno + 1,) + + assert ( + expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ) + +``` + +## Black Output + +```py +class C: + def test(self) -> None: + with patch("black.out", print): + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 1 file failed to reformat." + ) + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 1 file left unchanged, 1 file failed to reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 1 file left unchanged, 1 file failed to" + " reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, 2 files failed to" + " reformat.", + ) + for i in (a,): + if ( + # Rule 1 + i % 2 == 0 + # Rule 2 + and i % 3 == 0 + ): + while ( + # Just a comment + call() + # Another + ): + print(i) + xxxxxxxxxxxxxxxx = Yyyy2YyyyyYyyyyy( + push_manager=context.request.resource_manager, + max_items_to_push=num_items, + batch_size=Yyyy2YyyyYyyyyYyyy.FULL_SIZE, + ).push( + # Only send the first n items. + items=items[:num_items] + ) + return ( + 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' + % (test.name, test.filename, lineno, lname, err) + ) + + def omitting_trailers(self) -> None: + get_collection( + hey_this_is_a_very_long_call, it_has_funny_attributes, really=True + )[OneLevelIndex] + get_collection( + hey_this_is_a_very_long_call, it_has_funny_attributes, really=True + )[OneLevelIndex][TwoLevelIndex][ThreeLevelIndex][FourLevelIndex] + d[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][ + 22 + ] + assignment = ( + some.rather.elaborate.rule() and another.rule.ending_with.index[123] + ) + + def easy_asserts(self) -> None: + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } == expected, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + }, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + + def tricky_asserts(self) -> None: + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } == expected( + value, is_going_to_be="too long to fit in a single line", srsly=True + ), "Not what we expected" + + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } == expected, ( + "Not what we expected and the message is too long to fit in one line" + ) + + assert expected( + value, is_going_to_be="too long to fit in a single line", srsly=True + ) == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + }, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + }, ( + "Not what we expected and the message is too long to fit in one line" + " because it's too long" + ) + + dis_c_instance_method = """\ + %3d 0 LOAD_FAST 1 (x) + 2 LOAD_CONST 1 (1) + 4 COMPARE_OP 2 (==) + 6 LOAD_FAST 0 (self) + 8 STORE_ATTR 0 (x) + 10 LOAD_CONST 0 (None) + 12 RETURN_VALUE + """ % ( + _C.__init__.__code__.co_firstlineno + 1, + ) + + assert ( + expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ) +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__composition_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__composition_py.snap new file mode 100644 index 0000000000..4dc8f2b30d --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__composition_py.snap @@ -0,0 +1,882 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/composition.py +--- +## Input + +```py +class C: + def test(self) -> None: + with patch("black.out", print): + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 1 file failed to reformat." + ) + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 1 file left unchanged, 1 file failed to reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 1 file left unchanged, 1 file failed to" + " reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, 2 files failed to" + " reformat.", + ) + for i in (a,): + if ( + # Rule 1 + i % 2 == 0 + # Rule 2 + and i % 3 == 0 + ): + while ( + # Just a comment + call() + # Another + ): + print(i) + xxxxxxxxxxxxxxxx = Yyyy2YyyyyYyyyyy( + push_manager=context.request.resource_manager, + max_items_to_push=num_items, + batch_size=Yyyy2YyyyYyyyyYyyy.FULL_SIZE, + ).push( + # Only send the first n items. + items=items[:num_items] + ) + return ( + 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' + % (test.name, test.filename, lineno, lname, err) + ) + + def omitting_trailers(self) -> None: + get_collection( + hey_this_is_a_very_long_call, it_has_funny_attributes, really=True + )[OneLevelIndex] + get_collection( + hey_this_is_a_very_long_call, it_has_funny_attributes, really=True + )[OneLevelIndex][TwoLevelIndex][ThreeLevelIndex][FourLevelIndex] + d[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][ + 22 + ] + assignment = ( + some.rather.elaborate.rule() and another.rule.ending_with.index[123] + ) + + def easy_asserts(self) -> None: + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } == expected, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + }, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + + def tricky_asserts(self) -> None: + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } == expected( + value, is_going_to_be="too long to fit in a single line", srsly=True + ), "Not what we expected" + + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } == expected, ( + "Not what we expected and the message is too long to fit in one line" + ) + + assert expected( + value, is_going_to_be="too long to fit in a single line", srsly=True + ) == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + }, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + }, ( + "Not what we expected and the message is too long to fit in one line" + " because it's too long" + ) + + dis_c_instance_method = """\ + %3d 0 LOAD_FAST 1 (x) + 2 LOAD_CONST 1 (1) + 4 COMPARE_OP 2 (==) + 6 LOAD_FAST 0 (self) + 8 STORE_ATTR 0 (x) + 10 LOAD_CONST 0 (None) + 12 RETURN_VALUE + """ % ( + _C.__init__.__code__.co_firstlineno + 1, + ) + + assert ( + expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -2,7 +2,8 @@ + def test(self) -> None: + with patch("black.out", print): + self.assertEqual( +- unstyle(str(report)), "1 file reformatted, 1 file failed to reformat." ++ unstyle(str(report)), ++ "1 file reformatted, 1 file failed to reformat.", + ) + self.assertEqual( + unstyle(str(report)), +@@ -22,133 +23,156 @@ + if ( + # Rule 1 + i % 2 == 0 +- # Rule 2 +- and i % 3 == 0 ++ and # Rule 2 ++ i % 3 ++ == 0 + ): +- while ( +- # Just a comment +- call() ++ while # Just a comment ++ call(): + # Another +- ): + print(i) + xxxxxxxxxxxxxxxx = Yyyy2YyyyyYyyyyy( + push_manager=context.request.resource_manager, + max_items_to_push=num_items, + batch_size=Yyyy2YyyyYyyyyYyyy.FULL_SIZE, + ).push( +- # Only send the first n items. +- items=items[:num_items] ++ items=# Only send the first n items. ++ items[:num_items] + ) +- return ( +- 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' +- % (test.name, test.filename, lineno, lname, err) +- ) ++ return 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' ++ % (test.name, test.filename, lineno, lname, err) + + def omitting_trailers(self) -> None: + get_collection( +- hey_this_is_a_very_long_call, it_has_funny_attributes, really=True ++ hey_this_is_a_very_long_call, ++ it_has_funny_attributes, ++ really=True, + )[OneLevelIndex] + get_collection( +- hey_this_is_a_very_long_call, it_has_funny_attributes, really=True ++ hey_this_is_a_very_long_call, ++ it_has_funny_attributes, ++ really=True, + )[OneLevelIndex][TwoLevelIndex][ThreeLevelIndex][FourLevelIndex] + d[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][ + 22 + ] +- assignment = ( +- some.rather.elaborate.rule() and another.rule.ending_with.index[123] +- ) ++ assignment = some.rather.elaborate.rule() ++ and another.rule.ending_with.index[123] + + def easy_asserts(self) -> None: +- assert { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- } == expected, "Not what we expected" ++ assert ( ++ { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ == expected ++ ), "Not what we expected" + +- assert expected == { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- }, "Not what we expected" ++ assert ( ++ expected ++ == { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ ), "Not what we expected" + +- assert expected == { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- } ++ assert ( ++ expected ++ == { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ ) + + def tricky_asserts(self) -> None: +- assert { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- } == expected( +- value, is_going_to_be="too long to fit in a single line", srsly=True ++ assert ( ++ { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ == expected( ++ value, ++ is_going_to_be="too long to fit in a single line", ++ srsly=True, ++ ) + ), "Not what we expected" + +- assert { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- } == expected, ( +- "Not what we expected and the message is too long to fit in one line" +- ) ++ assert ( ++ { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ == expected ++ ), "Not what we expected and the message is too long to fit in one line" + +- assert expected( +- value, is_going_to_be="too long to fit in a single line", srsly=True +- ) == { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- }, "Not what we expected" ++ assert ( ++ expected( ++ value, ++ is_going_to_be="too long to fit in a single line", ++ srsly=True, ++ ) ++ == { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ ), "Not what we expected" + +- assert expected == { +- key1: value1, +- key2: value2, +- key3: value3, +- key4: value4, +- key5: value5, +- key6: value6, +- key7: value7, +- key8: value8, +- key9: value9, +- }, ( ++ assert ( ++ expected ++ == { ++ key1: value1, ++ key2: value2, ++ key3: value3, ++ key4: value4, ++ key5: value5, ++ key6: value6, ++ key7: value7, ++ key8: value8, ++ key9: value9, ++ } ++ ), ( + "Not what we expected and the message is too long to fit in one line" + " because it's too long" + ) +@@ -161,9 +185,8 @@ + 8 STORE_ATTR 0 (x) + 10 LOAD_CONST 0 (None) + 12 RETURN_VALUE +- """ % ( +- _C.__init__.__code__.co_firstlineno + 1, +- ) ++ """ ++ % (_C.__init__.__code__.co_firstlineno + 1,) + + assert ( + expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect +@@ -178,4 +201,4 @@ + key8: value8, + key9: value9, + } +- ) +\ No newline at end of file ++ ) + +``` + +## Ruff Output + +```py +class C: + def test(self) -> None: + with patch("black.out", print): + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 1 file failed to reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 1 file left unchanged, 1 file failed to reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 1 file left unchanged, 1 file failed to" + " reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, 2 files failed to" + " reformat.", + ) + for i in (a,): + if ( + # Rule 1 + i % 2 == 0 + and # Rule 2 + i % 3 + == 0 + ): + while # Just a comment + call(): + # Another + print(i) + xxxxxxxxxxxxxxxx = Yyyy2YyyyyYyyyyy( + push_manager=context.request.resource_manager, + max_items_to_push=num_items, + batch_size=Yyyy2YyyyYyyyyYyyy.FULL_SIZE, + ).push( + items=# Only send the first n items. + items[:num_items] + ) + return 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' + % (test.name, test.filename, lineno, lname, err) + + def omitting_trailers(self) -> None: + get_collection( + hey_this_is_a_very_long_call, + it_has_funny_attributes, + really=True, + )[OneLevelIndex] + get_collection( + hey_this_is_a_very_long_call, + it_has_funny_attributes, + really=True, + )[OneLevelIndex][TwoLevelIndex][ThreeLevelIndex][FourLevelIndex] + d[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][ + 22 + ] + assignment = some.rather.elaborate.rule() + and another.rule.ending_with.index[123] + + def easy_asserts(self) -> None: + assert ( + { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + == expected + ), "Not what we expected" + + assert ( + expected + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ), "Not what we expected" + + assert ( + expected + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ) + + def tricky_asserts(self) -> None: + assert ( + { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + == expected( + value, + is_going_to_be="too long to fit in a single line", + srsly=True, + ) + ), "Not what we expected" + + assert ( + { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + == expected + ), "Not what we expected and the message is too long to fit in one line" + + assert ( + expected( + value, + is_going_to_be="too long to fit in a single line", + srsly=True, + ) + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ), "Not what we expected" + + assert ( + expected + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ), ( + "Not what we expected and the message is too long to fit in one line" + " because it's too long" + ) + + dis_c_instance_method = """\ + %3d 0 LOAD_FAST 1 (x) + 2 LOAD_CONST 1 (1) + 4 COMPARE_OP 2 (==) + 6 LOAD_FAST 0 (self) + 8 STORE_ATTR 0 (x) + 10 LOAD_CONST 0 (None) + 12 RETURN_VALUE + """ + % (_C.__init__.__code__.co_firstlineno + 1,) + + assert ( + expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ) + +``` + +## Black Output + +```py +class C: + def test(self) -> None: + with patch("black.out", print): + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 1 file failed to reformat." + ) + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 1 file left unchanged, 1 file failed to reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 1 file left unchanged, 1 file failed to" + " reformat.", + ) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, 2 files failed to" + " reformat.", + ) + for i in (a,): + if ( + # Rule 1 + i % 2 == 0 + # Rule 2 + and i % 3 == 0 + ): + while ( + # Just a comment + call() + # Another + ): + print(i) + xxxxxxxxxxxxxxxx = Yyyy2YyyyyYyyyyy( + push_manager=context.request.resource_manager, + max_items_to_push=num_items, + batch_size=Yyyy2YyyyYyyyyYyyy.FULL_SIZE, + ).push( + # Only send the first n items. + items=items[:num_items] + ) + return ( + 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' + % (test.name, test.filename, lineno, lname, err) + ) + + def omitting_trailers(self) -> None: + get_collection( + hey_this_is_a_very_long_call, it_has_funny_attributes, really=True + )[OneLevelIndex] + get_collection( + hey_this_is_a_very_long_call, it_has_funny_attributes, really=True + )[OneLevelIndex][TwoLevelIndex][ThreeLevelIndex][FourLevelIndex] + d[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][ + 22 + ] + assignment = ( + some.rather.elaborate.rule() and another.rule.ending_with.index[123] + ) + + def easy_asserts(self) -> None: + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } == expected, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + }, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + + def tricky_asserts(self) -> None: + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } == expected( + value, is_going_to_be="too long to fit in a single line", srsly=True + ), "Not what we expected" + + assert { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } == expected, ( + "Not what we expected and the message is too long to fit in one line" + ) + + assert expected( + value, is_going_to_be="too long to fit in a single line", srsly=True + ) == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + }, "Not what we expected" + + assert expected == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + }, ( + "Not what we expected and the message is too long to fit in one line" + " because it's too long" + ) + + dis_c_instance_method = """\ + %3d 0 LOAD_FAST 1 (x) + 2 LOAD_CONST 1 (1) + 4 COMPARE_OP 2 (==) + 6 LOAD_FAST 0 (self) + 8 STORE_ATTR 0 (x) + 10 LOAD_CONST 0 (None) + 12 RETURN_VALUE + """ % ( + _C.__init__.__code__.co_firstlineno + 1, + ) + + assert ( + expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect + == { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + key5: value5, + key6: value6, + key7: value7, + key8: value8, + key9: value9, + } + ) +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_no_extra_empty_line_before_eof_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_no_extra_empty_line_before_eof_py.snap new file mode 100644 index 0000000000..0f3da70a1b --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_no_extra_empty_line_before_eof_py.snap @@ -0,0 +1,50 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_no_extra_empty_line_before_eof.py +--- +## Input + +```py +# Make sure when the file ends with class's docstring, +# It doesn't add extra blank lines. +class ClassWithDocstring: + """A docstring.""" + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,4 +1,4 @@ + # Make sure when the file ends with class's docstring, + # It doesn't add extra blank lines. + class ClassWithDocstring: +- """A docstring.""" +\ No newline at end of file ++ """A docstring.""" + +``` + +## Ruff Output + +```py +# Make sure when the file ends with class's docstring, +# It doesn't add extra blank lines. +class ClassWithDocstring: + """A docstring.""" + +``` + +## Black Output + +```py +# Make sure when the file ends with class's docstring, +# It doesn't add extra blank lines. +class ClassWithDocstring: + """A docstring.""" +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap new file mode 100644 index 0000000000..34ebdc1be9 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap @@ -0,0 +1,419 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/empty_lines.py +--- +## Input + +```py +"""Docstring.""" + + +# leading comment +def f(): + NO = '' + SPACE = ' ' + DOUBLESPACE = ' ' + + t = leaf.type + p = leaf.parent # trailing comment + v = leaf.value + + if t in ALWAYS_NO_SPACE: + pass + if t == token.COMMENT: # another trailing comment + return DOUBLESPACE + + + assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}" + + + prev = leaf.prev_sibling + if not prev: + prevp = preceding_leaf(p) + if not prevp or prevp.type in OPENING_BRACKETS: + + + return NO + + + if prevp.type == token.EQUAL: + if prevp.parent and prevp.parent.type in { + syms.typedargslist, + syms.varargslist, + syms.parameters, + syms.arglist, + syms.argument, + }: + return NO + + elif prevp.type == token.DOUBLESTAR: + if prevp.parent and prevp.parent.type in { + syms.typedargslist, + syms.varargslist, + syms.parameters, + syms.arglist, + syms.dictsetmaker, + }: + return NO + +############################################################################### +# SECTION BECAUSE SECTIONS +############################################################################### + +def g(): + NO = '' + SPACE = ' ' + DOUBLESPACE = ' ' + + t = leaf.type + p = leaf.parent + v = leaf.value + + # Comment because comments + + if t in ALWAYS_NO_SPACE: + pass + if t == token.COMMENT: + return DOUBLESPACE + + # Another comment because more comments + assert p is not None, f'INTERNAL ERROR: hand-made leaf without parent: {leaf!r}' + + prev = leaf.prev_sibling + if not prev: + prevp = preceding_leaf(p) + + if not prevp or prevp.type in OPENING_BRACKETS: + # Start of the line or a bracketed expression. + # More than one line for the comment. + return NO + + if prevp.type == token.EQUAL: + if prevp.parent and prevp.parent.type in { + syms.typedargslist, + syms.varargslist, + syms.parameters, + syms.arglist, + syms.argument, + }: + return NO + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -3,9 +3,9 @@ + + # leading comment + def f(): +- NO = "" +- SPACE = " " +- DOUBLESPACE = " " ++ NO = '' ++ SPACE = ' ' ++ DOUBLESPACE = ' ' + + t = leaf.type + p = leaf.parent # trailing comment +@@ -25,23 +25,30 @@ + return NO + + if prevp.type == token.EQUAL: +- if prevp.parent and prevp.parent.type in { +- syms.typedargslist, +- syms.varargslist, +- syms.parameters, +- syms.arglist, +- syms.argument, +- }: ++ if ( ++ prevp.parent ++ and prevp.parent.type ++ in { ++ syms.typedargslist, ++ syms.varargslist, ++ syms.parameters, ++ syms.arglist, ++ syms.argument, ++ } ++ ): + return NO +- + elif prevp.type == token.DOUBLESTAR: +- if prevp.parent and prevp.parent.type in { +- syms.typedargslist, +- syms.varargslist, +- syms.parameters, +- syms.arglist, +- syms.dictsetmaker, +- }: ++ if ( ++ prevp.parent ++ and prevp.parent.type ++ in { ++ syms.typedargslist, ++ syms.varargslist, ++ syms.parameters, ++ syms.arglist, ++ syms.dictsetmaker, ++ } ++ ): + return NO + + +@@ -49,11 +56,10 @@ + # SECTION BECAUSE SECTIONS + ############################################################################### + +- + def g(): +- NO = "" +- SPACE = " " +- DOUBLESPACE = " " ++ NO = '' ++ SPACE = ' ' ++ DOUBLESPACE = ' ' + + t = leaf.type + p = leaf.parent +@@ -67,7 +73,7 @@ + return DOUBLESPACE + + # Another comment because more comments +- assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}" ++ assert p is not None, f'INTERNAL ERROR: hand-made leaf without parent: {leaf!r}' + + prev = leaf.prev_sibling + if not prev: +@@ -79,11 +85,15 @@ + return NO + + if prevp.type == token.EQUAL: +- if prevp.parent and prevp.parent.type in { +- syms.typedargslist, +- syms.varargslist, +- syms.parameters, +- syms.arglist, +- syms.argument, +- }: +- return NO +\ No newline at end of file ++ if ( ++ prevp.parent ++ and prevp.parent.type ++ in { ++ syms.typedargslist, ++ syms.varargslist, ++ syms.parameters, ++ syms.arglist, ++ syms.argument, ++ } ++ ): ++ return NO + +``` + +## Ruff Output + +```py +"""Docstring.""" + + +# leading comment +def f(): + NO = '' + SPACE = ' ' + DOUBLESPACE = ' ' + + t = leaf.type + p = leaf.parent # trailing comment + v = leaf.value + + if t in ALWAYS_NO_SPACE: + pass + if t == token.COMMENT: # another trailing comment + return DOUBLESPACE + + assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}" + + prev = leaf.prev_sibling + if not prev: + prevp = preceding_leaf(p) + if not prevp or prevp.type in OPENING_BRACKETS: + return NO + + if prevp.type == token.EQUAL: + if ( + prevp.parent + and prevp.parent.type + in { + syms.typedargslist, + syms.varargslist, + syms.parameters, + syms.arglist, + syms.argument, + } + ): + return NO + elif prevp.type == token.DOUBLESTAR: + if ( + prevp.parent + and prevp.parent.type + in { + syms.typedargslist, + syms.varargslist, + syms.parameters, + syms.arglist, + syms.dictsetmaker, + } + ): + return NO + + +############################################################################### +# SECTION BECAUSE SECTIONS +############################################################################### + +def g(): + NO = '' + SPACE = ' ' + DOUBLESPACE = ' ' + + t = leaf.type + p = leaf.parent + v = leaf.value + + # Comment because comments + + if t in ALWAYS_NO_SPACE: + pass + if t == token.COMMENT: + return DOUBLESPACE + + # Another comment because more comments + assert p is not None, f'INTERNAL ERROR: hand-made leaf without parent: {leaf!r}' + + prev = leaf.prev_sibling + if not prev: + prevp = preceding_leaf(p) + + if not prevp or prevp.type in OPENING_BRACKETS: + # Start of the line or a bracketed expression. + # More than one line for the comment. + return NO + + if prevp.type == token.EQUAL: + if ( + prevp.parent + and prevp.parent.type + in { + syms.typedargslist, + syms.varargslist, + syms.parameters, + syms.arglist, + syms.argument, + } + ): + return NO + +``` + +## Black Output + +```py +"""Docstring.""" + + +# leading comment +def f(): + NO = "" + SPACE = " " + DOUBLESPACE = " " + + t = leaf.type + p = leaf.parent # trailing comment + v = leaf.value + + if t in ALWAYS_NO_SPACE: + pass + if t == token.COMMENT: # another trailing comment + return DOUBLESPACE + + assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}" + + prev = leaf.prev_sibling + if not prev: + prevp = preceding_leaf(p) + if not prevp or prevp.type in OPENING_BRACKETS: + return NO + + if prevp.type == token.EQUAL: + if prevp.parent and prevp.parent.type in { + syms.typedargslist, + syms.varargslist, + syms.parameters, + syms.arglist, + syms.argument, + }: + return NO + + elif prevp.type == token.DOUBLESTAR: + if prevp.parent and prevp.parent.type in { + syms.typedargslist, + syms.varargslist, + syms.parameters, + syms.arglist, + syms.dictsetmaker, + }: + return NO + + +############################################################################### +# SECTION BECAUSE SECTIONS +############################################################################### + + +def g(): + NO = "" + SPACE = " " + DOUBLESPACE = " " + + t = leaf.type + p = leaf.parent + v = leaf.value + + # Comment because comments + + if t in ALWAYS_NO_SPACE: + pass + if t == token.COMMENT: + return DOUBLESPACE + + # Another comment because more comments + assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}" + + prev = leaf.prev_sibling + if not prev: + prevp = preceding_leaf(p) + + if not prevp or prevp.type in OPENING_BRACKETS: + # Start of the line or a bracketed expression. + # More than one line for the comment. + return NO + + if prevp.type == token.EQUAL: + if prevp.parent and prevp.parent.type in { + syms.typedargslist, + syms.varargslist, + syms.parameters, + syms.arglist, + syms.argument, + }: + return NO +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap new file mode 100644 index 0000000000..9a6bfdee10 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap @@ -0,0 +1,1302 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +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,5 +1,6 @@ +-"some_string" +-b"\\xa3" ++... ++'some_string' ++b'\\xa3' + Name + None + True +@@ -35,10 +36,11 @@ + 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 ++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: { ++foo = lambda port_id, ++ignore_missing,: { + "port1": port1_resource, + "port2": port2_resource, + }[port_id] +@@ -52,11 +54,11 @@ + 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}} ++{'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 ++{'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) +@@ -88,33 +90,34 @@ + ] + {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) 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) 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"))} ++{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 ++ 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(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 ++ kwarg='hey', ++ **kwargs, + ) # note: no trailing comma pre-3.6 + call(*gidgets[:2]) + call(a, *gidgets[:2]) +@@ -122,8 +125,8 @@ + call(b, **self.screen_kwargs) + lukasz.langa.pl + call.me(maybe) +-(1).real +-(1.0).real ++1.real ++1.0.real + ....__class__ + list[str] + dict[str, int] +@@ -138,33 +141,33 @@ + 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_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = ( ++ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore ++) ++xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = ( ++ classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore + ) +-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__)) + ) +-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[::] + slice[:-1] +-slice[1:] ++slice[1::] + slice[::-1] + slice[d :: d + 1] + slice[:c, c - 1] + numpy[:, 0:1] + numpy[:, :-1] +-numpy[0, :] ++numpy[0, ::] + numpy[:, i] + numpy[0, :2] + numpy[:N, 0] + numpy[:2, :4] + numpy[2:4, 1:5] +-numpy[4:, 2:] ++numpy[4:, 2::] + numpy[:, (0, 1, 2, 5)] + numpy[0, [0]] + numpy[:, [i]] +@@ -172,17 +175,17 @@ + numpy[-(c + 1) :, d] + numpy[:, l[-2]] + numpy[:, ::-1] +-numpy[np.newaxis, :] ++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"} ++{'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) 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,) + { +@@ -201,30 +204,26 @@ + 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() ++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() ++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 = { +@@ -250,9 +249,9 @@ + 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 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,): + ... +@@ -272,7 +271,7 @@ + addr_proto, + addr_canonname, + addr_sockaddr, +-) in socket.getaddrinfo("google.com", "http"): ++) in socket.getaddrinfo('google.com', 'http'): + pass + a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp +@@ -327,13 +326,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 +@@ -341,7 +345,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 +@@ -366,5 +371,4 @@ + ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ) +-last_call() +-# standalone comment at ENDMARKER +\ No newline at end of file ++last_call()# standalone comment at ENDMARKER + +``` + +## Ruff 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(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore +) +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = ( + classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore +) +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = ( + classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) +) +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/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff2_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff2_py.snap new file mode 100644 index 0000000000..0549e77881 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff2_py.snap @@ -0,0 +1,210 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff2.py +--- +## Input + +```py +import pytest + +TmSt = 1 +TmEx = 2 + +# fmt: off + +# Test data: +# Position, Volume, State, TmSt/TmEx/None, [call, [arg1...]] + +@pytest.mark.parametrize('test', [ + + # Test don't manage the volume + [ + ('stuff', 'in') + ], +]) +def test_fader(test): + pass + +def check_fader(test): + + pass + +def verify_fader(test): + # misaligned comment + pass + +def verify_fader(test): + """Hey, ho.""" + assert test.passed() + +def test_calculate_fades(): + calcs = [ + # one is zero/none + (0, 4, 0, 0, 10, 0, 0, 6, 10), + (None, 4, 0, 0, 10, 0, 0, 6, 10), + ] + +# fmt: on + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -3,38 +3,42 @@ + TmSt = 1 + TmEx = 2 + ++ + # fmt: off + + # Test data: + # Position, Volume, State, TmSt/TmEx/None, [call, [arg1...]] +- +-@pytest.mark.parametrize('test', [ + +- # Test don't manage the volume ++@pytest.mark.parametrize( ++ 'test', + [ +- ('stuff', 'in') ++ # Test don't manage the volume ++ [('stuff', 'in')], + ], +-]) ++) + def test_fader(test): + pass + ++ + def check_fader(test): ++ pass + +- pass + + def verify_fader(test): +- # misaligned comment ++ # misaligned comment + pass + ++ + def verify_fader(test): + """Hey, ho.""" + assert test.passed() + ++ + def test_calculate_fades(): + calcs = [ +- # one is zero/none +- (0, 4, 0, 0, 10, 0, 0, 6, 10), +- (None, 4, 0, 0, 10, 0, 0, 6, 10), ++ (# one is zero/none ++ 0, 4, 0, 0, 10, 0, 0, 6, 10), ++ (None, 4, 0, 0, 10, 0, 0, 6, 10), + ] + +-# fmt: on +\ No newline at end of file ++# fmt: on + +``` + +## Ruff Output + +```py +import pytest + +TmSt = 1 +TmEx = 2 + + +# fmt: off + +# Test data: +# Position, Volume, State, TmSt/TmEx/None, [call, [arg1...]] + +@pytest.mark.parametrize( + 'test', + [ + # Test don't manage the volume + [('stuff', 'in')], + ], +) +def test_fader(test): + pass + + +def check_fader(test): + pass + + +def verify_fader(test): + # misaligned comment + pass + + +def verify_fader(test): + """Hey, ho.""" + assert test.passed() + + +def test_calculate_fades(): + calcs = [ + (# one is zero/none + 0, 4, 0, 0, 10, 0, 0, 6, 10), + (None, 4, 0, 0, 10, 0, 0, 6, 10), + ] + +# fmt: on + +``` + +## Black Output + +```py +import pytest + +TmSt = 1 +TmEx = 2 + +# fmt: off + +# Test data: +# Position, Volume, State, TmSt/TmEx/None, [call, [arg1...]] + +@pytest.mark.parametrize('test', [ + + # Test don't manage the volume + [ + ('stuff', 'in') + ], +]) +def test_fader(test): + pass + +def check_fader(test): + + pass + +def verify_fader(test): + # misaligned comment + pass + +def verify_fader(test): + """Hey, ho.""" + assert test.passed() + +def test_calculate_fades(): + calcs = [ + # one is zero/none + (0, 4, 0, 0, 10, 0, 0, 6, 10), + (None, 4, 0, 0, 10, 0, 0, 6, 10), + ] + +# fmt: on +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff3_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff3_py.snap new file mode 100644 index 0000000000..20c59c16e0 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff3_py.snap @@ -0,0 +1,104 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff3.py +--- +## Input + +```py +# fmt: off +x = [ + 1, 2, + 3, 4, +] +# fmt: on + +# fmt: off +x = [ + 1, 2, + 3, 4, +] +# fmt: on + +x = [ + 1, 2, 3, 4 +] + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,14 +1,18 @@ + # fmt: off + x = [ +- 1, 2, +- 3, 4, ++ 1, ++ 2, ++ 3, ++ 4, + ] + # fmt: on + + # fmt: off + x = [ +- 1, 2, +- 3, 4, ++ 1, ++ 2, ++ 3, ++ 4, + ] + # fmt: on + + +``` + +## Ruff Output + +```py +# fmt: off +x = [ + 1, + 2, + 3, + 4, +] +# fmt: on + +# fmt: off +x = [ + 1, + 2, + 3, + 4, +] +# fmt: on + +x = [1, 2, 3, 4] +``` + +## Black Output + +```py +# fmt: off +x = [ + 1, 2, + 3, 4, +] +# fmt: on + +# fmt: off +x = [ + 1, 2, + 3, 4, +] +# fmt: on + +x = [1, 2, 3, 4] +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff4_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff4_py.snap new file mode 100644 index 0000000000..8fe64114cd --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff4_py.snap @@ -0,0 +1,115 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff4.py +--- +## Input + +```py +# fmt: off +@test([ + 1, 2, + 3, 4, +]) +# fmt: on +def f(): pass + +@test([ + 1, 2, + 3, 4, +]) +def f(): pass + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,10 +1,14 @@ + # fmt: off +-@test([ +- 1, 2, +- 3, 4, +-]) +-# fmt: on ++@test( ++ [ ++ 1, ++ 2, ++ 3, ++ 4, ++ ] ++) + def f(): ++ # fmt: on + pass + + +@@ -17,4 +21,4 @@ + ] + ) + def f(): +- pass +\ No newline at end of file ++ pass + +``` + +## Ruff Output + +```py +# fmt: off +@test( + [ + 1, + 2, + 3, + 4, + ] +) +def f(): + # fmt: on + pass + + +@test( + [ + 1, + 2, + 3, + 4, + ] +) +def f(): + pass + +``` + +## Black Output + +```py +# fmt: off +@test([ + 1, 2, + 3, 4, +]) +# fmt: on +def f(): + pass + + +@test( + [ + 1, + 2, + 3, + 4, + ] +) +def f(): + pass +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff5_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff5_py.snap new file mode 100644 index 0000000000..624f6aa5ff --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff5_py.snap @@ -0,0 +1,378 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtonoff5.py +--- +## Input + +```py +# Regression test for https://github.com/psf/black/issues/3129. +setup( + entry_points={ + # fmt: off + "console_scripts": [ + "foo-bar" + "=foo.bar.:main", + # fmt: on + ] # Includes an formatted indentation. + }, +) + + +# Regression test for https://github.com/psf/black/issues/2015. +run( + # fmt: off + [ + "ls", + "-la", + ] + # fmt: on + + path, + check=True, +) + + +# Regression test for https://github.com/psf/black/issues/3026. +def test_func(): + # yapf: disable + if unformatted( args ): + return True + # yapf: enable + elif b: + return True + + return False + + +# Regression test for https://github.com/psf/black/issues/2567. +if True: + # fmt: off + for _ in range( 1 ): + # fmt: on + print ( "This won't be formatted" ) + print ( "This won't be formatted either" ) +else: + print ( "This will be formatted" ) + + +# Regression test for https://github.com/psf/black/issues/3184. +class A: + async def call(param): + if param: + # fmt: off + if param[0:4] in ( + "ABCD", "EFGH" + ) : + # fmt: on + print ( "This won't be formatted" ) + + elif param[0:4] in ("ZZZZ",): + print ( "This won't be formatted either" ) + + print ( "This will be formatted" ) + + +# Regression test for https://github.com/psf/black/issues/2985. +class Named(t.Protocol): + # fmt: off + @property + def this_wont_be_formatted ( self ) -> str: ... + +class Factory(t.Protocol): + def this_will_be_formatted ( self, **kwargs ) -> Named: ... + # fmt: on + + +# Regression test for https://github.com/psf/black/issues/3436. +if x: + return x +# fmt: off +elif unformatted: +# fmt: on + will_be_formatted () + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -5,8 +5,7 @@ + "console_scripts": [ + "foo-bar" + "=foo.bar.:main", +- # fmt: on +- ] # Includes an formatted indentation. ++ ], + }, + ) + +@@ -18,8 +17,8 @@ + "ls", + "-la", + ] +- # fmt: on +- + path, ++ + # fmt: on ++ path, + check=True, + ) + +@@ -27,9 +26,8 @@ + # Regression test for https://github.com/psf/black/issues/3026. + def test_func(): + # yapf: disable +- if unformatted( args ): ++ if unformatted(args): + return True +- # yapf: enable + elif b: + return True + +@@ -39,10 +37,10 @@ + # Regression test for https://github.com/psf/black/issues/2567. + if True: + # fmt: off +- for _ in range( 1 ): +- # fmt: on +- print ( "This won't be formatted" ) +- print ( "This won't be formatted either" ) ++ for _ in range(1): ++ # fmt: on ++ print("This won't be formatted") ++ print("This won't be formatted either") + else: + print("This will be formatted") + +@@ -52,14 +50,11 @@ + async def call(param): + if param: + # fmt: off +- if param[0:4] in ( +- "ABCD", "EFGH" +- ) : ++ if param[0:4] in ("ABCD", "EFGH"): + # fmt: on +- print ( "This won't be formatted" ) +- ++ print("This won't be formatted") + elif param[0:4] in ("ZZZZ",): +- print ( "This won't be formatted either" ) ++ print("This won't be formatted either") + + print("This will be formatted") + +@@ -68,20 +63,21 @@ + class Named(t.Protocol): + # fmt: off + @property +- def this_wont_be_formatted ( self ) -> str: ... ++ def this_wont_be_formatted(self) -> str: ++ ... + + + class Factory(t.Protocol): + def this_will_be_formatted(self, **kwargs) -> Named: + ... + +- # fmt: on + ++# fmt: on ++ + + # Regression test for https://github.com/psf/black/issues/3436. + if x: + return x +-# fmt: off +-elif unformatted: ++elif unformatted: + # fmt: on +- will_be_formatted() +\ No newline at end of file ++ will_be_formatted() + +``` + +## Ruff Output + +```py +# Regression test for https://github.com/psf/black/issues/3129. +setup( + entry_points={ + # fmt: off + "console_scripts": [ + "foo-bar" + "=foo.bar.:main", + ], + }, +) + + +# Regression test for https://github.com/psf/black/issues/2015. +run( + # fmt: off + [ + "ls", + "-la", + ] + + # fmt: on + path, + check=True, +) + + +# Regression test for https://github.com/psf/black/issues/3026. +def test_func(): + # yapf: disable + if unformatted(args): + return True + elif b: + return True + + return False + + +# Regression test for https://github.com/psf/black/issues/2567. +if True: + # fmt: off + for _ in range(1): + # fmt: on + print("This won't be formatted") + print("This won't be formatted either") +else: + print("This will be formatted") + + +# Regression test for https://github.com/psf/black/issues/3184. +class A: + async def call(param): + if param: + # fmt: off + if param[0:4] in ("ABCD", "EFGH"): + # fmt: on + print("This won't be formatted") + elif param[0:4] in ("ZZZZ",): + print("This won't be formatted either") + + print("This will be formatted") + + +# Regression test for https://github.com/psf/black/issues/2985. +class Named(t.Protocol): + # fmt: off + @property + def this_wont_be_formatted(self) -> str: + ... + + +class Factory(t.Protocol): + def this_will_be_formatted(self, **kwargs) -> Named: + ... + + +# fmt: on + + +# Regression test for https://github.com/psf/black/issues/3436. +if x: + return x +elif unformatted: + # fmt: on + will_be_formatted() + +``` + +## Black Output + +```py +# Regression test for https://github.com/psf/black/issues/3129. +setup( + entry_points={ + # fmt: off + "console_scripts": [ + "foo-bar" + "=foo.bar.:main", + # fmt: on + ] # Includes an formatted indentation. + }, +) + + +# Regression test for https://github.com/psf/black/issues/2015. +run( + # fmt: off + [ + "ls", + "-la", + ] + # fmt: on + + path, + check=True, +) + + +# Regression test for https://github.com/psf/black/issues/3026. +def test_func(): + # yapf: disable + if unformatted( args ): + return True + # yapf: enable + elif b: + return True + + return False + + +# Regression test for https://github.com/psf/black/issues/2567. +if True: + # fmt: off + for _ in range( 1 ): + # fmt: on + print ( "This won't be formatted" ) + print ( "This won't be formatted either" ) +else: + print("This will be formatted") + + +# Regression test for https://github.com/psf/black/issues/3184. +class A: + async def call(param): + if param: + # fmt: off + if param[0:4] in ( + "ABCD", "EFGH" + ) : + # fmt: on + print ( "This won't be formatted" ) + + elif param[0:4] in ("ZZZZ",): + print ( "This won't be formatted either" ) + + print("This will be formatted") + + +# Regression test for https://github.com/psf/black/issues/2985. +class Named(t.Protocol): + # fmt: off + @property + def this_wont_be_formatted ( self ) -> str: ... + + +class Factory(t.Protocol): + def this_will_be_formatted(self, **kwargs) -> Named: + ... + + # fmt: on + + +# Regression test for https://github.com/psf/black/issues/3436. +if x: + return x +# fmt: off +elif unformatted: + # fmt: on + will_be_formatted() +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip2_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip2_py.snap new file mode 100644 index 0000000000..86efc9430f --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip2_py.snap @@ -0,0 +1,72 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip2.py +--- +## Input + +```py +l1 = ["This list should be broken up", "into multiple lines", "because it is way too long"] +l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] # fmt: skip +l3 = ["I have", "trailing comma", "so I should be braked",] + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -3,7 +3,11 @@ + "into multiple lines", + "because it is way too long", + ] +-l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] # fmt: skip ++l2 = [ ++ "But this list shouldn't", ++ "even though it also has", ++ "way too many characters in it", ++] # fmt: skip + l3 = [ + "I have", + "trailing comma", + +``` + +## Ruff Output + +```py +l1 = [ + "This list should be broken up", + "into multiple lines", + "because it is way too long", +] +l2 = [ + "But this list shouldn't", + "even though it also has", + "way too many characters in it", +] # fmt: skip +l3 = [ + "I have", + "trailing comma", + "so I should be braked", +] +``` + +## Black Output + +```py +l1 = [ + "This list should be broken up", + "into multiple lines", + "because it is way too long", +] +l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] # fmt: skip +l3 = [ + "I have", + "trailing comma", + "so I should be braked", +] +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip3_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip3_py.snap new file mode 100644 index 0000000000..5cbf4112dd --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip3_py.snap @@ -0,0 +1,67 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip3.py +--- +## Input + +```py +a = 3 +# fmt: off +b, c = 1, 2 +d = 6 # fmt: skip +e = 5 +# fmt: on +f = ["This is a very long line that should be formatted into a clearer line ", "by rearranging."] + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,7 +1,7 @@ + a = 3 + # fmt: off +-b, c = 1, 2 +-d = 6 # fmt: skip ++b, c = 1, 2 ++d = 6 # fmt: skip + e = 5 + # fmt: on + f = [ + +``` + +## Ruff Output + +```py +a = 3 +# fmt: off +b, c = 1, 2 +d = 6 # fmt: skip +e = 5 +# fmt: on +f = [ + "This is a very long line that should be formatted into a clearer line ", + "by rearranging.", +] +``` + +## Black Output + +```py +a = 3 +# fmt: off +b, c = 1, 2 +d = 6 # fmt: skip +e = 5 +# fmt: on +f = [ + "This is a very long line that should be formatted into a clearer line ", + "by rearranging.", +] +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip5_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip5_py.snap new file mode 100644 index 0000000000..a82a9470a2 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip5_py.snap @@ -0,0 +1,67 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip5.py +--- +## Input + +```py +a, b, c = 3, 4, 5 +if ( + a == 3 + and b != 9 # fmt: skip + and c is not None +): + print("I'm good!") +else: + print("I'm bad") + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,9 +1,5 @@ + a, b, c = 3, 4, 5 +-if ( +- a == 3 +- and b != 9 # fmt: skip +- and c is not None +-): ++if a == 3 and b != 9 and c is not None: # fmt: skip + print("I'm good!") + else: +- print("I'm bad") +\ No newline at end of file ++ print("I'm bad") + +``` + +## Ruff Output + +```py +a, b, c = 3, 4, 5 +if a == 3 and b != 9 and c is not None: # fmt: skip + print("I'm good!") +else: + print("I'm bad") + +``` + +## Black Output + +```py +a, b, c = 3, 4, 5 +if ( + a == 3 + and b != 9 # fmt: skip + and c is not None +): + print("I'm good!") +else: + print("I'm bad") +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip6_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip6_py.snap new file mode 100644 index 0000000000..03ecfe5c8c --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip6_py.snap @@ -0,0 +1,53 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip6.py +--- +## Input + +```py +class A: + def f(self): + for line in range(10): + if True: + pass # fmt: skip + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -2,4 +2,4 @@ + def f(self): + for line in range(10): + if True: +- pass # fmt: skip +\ No newline at end of file ++ pass + +``` + +## Ruff Output + +```py +class A: + def f(self): + for line in range(10): + if True: + pass + +``` + +## Black Output + +```py +class A: + def f(self): + for line in range(10): + if True: + pass # fmt: skip +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip7_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip7_py.snap new file mode 100644 index 0000000000..f470fd7332 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip7_py.snap @@ -0,0 +1,52 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip7.py +--- +## Input + +```py +a = "this is some code" +b = 5 #fmt:skip +c = 9 #fmt: skip +d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" #fmt:skip + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,4 +1,4 @@ + a = "this is some code" +-b = 5 # fmt:skip +-c = 9 # fmt: skip +-d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip +\ No newline at end of file ++b = 5 #fmt:skip ++c = 9 #fmt: skip ++d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" #fmt:skip +\ No newline at end of file + +``` + +## Ruff Output + +```py +a = "this is some code" +b = 5 #fmt:skip +c = 9 #fmt: skip +d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" #fmt:skip +``` + +## Black Output + +```py +a = "this is some code" +b = 5 # fmt:skip +c = 9 # fmt: skip +d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip_py.snap new file mode 100644 index 0000000000..ba9c9f7837 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip_py.snap @@ -0,0 +1,45 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip.py +--- +## Input + +```py +a, b = 1, 2 +c = 6 # fmt: skip +d = 5 + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,3 +1,3 @@ + a, b = 1, 2 +-c = 6 # fmt: skip ++c = 6 # fmt: skip + d = 5 +\ No newline at end of file + +``` + +## Ruff Output + +```py +a, b = 1, 2 +c = 6 # fmt: skip +d = 5 +``` + +## Black Output + +```py +a, b = 1, 2 +c = 6 # fmt: skip +d = 5 +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fstring_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fstring_py.snap new file mode 100644 index 0000000000..a7da17e03c --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fstring_py.snap @@ -0,0 +1,65 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fstring.py +--- +## Input + +```py +f"f-string without formatted values is just a string" +f"{{NOT a formatted value}}" +f"{{NOT 'a' \"formatted\" \"value\"}}" +f"some f-string with {a} {few():.2f} {formatted.values!r}" +f'some f-string with {a} {few(""):.2f} {formatted.values!r}' +f"{f'''{'nested'} inner'''} outer" +f"\"{f'{nested} inner'}\" outer" +f"space between opening braces: { {a for a in (1, 2, 3)}}" +f'Hello \'{tricky + "example"}\'' + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,6 +1,6 @@ + f"f-string without formatted values is just a string" + f"{{NOT a formatted value}}" +-f'{{NOT \'a\' "formatted" "value"}}' ++f"{{NOT 'a' \"formatted\" \"value\"}}" + f"some f-string with {a} {few():.2f} {formatted.values!r}" + f'some f-string with {a} {few(""):.2f} {formatted.values!r}' + f"{f'''{'nested'} inner'''} outer" + +``` + +## Ruff Output + +```py +f"f-string without formatted values is just a string" +f"{{NOT a formatted value}}" +f"{{NOT 'a' \"formatted\" \"value\"}}" +f"some f-string with {a} {few():.2f} {formatted.values!r}" +f'some f-string with {a} {few(""):.2f} {formatted.values!r}' +f"{f'''{'nested'} inner'''} outer" +f"\"{f'{nested} inner'}\" outer" +f"space between opening braces: { {a for a in (1, 2, 3)}}" +f'Hello \'{tricky + "example"}\'' +``` + +## Black Output + +```py +f"f-string without formatted values is just a string" +f"{{NOT a formatted value}}" +f'{{NOT \'a\' "formatted" "value"}}' +f"some f-string with {a} {few():.2f} {formatted.values!r}" +f'some f-string with {a} {few(""):.2f} {formatted.values!r}' +f"{f'''{'nested'} inner'''} outer" +f"\"{f'{nested} inner'}\" outer" +f"space between opening braces: { {a for a in (1, 2, 3)}}" +f'Hello \'{tricky + "example"}\'' +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function2_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function2_py.snap new file mode 100644 index 0000000000..0a878dbab0 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function2_py.snap @@ -0,0 +1,223 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function2.py +--- +## Input + +```py +def f( + a, + **kwargs, +) -> A: + with cache_dir(): + if something: + result = ( + CliRunner().invoke(black.main, [str(src1), str(src2), "--diff", "--check"]) + ) + limited.append(-limited.pop()) # negate top + return A( + very_long_argument_name1=very_long_value_for_the_argument, + very_long_argument_name2=-very.long.value.for_the_argument, + **kwargs, + ) +def g(): + "Docstring." + def inner(): + pass + print("Inner defs should breathe a little.") +def h(): + def inner(): + pass + print("Inner defs should breathe a little.") + + +if os.name == "posix": + import termios + def i_should_be_followed_by_only_one_newline(): + pass +elif os.name == "nt": + try: + import msvcrt + def i_should_be_followed_by_only_one_newline(): + pass + + except ImportError: + + def i_should_be_followed_by_only_one_newline(): + pass + +elif False: + + class IHopeYouAreHavingALovelyDay: + def __call__(self): + print("i_should_be_followed_by_only_one_newline") +else: + + def foo(): + pass + +with hmm_but_this_should_get_two_preceding_newlines(): + pass + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -63,4 +63,4 @@ + + + with hmm_but_this_should_get_two_preceding_newlines(): +- pass +\ No newline at end of file ++ pass + +``` + +## Ruff Output + +```py +def f( + a, + **kwargs, +) -> A: + with cache_dir(): + if something: + result = CliRunner().invoke( + black.main, + [str(src1), str(src2), "--diff", "--check"], + ) + limited.append(-limited.pop()) # negate top + return A( + very_long_argument_name1=very_long_value_for_the_argument, + very_long_argument_name2=-very.long.value.for_the_argument, + **kwargs, + ) + + +def g(): + "Docstring." + + def inner(): + pass + + print("Inner defs should breathe a little.") + + +def h(): + def inner(): + pass + + print("Inner defs should breathe a little.") + + +if os.name == "posix": + import termios + + def i_should_be_followed_by_only_one_newline(): + pass + +elif os.name == "nt": + try: + import msvcrt + + def i_should_be_followed_by_only_one_newline(): + pass + + except ImportError: + + def i_should_be_followed_by_only_one_newline(): + pass + +elif False: + + class IHopeYouAreHavingALovelyDay: + def __call__(self): + print("i_should_be_followed_by_only_one_newline") + +else: + + def foo(): + pass + + +with hmm_but_this_should_get_two_preceding_newlines(): + pass + +``` + +## Black Output + +```py +def f( + a, + **kwargs, +) -> A: + with cache_dir(): + if something: + result = CliRunner().invoke( + black.main, + [str(src1), str(src2), "--diff", "--check"], + ) + limited.append(-limited.pop()) # negate top + return A( + very_long_argument_name1=very_long_value_for_the_argument, + very_long_argument_name2=-very.long.value.for_the_argument, + **kwargs, + ) + + +def g(): + "Docstring." + + def inner(): + pass + + print("Inner defs should breathe a little.") + + +def h(): + def inner(): + pass + + print("Inner defs should breathe a little.") + + +if os.name == "posix": + import termios + + def i_should_be_followed_by_only_one_newline(): + pass + +elif os.name == "nt": + try: + import msvcrt + + def i_should_be_followed_by_only_one_newline(): + pass + + except ImportError: + + def i_should_be_followed_by_only_one_newline(): + pass + +elif False: + + class IHopeYouAreHavingALovelyDay: + def __call__(self): + print("i_should_be_followed_by_only_one_newline") + +else: + + def foo(): + pass + + +with hmm_but_this_should_get_two_preceding_newlines(): + pass +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap new file mode 100644 index 0000000000..0c12901b77 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap @@ -0,0 +1,493 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +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) + +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 +@@ -6,7 +6,7 @@ + + from library import some_connection, some_decorator + +-f"trigger 3.6 mode" ++f'trigger 3.6 mode' + + + def func_no_args(): +@@ -27,7 +27,7 @@ + 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 conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2) + await asyncio.sleep(1) + + +@@ -44,7 +44,7 @@ + return text[number:-1] + + +-def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""): ++def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r''): + offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200000))) + assert task._cancel_stack[: len(old_stack)] == old_stack + +@@ -58,25 +58,20 @@ + f: int = -1, + g: int = 1 if False else 2, + h: str = "", +- i: str = r"", ++ i: str = r'', + ): + ... + + + def spaces2(result=_core.Value(None)): +- assert fut is self._read_fut, (fut, self._read_fut) ++ 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() +- ) ++ 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(): +@@ -135,14 +130,13 @@ + a, + **kwargs, + ) -> A: +- return ( ++ 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) +\ No newline at end of file ++ return yield + +``` + +## 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'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 + +``` + +## 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/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap new file mode 100644 index 0000000000..578d604fbb --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap @@ -0,0 +1,419 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function_trailing_comma.py +--- +## Input + +```py +def f(a,): + d = {'key': 'value',} + tup = (1,) + +def f2(a,b,): + d = {'key': 'value', 'key2': 'value2',} + tup = (1,2,) + +def f(a:int=1,): + call(arg={'explode': 'this',}) + call2(arg=[1,2,3],) + x = { + "a": 1, + "b": 2, + }["a"] + if a == {"a": 1,"b": 2,"c": 3,"d": 4,"e": 5,"f": 6,"g": 7,"h": 8,}["a"]: + pass + +def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[ + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +]: + json = {"k": {"k2": {"k3": [1,]}}} + + + +# The type annotation shouldn't get a trailing comma since that would change its type. +# Relevant bug report: https://github.com/psf/black/issues/2381. +def some_function_with_a_really_long_name() -> ( + returning_a_deeply_nested_import_of_a_type_i_suppose +): + pass + + +def some_method_with_a_really_long_name(very_long_parameter_so_yeah: str, another_long_parameter: int) -> ( + another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not +): + pass + + +def func() -> ( + also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(this_shouldn_t_get_a_trailing_comma_too) +): + pass + + +def func() -> ((also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( + this_shouldn_t_get_a_trailing_comma_too + )) +): + pass + + +# Make sure inner one-element tuple won't explode +some_module.some_function( + argument1, (one_element_tuple,), argument4, argument5, argument6 +) + +# Inner trailing comma causes outer to explode +some_module.some_function( + argument1, (one, two,), argument4, argument5, argument6 +) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -2,7 +2,7 @@ + a, + ): + d = { +- "key": "value", ++ 'key': 'value', + } + tup = (1,) + +@@ -12,8 +12,8 @@ + b, + ): + d = { +- "key": "value", +- "key2": "value2", ++ 'key': 'value', ++ 'key2': 'value2', + } + tup = ( + 1, +@@ -26,7 +26,7 @@ + ): + call( + arg={ +- "explode": "this", ++ 'explode': 'this', + } + ) + call2( +@@ -52,53 +52,52 @@ + pass + + +-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( +- Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] +-): ++def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[ ++ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ++]: + json = { + "k": { + "k2": { + "k3": [ + 1, +- ] +- } +- } ++ ], ++ }, ++ }, + } + + + # The type annotation shouldn't get a trailing comma since that would change its type. + # Relevant bug report: https://github.com/psf/black/issues/2381. +-def some_function_with_a_really_long_name() -> ( +- returning_a_deeply_nested_import_of_a_type_i_suppose +-): ++def some_function_with_a_really_long_name() -> returning_a_deeply_nested_import_of_a_type_i_suppose: + pass + + + def some_method_with_a_really_long_name( +- very_long_parameter_so_yeah: str, another_long_parameter: int ++ very_long_parameter_so_yeah: str, ++ another_long_parameter: int, + ) -> another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not: + pass + + +-def func() -> ( +- also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( +- this_shouldn_t_get_a_trailing_comma_too +- ) ++def func() -> also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( ++ this_shouldn_t_get_a_trailing_comma_too + ): + pass + + +-def func() -> ( +- also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( +- this_shouldn_t_get_a_trailing_comma_too +- ) ++def func() -> also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( ++ this_shouldn_t_get_a_trailing_comma_too + ): + pass + + + # Make sure inner one-element tuple won't explode + some_module.some_function( +- argument1, (one_element_tuple,), argument4, argument5, argument6 ++ argument1, ++ (one_element_tuple,), ++ argument4, ++ argument5, ++ argument6, + ) + + # Inner trailing comma causes outer to explode + +``` + +## Ruff Output + +```py +def f( + a, +): + d = { + 'key': 'value', + } + tup = (1,) + + +def f2( + a, + b, +): + d = { + 'key': 'value', + 'key2': 'value2', + } + tup = ( + 1, + 2, + ) + + +def f( + a: int = 1, +): + call( + arg={ + 'explode': 'this', + } + ) + call2( + arg=[1, 2, 3], + ) + x = { + "a": 1, + "b": 2, + }["a"] + if ( + a + == { + "a": 1, + "b": 2, + "c": 3, + "d": 4, + "e": 5, + "f": 6, + "g": 7, + "h": 8, + }["a"] + ): + pass + + +def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[ + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +]: + json = { + "k": { + "k2": { + "k3": [ + 1, + ], + }, + }, + } + + +# The type annotation shouldn't get a trailing comma since that would change its type. +# Relevant bug report: https://github.com/psf/black/issues/2381. +def some_function_with_a_really_long_name() -> returning_a_deeply_nested_import_of_a_type_i_suppose: + pass + + +def some_method_with_a_really_long_name( + very_long_parameter_so_yeah: str, + another_long_parameter: int, +) -> another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not: + pass + + +def func() -> also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( + this_shouldn_t_get_a_trailing_comma_too +): + pass + + +def func() -> also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( + this_shouldn_t_get_a_trailing_comma_too +): + pass + + +# Make sure inner one-element tuple won't explode +some_module.some_function( + argument1, + (one_element_tuple,), + argument4, + argument5, + argument6, +) + +# Inner trailing comma causes outer to explode +some_module.some_function( + argument1, + ( + one, + two, + ), + argument4, + argument5, + argument6, +) +``` + +## Black Output + +```py +def f( + a, +): + d = { + "key": "value", + } + tup = (1,) + + +def f2( + a, + b, +): + d = { + "key": "value", + "key2": "value2", + } + tup = ( + 1, + 2, + ) + + +def f( + a: int = 1, +): + call( + arg={ + "explode": "this", + } + ) + call2( + arg=[1, 2, 3], + ) + x = { + "a": 1, + "b": 2, + }["a"] + if ( + a + == { + "a": 1, + "b": 2, + "c": 3, + "d": 4, + "e": 5, + "f": 6, + "g": 7, + "h": 8, + }["a"] + ): + pass + + +def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( + Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] +): + json = { + "k": { + "k2": { + "k3": [ + 1, + ] + } + } + } + + +# The type annotation shouldn't get a trailing comma since that would change its type. +# Relevant bug report: https://github.com/psf/black/issues/2381. +def some_function_with_a_really_long_name() -> ( + returning_a_deeply_nested_import_of_a_type_i_suppose +): + pass + + +def some_method_with_a_really_long_name( + very_long_parameter_so_yeah: str, another_long_parameter: int +) -> another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not: + pass + + +def func() -> ( + also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( + this_shouldn_t_get_a_trailing_comma_too + ) +): + pass + + +def func() -> ( + also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( + this_shouldn_t_get_a_trailing_comma_too + ) +): + pass + + +# Make sure inner one-element tuple won't explode +some_module.some_function( + argument1, (one_element_tuple,), argument4, argument5, argument6 +) + +# Inner trailing comma causes outer to explode +some_module.some_function( + argument1, + ( + one, + two, + ), + argument4, + argument5, + argument6, +) +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_await_parens_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_await_parens_py.snap new file mode 100644 index 0000000000..9c96d7df54 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_await_parens_py.snap @@ -0,0 +1,407 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +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 +@@ -8,59 +8,63 @@ + + # Remove brackets for short coroutine/task + async def main(): +- await asyncio.sleep(1) ++ await (asyncio.sleep(1)) + + + async def main(): +- await asyncio.sleep(1) ++ await (asyncio.sleep(1)) + + + async def main(): +- await asyncio.sleep(1) ++ await (asyncio.sleep(1)) + + + # Check comments + async def main(): +- await asyncio.sleep(1) # Hello ++ await (asyncio.sleep(1)) + + + async def main(): +- await asyncio.sleep(1) # Hello ++ await (asyncio.sleep(1)) # Hello + + +-async def main(): +- await asyncio.sleep(1) # Hello ++async def main(): # Hello ++ await (asyncio.sleep(1)) + + + # 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), ++ ( ++ 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), ++ ( ++ 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) ++ await (black(1)) + + + # Keep brackets around non power operations and nested awaits +@@ -82,12 +86,12 @@ + + + async def main(): +- await (await asyncio.sleep(1)) ++ await (await (asyncio.sleep(1))) + + + async def main(): +- await (await (await (await (await asyncio.sleep(1))))) ++ await (await (await (await (await (asyncio.sleep(1)))))) + + + async def main(): +- await (yield) +\ No newline at end of file ++ await (yield) + +``` + +## 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 (asyncio.sleep(1)) + + +async def main(): + await (asyncio.sleep(1)) # Hello + + +async def main(): # Hello + await (asyncio.sleep(1)) + + +# 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/src/snapshots/ruff_python_formatter__tests__black_test__remove_except_parens_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_except_parens_py.snap new file mode 100644 index 0000000000..75c880d728 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_except_parens_py.snap @@ -0,0 +1,175 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_except_parens.py +--- +## Input + +```py +# These brackets are redundant, therefore remove. +try: + a.something +except (AttributeError) as err: + raise err + +# This is tuple of exceptions. +# Although this could be replaced with just the exception, +# we do not remove brackets to preserve AST. +try: + a.something +except (AttributeError,) as err: + raise err + +# This is a tuple of exceptions. Do not remove brackets. +try: + a.something +except (AttributeError, ValueError) as err: + raise err + +# Test long variants. +try: + a.something +except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error) as err: + raise err + +try: + a.something +except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,) as err: + raise err + +try: + a.something +except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error) as err: + raise err + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,7 +1,7 @@ + # These brackets are redundant, therefore remove. + try: + a.something +-except AttributeError as err: ++except (AttributeError) as err: + raise err + + # This is tuple of exceptions. +@@ -21,9 +21,7 @@ + # Test long variants. + try: + a.something +-except ( +- some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error +-) as err: ++except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error) as err: + raise err + + try: +@@ -39,4 +37,4 @@ + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, + ) as err: +- raise err +\ No newline at end of file ++ raise err + +``` + +## Ruff Output + +```py +# These brackets are redundant, therefore remove. +try: + a.something +except (AttributeError) as err: + raise err + +# This is tuple of exceptions. +# Although this could be replaced with just the exception, +# we do not remove brackets to preserve AST. +try: + a.something +except (AttributeError,) as err: + raise err + +# This is a tuple of exceptions. Do not remove brackets. +try: + a.something +except (AttributeError, ValueError) as err: + raise err + +# Test long variants. +try: + a.something +except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error) as err: + raise err + +try: + a.something +except ( + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, +) as err: + raise err + +try: + a.something +except ( + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, +) as err: + raise err + +``` + +## Black Output + +```py +# These brackets are redundant, therefore remove. +try: + a.something +except AttributeError as err: + raise err + +# This is tuple of exceptions. +# Although this could be replaced with just the exception, +# we do not remove brackets to preserve AST. +try: + a.something +except (AttributeError,) as err: + raise err + +# This is a tuple of exceptions. Do not remove brackets. +try: + a.something +except (AttributeError, ValueError) as err: + raise err + +# Test long variants. +try: + a.something +except ( + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error +) as err: + raise err + +try: + a.something +except ( + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, +) as err: + raise err + +try: + a.something +except ( + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, +) as err: + raise err +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_for_brackets_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_for_brackets_py.snap new file mode 100644 index 0000000000..19687a4bc8 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_for_brackets_py.snap @@ -0,0 +1,126 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_for_brackets.py +--- +## Input + +```py +# Only remove tuple brackets after `for` +for (k, v) in d.items(): + print(k, v) + +# Don't touch tuple brackets after `in` +for module in (core, _unicodefun): + if hasattr(module, "_verify_python3_env"): + module._verify_python3_env = lambda: None + +# Brackets remain for long for loop lines +for (why_would_anyone_choose_to_name_a_loop_variable_with_a_name_this_long, i_dont_know_but_we_should_still_check_the_behaviour_if_they_do) in d.items(): + print(k, v) + +for (k, v) in dfkasdjfldsjflkdsjflkdsjfdslkfjldsjfgkjdshgkljjdsfldgkhsdofudsfudsofajdslkfjdslkfjldisfjdffjsdlkfjdlkjjkdflskadjldkfjsalkfjdasj.items(): + print(k, v) + +# Test deeply nested brackets +for (((((k, v))))) in d.items(): + print(k, v) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -3,7 +3,7 @@ + print(k, v) + + # Don't touch tuple brackets after `in` +-for module in (core, _unicodefun): ++for module in core, _unicodefun: + if hasattr(module, "_verify_python3_env"): + module._verify_python3_env = lambda: None + +@@ -17,11 +17,9 @@ + for ( + k, + v, +-) in ( +- dfkasdjfldsjflkdsjflkdsjfdslkfjldsjfgkjdshgkljjdsfldgkhsdofudsfudsofajdslkfjdslkfjldisfjdffjsdlkfjdlkjjkdflskadjldkfjsalkfjdasj.items() +-): ++) in dfkasdjfldsjflkdsjflkdsjfdslkfjldsjfgkjdshgkljjdsfldgkhsdofudsfudsofajdslkfjdslkfjldisfjdffjsdlkfjdlkjjkdflskadjldkfjsalkfjdasj.items(): + print(k, v) + + # Test deeply nested brackets + for k, v in d.items(): +- print(k, v) +\ No newline at end of file ++ print(k, v) + +``` + +## Ruff Output + +```py +# Only remove tuple brackets after `for` +for k, v in d.items(): + print(k, v) + +# Don't touch tuple brackets after `in` +for module in core, _unicodefun: + if hasattr(module, "_verify_python3_env"): + module._verify_python3_env = lambda: None + +# Brackets remain for long for loop lines +for ( + why_would_anyone_choose_to_name_a_loop_variable_with_a_name_this_long, + i_dont_know_but_we_should_still_check_the_behaviour_if_they_do, +) in d.items(): + print(k, v) + +for ( + k, + v, +) in dfkasdjfldsjflkdsjflkdsjfdslkfjldsjfgkjdshgkljjdsfldgkhsdofudsfudsofajdslkfjdslkfjldisfjdffjsdlkfjdlkjjkdflskadjldkfjsalkfjdasj.items(): + print(k, v) + +# Test deeply nested brackets +for k, v in d.items(): + print(k, v) + +``` + +## Black Output + +```py +# Only remove tuple brackets after `for` +for k, v in d.items(): + print(k, v) + +# Don't touch tuple brackets after `in` +for module in (core, _unicodefun): + if hasattr(module, "_verify_python3_env"): + module._verify_python3_env = lambda: None + +# Brackets remain for long for loop lines +for ( + why_would_anyone_choose_to_name_a_loop_variable_with_a_name_this_long, + i_dont_know_but_we_should_still_check_the_behaviour_if_they_do, +) in d.items(): + print(k, v) + +for ( + k, + v, +) in ( + dfkasdjfldsjflkdsjflkdsjfdslkfjldsjfgkjdshgkljjdsfldgkhsdofudsfudsofajdslkfjdslkfjldisfjdffjsdlkfjdlkjjkdflskadjldkfjsalkfjdasj.items() +): + print(k, v) + +# Test deeply nested brackets +for k, v in d.items(): + print(k, v) +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_newline_after_code_block_open_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_newline_after_code_block_open_py.snap new file mode 100644 index 0000000000..682a891c50 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_newline_after_code_block_open_py.snap @@ -0,0 +1,302 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_newline_after_code_block_open.py +--- +## Input + +```py +import random + + +def foo1(): + + print("The newline above me should be deleted!") + + +def foo2(): + + + + print("All the newlines above me should be deleted!") + + +def foo3(): + + print("No newline above me!") + + print("There is a newline above me, and that's OK!") + + +def foo4(): + + # There is a comment here + + print("The newline above me should not be deleted!") + + +class Foo: + def bar(self): + + print("The newline above me should be deleted!") + + +for i in range(5): + + print(f"{i}) The line above me should be removed!") + + +for i in range(5): + + + + print(f"{i}) The lines above me should be removed!") + + +for i in range(5): + + for j in range(7): + + print(f"{i}) The lines above me should be removed!") + + +if random.randint(0, 3) == 0: + + print("The new line above me is about to be removed!") + + +if random.randint(0, 3) == 0: + + + + + print("The new lines above me is about to be removed!") + + +if random.randint(0, 3) == 0: + if random.uniform(0, 1) > 0.5: + print("Two lines above me are about to be removed!") + + +while True: + + print("The newline above me should be deleted!") + + +while True: + + + + print("The newlines above me should be deleted!") + + +while True: + + while False: + + print("The newlines above me should be deleted!") + + +with open("/path/to/file.txt", mode="w") as file: + + file.write("The new line above me is about to be removed!") + + +with open("/path/to/file.txt", mode="w") as file: + + + + file.write("The new lines above me is about to be removed!") + + +with open("/path/to/file.txt", mode="r") as read_file: + + with open("/path/to/output_file.txt", mode="w") as write_file: + + write_file.writelines(read_file.readlines()) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -75,4 +75,4 @@ + + with open("/path/to/file.txt", mode="r") as read_file: + with open("/path/to/output_file.txt", mode="w") as write_file: +- write_file.writelines(read_file.readlines()) +\ No newline at end of file ++ write_file.writelines(read_file.readlines()) + +``` + +## Ruff Output + +```py +import random + + +def foo1(): + print("The newline above me should be deleted!") + + +def foo2(): + print("All the newlines above me should be deleted!") + + +def foo3(): + print("No newline above me!") + + print("There is a newline above me, and that's OK!") + + +def foo4(): + # There is a comment here + + print("The newline above me should not be deleted!") + + +class Foo: + def bar(self): + print("The newline above me should be deleted!") + + +for i in range(5): + print(f"{i}) The line above me should be removed!") + + +for i in range(5): + print(f"{i}) The lines above me should be removed!") + + +for i in range(5): + for j in range(7): + print(f"{i}) The lines above me should be removed!") + + +if random.randint(0, 3) == 0: + print("The new line above me is about to be removed!") + + +if random.randint(0, 3) == 0: + print("The new lines above me is about to be removed!") + + +if random.randint(0, 3) == 0: + if random.uniform(0, 1) > 0.5: + print("Two lines above me are about to be removed!") + + +while True: + print("The newline above me should be deleted!") + + +while True: + print("The newlines above me should be deleted!") + + +while True: + while False: + print("The newlines above me should be deleted!") + + +with open("/path/to/file.txt", mode="w") as file: + file.write("The new line above me is about to be removed!") + + +with open("/path/to/file.txt", mode="w") as file: + file.write("The new lines above me is about to be removed!") + + +with open("/path/to/file.txt", mode="r") as read_file: + with open("/path/to/output_file.txt", mode="w") as write_file: + write_file.writelines(read_file.readlines()) + +``` + +## Black Output + +```py +import random + + +def foo1(): + print("The newline above me should be deleted!") + + +def foo2(): + print("All the newlines above me should be deleted!") + + +def foo3(): + print("No newline above me!") + + print("There is a newline above me, and that's OK!") + + +def foo4(): + # There is a comment here + + print("The newline above me should not be deleted!") + + +class Foo: + def bar(self): + print("The newline above me should be deleted!") + + +for i in range(5): + print(f"{i}) The line above me should be removed!") + + +for i in range(5): + print(f"{i}) The lines above me should be removed!") + + +for i in range(5): + for j in range(7): + print(f"{i}) The lines above me should be removed!") + + +if random.randint(0, 3) == 0: + print("The new line above me is about to be removed!") + + +if random.randint(0, 3) == 0: + print("The new lines above me is about to be removed!") + + +if random.randint(0, 3) == 0: + if random.uniform(0, 1) > 0.5: + print("Two lines above me are about to be removed!") + + +while True: + print("The newline above me should be deleted!") + + +while True: + print("The newlines above me should be deleted!") + + +while True: + while False: + print("The newlines above me should be deleted!") + + +with open("/path/to/file.txt", mode="w") as file: + file.write("The new line above me is about to be removed!") + + +with open("/path/to/file.txt", mode="w") as file: + file.write("The new lines above me is about to be removed!") + + +with open("/path/to/file.txt", mode="r") as read_file: + with open("/path/to/output_file.txt", mode="w") as write_file: + write_file.writelines(read_file.readlines()) +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_parens_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_parens_py.snap new file mode 100644 index 0000000000..f376829eca --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_parens_py.snap @@ -0,0 +1,294 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_parens.py +--- +## Input + +```py +x = (1) +x = (1.2) + +data = ( + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +).encode() + +async def show_status(): + while True: + try: + if report_host: + data = ( + f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ).encode() + except Exception as e: + pass + +def example(): + return (("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")) + + +def example1(): + return ((1111111111111111111111111111111111111111111111111111111111111111111111111111111111111)) + + +def example1point5(): + return ((((((1111111111111111111111111111111111111111111111111111111111111111111111111111111111111)))))) + + +def example2(): + return (("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")) + + +def example3(): + return ((1111111111111111111111111111111111111111111111111111111111111111111111111111111)) + + +def example4(): + return ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((True)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + + +def example5(): + return ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + + +def example6(): + return ((((((((({a:a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}))))))))) + + +def example7(): + return ((((((((({a:a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20000000000000000000]}))))))))) + + +def example8(): + return (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((None))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,18 +1,14 @@ + x = 1 + x = 1.2 + +-data = ( +- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +-).encode() ++data = ("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx").encode() + + + async def show_status(): + while True: + try: + if report_host: +- data = ( +- f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +- ).encode() ++ data = (f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx").encode() + except Exception as e: + pass + +@@ -30,15 +26,11 @@ + + + def example2(): +- return ( +- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +- ) ++ return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + + + def example3(): +- return ( +- 1111111111111111111111111111111111111111111111111111111111111111111111111111111 +- ) ++ return 1111111111111111111111111111111111111111111111111111111111111111111111111111111 + + + def example4(): +@@ -82,4 +74,4 @@ + + + def example8(): +- return None +\ No newline at end of file ++ return None + +``` + +## Ruff Output + +```py +x = 1 +x = 1.2 + +data = ("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx").encode() + + +async def show_status(): + while True: + try: + if report_host: + data = (f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx").encode() + except Exception as e: + pass + + +def example(): + return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + + +def example1(): + return 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 + + +def example1point5(): + return 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 + + +def example2(): + return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + + +def example3(): + return 1111111111111111111111111111111111111111111111111111111111111111111111111111111 + + +def example4(): + return True + + +def example5(): + return () + + +def example6(): + return {a: a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]} + + +def example7(): + return { + a: a + for a in [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20000000000000000000, + ] + } + + +def example8(): + return None + +``` + +## Black Output + +```py +x = 1 +x = 1.2 + +data = ( + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +).encode() + + +async def show_status(): + while True: + try: + if report_host: + data = ( + f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ).encode() + except Exception as e: + pass + + +def example(): + return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + + +def example1(): + return 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 + + +def example1point5(): + return 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 + + +def example2(): + return ( + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ) + + +def example3(): + return ( + 1111111111111111111111111111111111111111111111111111111111111111111111111111111 + ) + + +def example4(): + return True + + +def example5(): + return () + + +def example6(): + return {a: a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]} + + +def example7(): + return { + a: a + for a in [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20000000000000000000, + ] + } + + +def example8(): + return None +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__return_annotation_brackets_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__return_annotation_brackets_py.snap new file mode 100644 index 0000000000..dd42deff39 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__return_annotation_brackets_py.snap @@ -0,0 +1,497 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/return_annotation_brackets.py +--- +## Input + +```py +# Control +def double(a: int) -> int: + return 2*a + +# Remove the brackets +def double(a: int) -> (int): + return 2*a + +# Some newline variations +def double(a: int) -> ( + int): + return 2*a + +def double(a: int) -> (int +): + return 2*a + +def double(a: int) -> ( + int +): + return 2*a + +# Don't lose the comments +def double(a: int) -> ( # Hello + int +): + return 2*a + +def double(a: int) -> ( + int # Hello +): + return 2*a + +# Really long annotations +def foo() -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + +def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + +def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + +def foo(a: int, b: int, c: int,) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + +def foo(a: int, b: int, c: int,) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + +# Split args but no need to split return +def foo(a: int, b: int, c: int,) -> int: + return 2 + +# Deeply nested brackets +# with *interesting* spacing +def double(a: int) -> (((((int))))): + return 2*a + +def double(a: int) -> ( + ( ( + ((int) + ) + ) + ) + ): + return 2*a + +def foo() -> ( + ( ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +) +)): + return 2 + +# Return type with commas +def foo() -> ( + tuple[int, int, int] +): + return 2 + +def foo() -> tuple[loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong]: + return 2 + +# Magic trailing comma example +def foo() -> tuple[int, int, int,]: + return 2 + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,52 +1,53 @@ + # Control + def double(a: int) -> int: +- return 2 * a ++ return 2 ++ * a + + + # Remove the brackets + def double(a: int) -> int: +- return 2 * a ++ return 2 ++ * a + + + # Some newline variations + def double(a: int) -> int: +- return 2 * a ++ return 2 ++ * a + + + def double(a: int) -> int: +- return 2 * a ++ return 2 ++ * a + + + def double(a: int) -> int: +- return 2 * a ++ return 2 ++ * a + + + # Don't lose the comments +-def double(a: int) -> int: # Hello +- return 2 * a ++def double(a: int) -> int: ++ return 2 ++ * a + + +-def double(a: int) -> int: # Hello +- return 2 * a ++def double(a: int) -> int: ++ return 2 ++ * a + + + # Really long annotations +-def foo() -> ( +- intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +-): ++def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +-def foo() -> ( +- intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +-): ++def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +-def foo() -> ( +- intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +- | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +-): ++def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds ++| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +@@ -62,10 +63,8 @@ + a: int, + b: int, + c: int, +-) -> ( +- intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +- | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +-): ++) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds ++| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +@@ -81,16 +80,16 @@ + # Deeply nested brackets + # with *interesting* spacing + def double(a: int) -> int: +- return 2 * a ++ return 2 ++ * a + + + def double(a: int) -> int: +- return 2 * a ++ return 2 ++ * a + + +-def foo() -> ( +- intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +-): ++def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +@@ -99,22 +98,18 @@ + return 2 + + +-def foo() -> ( +- tuple[ +- loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, +- loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, +- loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, +- ] +-): ++def foo() -> tuple[ ++ loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, ++ loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, ++ loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, ++]: + return 2 + + + # Magic trailing comma example +-def foo() -> ( +- tuple[ +- int, +- int, +- int, +- ] +-): +- return 2 +\ No newline at end of file ++def foo() -> tuple[ ++ int, ++ int, ++ int, ++]: ++ return 2 + +``` + +## Ruff Output + +```py +# Control +def double(a: int) -> int: + return 2 + * a + + +# Remove the brackets +def double(a: int) -> int: + return 2 + * a + + +# Some newline variations +def double(a: int) -> int: + return 2 + * a + + +def double(a: int) -> int: + return 2 + * a + + +def double(a: int) -> int: + return 2 + * a + + +# Don't lose the comments +def double(a: int) -> int: + return 2 + * a + + +def double(a: int) -> int: + return 2 + * a + + +# Really long annotations +def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +def foo( + a: int, + b: int, + c: int, +) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +def foo( + a: int, + b: int, + c: int, +) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +| intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +# Split args but no need to split return +def foo( + a: int, + b: int, + c: int, +) -> int: + return 2 + + +# Deeply nested brackets +# with *interesting* spacing +def double(a: int) -> int: + return 2 + * a + + +def double(a: int) -> int: + return 2 + * a + + +def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +# Return type with commas +def foo() -> tuple[int, int, int]: + return 2 + + +def foo() -> tuple[ + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, +]: + return 2 + + +# Magic trailing comma example +def foo() -> tuple[ + int, + int, + int, +]: + return 2 + +``` + +## Black Output + +```py +# Control +def double(a: int) -> int: + return 2 * a + + +# Remove the brackets +def double(a: int) -> int: + return 2 * a + + +# Some newline variations +def double(a: int) -> int: + return 2 * a + + +def double(a: int) -> int: + return 2 * a + + +def double(a: int) -> int: + return 2 * a + + +# Don't lose the comments +def double(a: int) -> int: # Hello + return 2 * a + + +def double(a: int) -> int: # Hello + return 2 * a + + +# Really long annotations +def foo() -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + + +def foo() -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + + +def foo() -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds + | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + + +def foo( + a: int, + b: int, + c: int, +) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +def foo( + a: int, + b: int, + c: int, +) -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds + | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + + +# Split args but no need to split return +def foo( + a: int, + b: int, + c: int, +) -> int: + return 2 + + +# Deeply nested brackets +# with *interesting* spacing +def double(a: int) -> int: + return 2 * a + + +def double(a: int) -> int: + return 2 * a + + +def foo() -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + + +# Return type with commas +def foo() -> tuple[int, int, int]: + return 2 + + +def foo() -> ( + tuple[ + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, + ] +): + return 2 + + +# Magic trailing comma example +def foo() -> ( + tuple[ + int, + int, + int, + ] +): + return 2 +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__skip_magic_trailing_comma_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__skip_magic_trailing_comma_py.snap new file mode 100644 index 0000000000..2fbeae7ea0 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__skip_magic_trailing_comma_py.snap @@ -0,0 +1,239 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.py +--- +## Input + +```py +# We should not remove the trailing comma in a single-element subscript. +a: tuple[int,] +b = tuple[int,] + +# But commas in multiple element subscripts should be removed. +c: tuple[int, int,] +d = tuple[int, int,] + +# Remove commas for non-subscripts. +small_list = [1,] +list_of_types = [tuple[int,],] +small_set = {1,} +set_of_types = {tuple[int,],} + +# Except single element tuples +small_tuple = (1,) + +# Trailing commas in multiple chained non-nested parens. +zero( + one, +).two( + three, +).four( + five, +) + +func1(arg1).func2(arg2,).func3(arg3).func4(arg4,).func5(arg5) + +( + a, + b, + c, + d, +) = func1( + arg1 +) and func2(arg2) + +func( + argument1, + ( + one, + two, + ), + argument4, + argument5, + argument6, +) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -3,23 +3,61 @@ + b = tuple[int,] + + # But commas in multiple element subscripts should be removed. +-c: tuple[int, int] +-d = tuple[int, int] ++c: tuple[ ++ int, ++ int, ++] ++d = tuple[ ++ int, ++ int, ++] + + # Remove commas for non-subscripts. +-small_list = [1] +-list_of_types = [tuple[int,]] +-small_set = {1} +-set_of_types = {tuple[int,]} ++small_list = [ ++ 1, ++] ++list_of_types = [ ++ tuple[int,], ++] ++small_set = { ++ 1, ++} ++set_of_types = { ++ tuple[int,], ++} + + # Except single element tuples + small_tuple = (1,) + + # Trailing commas in multiple chained non-nested parens. +-zero(one).two(three).four(five) ++zero( ++ one, ++).two( ++ three, ++).four( ++ five, ++) + +-func1(arg1).func2(arg2).func3(arg3).func4(arg4).func5(arg5) ++func1(arg1).func2( ++ arg2, ++).func3(arg3).func4( ++ arg4, ++).func5(arg5) + +-(a, b, c, d) = func1(arg1) and func2(arg2) ++( ++ a, ++ b, ++ c, ++ d, ++) = func1(arg1) and func2(arg2) + +-func(argument1, (one, two), argument4, argument5, argument6) +\ No newline at end of file ++func( ++ argument1, ++ ( ++ one, ++ two, ++ ), ++ argument4, ++ argument5, ++ argument6, ++) +\ No newline at end of file + +``` + +## Ruff Output + +```py +# We should not remove the trailing comma in a single-element subscript. +a: tuple[int,] +b = tuple[int,] + +# But commas in multiple element subscripts should be removed. +c: tuple[ + int, + int, +] +d = tuple[ + int, + int, +] + +# Remove commas for non-subscripts. +small_list = [ + 1, +] +list_of_types = [ + tuple[int,], +] +small_set = { + 1, +} +set_of_types = { + tuple[int,], +} + +# Except single element tuples +small_tuple = (1,) + +# Trailing commas in multiple chained non-nested parens. +zero( + one, +).two( + three, +).four( + five, +) + +func1(arg1).func2( + arg2, +).func3(arg3).func4( + arg4, +).func5(arg5) + +( + a, + b, + c, + d, +) = func1(arg1) and func2(arg2) + +func( + argument1, + ( + one, + two, + ), + argument4, + argument5, + argument6, +) +``` + +## Black Output + +```py +# We should not remove the trailing comma in a single-element subscript. +a: tuple[int,] +b = tuple[int,] + +# But commas in multiple element subscripts should be removed. +c: tuple[int, int] +d = tuple[int, int] + +# Remove commas for non-subscripts. +small_list = [1] +list_of_types = [tuple[int,]] +small_set = {1} +set_of_types = {tuple[int,]} + +# Except single element tuples +small_tuple = (1,) + +# Trailing commas in multiple chained non-nested parens. +zero(one).two(three).four(five) + +func1(arg1).func2(arg2).func3(arg3).func4(arg4).func5(arg5) + +(a, b, c, d) = func1(arg1) and func2(arg2) + +func(argument1, (one, two), argument4, argument5, argument6) +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap new file mode 100644 index 0000000000..575db53147 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap @@ -0,0 +1,117 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/string_prefixes.py +--- +## Input + +```py +#!/usr/bin/env python3 + +name = "Łukasz" +(f"hello {name}", F"hello {name}") +(b"", B"") +(u"", U"") +(r"", R"") + +(rf"", fr"", Rf"", fR"", rF"", Fr"", RF"", FR"") +(rb"", br"", Rb"", bR"", rB"", Br"", RB"", BR"") + + +def docstring_singleline(): + R"""2020 was one hell of a year. The good news is that we were able to""" + + +def docstring_multiline(): + R""" + clear out all of the issues opened in that time :p + """ + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,13 +1,13 @@ + #!/usr/bin/env python3 + + name = "Łukasz" +-(f"hello {name}", f"hello {name}") +-(b"", b"") +-("", "") ++(f"hello {name}", F"hello {name}") ++(b"", B"") ++(u"", U"") + (r"", R"") + +-(rf"", rf"", Rf"", Rf"", rf"", rf"", Rf"", Rf"") +-(rb"", rb"", Rb"", Rb"", rb"", rb"", Rb"", Rb"") ++(rf"", fr"", Rf"", fR"", rF"", Fr"", RF"", FR"") ++(rb"", br"", Rb"", bR"", rB"", Br"", RB"", BR"") + + + def docstring_singleline(): +@@ -17,4 +17,4 @@ + def docstring_multiline(): + R""" + clear out all of the issues opened in that time :p +- """ +\ No newline at end of file ++ """ + +``` + +## Ruff Output + +```py +#!/usr/bin/env python3 + +name = "Łukasz" +(f"hello {name}", F"hello {name}") +(b"", B"") +(u"", U"") +(r"", R"") + +(rf"", fr"", Rf"", fR"", rF"", Fr"", RF"", FR"") +(rb"", br"", Rb"", bR"", rB"", Br"", RB"", BR"") + + +def docstring_singleline(): + R"""2020 was one hell of a year. The good news is that we were able to""" + + +def docstring_multiline(): + R""" + clear out all of the issues opened in that time :p + """ + +``` + +## Black Output + +```py +#!/usr/bin/env python3 + +name = "Łukasz" +(f"hello {name}", f"hello {name}") +(b"", b"") +("", "") +(r"", R"") + +(rf"", rf"", Rf"", Rf"", rf"", rf"", Rf"", Rf"") +(rb"", rb"", Rb"", Rb"", rb"", rb"", Rb"", Rb"") + + +def docstring_singleline(): + R"""2020 was one hell of a year. The good news is that we were able to""" + + +def docstring_multiline(): + R""" + clear out all of the issues opened in that time :p + """ +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__torture_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__torture_py.snap new file mode 100644 index 0000000000..824a9bf55d --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__torture_py.snap @@ -0,0 +1,267 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/torture.py +--- +## Input + +```py +importA;() << 0 ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 # + +assert sort_by_dependency( + { + "1": {"2", "3"}, "2": {"2a", "2b"}, "3": {"3a", "3b"}, + "2a": set(), "2b": set(), "3a": set(), "3b": set() + } +) == ["2a", "2b", "2", "3a", "3b", "3", "1"] + +importA +0;0^0# + +class A: + def foo(self): + for _ in range(10): + aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( # pylint: disable=no-member + xxxxxxxxxxxx + ) + +def test(self, othr): + return (1 == 2 and + (name, description, self.default, self.selected, self.auto_generated, self.parameters, self.meta_data, self.schedule) == + (name, description, othr.default, othr.selected, othr.auto_generated, othr.parameters, othr.meta_data, othr.schedule)) + + +assert ( + a_function(very_long_arguments_that_surpass_the_limit, which_is_eighty_eight_in_this_case_plus_a_bit_more) + == {"x": "this need to pass the line limit as well", "b": "but only by a little bit"} +) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,21 +1,23 @@ + importA + ( + () +- << 0 +- ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 ++ << 0**101234234242352525425252352352525234890264906820496920680926538059059209922523523525 + ) # + +-assert sort_by_dependency( +- { +- "1": {"2", "3"}, +- "2": {"2a", "2b"}, +- "3": {"3a", "3b"}, +- "2a": set(), +- "2b": set(), +- "3a": set(), +- "3b": set(), +- } +-) == ["2a", "2b", "2", "3a", "3b", "3", "1"] ++assert ( ++ sort_by_dependency( ++ { ++ "1": {"2", "3"}, ++ "2": {"2a", "2b"}, ++ "3": {"3a", "3b"}, ++ "2a": set(), ++ "2b": set(), ++ "3a": set(), ++ "3b": set(), ++ } ++ ) ++ == ["2a", "2b", "2", "3a", "3b", "3", "1"] ++) + + importA + 0 +@@ -25,13 +27,12 @@ + class A: + def foo(self): + for _ in range(10): +- aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( +- xxxxxxxxxxxx +- ) # pylint: disable=no-member ++ aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc(xxxxxxxxxxxx) # pylint: disable=no-member + + + def test(self, othr): +- return 1 == 2 and ( ++ return 1 == 2 ++ and ( + name, + description, + self.default, +@@ -40,7 +41,8 @@ + self.parameters, + self.meta_data, + self.schedule, +- ) == ( ++ ) ++ == ( + name, + description, + othr.default, +@@ -52,7 +54,13 @@ + ) + + +-assert a_function( +- very_long_arguments_that_surpass_the_limit, +- which_is_eighty_eight_in_this_case_plus_a_bit_more, +-) == {"x": "this need to pass the line limit as well", "b": "but only by a little bit"} +\ No newline at end of file ++assert ( ++ a_function( ++ very_long_arguments_that_surpass_the_limit, ++ which_is_eighty_eight_in_this_case_plus_a_bit_more, ++ ) ++ == { ++ "x": "this need to pass the line limit as well", ++ "b": "but only by a little bit", ++ } ++) +\ No newline at end of file + +``` + +## Ruff Output + +```py +importA +( + () + << 0**101234234242352525425252352352525234890264906820496920680926538059059209922523523525 +) # + +assert ( + sort_by_dependency( + { + "1": {"2", "3"}, + "2": {"2a", "2b"}, + "3": {"3a", "3b"}, + "2a": set(), + "2b": set(), + "3a": set(), + "3b": set(), + } + ) + == ["2a", "2b", "2", "3a", "3b", "3", "1"] +) + +importA +0 +0 ^ 0 # + + +class A: + def foo(self): + for _ in range(10): + aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc(xxxxxxxxxxxx) # pylint: disable=no-member + + +def test(self, othr): + return 1 == 2 + and ( + name, + description, + self.default, + self.selected, + self.auto_generated, + self.parameters, + self.meta_data, + self.schedule, + ) + == ( + name, + description, + othr.default, + othr.selected, + othr.auto_generated, + othr.parameters, + othr.meta_data, + othr.schedule, + ) + + +assert ( + a_function( + very_long_arguments_that_surpass_the_limit, + which_is_eighty_eight_in_this_case_plus_a_bit_more, + ) + == { + "x": "this need to pass the line limit as well", + "b": "but only by a little bit", + } +) +``` + +## Black Output + +```py +importA +( + () + << 0 + ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 +) # + +assert sort_by_dependency( + { + "1": {"2", "3"}, + "2": {"2a", "2b"}, + "3": {"3a", "3b"}, + "2a": set(), + "2b": set(), + "3a": set(), + "3b": set(), + } +) == ["2a", "2b", "2", "3a", "3b", "3", "1"] + +importA +0 +0 ^ 0 # + + +class A: + def foo(self): + for _ in range(10): + aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( + xxxxxxxxxxxx + ) # pylint: disable=no-member + + +def test(self, othr): + return 1 == 2 and ( + name, + description, + self.default, + self.selected, + self.auto_generated, + self.parameters, + self.meta_data, + self.schedule, + ) == ( + name, + description, + othr.default, + othr.selected, + othr.auto_generated, + othr.parameters, + othr.meta_data, + othr.schedule, + ) + + +assert a_function( + very_long_arguments_that_surpass_the_limit, + which_is_eighty_eight_in_this_case_plus_a_bit_more, +) == {"x": "this need to pass the line limit as well", "b": "but only by a little bit"} +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens1_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens1_py.snap new file mode 100644 index 0000000000..2ace4efb68 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens1_py.snap @@ -0,0 +1,179 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens1.py +--- +## Input + +```py +if e1234123412341234.winerror not in (_winapi.ERROR_SEM_TIMEOUT, + _winapi.ERROR_PIPE_BUSY) or _check_timeout(t): + pass + +if x: + if y: + new_id = max(Vegetable.objects.order_by('-id')[0].id, + Mineral.objects.order_by('-id')[0].id) + 1 + +class X: + def get_help_text(self): + return ngettext( + "Your password must contain at least %(min_length)d character.", + "Your password must contain at least %(min_length)d characters.", + self.min_length, + ) % {'min_length': self.min_length} + +class A: + def b(self): + if self.connection.mysql_is_mariadb and ( + 10, + 4, + 3, + ) < self.connection.mysql_version < (10, 5, 2): + pass + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,15 +1,14 @@ +-if e1234123412341234.winerror not in ( +- _winapi.ERROR_SEM_TIMEOUT, +- _winapi.ERROR_PIPE_BUSY, +-) or _check_timeout(t): ++if e1234123412341234.winerror ++not in (_winapi.ERROR_SEM_TIMEOUT, _winapi.ERROR_PIPE_BUSY) ++or _check_timeout(t): + pass + + if x: + if y: + new_id = ( + max( +- Vegetable.objects.order_by("-id")[0].id, +- Mineral.objects.order_by("-id")[0].id, ++ Vegetable.objects.order_by('-id')[0].id, ++ Mineral.objects.order_by('-id')[0].id, + ) + + 1 + ) +@@ -21,14 +20,20 @@ + "Your password must contain at least %(min_length)d character.", + "Your password must contain at least %(min_length)d characters.", + self.min_length, +- ) % {"min_length": self.min_length} ++ ) ++ % {'min_length': self.min_length} + + + class A: + def b(self): +- if self.connection.mysql_is_mariadb and ( +- 10, +- 4, +- 3, +- ) < self.connection.mysql_version < (10, 5, 2): +- pass +\ No newline at end of file ++ if ( ++ self.connection.mysql_is_mariadb ++ and ( ++ 10, ++ 4, ++ 3, ++ ) ++ < self.connection.mysql_version ++ < (10, 5, 2) ++ ): ++ pass + +``` + +## Ruff Output + +```py +if e1234123412341234.winerror +not in (_winapi.ERROR_SEM_TIMEOUT, _winapi.ERROR_PIPE_BUSY) +or _check_timeout(t): + pass + +if x: + if y: + new_id = ( + max( + Vegetable.objects.order_by('-id')[0].id, + Mineral.objects.order_by('-id')[0].id, + ) + + 1 + ) + + +class X: + def get_help_text(self): + return ngettext( + "Your password must contain at least %(min_length)d character.", + "Your password must contain at least %(min_length)d characters.", + self.min_length, + ) + % {'min_length': self.min_length} + + +class A: + def b(self): + if ( + self.connection.mysql_is_mariadb + and ( + 10, + 4, + 3, + ) + < self.connection.mysql_version + < (10, 5, 2) + ): + pass + +``` + +## Black Output + +```py +if e1234123412341234.winerror not in ( + _winapi.ERROR_SEM_TIMEOUT, + _winapi.ERROR_PIPE_BUSY, +) or _check_timeout(t): + pass + +if x: + if y: + new_id = ( + max( + Vegetable.objects.order_by("-id")[0].id, + Mineral.objects.order_by("-id")[0].id, + ) + + 1 + ) + + +class X: + def get_help_text(self): + return ngettext( + "Your password must contain at least %(min_length)d character.", + "Your password must contain at least %(min_length)d characters.", + self.min_length, + ) % {"min_length": self.min_length} + + +class A: + def b(self): + if self.connection.mysql_is_mariadb and ( + 10, + 4, + 3, + ) < self.connection.mysql_version < (10, 5, 2): + pass +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens2_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens2_py.snap new file mode 100644 index 0000000000..72dac4affc --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens2_py.snap @@ -0,0 +1,58 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens2.py +--- +## Input + +```py +if (e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') or + (8, 5, 8) <= get_tk_patchlevel() < (8, 6)): + pass + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -1,6 +1,5 @@ +-if e123456.get_tk_patchlevel() >= (8, 6, 0, "final") or ( +- 8, +- 5, +- 8, +-) <= get_tk_patchlevel() < (8, 6): +- pass +\ No newline at end of file ++if ( ++ e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') ++ or (8, 5, 8) <= get_tk_patchlevel() < (8, 6) ++): ++ pass + +``` + +## Ruff Output + +```py +if ( + e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') + or (8, 5, 8) <= get_tk_patchlevel() < (8, 6) +): + pass + +``` + +## Black Output + +```py +if e123456.get_tk_patchlevel() >= (8, 6, 0, "final") or ( + 8, + 5, + 8, +) <= get_tk_patchlevel() < (8, 6): + pass +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens3_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens3_py.snap new file mode 100644 index 0000000000..24be9e641a --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_comma_optional_parens3_py.snap @@ -0,0 +1,64 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_comma_optional_parens3.py +--- +## Input + +```py +if True: + if True: + if True: + return _( + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas " + + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.", + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe", + ) % {"reported_username": reported_username, "report_reason": report_reason} + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -5,4 +5,5 @@ + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas " + + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.", + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe", +- ) % {"reported_username": reported_username, "report_reason": report_reason} +\ No newline at end of file ++ ) ++ % {"reported_username": reported_username, "report_reason": report_reason} + +``` + +## Ruff Output + +```py +if True: + if True: + if True: + return _( + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas " + + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.", + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe", + ) + % {"reported_username": reported_username, "report_reason": report_reason} + +``` + +## Black Output + +```py +if True: + if True: + if True: + return _( + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas " + + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.", + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe", + ) % {"reported_username": reported_username, "report_reason": report_reason} +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_commas_in_leading_parts_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_commas_in_leading_parts_py.snap new file mode 100644 index 0000000000..629074de44 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_commas_in_leading_parts_py.snap @@ -0,0 +1,181 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_commas_in_leading_parts.py +--- +## Input + +```py +zero(one,).two(three,).four(five,) + +func1(arg1).func2(arg2,).func3(arg3).func4(arg4,).func5(arg5) + +# Inner one-element tuple shouldn't explode +func1(arg1).func2(arg1, (one_tuple,)).func3(arg3) + +(a, b, c, d,) = func1(arg1) and func2(arg2) + + +# Example from https://github.com/psf/black/issues/3229 +def refresh_token(self, device_family, refresh_token, api_key): + return self.orchestration.refresh_token( + data={ + "refreshToken": refresh_token, + }, + api_key=api_key, + )["extensions"]["sdk"]["token"] + + +# Edge case where a bug in a working-in-progress version of +# https://github.com/psf/black/pull/3370 causes an infinite recursion. +assert ( + long_module.long_class.long_func().another_func() + == long_module.long_class.long_func()["some_key"].another_func(arg1) +) + +# Regression test for https://github.com/psf/black/issues/3414. +assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( + xxxxxxxxx +).xxxxxxxxxxxxxxxxxx(), ( + "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +) + +``` + +## Black Differences + +```diff +--- Black ++++ Ruff +@@ -20,9 +20,7 @@ + b, + c, + d, +-) = func1( +- arg1 +-) and func2(arg2) ++) = func1(arg1) and func2(arg2) + + + # Example from https://github.com/psf/black/issues/3229 +@@ -43,8 +41,6 @@ + ) + + # Regression test for https://github.com/psf/black/issues/3414. +-assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( +- xxxxxxxxx +-).xxxxxxxxxxxxxxxxxx(), ( ++assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(xxxxxxxxx).xxxxxxxxxxxxxxxxxx(), ( + "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ) +\ No newline at end of file + +``` + +## Ruff Output + +```py +zero( + one, +).two( + three, +).four( + five, +) + +func1(arg1).func2( + arg2, +).func3(arg3).func4( + arg4, +).func5(arg5) + +# Inner one-element tuple shouldn't explode +func1(arg1).func2(arg1, (one_tuple,)).func3(arg3) + +( + a, + b, + c, + d, +) = func1(arg1) and func2(arg2) + + +# Example from https://github.com/psf/black/issues/3229 +def refresh_token(self, device_family, refresh_token, api_key): + return self.orchestration.refresh_token( + data={ + "refreshToken": refresh_token, + }, + api_key=api_key, + )["extensions"]["sdk"]["token"] + + +# Edge case where a bug in a working-in-progress version of +# https://github.com/psf/black/pull/3370 causes an infinite recursion. +assert ( + long_module.long_class.long_func().another_func() + == long_module.long_class.long_func()["some_key"].another_func(arg1) +) + +# Regression test for https://github.com/psf/black/issues/3414. +assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(xxxxxxxxx).xxxxxxxxxxxxxxxxxx(), ( + "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +) +``` + +## Black Output + +```py +zero( + one, +).two( + three, +).four( + five, +) + +func1(arg1).func2( + arg2, +).func3(arg3).func4( + arg4, +).func5(arg5) + +# Inner one-element tuple shouldn't explode +func1(arg1).func2(arg1, (one_tuple,)).func3(arg3) + +( + a, + b, + c, + d, +) = func1( + arg1 +) and func2(arg2) + + +# Example from https://github.com/psf/black/issues/3229 +def refresh_token(self, device_family, refresh_token, api_key): + return self.orchestration.refresh_token( + data={ + "refreshToken": refresh_token, + }, + api_key=api_key, + )["extensions"]["sdk"]["token"] + + +# Edge case where a bug in a working-in-progress version of +# https://github.com/psf/black/pull/3370 causes an infinite recursion. +assert ( + long_module.long_class.long_func().another_func() + == long_module.long_class.long_func()["some_key"].another_func(arg1) +) + +# Regression test for https://github.com/psf/black/issues/3414. +assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( + xxxxxxxxx +).xxxxxxxxxxxxxxxxxx(), ( + "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +) +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__beginning_backslash.py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__beginning_backslash.py.snap deleted file mode 100644 index 7464ff1f1d..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__beginning_backslash.py.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- -print("hello, world") - diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__bracketmatch.py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__bracketmatch.py.snap deleted file mode 100644 index 37c5cb0d6a..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__bracketmatch.py.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- -for ((x in {}) or {})["a"] in x: - pass -pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip()) -lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x - diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__tricky_unicode_symbols.py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__tricky_unicode_symbols.py.snap deleted file mode 100644 index 127bddd4f5..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__tricky_unicode_symbols.py.snap +++ /dev/null @@ -1,15 +0,0 @@ ---- -source: src/source_code/mod.rs -assertion_line: 0 -expression: formatted ---- -ä = 1 -µ = 2 -蟒 = 3 -x󠄀 = 4 -មុ = 1 -Q̇_per_meter = 4 - -A᧚ = 3 -A፩ = 8 - diff --git a/crates/ruff_python_formatter/src/test.rs b/crates/ruff_python_formatter/src/test.rs deleted file mode 100644 index 24aee682d5..0000000000 --- a/crates/ruff_python_formatter/src/test.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![cfg(test)] - -use std::path::Path; - -pub fn test_resource_path(path: impl AsRef) -> std::path::PathBuf { - Path::new("./resources/test/").join(path) -} diff --git a/crates/ruff_testing_macros/Cargo.toml b/crates/ruff_testing_macros/Cargo.toml new file mode 100644 index 0000000000..7f2a3b4a8a --- /dev/null +++ b/crates/ruff_testing_macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ruff_testing_macros" +edition = "2021" +version = "0.0.0" +publish = false + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +glob = "0.3.1" +proc-macro-error = { version = "1.0.4", default-features = false } +proc-macro2 = "1.0.36" +quote = "1.0.14" +syn = "1.0.107" diff --git a/crates/ruff_testing_macros/src/lib.rs b/crates/ruff_testing_macros/src/lib.rs new file mode 100644 index 0000000000..f269bdfbdb --- /dev/null +++ b/crates/ruff_testing_macros/src/lib.rs @@ -0,0 +1,397 @@ +use glob::{glob, Pattern}; +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{format_ident, quote}; +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::env; +use std::path::{Component, PathBuf}; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::{bracketed, parse_macro_input, parse_quote, Error, FnArg, ItemFn, LitStr, Pat, Token}; + +#[derive(Debug)] +struct FixtureConfiguration { + pattern: Pattern, + pattern_span: Span, + exclude: Vec, +} + +struct Arg { + name: syn::Ident, + value: ArgValue, +} + +impl Parse for Arg { + fn parse(input: ParseStream) -> syn::Result { + let name = input.parse()?; + let _equal_token: Token![=] = input.parse()?; + let value = input.parse()?; + + Ok(Self { name, value }) + } +} + +enum ArgValue { + LitStr(LitStr), + List(Punctuated), +} + +impl Parse for ArgValue { + fn parse(input: ParseStream) -> syn::Result { + let value = if input.peek(syn::token::Bracket) { + let inner; + let _ = bracketed!(inner in input); + + let values = inner.parse_terminated(|parser| { + let value: LitStr = parser.parse()?; + Ok(value) + })?; + ArgValue::List(values) + } else { + ArgValue::LitStr(input.parse()?) + }; + + Ok(value) + } +} + +impl Parse for FixtureConfiguration { + fn parse(input: ParseStream) -> syn::Result { + let args: Punctuated<_, Token![,]> = input.parse_terminated(Arg::parse)?; + + let mut pattern = None; + let mut exclude = None; + + for arg in args { + match arg.name.to_string().as_str() { + "pattern" => match arg.value { + ArgValue::LitStr(value) => { + pattern = Some(try_parse_pattern(&value)?); + } + ArgValue::List(list) => { + return Err(Error::new( + list.span(), + "The pattern must be a string literal", + )) + } + }, + + "exclude" => { + match arg.value { + ArgValue::LitStr(lit) => return Err(Error::new( + lit.span(), + "The exclude argument must be an array of globs: 'exclude=[\"a.py\"]", + )), + ArgValue::List(list) => { + let mut exclude_patterns = Vec::with_capacity(list.len()); + + for pattern in list { + let (pattern, _) = try_parse_pattern(&pattern)?; + exclude_patterns.push(pattern); + } + + exclude = Some(exclude_patterns); + } + } + } + + _ => { + return Err(Error::new( + arg.name.span(), + format!("Unknown argument {}.", arg.name), + )); + } + } + } + + let exclude = exclude.unwrap_or_default(); + + match pattern { + None => Err(Error::new( + input.span(), + "'fixture' macro must have a pattern attribute", + )), + Some((pattern, pattern_span)) => Ok(Self { + pattern, + pattern_span, + exclude, + }), + } + } +} + +fn try_parse_pattern(pattern_lit: &LitStr) -> syn::Result<(Pattern, Span)> { + let raw_pattern = pattern_lit.value(); + match Pattern::new(&raw_pattern) { + Ok(pattern) => Ok((pattern, pattern_lit.span())), + Err(err) => Err(Error::new( + pattern_lit.span(), + format!("'{raw_pattern}' is not a valid glob pattern: '{}'", err.msg), + )), + } +} + +/// Generates a test for each file that matches the specified pattern. +/// +/// The attributed function must have exactly one argument of the type `&Path`. +/// The `#[test]` attribute must come after the `#[fixture]` argument or `test` will complain +/// that your function can not have any arguments. +/// +/// ## Examples +/// +/// Creates a test for every python file file in the `fixtures` directory. +/// +/// ```ignore +/// #[fixture(pattern="fixtures/*.py")] +/// #[test] +/// fn my_test(path: &Path) -> std::io::Result<()> { +/// // ... implement the test +/// Ok(()) +/// } +/// ``` +/// +/// ### Excluding Files +/// +/// You can exclude files by specifying optional `exclude` patterns. +/// +/// ```ignore +/// #[fixture(pattern="fixtures/*.py", exclude=["a_*.py"])] +/// #[test] +/// fn my_test(path: &Path) -> std::io::Result<()> { +/// // ... implement the test +/// Ok(()) +/// } +/// ``` +/// +/// Creates tests for each python file in the `fixtures` directory except for files matching the `a_*.py` pattern. +#[proc_macro_attribute] +pub fn fixture(attribute: TokenStream, item: TokenStream) -> TokenStream { + let test_fn = parse_macro_input!(item as ItemFn); + let configuration = parse_macro_input!(attribute as FixtureConfiguration); + + let result = generate_fixtures(test_fn, &configuration); + + let stream = match result { + Ok(output) => output, + Err(err) => err.to_compile_error(), + }; + + TokenStream::from(stream) +} + +fn generate_fixtures( + mut test_fn: ItemFn, + configuration: &FixtureConfiguration, +) -> syn::Result { + // Remove the fixtures attribute + test_fn.attrs.retain(|attr| !attr.path.is_ident("fixtures")); + + // Extract the name of the only argument of the test function. + let last_arg = test_fn.sig.inputs.last(); + let path_ident = match (test_fn.sig.inputs.len(), last_arg) { + (1, Some(last_arg)) => match last_arg { + FnArg::Typed(typed) => match typed.pat.as_ref() { + Pat::Ident(ident) => ident.ident.clone(), + pat => { + return Err(Error::new( + pat.span(), + "#[fixture] function argument name must be an identifier", + )); + } + }, + FnArg::Receiver(receiver) => { + return Err(Error::new( + receiver.span(), + "#[fixture] function argument name must be an identifier", + )); + } + }, + _ => { + return Err(Error::new( + test_fn.sig.inputs.span(), + "#[fixture] function must have exactly one argument with the type '&Path'", + )); + } + }; + + // Remove all arguments + test_fn.sig.inputs.clear(); + + let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect( + "#[fixture] requires CARGO_MANIFEST_DIR to be set during the build to resolve the relative paths to the test files.", + )); + + let pattern = if configuration.pattern.as_str().starts_with('/') { + Cow::from(configuration.pattern.as_str()) + } else { + Cow::from(format!( + "{}/{}", + crate_dir + .to_str() + .expect("CARGO_MANIFEST_DIR must point to a directory with a UTF8 path"), + configuration.pattern.as_str() + )) + }; + + let files = glob(&pattern).expect("Pattern to be valid").flatten(); + let mut modules = Modules::default(); + + for file in files { + if configuration + .exclude + .iter() + .any(|exclude| exclude.matches_path(&file)) + { + continue; + } + + let mut test_fn = test_fn.clone(); + + let test_name = file + .file_name() + // SAFETY: Glob only matches on file names. + .unwrap() + .to_str() + .expect("Expected path to be valid UTF8") + .replace('.', "_"); + + test_fn.sig.ident = format_ident!("{test_name}"); + + let path = file.as_os_str().to_str().unwrap(); + + test_fn.block.stmts.insert( + 0, + parse_quote!(let #path_ident = std::path::Path::new(#path);), + ); + + modules.push_test(Test { + path: file, + test_fn, + }); + } + + if modules.is_empty() { + return Err(Error::new( + configuration.pattern_span, + "No file matches the specified glob pattern", + )); + } + + let root = find_highest_common_ancestor_module(&modules.root); + + root.generate(&test_fn.sig.ident.to_string()) +} + +fn find_highest_common_ancestor_module(module: &Module) -> &Module { + let children = &module.children; + + if children.len() == 1 { + let (_, child) = children.iter().next().unwrap(); + + match child { + Child::Module(common_child) => find_highest_common_ancestor_module(common_child), + Child::Test(_) => module, + } + } else { + module + } +} + +#[derive(Debug)] +struct Test { + path: PathBuf, + test_fn: ItemFn, +} + +impl Test { + fn generate(&self, _: &str) -> proc_macro2::TokenStream { + let test_fn = &self.test_fn; + quote!(#test_fn) + } +} + +#[derive(Debug, Default)] +struct Module { + children: BTreeMap, +} + +impl Module { + fn generate(&self, name: &str) -> syn::Result { + let mut inner = Vec::with_capacity(self.children.len()); + + for (name, child) in &self.children { + inner.push(child.generate(name)?); + } + + let module_ident = format_ident!("{name}"); + + Ok(quote!( + mod #module_ident { + use super::*; + + #(#inner)* + } + )) + } +} + +#[derive(Debug)] +enum Child { + Module(Module), + Test(Test), +} + +impl Child { + fn generate(&self, name: &str) -> syn::Result { + match self { + Child::Module(module) => module.generate(name), + Child::Test(test) => Ok(test.generate(name)), + } + } +} + +#[derive(Debug, Default)] +struct Modules { + root: Module, +} + +impl Modules { + fn push_test(&mut self, test: Test) { + let mut components = test + .path + .as_path() + .components() + .skip_while(|c| matches!(c, Component::RootDir)) + .peekable(); + + let mut current = &mut self.root; + while let Some(component) = components.next() { + let name = component.as_os_str().to_str().unwrap(); + // A directory + if components.peek().is_some() { + let name = component.as_os_str().to_str().unwrap(); + let entry = current.children.entry(name.to_owned()); + + match entry.or_insert_with(|| Child::Module(Module::default())) { + Child::Module(module) => { + current = module; + } + Child::Test(_) => { + unreachable!() + } + } + } else { + // We reached the final component, insert the test + drop(components); + current.children.insert(name.to_owned(), Child::Test(test)); + break; + } + } + } + + fn is_empty(&self) -> bool { + self.root.children.is_empty() + } +}