From f41f6d9b759335db6b25413b5569ce210f04921e Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Wed, 18 Oct 2023 10:58:10 -0700 Subject: [PATCH] Rebase _Pyink_ to _Black_ v23.10.0. PiperOrigin-RevId: 574528648 --- patches/pyink.patch | 159 ++++----- pyproject.toml | 2 +- src/pyink/cache.py | 4 +- src/pyink/concurrency.py | 7 +- src/pyink/debug.py | 21 +- src/pyink/linegen.py | 88 ++++- src/pyink/lines.py | 24 +- src/pyink/mode.py | 5 + src/pyink/numerics.py | 1 + src/pyink/parsing.py | 23 +- src/pyink/report.py | 1 + src/pyink/rusty.py | 1 + src/pyink/trans.py | 4 + tests/conftest.py | 27 ++ .../attribute_access_on_number_literals.py | 0 .../beginning_backslash.py | 0 .../{simple_cases => cases}/bracketmatch.py | 0 .../class_blank_parentheses.py | 0 .../class_methods_new_line.py | 0 .../{simple_cases => cases}/collections.py | 0 .../comment_after_escaped_newline.py | 0 .../data/{simple_cases => cases}/comments.py | 0 .../data/{simple_cases => cases}/comments2.py | 0 .../data/{simple_cases => cases}/comments3.py | 0 .../data/{simple_cases => cases}/comments4.py | 0 .../data/{simple_cases => cases}/comments5.py | 0 .../data/{simple_cases => cases}/comments6.py | 0 .../data/{simple_cases => cases}/comments8.py | 0 .../data/{simple_cases => cases}/comments9.py | 0 .../comments_non_breaking_space.py | 0 .../{simple_cases => cases}/composition.py | 0 .../composition_no_trailing_comma.py | 0 .../{ => cases}/conditional_expression.py | 1 + .../data/{simple_cases => cases}/docstring.py | 0 ...ocstring_no_extra_empty_line_before_eof.py | 0 .../docstring_no_string_normalization.py | 1 + .../docstring_preview.py | 0 ...cstring_preview_no_string_normalization.py | 1 + .../{simple_cases => cases}/empty_lines.py | 0 .../{simple_cases => cases}/expression.diff | 0 .../{simple_cases => cases}/expression.py | 0 .../data/{simple_cases => cases}/fmtonoff.py | 0 .../data/{simple_cases => cases}/fmtonoff2.py | 0 .../data/{simple_cases => cases}/fmtonoff3.py | 0 .../data/{simple_cases => cases}/fmtonoff4.py | 0 .../data/{simple_cases => cases}/fmtonoff5.py | 0 .../fmtpass_imports.py | 0 tests/data/{simple_cases => cases}/fmtskip.py | 0 .../data/{simple_cases => cases}/fmtskip2.py | 0 .../data/{simple_cases => cases}/fmtskip3.py | 0 .../data/{simple_cases => cases}/fmtskip4.py | 0 .../data/{simple_cases => cases}/fmtskip5.py | 0 .../data/{simple_cases => cases}/fmtskip6.py | 0 .../data/{simple_cases => cases}/fmtskip7.py | 0 .../data/{simple_cases => cases}/fmtskip8.py | 0 tests/data/{simple_cases => cases}/fstring.py | 0 .../funcdef_return_type_trailing_comma.py | 301 ++++++++++++++++++ .../data/{simple_cases => cases}/function.py | 0 .../data/{simple_cases => cases}/function2.py | 0 .../function_trailing_comma.py | 0 .../{simple_cases => cases}/ignore_pyi.py | 1 + .../{simple_cases => cases}/import_spacing.py | 0 .../{miscellaneous => cases}/linelength6.py | 1 + .../long_strings_flag_disabled.py | 0 tests/data/cases/module_docstring_1.py | 26 ++ tests/data/cases/module_docstring_2.py | 68 ++++ tests/data/cases/module_docstring_3.py | 8 + tests/data/cases/module_docstring_4.py | 9 + ...ine_consecutive_open_parentheses_ignore.py | 0 .../nested_stub.pyi => cases/nested_stub.py} | 1 + .../data/{py_36 => cases}/numeric_literals.py | 5 - .../numeric_literals_skip_underscores.py | 4 - .../one_element_subscript.py | 0 .../parenthesized_context_managers.py | 1 + .../pattern_matching_complex.py | 5 + .../pattern_matching_extras.py | 1 + .../pattern_matching_generic.py | 1 + .../pattern_matching_simple.py | 1 + .../pattern_matching_style.py | 1 + .../cases/pep604_union_types_line_breaks.py | 188 +++++++++++ tests/data/{py_38 => cases}/pep_570.py | 1 + tests/data/{py_38 => cases}/pep_572.py | 1 + .../pep_572_do_not_remove_parens.py | 1 + tests/data/{py_310 => cases}/pep_572_py310.py | 1 + tests/data/{py_39 => cases}/pep_572_py39.py | 1 + .../{py_38 => cases}/pep_572_remove_parens.py | 1 + tests/data/{simple_cases => cases}/pep_604.py | 0 tests/data/{py_311 => cases}/pep_646.py | 1 + tests/data/{py_311 => cases}/pep_654.py | 1 + tests/data/{py_311 => cases}/pep_654_style.py | 1 + .../power_op_newline.py | 1 + .../power_op_spacing.py | 18 ++ .../prefer_rhs_split_reformatted.py | 0 .../preview_async_stmts.py} | 1 + .../cantfit.py => cases/preview_cantfit.py} | 1 + .../preview_comments7.py} | 25 ++ .../preview_context_managers_38.py} | 1 + .../preview_context_managers_39.py} | 1 + ...review_context_managers_autodetect_310.py} | 1 + ...review_context_managers_autodetect_311.py} | 1 + ...preview_context_managers_autodetect_38.py} | 1 + ...preview_context_managers_autodetect_39.py} | 1 + .../preview_dummy_implementations.py} | 1 + .../preview_format_unicode_escape_seq.py} | 1 + .../preview_long_dict_values.py} | 1 + .../preview_long_strings.py} | 1 + ...preview_long_strings__east_asian_width.py} | 1 + .../preview_long_strings__edge_case.py} | 1 + .../preview_long_strings__regression.py} | 1 + ...preview_long_strings__type_annotations.py} | 3 +- .../preview_multiline_strings.py} | 1 + ...preview_no_blank_line_before_docstring.py} | 1 + .../pep_572.py => cases/preview_pep_572.py} | 1 + .../preview_percent_precedence.py} | 1 + tests/data/cases/preview_power_op_spacing.py | 97 ++++++ .../preview_prefer_rhs_split.py} | 1 + ...eview_return_annotation_brackets_string.py | 24 ++ .../preview_trailing_comma.py} | 1 + .../pep_572.py => cases/py310_pep572.py} | 1 + .../pyink_empty_lines.py} | 1 + .../imports.py => cases/pyink_imports.py} | 1 + .../indent.py => cases/pyink_indent.py} | 1 + .../pyink_module_docstring_1.py} | 2 +- .../pyink_module_docstring_2.py} | 1 + .../pyink_module_docstring_3.py} | 1 + .../pyink_module_docstring_4.py} | 1 + .../pyink_nested_brackets.py} | 1 + .../pyink_nested_brackets_explodes.py} | 1 + .../pyink_pragma_comments.py} | 1 + .../pyink_str_concat_in_func_args.py} | 1 + tests/data/{py_37 => cases}/python37.py | 5 +- tests/data/{py_38 => cases}/python38.py | 5 +- tests/data/{py_39 => cases}/python39.py | 6 +- .../remove_await_parens.py | 0 .../remove_except_parens.py | 0 .../remove_for_brackets.py | 0 .../remove_newline_after_code_block_open.py | 0 .../remove_newline_after_match.py | 1 + .../{simple_cases => cases}/remove_parens.py | 0 .../{py_39 => cases}/remove_with_brackets.py | 1 + .../return_annotation_brackets.py | 13 + .../skip_magic_trailing_comma.py | 1 + tests/data/{simple_cases => cases}/slices.py | 0 .../{py_310 => cases}/starred_for_target.py | 1 + .../string_prefixes.py | 0 .../{miscellaneous/stub.pyi => cases/stub.py} | 1 + tests/data/{simple_cases => cases}/torture.py | 0 .../trailing_comma_optional_parens1.py | 0 .../trailing_comma_optional_parens2.py | 0 .../trailing_comma_optional_parens3.py | 0 .../trailing_commas_in_leading_parts.py | 0 .../tricky_unicode_symbols.py | 0 .../{simple_cases => cases}/tupleassign.py | 0 tests/data/cases/type_aliases.py | 30 ++ .../type_comment_syntax_error.py | 0 tests/data/{py_312 => cases}/type_params.py | 1 + .../{simple_cases => cases}/whitespace.py | 0 tests/data/miscellaneous/force_pyi.py | 1 + tests/data/miscellaneous/string_quotes.py | 2 + .../return_annotation_brackets_string.py | 12 - tests/data/py_312/type_aliases.py | 13 - tests/data/raw_docstring.py | 32 ++ tests/optional.py | 2 +- tests/test_black.py | 75 +++-- tests/test_format.py | 209 ++---------- tests/util.py | 114 ++++++- tox.ini | 2 +- 167 files changed, 1344 insertions(+), 386 deletions(-) rename tests/data/{simple_cases => cases}/attribute_access_on_number_literals.py (100%) rename tests/data/{simple_cases => cases}/beginning_backslash.py (100%) rename tests/data/{simple_cases => cases}/bracketmatch.py (100%) rename tests/data/{simple_cases => cases}/class_blank_parentheses.py (100%) rename tests/data/{simple_cases => cases}/class_methods_new_line.py (100%) rename tests/data/{simple_cases => cases}/collections.py (100%) rename tests/data/{simple_cases => cases}/comment_after_escaped_newline.py (100%) rename tests/data/{simple_cases => cases}/comments.py (100%) rename tests/data/{simple_cases => cases}/comments2.py (100%) rename tests/data/{simple_cases => cases}/comments3.py (100%) rename tests/data/{simple_cases => cases}/comments4.py (100%) rename tests/data/{simple_cases => cases}/comments5.py (100%) rename tests/data/{simple_cases => cases}/comments6.py (100%) rename tests/data/{simple_cases => cases}/comments8.py (100%) rename tests/data/{simple_cases => cases}/comments9.py (100%) rename tests/data/{simple_cases => cases}/comments_non_breaking_space.py (100%) rename tests/data/{simple_cases => cases}/composition.py (100%) rename tests/data/{simple_cases => cases}/composition_no_trailing_comma.py (100%) rename tests/data/{ => cases}/conditional_expression.py (99%) rename tests/data/{simple_cases => cases}/docstring.py (100%) rename tests/data/{simple_cases => cases}/docstring_no_extra_empty_line_before_eof.py (100%) rename tests/data/{miscellaneous => cases}/docstring_no_string_normalization.py (98%) rename tests/data/{simple_cases => cases}/docstring_preview.py (100%) rename tests/data/{miscellaneous => cases}/docstring_preview_no_string_normalization.py (88%) rename tests/data/{simple_cases => cases}/empty_lines.py (100%) rename tests/data/{simple_cases => cases}/expression.diff (100%) rename tests/data/{simple_cases => cases}/expression.py (100%) rename tests/data/{simple_cases => cases}/fmtonoff.py (100%) rename tests/data/{simple_cases => cases}/fmtonoff2.py (100%) rename tests/data/{simple_cases => cases}/fmtonoff3.py (100%) rename tests/data/{simple_cases => cases}/fmtonoff4.py (100%) rename tests/data/{simple_cases => cases}/fmtonoff5.py (100%) rename tests/data/{simple_cases => cases}/fmtpass_imports.py (100%) rename tests/data/{simple_cases => cases}/fmtskip.py (100%) rename tests/data/{simple_cases => cases}/fmtskip2.py (100%) rename tests/data/{simple_cases => cases}/fmtskip3.py (100%) rename tests/data/{simple_cases => cases}/fmtskip4.py (100%) rename tests/data/{simple_cases => cases}/fmtskip5.py (100%) rename tests/data/{simple_cases => cases}/fmtskip6.py (100%) rename tests/data/{simple_cases => cases}/fmtskip7.py (100%) rename tests/data/{simple_cases => cases}/fmtskip8.py (100%) rename tests/data/{simple_cases => cases}/fstring.py (100%) create mode 100644 tests/data/cases/funcdef_return_type_trailing_comma.py rename tests/data/{simple_cases => cases}/function.py (100%) rename tests/data/{simple_cases => cases}/function2.py (100%) rename tests/data/{simple_cases => cases}/function_trailing_comma.py (100%) rename tests/data/{simple_cases => cases}/ignore_pyi.py (97%) rename tests/data/{simple_cases => cases}/import_spacing.py (100%) rename tests/data/{miscellaneous => cases}/linelength6.py (80%) rename tests/data/{miscellaneous => cases}/long_strings_flag_disabled.py (100%) create mode 100644 tests/data/cases/module_docstring_1.py create mode 100644 tests/data/cases/module_docstring_2.py create mode 100644 tests/data/cases/module_docstring_3.py create mode 100644 tests/data/cases/module_docstring_4.py rename tests/data/{simple_cases => cases}/multiline_consecutive_open_parentheses_ignore.py (100%) rename tests/data/{miscellaneous/nested_stub.pyi => cases/nested_stub.py} (97%) rename tests/data/{py_36 => cases}/numeric_literals.py (91%) rename tests/data/{py_36 => cases}/numeric_literals_skip_underscores.py (80%) rename tests/data/{simple_cases => cases}/one_element_subscript.py (100%) rename tests/data/{py_310 => cases}/parenthesized_context_managers.py (95%) rename tests/data/{py_310 => cases}/pattern_matching_complex.py (96%) rename tests/data/{py_310 => cases}/pattern_matching_extras.py (98%) rename tests/data/{py_310 => cases}/pattern_matching_generic.py (98%) rename tests/data/{py_310 => cases}/pattern_matching_simple.py (98%) rename tests/data/{py_310 => cases}/pattern_matching_style.py (97%) create mode 100644 tests/data/cases/pep604_union_types_line_breaks.py rename tests/data/{py_38 => cases}/pep_570.py (95%) rename tests/data/{py_38 => cases}/pep_572.py (96%) rename tests/data/{fast => cases}/pep_572_do_not_remove_parens.py (96%) rename tests/data/{py_310 => cases}/pep_572_py310.py (93%) rename tests/data/{py_39 => cases}/pep_572_py39.py (89%) rename tests/data/{py_38 => cases}/pep_572_remove_parens.py (98%) rename tests/data/{simple_cases => cases}/pep_604.py (100%) rename tests/data/{py_311 => cases}/pep_646.py (98%) rename tests/data/{py_311 => cases}/pep_654.py (96%) rename tests/data/{py_311 => cases}/pep_654_style.py (98%) rename tests/data/{miscellaneous => cases}/power_op_newline.py (73%) rename tests/data/{simple_cases => cases}/power_op_spacing.py (95%) rename tests/data/{simple_cases => cases}/prefer_rhs_split_reformatted.py (100%) rename tests/data/{preview/async_stmts.py => cases/preview_async_stmts.py} (93%) rename tests/data/{preview/cantfit.py => cases/preview_cantfit.py} (99%) rename tests/data/{preview/comments7.py => cases/preview_comments7.py} (92%) rename tests/data/{preview_context_managers/targeting_py38.py => cases/preview_context_managers_38.py} (96%) rename tests/data/{preview_context_managers/targeting_py39.py => cases/preview_context_managers_39.py} (98%) rename tests/data/{preview_context_managers/auto_detect/features_3_10.py => cases/preview_context_managers_autodetect_310.py} (93%) rename tests/data/{preview_context_managers/auto_detect/features_3_11.py => cases/preview_context_managers_autodetect_311.py} (92%) rename tests/data/{preview_context_managers/auto_detect/features_3_8.py => cases/preview_context_managers_autodetect_38.py} (98%) rename tests/data/{preview_context_managers/auto_detect/features_3_9.py => cases/preview_context_managers_autodetect_39.py} (93%) rename tests/data/{preview/dummy_implementations.py => cases/preview_dummy_implementations.py} (98%) rename tests/data/{preview/format_unicode_escape_seq.py => cases/preview_format_unicode_escape_seq.py} (96%) rename tests/data/{preview/long_dict_values.py => cases/preview_long_dict_values.py} (99%) rename tests/data/{preview/long_strings.py => cases/preview_long_strings.py} (99%) rename tests/data/{preview/long_strings__east_asian_width.py => cases/preview_long_strings__east_asian_width.py} (96%) rename tests/data/{preview/long_strings__edge_case.py => cases/preview_long_strings__edge_case.py} (99%) rename tests/data/{preview/long_strings__regression.py => cases/preview_long_strings__regression.py} (99%) rename tests/data/{preview/long_strings__type_annotations.py => cases/preview_long_strings__type_annotations.py} (95%) rename tests/data/{preview/multiline_strings.py => cases/preview_multiline_strings.py} (99%) rename tests/data/{preview/no_blank_line_before_docstring.py => cases/preview_no_blank_line_before_docstring.py} (97%) rename tests/data/{preview/pep_572.py => cases/preview_pep_572.py} (75%) rename tests/data/{preview/percent_precedence.py => cases/preview_percent_precedence.py} (96%) create mode 100644 tests/data/cases/preview_power_op_spacing.py rename tests/data/{preview/prefer_rhs_split.py => cases/preview_prefer_rhs_split.py} (99%) create mode 100644 tests/data/cases/preview_return_annotation_brackets_string.py rename tests/data/{preview/trailing_comma.py => cases/preview_trailing_comma.py} (97%) rename tests/data/{preview_py_310/pep_572.py => cases/py310_pep572.py} (77%) rename tests/data/{pyink/empty_lines.py => cases/pyink_empty_lines.py} (78%) rename tests/data/{pyink/imports.py => cases/pyink_imports.py} (78%) rename tests/data/{pyink/indent.py => cases/pyink_indent.py} (92%) rename tests/data/{pyink/module_docstring_1.py => cases/pyink_module_docstring_1.py} (83%) rename tests/data/{pyink/module_docstring_2.py => cases/pyink_module_docstring_2.py} (63%) rename tests/data/{pyink/module_docstring_3.py => cases/pyink_module_docstring_3.py} (70%) rename tests/data/{pyink/module_docstring_4.py => cases/pyink_module_docstring_4.py} (61%) rename tests/data/{pyink/nested_brackets.py => cases/pyink_nested_brackets.py} (97%) rename tests/data/{pyink/nested_brackets_explodes.py => cases/pyink_nested_brackets_explodes.py} (96%) rename tests/data/{pyink/pragma_comments.py => cases/pyink_pragma_comments.py} (93%) rename tests/data/{pyink/str_concat_in_func_args.py => cases/pyink_str_concat_in_func_args.py} (97%) rename tests/data/{py_37 => cases}/python37.py (95%) rename tests/data/{py_38 => cases}/python38.py (93%) rename tests/data/{py_39 => cases}/python39.py (92%) rename tests/data/{simple_cases => cases}/remove_await_parens.py (100%) rename tests/data/{simple_cases => cases}/remove_except_parens.py (100%) rename tests/data/{simple_cases => cases}/remove_for_brackets.py (100%) rename tests/data/{simple_cases => cases}/remove_newline_after_code_block_open.py (100%) rename tests/data/{py_310 => cases}/remove_newline_after_match.py (94%) rename tests/data/{simple_cases => cases}/remove_parens.py (100%) rename tests/data/{py_39 => cases}/remove_with_brackets.py (98%) rename tests/data/{simple_cases => cases}/return_annotation_brackets.py (92%) rename tests/data/{simple_cases => cases}/skip_magic_trailing_comma.py (97%) rename tests/data/{simple_cases => cases}/slices.py (100%) rename tests/data/{py_310 => cases}/starred_for_target.py (92%) rename tests/data/{simple_cases => cases}/string_prefixes.py (100%) rename tests/data/{miscellaneous/stub.pyi => cases/stub.py} (99%) rename tests/data/{simple_cases => cases}/torture.py (100%) rename tests/data/{simple_cases => cases}/trailing_comma_optional_parens1.py (100%) rename tests/data/{simple_cases => cases}/trailing_comma_optional_parens2.py (100%) rename tests/data/{simple_cases => cases}/trailing_comma_optional_parens3.py (100%) rename tests/data/{simple_cases => cases}/trailing_commas_in_leading_parts.py (100%) rename tests/data/{simple_cases => cases}/tricky_unicode_symbols.py (100%) rename tests/data/{simple_cases => cases}/tupleassign.py (100%) create mode 100644 tests/data/cases/type_aliases.py rename tests/data/{type_comments => cases}/type_comment_syntax_error.py (100%) rename tests/data/{py_312 => cases}/type_params.py (97%) rename tests/data/{simple_cases => cases}/whitespace.py (100%) delete mode 100644 tests/data/preview/return_annotation_brackets_string.py delete mode 100644 tests/data/py_312/type_aliases.py create mode 100644 tests/data/raw_docstring.py diff --git a/patches/pyink.patch b/patches/pyink.patch index 8a58fcb0db0..20fd5fb7bfe 100644 --- a/patches/pyink.patch +++ b/patches/pyink.patch @@ -376,7 +376,7 @@ +) --- a/linegen.py +++ b/linegen.py -@@ -7,6 +7,11 @@ from enum import Enum, auto +@@ -8,6 +8,11 @@ from enum import Enum, auto from functools import partial, wraps from typing import Collection, Iterator, List, Optional, Set, Union, cast @@ -388,7 +388,7 @@ from pyink.brackets import ( COMMA_PRIORITY, DOT_PRIORITY, -@@ -15,6 +20,7 @@ from pyink.brackets import ( +@@ -16,6 +21,7 @@ from pyink.brackets import ( ) from pyink.comments import FMT_OFF, generate_comments, list_comments from pyink.lines import ( @@ -396,7 +396,7 @@ Line, RHSResult, append_leaves, -@@ -83,6 +89,15 @@ LeafID = int +@@ -84,6 +90,15 @@ LeafID = int LN = Union[Leaf, Node] @@ -412,7 +412,7 @@ class CannotSplit(CannotTransform): """A readable split that fits the allotted line length is impossible.""" -@@ -102,7 +117,9 @@ class LineGenerator(Visitor[Line]): +@@ -103,7 +118,9 @@ class LineGenerator(Visitor[Line]): self.current_line: Line self.__post_init__() @@ -423,7 +423,7 @@ """Generate a line. If the line is empty, only emit if it makes sense. -@@ -111,7 +128,10 @@ class LineGenerator(Visitor[Line]): +@@ -112,7 +129,10 @@ class LineGenerator(Visitor[Line]): If any lines were generated, set up a new current_line. """ if not self.current_line: @@ -435,7 +435,7 @@ return # Line is empty, don't emit. Creating a new one unnecessary. if ( -@@ -126,7 +146,13 @@ class LineGenerator(Visitor[Line]): +@@ -127,7 +147,13 @@ class LineGenerator(Visitor[Line]): return complete_line = self.current_line @@ -450,7 +450,7 @@ yield complete_line def visit_default(self, node: LN) -> Iterator[Line]: -@@ -152,7 +178,9 @@ class LineGenerator(Visitor[Line]): +@@ -153,7 +179,9 @@ class LineGenerator(Visitor[Line]): normalize_prefix(node, inside_brackets=any_open_brackets) if self.mode.string_normalization and node.type == token.STRING: node.value = normalize_string_prefix(node.value) @@ -461,7 +461,7 @@ if node.type == token.NUMBER: normalize_numeric_literal(node) if node.type not in WHITESPACE: -@@ -162,7 +190,10 @@ class LineGenerator(Visitor[Line]): +@@ -163,7 +191,10 @@ class LineGenerator(Visitor[Line]): def visit_test(self, node: Node) -> Iterator[Line]: """Visit an `x if y else z` test""" @@ -473,7 +473,7 @@ already_parenthesized = ( node.prev_sibling and node.prev_sibling.type == token.LPAR ) -@@ -178,7 +209,7 @@ class LineGenerator(Visitor[Line]): +@@ -179,7 +210,7 @@ class LineGenerator(Visitor[Line]): def visit_INDENT(self, node: Leaf) -> Iterator[Line]: """Increase indentation level, maybe yield a line.""" # In blib2to3 INDENT never holds comments. @@ -482,7 +482,7 @@ yield from self.visit_default(node) def visit_DEDENT(self, node: Leaf) -> Iterator[Line]: -@@ -193,7 +224,7 @@ class LineGenerator(Visitor[Line]): +@@ -194,7 +225,7 @@ class LineGenerator(Visitor[Line]): yield from self.visit_default(node) # Finally, emit the dedent. @@ -491,7 +491,7 @@ def visit_stmt( self, node: Node, keywords: Set[str], parens: Set[str] -@@ -305,9 +336,9 @@ class LineGenerator(Visitor[Line]): +@@ -306,9 +337,9 @@ class LineGenerator(Visitor[Line]): ) and is_stub_body(node): yield from self.visit_default(node) else: @@ -503,7 +503,7 @@ else: if ( -@@ -400,10 +431,13 @@ class LineGenerator(Visitor[Line]): +@@ -419,10 +450,13 @@ class LineGenerator(Visitor[Line]): yield from self.visit_default(node) def visit_STRING(self, leaf: Leaf) -> Iterator[Line]: @@ -519,7 +519,7 @@ # We're ignoring docstrings with backslash newline escapes because changing # indentation of those changes the AST representation of the code. if self.mode.string_normalization: -@@ -414,7 +448,9 @@ class LineGenerator(Visitor[Line]): +@@ -433,7 +467,9 @@ class LineGenerator(Visitor[Line]): # formatting as visit_default() is called *after*. To avoid a # situation where this function formats a docstring differently on # the second pass, normalize it early. @@ -530,7 +530,7 @@ else: docstring = leaf.value prefix = get_string_prefix(docstring) -@@ -428,8 +464,9 @@ class LineGenerator(Visitor[Line]): +@@ -447,8 +483,9 @@ class LineGenerator(Visitor[Line]): quote_len = 1 if docstring[1] != quote_char else 3 docstring = docstring[quote_len:-quote_len] docstring_started_empty = not docstring @@ -541,7 +541,7 @@ if is_multiline_string(leaf): docstring = fix_docstring(docstring, indent) else: -@@ -470,7 +507,13 @@ class LineGenerator(Visitor[Line]): +@@ -489,7 +526,13 @@ class LineGenerator(Visitor[Line]): # If docstring is one line, we don't put the closing quotes on a # separate line because it looks ugly (#3320). lines = docstring.splitlines() @@ -556,7 +556,7 @@ # If adding closing quotes would cause the last line to exceed # the maximum line length then put a line break before the -@@ -482,6 +525,15 @@ class LineGenerator(Visitor[Line]): +@@ -501,6 +544,15 @@ class LineGenerator(Visitor[Line]): and not has_trailing_backslash ): leaf.value = prefix + quote + docstring + "\n" + indent + quote @@ -572,9 +572,9 @@ else: leaf.value = prefix + quote + docstring + quote else: -@@ -509,7 +561,8 @@ class LineGenerator(Visitor[Line]): - self.visit_classdef = partial(v, keywords={"class"}, parens=Ø) - self.visit_expr_stmt = partial(v, keywords=Ø, parens=ASSIGNMENTS) +@@ -535,7 +587,8 @@ class LineGenerator(Visitor[Line]): + self.visit_expr_stmt = partial(v, keywords=Ø, parens=assignments) + self.visit_return_stmt = partial(v, keywords={"return"}, parens={"return"}) - self.visit_import_from = partial(v, keywords=Ø, parens={"import"}) + if not self.mode.is_pyink: @@ -582,7 +582,7 @@ self.visit_del_stmt = partial(v, keywords=Ø, parens={"del"}) self.visit_async_funcdef = self.visit_async_stmt self.visit_decorated = self.visit_decorators -@@ -536,10 +589,19 @@ def transform_line( +@@ -581,10 +634,19 @@ def transform_line( ll = mode.line_length sn = mode.string_normalization @@ -606,7 +606,7 @@ transformers: List[Transformer] if ( -@@ -714,8 +776,7 @@ def _first_right_hand_split( +@@ -793,8 +855,7 @@ def _first_right_hand_split( omit: Collection[LeafID] = (), ) -> RHSResult: """Split the line into head, body, tail starting with the last bracket pair. @@ -616,7 +616,7 @@ _maybe_split_omitting_optional_parens to get an opinion whether to prefer splitting on the right side of an assignment statement. """ -@@ -744,12 +805,53 @@ def _first_right_hand_split( +@@ -823,12 +884,53 @@ def _first_right_hand_split( tail_leaves.reverse() body_leaves.reverse() head_leaves.reverse() @@ -673,7 +673,7 @@ tail = bracket_split_build_line( tail_leaves, line, opening_bracket, component=_BracketSplitComponent.tail ) -@@ -908,7 +1010,7 @@ def bracket_split_build_line( +@@ -987,7 +1089,7 @@ def bracket_split_build_line( result = Line(mode=original.mode, depth=original.depth) if component is _BracketSplitComponent.body: result.inside_brackets = True @@ -682,7 +682,7 @@ if leaves: # Since body is a new indent level, remove spurious leading whitespace. normalize_prefix(leaves[0], inside_brackets=True) -@@ -939,6 +1041,13 @@ def bracket_split_build_line( +@@ -1018,6 +1120,13 @@ def bracket_split_build_line( and leaves[0].parent.next_sibling and leaves[0].parent.next_sibling.type == token.VBAR ) @@ -696,7 +696,7 @@ ) if original.is_import or no_commas: -@@ -1478,7 +1587,7 @@ def generate_trailers_to_omit(line: Line +@@ -1558,7 +1667,7 @@ def generate_trailers_to_omit(line: Line if not line.magic_trailing_comma: yield omit @@ -764,7 +764,7 @@ if self.is_comment: raise ValueError("cannot append to standalone comments") -@@ -303,6 +323,20 @@ class Line: +@@ -308,6 +328,20 @@ class Line: return False @@ -785,7 +785,7 @@ def contains_multiline_strings(self) -> bool: return any(is_multiline_string(leaf) for leaf in self.leaves) -@@ -470,7 +504,7 @@ class Line: +@@ -475,7 +509,7 @@ class Line: if not self: return "\n" @@ -794,7 +794,7 @@ leaves = iter(self.leaves) first = next(leaves) res = f"{first.prefix}{indent}{first.value}" -@@ -577,7 +611,7 @@ class EmptyLineTracker: +@@ -591,7 +625,7 @@ class EmptyLineTracker: def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: max_allowed = 1 @@ -803,7 +803,7 @@ max_allowed = 1 if self.mode.is_pyi else 2 if current_line.leaves: # Consume the first leaf's extra newlines. -@@ -592,7 +626,7 @@ class EmptyLineTracker: +@@ -606,7 +640,7 @@ class EmptyLineTracker: depth = current_line.depth previous_def = None @@ -812,7 +812,7 @@ previous_def = self.previous_defs.pop() if previous_def is not None: -@@ -641,10 +675,25 @@ class EmptyLineTracker: +@@ -655,10 +689,25 @@ class EmptyLineTracker: if ( self.previous_line @@ -840,7 +840,7 @@ ): return (before or 1), 0 -@@ -657,6 +706,14 @@ class EmptyLineTracker: +@@ -671,6 +720,14 @@ class EmptyLineTracker: return 0, 1 return before, 1 @@ -855,7 +855,7 @@ if self.previous_line and self.previous_line.opens_block: return 0, 0 return before, 0 -@@ -677,15 +734,16 @@ class EmptyLineTracker: +@@ -691,15 +748,16 @@ class EmptyLineTracker: return 0, 0 @@ -875,7 +875,7 @@ and before == 0 ): slc = self.semantic_leading_comment -@@ -702,9 +760,9 @@ class EmptyLineTracker: +@@ -716,9 +774,9 @@ class EmptyLineTracker: if self.mode.is_pyi: if current_line.is_class or self.previous_line.is_class: @@ -887,7 +887,7 @@ newlines = 1 elif current_line.is_stub_class and self.previous_line.is_stub_class: # No blank line between classes with an empty body -@@ -733,7 +791,7 @@ class EmptyLineTracker: +@@ -747,7 +805,7 @@ class EmptyLineTracker: # Blank line between a block of functions (maybe with preceding # decorators) and a block of non-functions newlines = 1 @@ -896,7 +896,7 @@ newlines = 1 else: newlines = 0 -@@ -801,10 +859,14 @@ def is_line_short_enough( # noqa: C901 +@@ -815,10 +873,14 @@ def is_line_short_enough( # noqa: C901 line_str = line_to_string(line) width = str_width if mode.preview else len @@ -912,7 +912,7 @@ and "\n" not in line_str # multiline strings and not line.contains_standalone_comments() ) -@@ -813,7 +875,7 @@ def is_line_short_enough( # noqa: C901 +@@ -827,7 +889,7 @@ def is_line_short_enough( # noqa: C901 return False if "\n" not in line_str: # No multiline strings (MLS) present @@ -921,7 +921,7 @@ first, *_, last = line_str.split("\n") if width(first) > mode.line_length or width(last) > mode.line_length: -@@ -1003,7 +1065,7 @@ def can_omit_invisible_parens( +@@ -1017,7 +1079,7 @@ def can_omit_invisible_parens( def _can_omit_opening_paren(line: Line, *, first: Leaf, line_length: int) -> bool: """See `can_omit_invisible_parens`.""" remainder = False @@ -930,7 +930,7 @@ _index = -1 for _index, leaf, leaf_length in line.enumerate_with_length(): if leaf.type in CLOSING_BRACKETS and leaf.opening_bracket is first: -@@ -1027,7 +1089,7 @@ def _can_omit_opening_paren(line: Line, +@@ -1041,7 +1103,7 @@ def _can_omit_opening_paren(line: Line, def _can_omit_closing_paren(line: Line, *, last: Leaf, line_length: int) -> bool: """See `can_omit_invisible_parens`.""" @@ -950,7 +950,7 @@ from warnings import warn from pyink.const import DEFAULT_LINE_LENGTH -@@ -191,11 +191,33 @@ class Deprecated(UserWarning): +@@ -196,11 +196,33 @@ class Deprecated(UserWarning): """Visible deprecation warning.""" @@ -984,7 +984,7 @@ is_pyi: bool = False is_ipynb: bool = False skip_source_first_line: bool = False -@@ -203,6 +225,8 @@ class Mode: +@@ -208,6 +230,8 @@ class Mode: experimental_string_processing: bool = False python_cell_magics: Set[str] = field(default_factory=set) preview: bool = False @@ -993,7 +993,7 @@ def __post_init__(self) -> None: if self.experimental_string_processing: -@@ -221,6 +245,9 @@ class Mode: +@@ -226,6 +250,9 @@ class Mode: """ if feature is Preview.string_processing: return self.preview or self.experimental_string_processing @@ -1003,7 +1003,7 @@ return self.preview def get_cache_key(self) -> str: -@@ -235,12 +262,25 @@ class Mode: +@@ -240,12 +267,25 @@ class Mode: version_str, str(self.line_length), str(int(self.string_normalization)), @@ -1123,7 +1123,7 @@ "platformdirs>=2", "tomli>=1.1.0; python_version < '3.11'", "typing_extensions>=4.0.1; python_version < '3.11'", -+ "black>=23.9.1", ++ "black==23.10.0", ] -dynamic = ["readme", "version"] +dynamic = ["version"] @@ -1181,7 +1181,7 @@ "no_jupyter: run when `jupyter` extra NOT installed", ] markers = [ -@@ -142,12 +101,3 @@ filterwarnings = [ +@@ -142,35 +101,3 @@ filterwarnings = [ # https://github.com/aio-libs/aiohttp/pull/7302 "ignore:datetime.*utcfromtimestamp\\(\\) is deprecated and scheduled for removal:DeprecationWarning", ] @@ -1194,6 +1194,29 @@ -] -[tool.coverage.run] -relative_files = true +- +-[tool.mypy] +-# Specify the target platform details in config, so your developers are +-# free to run mypy on Windows, Linux, or macOS and get consistent +-# results. +-python_version = "3.8" +-mypy_path = "src" +-strict = true +-# Unreachable blocks have been an issue when compiling mypyc, let's try to avoid 'em in the first place. +-warn_unreachable = true +-implicit_reexport = true +-show_error_codes = true +-show_column_numbers = true +- +-[[tool.mypy.overrides]] +-module = ["pathspec.*", "IPython.*", "colorama.*", "tokenize_rt.*", "uvloop.*", "_black_version.*"] +-ignore_missing_imports = true +- +-# CI only checks src/, but in case users are running LSP or similar we explicitly ignore +-# errors in test data files. +-[[tool.mypy.overrides]] +-module = ["tests.data.*"] +-ignore_errors = true --- a/strings.py +++ b/strings.py @@ -8,6 +8,7 @@ from functools import lru_cache @@ -1238,7 +1261,7 @@ +pyink = false --- a/tests/test_black.py +++ b/tests/test_black.py -@@ -1243,7 +1243,7 @@ class BlackTestCase(BlackBaseTestCase): +@@ -1268,7 +1268,7 @@ class BlackTestCase(BlackBaseTestCase): report=report, ) fsts.assert_called_once_with( @@ -1247,7 +1270,7 @@ ) # __PYINK_STDIN_FILENAME__ should have been stripped report.done.assert_called_with(expected, pyink.Changed.YES) -@@ -1269,6 +1269,7 @@ class BlackTestCase(BlackBaseTestCase): +@@ -1294,6 +1294,7 @@ class BlackTestCase(BlackBaseTestCase): fast=True, write_back=pyink.WriteBack.YES, mode=replace(DEFAULT_MODE, is_pyi=True), @@ -1255,7 +1278,7 @@ ) # __PYINK_STDIN_FILENAME__ should have been stripped report.done.assert_called_with(expected, pyink.Changed.YES) -@@ -1294,6 +1295,7 @@ class BlackTestCase(BlackBaseTestCase): +@@ -1319,6 +1320,7 @@ class BlackTestCase(BlackBaseTestCase): fast=True, write_back=pyink.WriteBack.YES, mode=replace(DEFAULT_MODE, is_ipynb=True), @@ -1263,7 +1286,7 @@ ) # __PYINK_STDIN_FILENAME__ should have been stripped report.done.assert_called_with(expected, pyink.Changed.YES) -@@ -1380,6 +1382,28 @@ class BlackTestCase(BlackBaseTestCase): +@@ -1405,6 +1407,28 @@ class BlackTestCase(BlackBaseTestCase): pass # StringIO does not support detach assert output.getvalue() == "" @@ -1292,7 +1315,7 @@ def test_invalid_cli_regex(self) -> None: for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]: self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2) -@@ -2481,6 +2505,119 @@ class TestFileCollection: +@@ -2506,6 +2530,119 @@ class TestFileCollection: stdin_filename=stdin_filename, ) @@ -1412,24 +1435,6 @@ class TestDeFactoAPI: """Test that certain symbols that are commonly used externally keep working. ---- a/tests/test_format.py -+++ b/tests/test_format.py -@@ -44,6 +44,15 @@ def test_preview_format(filename: str) - - check_file("preview", filename, pyink.Mode(preview=True)) - - -+@pytest.mark.parametrize("filename", all_data_cases("pyink")) -+def test_pyink_format(filename: str) -> None: -+ check_file( -+ "pyink", -+ filename, -+ pyink.Mode(preview=True, line_length=80, is_pyink=True, pyink_indentation=2), -+ ) -+ -+ - def test_preview_context_managers_targeting_py38() -> None: - source, expected = read_data("preview_context_managers", "targeting_py38.py") - mode = pyink.Mode(preview=True, target_versions={pyink.TargetVersion.PY38}) --- a/tox.ini +++ b/tox.ini @@ -95,4 +95,4 @@ setenv = PYTHONPATH = {toxinidir}/src @@ -1440,7 +1445,7 @@ + pyink --check {toxinidir}/src {toxinidir}/tests --- a/trans.py +++ b/trans.py -@@ -27,8 +27,8 @@ from typing import ( +@@ -28,8 +28,8 @@ from typing import ( from mypy_extensions import trait from pyink.comments import contains_pragma_comment @@ -1451,7 +1456,7 @@ from pyink.nodes import ( CLOSING_BRACKETS, OPENING_BRACKETS, -@@ -192,9 +192,18 @@ class StringTransformer(ABC): +@@ -193,9 +193,18 @@ class StringTransformer(ABC): # Ideally this would be a dataclass, but unfortunately mypyc breaks when used with # `abc.ABC`. @@ -1471,7 +1476,7 @@ @abstractmethod def do_match(self, line: Line) -> TMatchResult: -@@ -649,7 +658,9 @@ class StringMerger(StringTransformer, Cu +@@ -650,7 +659,9 @@ class StringMerger(StringTransformer, Cu S_leaf = Leaf(token.STRING, S) if self.normalize_strings: @@ -1482,7 +1487,7 @@ # Fill the 'custom_splits' list with the appropriate CustomSplit objects. temp_string = S_leaf.value[len(prefix) + 1 : -1] -@@ -890,7 +901,13 @@ class StringParenStripper(StringTransfor +@@ -891,7 +902,13 @@ class StringParenStripper(StringTransfor idx += 1 if string_indices: @@ -1497,7 +1502,7 @@ return TErr("This line has no strings wrapped in parens.") def do_transform( -@@ -1091,7 +1108,7 @@ class BaseStringSplitter(StringTransform +@@ -1095,7 +1112,7 @@ class BaseStringSplitter(StringTransform # NN: The leaf that is after N. # WMA4 the whitespace at the beginning of the line. @@ -1506,7 +1511,7 @@ if is_valid_index(string_idx - 1): p_idx = string_idx - 1 -@@ -1445,7 +1462,7 @@ class StringSplitter(BaseStringSplitter, +@@ -1449,7 +1466,7 @@ class StringSplitter(BaseStringSplitter, characters expand to two columns). """ result = self.line_length @@ -1515,7 +1520,7 @@ result -= 1 if ends_with_comma else 0 result -= string_op_leaves_length return result -@@ -1456,11 +1473,11 @@ class StringSplitter(BaseStringSplitter, +@@ -1460,11 +1477,11 @@ class StringSplitter(BaseStringSplitter, # The last index of a string of length N is N-1. max_break_width -= 1 # Leading whitespace is not present in the string value (e.g. Leaf.value). @@ -1529,7 +1534,7 @@ ) return -@@ -1757,7 +1774,9 @@ class StringSplitter(BaseStringSplitter, +@@ -1761,7 +1778,9 @@ class StringSplitter(BaseStringSplitter, def _maybe_normalize_string_quotes(self, leaf: Leaf) -> None: if self.normalize_strings: @@ -1540,7 +1545,7 @@ def _normalize_f_string(self, string: str, prefix: str) -> str: """ -@@ -1880,7 +1899,8 @@ class StringParenWrapper(BaseStringSplit +@@ -1884,7 +1903,8 @@ class StringParenWrapper(BaseStringSplit char == " " or char in SPLIT_SAFE_CHARS for char in string_value ): # And will still violate the line length limit when split... @@ -1550,7 +1555,7 @@ if str_width(string_value) > max_string_width: # And has no associated custom splits... if not self.has_custom_splits(string_value): -@@ -2126,7 +2146,7 @@ class StringParenWrapper(BaseStringSplit +@@ -2130,7 +2150,7 @@ class StringParenWrapper(BaseStringSplit string_value = LL[string_idx].value string_line = Line( mode=line.mode, diff --git a/pyproject.toml b/pyproject.toml index fac260fbae8..1a080676837 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ dependencies = [ "platformdirs>=2", "tomli>=1.1.0; python_version < '3.11'", "typing_extensions>=4.0.1; python_version < '3.11'", - "black==23.9.1", + "black==23.10.0", ] dynamic = ["version"] diff --git a/src/pyink/cache.py b/src/pyink/cache.py index 02dee6bbee1..d6137ca10da 100644 --- a/src/pyink/cache.py +++ b/src/pyink/cache.py @@ -1,4 +1,5 @@ """Caching of formatted files with feature-based invalidation.""" + import hashlib import os import pickle @@ -36,8 +37,9 @@ def get_cache_dir() -> Path: repeated calls. """ # NOTE: Function mostly exists as a clean way to test getting the cache directory. - default_cache_dir = user_cache_dir("pyink", version=__version__) + default_cache_dir = user_cache_dir("pyink") cache_dir = Path(os.environ.get("PYINK_CACHE_DIR", default_cache_dir)) + cache_dir = cache_dir / __version__ return cache_dir diff --git a/src/pyink/concurrency.py b/src/pyink/concurrency.py index 3a923a1ec34..97bdc285add 100644 --- a/src/pyink/concurrency.py +++ b/src/pyink/concurrency.py @@ -9,6 +9,7 @@ import os import signal import sys +import traceback from concurrent.futures import Executor, ProcessPoolExecutor, ThreadPoolExecutor from multiprocessing import Manager from pathlib import Path @@ -170,8 +171,10 @@ async def schedule_formatting( src = tasks.pop(task) if task.cancelled(): cancelled.append(task) - elif task.exception(): - report.failed(src, str(task.exception())) + elif exc := task.exception(): + if report.verbose: + traceback.print_exception(type(exc), exc, exc.__traceback__) + report.failed(src, str(exc)) else: changed = Changed.YES if task.result() else Changed.NO # If the file was written back or was successfully checked as diff --git a/src/pyink/debug.py b/src/pyink/debug.py index 3b27e8081be..16c1f0e39a5 100644 --- a/src/pyink/debug.py +++ b/src/pyink/debug.py @@ -1,5 +1,5 @@ -from dataclasses import dataclass -from typing import Iterator, TypeVar, Union +from dataclasses import dataclass, field +from typing import Any, Iterator, List, TypeVar, Union from pyink.nodes import Visitor from pyink.output import out @@ -14,26 +14,33 @@ @dataclass class DebugVisitor(Visitor[T]): tree_depth: int = 0 + list_output: List[str] = field(default_factory=list) + print_output: bool = True + + def out(self, message: str, *args: Any, **kwargs: Any) -> None: + self.list_output.append(message) + if self.print_output: + out(message, *args, **kwargs) def visit_default(self, node: LN) -> Iterator[T]: indent = " " * (2 * self.tree_depth) if isinstance(node, Node): _type = type_repr(node.type) - out(f"{indent}{_type}", fg="yellow") + self.out(f"{indent}{_type}", fg="yellow") self.tree_depth += 1 for child in node.children: yield from self.visit(child) self.tree_depth -= 1 - out(f"{indent}/{_type}", fg="yellow", bold=False) + self.out(f"{indent}/{_type}", fg="yellow", bold=False) else: _type = token.tok_name.get(node.type, str(node.type)) - out(f"{indent}{_type}", fg="blue", nl=False) + self.out(f"{indent}{_type}", fg="blue", nl=False) if node.prefix: # We don't have to handle prefixes for `Node` objects since # that delegates to the first child anyway. - out(f" {node.prefix!r}", fg="green", bold=False, nl=False) - out(f" {node.value!r}", fg="blue", bold=False) + self.out(f" {node.prefix!r}", fg="green", bold=False, nl=False) + self.out(f" {node.value!r}", fg="blue", bold=False) @classmethod def show(cls, code: Union[str, Leaf, Node]) -> None: diff --git a/src/pyink/linegen.py b/src/pyink/linegen.py index 43ed13b23b1..6da9df46674 100644 --- a/src/pyink/linegen.py +++ b/src/pyink/linegen.py @@ -1,6 +1,7 @@ """ Generating lines of code. """ + import sys from dataclasses import replace from enum import Enum, auto @@ -428,6 +429,24 @@ def visit_factor(self, node: Node) -> Iterator[Line]: node.insert_child(index, Node(syms.atom, [lpar, operand, rpar])) yield from self.visit_default(node) + def visit_tname(self, node: Node) -> Iterator[Line]: + """ + Add potential parentheses around types in function parameter lists to be made + into real parentheses in case the type hint is too long to fit on a line + Examples: + def foo(a: int, b: float = 7): ... + + -> + + def foo(a: (int), b: (float) = 7): ... + """ + if Preview.parenthesize_long_type_hints in self.mode: + assert len(node.children) == 3 + if maybe_make_parens_invisible_in_atom(node.children[2], parent=node): + wrap_in_parentheses(node, node.children[2], visible=False) + + yield from self.visit_default(node) + def visit_STRING(self, leaf: Leaf) -> Iterator[Line]: if ( Preview.hex_codes_in_unicode_sequences in self.mode @@ -550,7 +569,14 @@ def __post_init__(self) -> None: self.visit_except_clause = partial(v, keywords={"except"}, parens={"except"}) self.visit_with_stmt = partial(v, keywords={"with"}, parens={"with"}) self.visit_classdef = partial(v, keywords={"class"}, parens=Ø) - self.visit_expr_stmt = partial(v, keywords=Ø, parens=ASSIGNMENTS) + + # When this is moved out of preview, add ":" directly to ASSIGNMENTS in nodes.py + if Preview.parenthesize_long_type_hints in self.mode: + assignments = ASSIGNMENTS | {":"} + else: + assignments = ASSIGNMENTS + self.visit_expr_stmt = partial(v, keywords=Ø, parens=assignments) + self.visit_return_stmt = partial(v, keywords={"return"}, parens={"return"}) if not self.mode.is_pyink: self.visit_import_from = partial(v, keywords=Ø, parens={"import"}) @@ -563,6 +589,17 @@ def __post_init__(self) -> None: self.visit_case_block = self.visit_match_case +def _hugging_power_ops_line_to_string( + line: Line, + features: Collection[Feature], + mode: Mode, +) -> Optional[str]: + try: + return line_to_string(next(hug_power_op(line, features, mode))) + except CannotTransform: + return None + + def transform_line( line: Line, mode: Mode, features: Collection[Feature] = () ) -> Iterator[Line]: @@ -578,6 +615,14 @@ def transform_line( line_str = line_to_string(line) + # We need the line string when power operators are hugging to determine if we should + # split the line. Default to line_str, if no power operator are present on the line. + line_str_hugging_power_ops = ( + (_hugging_power_ops_line_to_string(line, features, mode) or line_str) + if Preview.fix_power_op_line_length in mode + else line_str + ) + ll = mode.line_length sn = mode.string_normalization preferred_quote = mode.preferred_quote @@ -600,7 +645,7 @@ def transform_line( and not line.should_split_rhs and not line.magic_trailing_comma and ( - is_line_short_enough(line, mode=mode, line_str=line_str) + is_line_short_enough(line, mode=mode, line_str=line_str_hugging_power_ops) or line.contains_unsplittable_type_ignore() ) and not (line.inside_brackets and line.contains_standalone_comments()) @@ -610,7 +655,7 @@ def transform_line( transformers = [string_merge, string_paren_strip] else: transformers = [] - elif line.is_def: + elif line.is_def and not should_split_funcdef_with_rhs(line, mode): transformers = [left_hand_split] else: @@ -689,6 +734,40 @@ def _rhs( yield line +def should_split_funcdef_with_rhs(line: Line, mode: Mode) -> bool: + """If a funcdef has a magic trailing comma in the return type, then we should first + split the line with rhs to respect the comma. + """ + if Preview.respect_magic_trailing_comma_in_return_type not in mode: + return False + + return_type_leaves: List[Leaf] = [] + in_return_type = False + + for leaf in line.leaves: + if leaf.type == token.COLON: + in_return_type = False + if in_return_type: + return_type_leaves.append(leaf) + if leaf.type == token.RARROW: + in_return_type = True + + # using `bracket_split_build_line` will mess with whitespace, so we duplicate a + # couple lines from it. + result = Line(mode=line.mode, depth=line.depth) + leaves_to_track = get_leaves_inside_matching_brackets(return_type_leaves) + for leaf in return_type_leaves: + result.append( + leaf, + preformatted=True, + track_bracket=id(leaf) in leaves_to_track, + ) + + # we could also return true if the line is too long, and the return type is longer + # than the param list. Or if `should_split_rhs` returns True. + return result.magic_trailing_comma is not None + + class _BracketSplitComponent(Enum): head = auto() body = auto() @@ -1477,7 +1556,7 @@ def maybe_make_parens_invisible_in_atom( Returns whether the node should itself be wrapped in invisible parentheses. """ if ( - node.type != syms.atom + node.type not in (syms.atom, syms.expr) or is_empty_tuple(node) or is_one_tuple(node) or (is_yield(node) and parent.type != syms.expr_stmt) @@ -1501,6 +1580,7 @@ def maybe_make_parens_invisible_in_atom( syms.except_clause, syms.funcdef, syms.with_stmt, + syms.tname, # these ones aren't useful to end users, but they do please fuzzers syms.for_stmt, syms.del_stmt, diff --git a/src/pyink/lines.py b/src/pyink/lines.py index c38f944b396..1e2d6457539 100644 --- a/src/pyink/lines.py +++ b/src/pyink/lines.py @@ -213,11 +213,16 @@ def is_class_paren_empty(self) -> bool: @property def is_triple_quoted_string(self) -> bool: """Is the line a triple quoted string?""" - return ( - bool(self) - and self.leaves[0].type == token.STRING - and self.leaves[0].value.startswith(('"""', "'''")) - ) + if not self or self.leaves[0].type != token.STRING: + return False + value = self.leaves[0].value + if value.startswith(('"""', "'''")): + return True + if Preview.accept_raw_docstrings in self.mode and value.startswith( + ("r'''", 'r"""', "R'''", 'R"""') + ): + return True + return False @property def opens_block(self) -> bool: @@ -584,6 +589,15 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: if self.previous_line is None else before - previous_after ) + if ( + Preview.module_docstring_newlines in current_line.mode + and self.previous_block + and self.previous_block.previous_block is None + and len(self.previous_block.original_line.leaves) == 1 + and self.previous_block.original_line.is_triple_quoted_string + ): + before = 1 + block = LinesBlock( mode=self.mode, previous_block=self.previous_block, diff --git a/src/pyink/mode.py b/src/pyink/mode.py index 76db49da4bc..f7be716cceb 100644 --- a/src/pyink/mode.py +++ b/src/pyink/mode.py @@ -180,11 +180,16 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() parenthesize_conditional_expressions = auto() + parenthesize_long_type_hints = auto() + respect_magic_trailing_comma_in_return_type = auto() skip_magic_trailing_comma_in_subscript = auto() wrap_long_dict_values_in_parens = auto() wrap_multiple_context_managers_in_parens = auto() dummy_implementations = auto() walrus_subscript = auto() + module_docstring_newlines = auto() + accept_raw_docstrings = auto() + fix_power_op_line_length = auto() class Deprecated(UserWarning): diff --git a/src/pyink/numerics.py b/src/pyink/numerics.py index 879e5b2cf36..67ac8595fcc 100644 --- a/src/pyink/numerics.py +++ b/src/pyink/numerics.py @@ -1,6 +1,7 @@ """ Formatting numeric literals. """ + from blib2to3.pytree import Leaf diff --git a/src/pyink/parsing.py b/src/pyink/parsing.py index 79174b092bf..2f333a257cc 100644 --- a/src/pyink/parsing.py +++ b/src/pyink/parsing.py @@ -1,9 +1,10 @@ """ Parse Python code and perform AST validation. """ + import ast import sys -from typing import Final, Iterable, Iterator, List, Set, Tuple +from typing import Iterable, Iterator, List, Set, Tuple from pyink.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature from pyink.nodes import syms @@ -14,8 +15,6 @@ from blib2to3.pgen2.tokenize import TokenError from blib2to3.pytree import Leaf, Node -PY2_HINT: Final = "Python 2 support was removed in version 22.0." - class InvalidInput(ValueError): """Raised when input source code fails all parse attempts.""" @@ -26,9 +25,9 @@ def get_grammars(target_versions: Set[TargetVersion]) -> List[Grammar]: # No target_version specified, so try all grammars. return [ # Python 3.7-3.9 - pygram.python_grammar_no_print_statement_no_exec_statement_async_keywords, + pygram.python_grammar_async_keywords, # Python 3.0-3.6 - pygram.python_grammar_no_print_statement_no_exec_statement, + pygram.python_grammar, # Python 3.10+ pygram.python_grammar_soft_keywords, ] @@ -39,12 +38,10 @@ def get_grammars(target_versions: Set[TargetVersion]) -> List[Grammar]: target_versions, Feature.ASYNC_IDENTIFIERS ) and not supports_feature(target_versions, Feature.PATTERN_MATCHING): # Python 3.7-3.9 - grammars.append( - pygram.python_grammar_no_print_statement_no_exec_statement_async_keywords - ) + grammars.append(pygram.python_grammar_async_keywords) if not supports_feature(target_versions, Feature.ASYNC_KEYWORDS): # Python 3.0-3.6 - grammars.append(pygram.python_grammar_no_print_statement_no_exec_statement) + grammars.append(pygram.python_grammar) if any(Feature.PATTERN_MATCHING in VERSION_TO_FEATURES[v] for v in target_versions): # Python 3.10+ grammars.append(pygram.python_grammar_soft_keywords) @@ -89,14 +86,6 @@ def lib2to3_parse(src_txt: str, target_versions: Iterable[TargetVersion] = ()) - # Choose the latest version when raising the actual parsing error. assert len(errors) >= 1 exc = errors[max(errors)] - - if matches_grammar(src_txt, pygram.python_grammar) or matches_grammar( - src_txt, pygram.python_grammar_no_print_statement - ): - original_msg = exc.args[0] - msg = f"{original_msg}\n{PY2_HINT}" - raise InvalidInput(msg) from None - raise exc from None if isinstance(result, Leaf): diff --git a/src/pyink/report.py b/src/pyink/report.py index a494193df42..4f7aad26dba 100644 --- a/src/pyink/report.py +++ b/src/pyink/report.py @@ -1,6 +1,7 @@ """ Summarize Black runs to users. """ + from dataclasses import dataclass from enum import Enum from pathlib import Path diff --git a/src/pyink/rusty.py b/src/pyink/rusty.py index 84a80b5a2c2..ebd4c052d1f 100644 --- a/src/pyink/rusty.py +++ b/src/pyink/rusty.py @@ -2,6 +2,7 @@ See https://doc.rust-lang.org/book/ch09-00-error-handling.html. """ + from typing import Generic, TypeVar, Union T = TypeVar("T") diff --git a/src/pyink/trans.py b/src/pyink/trans.py index b1aaa851146..f78b98de0b4 100644 --- a/src/pyink/trans.py +++ b/src/pyink/trans.py @@ -1,6 +1,7 @@ """ String transformers that can split and merge strings. """ + import re from abc import ABC, abstractmethod from collections import defaultdict @@ -959,6 +960,9 @@ def _transform_to_new_line( LL[lpar_or_rpar_idx].remove() # Remove lpar. replace_child(LL[idx], string_leaf) new_line.append(string_leaf) + # replace comments + old_comments = new_line.comments.pop(id(LL[idx]), []) + new_line.comments.setdefault(id(string_leaf), []).extend(old_comments) else: LL[lpar_or_rpar_idx].remove() # This is a rpar. diff --git a/tests/conftest.py b/tests/conftest.py index 67517268d1b..1a0dd747d8e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1 +1,28 @@ +import pytest + pytest_plugins = ["tests.optional"] + +PRINT_FULL_TREE: bool = False +PRINT_TREE_DIFF: bool = True + + +def pytest_addoption(parser: pytest.Parser) -> None: + parser.addoption( + "--print-full-tree", + action="store_true", + default=False, + help="print full syntax trees on failed tests", + ) + parser.addoption( + "--print-tree-diff", + action="store_true", + default=True, + help="print diff of syntax trees on failed tests", + ) + + +def pytest_configure(config: pytest.Config) -> None: + global PRINT_FULL_TREE + global PRINT_TREE_DIFF + PRINT_FULL_TREE = config.getoption("--print-full-tree") + PRINT_TREE_DIFF = config.getoption("--print-tree-diff") diff --git a/tests/data/simple_cases/attribute_access_on_number_literals.py b/tests/data/cases/attribute_access_on_number_literals.py similarity index 100% rename from tests/data/simple_cases/attribute_access_on_number_literals.py rename to tests/data/cases/attribute_access_on_number_literals.py diff --git a/tests/data/simple_cases/beginning_backslash.py b/tests/data/cases/beginning_backslash.py similarity index 100% rename from tests/data/simple_cases/beginning_backslash.py rename to tests/data/cases/beginning_backslash.py diff --git a/tests/data/simple_cases/bracketmatch.py b/tests/data/cases/bracketmatch.py similarity index 100% rename from tests/data/simple_cases/bracketmatch.py rename to tests/data/cases/bracketmatch.py diff --git a/tests/data/simple_cases/class_blank_parentheses.py b/tests/data/cases/class_blank_parentheses.py similarity index 100% rename from tests/data/simple_cases/class_blank_parentheses.py rename to tests/data/cases/class_blank_parentheses.py diff --git a/tests/data/simple_cases/class_methods_new_line.py b/tests/data/cases/class_methods_new_line.py similarity index 100% rename from tests/data/simple_cases/class_methods_new_line.py rename to tests/data/cases/class_methods_new_line.py diff --git a/tests/data/simple_cases/collections.py b/tests/data/cases/collections.py similarity index 100% rename from tests/data/simple_cases/collections.py rename to tests/data/cases/collections.py diff --git a/tests/data/simple_cases/comment_after_escaped_newline.py b/tests/data/cases/comment_after_escaped_newline.py similarity index 100% rename from tests/data/simple_cases/comment_after_escaped_newline.py rename to tests/data/cases/comment_after_escaped_newline.py diff --git a/tests/data/simple_cases/comments.py b/tests/data/cases/comments.py similarity index 100% rename from tests/data/simple_cases/comments.py rename to tests/data/cases/comments.py diff --git a/tests/data/simple_cases/comments2.py b/tests/data/cases/comments2.py similarity index 100% rename from tests/data/simple_cases/comments2.py rename to tests/data/cases/comments2.py diff --git a/tests/data/simple_cases/comments3.py b/tests/data/cases/comments3.py similarity index 100% rename from tests/data/simple_cases/comments3.py rename to tests/data/cases/comments3.py diff --git a/tests/data/simple_cases/comments4.py b/tests/data/cases/comments4.py similarity index 100% rename from tests/data/simple_cases/comments4.py rename to tests/data/cases/comments4.py diff --git a/tests/data/simple_cases/comments5.py b/tests/data/cases/comments5.py similarity index 100% rename from tests/data/simple_cases/comments5.py rename to tests/data/cases/comments5.py diff --git a/tests/data/simple_cases/comments6.py b/tests/data/cases/comments6.py similarity index 100% rename from tests/data/simple_cases/comments6.py rename to tests/data/cases/comments6.py diff --git a/tests/data/simple_cases/comments8.py b/tests/data/cases/comments8.py similarity index 100% rename from tests/data/simple_cases/comments8.py rename to tests/data/cases/comments8.py diff --git a/tests/data/simple_cases/comments9.py b/tests/data/cases/comments9.py similarity index 100% rename from tests/data/simple_cases/comments9.py rename to tests/data/cases/comments9.py diff --git a/tests/data/simple_cases/comments_non_breaking_space.py b/tests/data/cases/comments_non_breaking_space.py similarity index 100% rename from tests/data/simple_cases/comments_non_breaking_space.py rename to tests/data/cases/comments_non_breaking_space.py diff --git a/tests/data/simple_cases/composition.py b/tests/data/cases/composition.py similarity index 100% rename from tests/data/simple_cases/composition.py rename to tests/data/cases/composition.py diff --git a/tests/data/simple_cases/composition_no_trailing_comma.py b/tests/data/cases/composition_no_trailing_comma.py similarity index 100% rename from tests/data/simple_cases/composition_no_trailing_comma.py rename to tests/data/cases/composition_no_trailing_comma.py diff --git a/tests/data/conditional_expression.py b/tests/data/cases/conditional_expression.py similarity index 99% rename from tests/data/conditional_expression.py rename to tests/data/cases/conditional_expression.py index 620a12dc986..c30cd76c791 100644 --- a/tests/data/conditional_expression.py +++ b/tests/data/cases/conditional_expression.py @@ -1,3 +1,4 @@ +# flags: --preview long_kwargs_single_line = my_function( foo="test, this is a sample value", bar=some_long_value_name_foo_bar_baz if some_boolean_variable else some_fallback_value_foo_bar_baz, diff --git a/tests/data/simple_cases/docstring.py b/tests/data/cases/docstring.py similarity index 100% rename from tests/data/simple_cases/docstring.py rename to tests/data/cases/docstring.py diff --git a/tests/data/simple_cases/docstring_no_extra_empty_line_before_eof.py b/tests/data/cases/docstring_no_extra_empty_line_before_eof.py similarity index 100% rename from tests/data/simple_cases/docstring_no_extra_empty_line_before_eof.py rename to tests/data/cases/docstring_no_extra_empty_line_before_eof.py diff --git a/tests/data/miscellaneous/docstring_no_string_normalization.py b/tests/data/cases/docstring_no_string_normalization.py similarity index 98% rename from tests/data/miscellaneous/docstring_no_string_normalization.py rename to tests/data/cases/docstring_no_string_normalization.py index a90b578f09a..4ec6b8a0153 100644 --- a/tests/data/miscellaneous/docstring_no_string_normalization.py +++ b/tests/data/cases/docstring_no_string_normalization.py @@ -1,3 +1,4 @@ +# flags: --skip-string-normalization class ALonelyClass: ''' A multiline class docstring. diff --git a/tests/data/simple_cases/docstring_preview.py b/tests/data/cases/docstring_preview.py similarity index 100% rename from tests/data/simple_cases/docstring_preview.py rename to tests/data/cases/docstring_preview.py diff --git a/tests/data/miscellaneous/docstring_preview_no_string_normalization.py b/tests/data/cases/docstring_preview_no_string_normalization.py similarity index 88% rename from tests/data/miscellaneous/docstring_preview_no_string_normalization.py rename to tests/data/cases/docstring_preview_no_string_normalization.py index 338cc01d33e..712c7364f51 100644 --- a/tests/data/miscellaneous/docstring_preview_no_string_normalization.py +++ b/tests/data/cases/docstring_preview_no_string_normalization.py @@ -1,3 +1,4 @@ +# flags: --preview --skip-string-normalization def do_not_touch_this_prefix(): R"""There was a bug where docstring prefixes would be normalized even with -S.""" diff --git a/tests/data/simple_cases/empty_lines.py b/tests/data/cases/empty_lines.py similarity index 100% rename from tests/data/simple_cases/empty_lines.py rename to tests/data/cases/empty_lines.py diff --git a/tests/data/simple_cases/expression.diff b/tests/data/cases/expression.diff similarity index 100% rename from tests/data/simple_cases/expression.diff rename to tests/data/cases/expression.diff diff --git a/tests/data/simple_cases/expression.py b/tests/data/cases/expression.py similarity index 100% rename from tests/data/simple_cases/expression.py rename to tests/data/cases/expression.py diff --git a/tests/data/simple_cases/fmtonoff.py b/tests/data/cases/fmtonoff.py similarity index 100% rename from tests/data/simple_cases/fmtonoff.py rename to tests/data/cases/fmtonoff.py diff --git a/tests/data/simple_cases/fmtonoff2.py b/tests/data/cases/fmtonoff2.py similarity index 100% rename from tests/data/simple_cases/fmtonoff2.py rename to tests/data/cases/fmtonoff2.py diff --git a/tests/data/simple_cases/fmtonoff3.py b/tests/data/cases/fmtonoff3.py similarity index 100% rename from tests/data/simple_cases/fmtonoff3.py rename to tests/data/cases/fmtonoff3.py diff --git a/tests/data/simple_cases/fmtonoff4.py b/tests/data/cases/fmtonoff4.py similarity index 100% rename from tests/data/simple_cases/fmtonoff4.py rename to tests/data/cases/fmtonoff4.py diff --git a/tests/data/simple_cases/fmtonoff5.py b/tests/data/cases/fmtonoff5.py similarity index 100% rename from tests/data/simple_cases/fmtonoff5.py rename to tests/data/cases/fmtonoff5.py diff --git a/tests/data/simple_cases/fmtpass_imports.py b/tests/data/cases/fmtpass_imports.py similarity index 100% rename from tests/data/simple_cases/fmtpass_imports.py rename to tests/data/cases/fmtpass_imports.py diff --git a/tests/data/simple_cases/fmtskip.py b/tests/data/cases/fmtskip.py similarity index 100% rename from tests/data/simple_cases/fmtskip.py rename to tests/data/cases/fmtskip.py diff --git a/tests/data/simple_cases/fmtskip2.py b/tests/data/cases/fmtskip2.py similarity index 100% rename from tests/data/simple_cases/fmtskip2.py rename to tests/data/cases/fmtskip2.py diff --git a/tests/data/simple_cases/fmtskip3.py b/tests/data/cases/fmtskip3.py similarity index 100% rename from tests/data/simple_cases/fmtskip3.py rename to tests/data/cases/fmtskip3.py diff --git a/tests/data/simple_cases/fmtskip4.py b/tests/data/cases/fmtskip4.py similarity index 100% rename from tests/data/simple_cases/fmtskip4.py rename to tests/data/cases/fmtskip4.py diff --git a/tests/data/simple_cases/fmtskip5.py b/tests/data/cases/fmtskip5.py similarity index 100% rename from tests/data/simple_cases/fmtskip5.py rename to tests/data/cases/fmtskip5.py diff --git a/tests/data/simple_cases/fmtskip6.py b/tests/data/cases/fmtskip6.py similarity index 100% rename from tests/data/simple_cases/fmtskip6.py rename to tests/data/cases/fmtskip6.py diff --git a/tests/data/simple_cases/fmtskip7.py b/tests/data/cases/fmtskip7.py similarity index 100% rename from tests/data/simple_cases/fmtskip7.py rename to tests/data/cases/fmtskip7.py diff --git a/tests/data/simple_cases/fmtskip8.py b/tests/data/cases/fmtskip8.py similarity index 100% rename from tests/data/simple_cases/fmtskip8.py rename to tests/data/cases/fmtskip8.py diff --git a/tests/data/simple_cases/fstring.py b/tests/data/cases/fstring.py similarity index 100% rename from tests/data/simple_cases/fstring.py rename to tests/data/cases/fstring.py diff --git a/tests/data/cases/funcdef_return_type_trailing_comma.py b/tests/data/cases/funcdef_return_type_trailing_comma.py new file mode 100644 index 00000000000..9b9b9c673de --- /dev/null +++ b/tests/data/cases/funcdef_return_type_trailing_comma.py @@ -0,0 +1,301 @@ +# flags: --preview --minimum-version=3.10 +# normal, short, function definition +def foo(a, b) -> tuple[int, float]: ... + + +# normal, short, function definition w/o return type +def foo(a, b): ... + + +# no splitting +def foo(a: A, b: B) -> list[p, q]: + pass + + +# magic trailing comma in param list +def foo(a, b,): ... + + +# magic trailing comma in nested params in param list +def foo(a, b: tuple[int, float,]): ... + + +# magic trailing comma in return type, no params +def a() -> tuple[ + a, + b, +]: ... + + +# magic trailing comma in return type, params +def foo(a: A, b: B) -> list[ + p, + q, +]: + pass + + +# magic trailing comma in param list and in return type +def foo( + a: a, + b: b, +) -> list[ + a, + a, +]: + pass + + +# long function definition, param list is longer +def aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( + bbbbbbbbbbbbbbbbbb, +) -> cccccccccccccccccccccccccccccc: ... + + +# long function definition, return type is longer +# this should maybe split on rhs? +def aaaaaaaaaaaaaaaaa(bbbbbbbbbbbbbbbbbb) -> list[ + Ccccccccccccccccccccccccccccccccccccccccccccccccccc, Dddddd +]: ... + + +# long return type, no param list +def foo() -> list[ + Loooooooooooooooooooooooooooooooooooong, + Loooooooooooooooooooong, + Looooooooooooong, +]: ... + + +# long function name, no param list, no return value +def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeeeeeeeeeeeeeery_looooooong(): + pass + + +# long function name, no param list +def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeeeeeeeeeeeeeery_looooooong() -> ( + list[int, float] +): ... + + +# long function name, no return value +def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeeeeeeeeeeeeeery_looooooong( + a, b +): ... + + +# unskippable type hint (??) +def foo(a) -> list[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]: # type: ignore + pass + + +def foo(a) -> list[ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +]: # abpedeifnore + pass + +def foo(a, b: list[Bad],): ... # type: ignore + +# don't lose any comments (no magic) +def foo( # 1 + a, # 2 + b) -> list[ # 3 + a, # 4 + b]: # 5 + ... # 6 + + +# don't lose any comments (param list magic) +def foo( # 1 + a, # 2 + b,) -> list[ # 3 + a, # 4 + b]: # 5 + ... # 6 + + +# don't lose any comments (return type magic) +def foo( # 1 + a, # 2 + b) -> list[ # 3 + a, # 4 + b,]: # 5 + ... # 6 + + +# don't lose any comments (both magic) +def foo( # 1 + a, # 2 + b,) -> list[ # 3 + a, # 4 + b,]: # 5 + ... # 6 + +# real life example +def SimplePyFn( + context: hl.GeneratorContext, + buffer_input: Buffer[UInt8, 2], + func_input: Buffer[Int32, 2], + float_arg: Scalar[Float32], + offset: int = 0, +) -> tuple[ + Buffer[UInt8, 2], + Buffer[UInt8, 2], +]: ... +# output +# normal, short, function definition +def foo(a, b) -> tuple[int, float]: ... + + +# normal, short, function definition w/o return type +def foo(a, b): ... + + +# no splitting +def foo(a: A, b: B) -> list[p, q]: + pass + + +# magic trailing comma in param list +def foo( + a, + b, +): ... + + +# magic trailing comma in nested params in param list +def foo( + a, + b: tuple[ + int, + float, + ], +): ... + + +# magic trailing comma in return type, no params +def a() -> tuple[ + a, + b, +]: ... + + +# magic trailing comma in return type, params +def foo(a: A, b: B) -> list[ + p, + q, +]: + pass + + +# magic trailing comma in param list and in return type +def foo( + a: a, + b: b, +) -> list[ + a, + a, +]: + pass + + +# long function definition, param list is longer +def aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( + bbbbbbbbbbbbbbbbbb, +) -> cccccccccccccccccccccccccccccc: ... + + +# long function definition, return type is longer +# this should maybe split on rhs? +def aaaaaaaaaaaaaaaaa( + bbbbbbbbbbbbbbbbbb, +) -> list[Ccccccccccccccccccccccccccccccccccccccccccccccccccc, Dddddd]: ... + + +# long return type, no param list +def foo() -> list[ + Loooooooooooooooooooooooooooooooooooong, + Loooooooooooooooooooong, + Looooooooooooong, +]: ... + + +# long function name, no param list, no return value +def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeeeeeeeeeeeeeery_looooooong(): + pass + + +# long function name, no param list +def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeeeeeeeeeeeeeery_looooooong() -> ( + list[int, float] +): ... + + +# long function name, no return value +def thiiiiiiiiiiiiiiiiiis_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiis_veeeeeeeeeeeeeeeeeeeeeeery_looooooong( + a, b +): ... + + +# unskippable type hint (??) +def foo(a) -> list[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]: # type: ignore + pass + + +def foo( + a, +) -> list[ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +]: # abpedeifnore + pass + + +def foo( + a, + b: list[Bad], +): ... # type: ignore + + +# don't lose any comments (no magic) +def foo(a, b) -> list[a, b]: # 1 # 2 # 3 # 4 # 5 + ... # 6 + + +# don't lose any comments (param list magic) +def foo( # 1 + a, # 2 + b, +) -> list[a, b]: # 3 # 4 # 5 + ... # 6 + + +# don't lose any comments (return type magic) +def foo(a, b) -> list[ # 1 # 2 # 3 + a, # 4 + b, +]: # 5 + ... # 6 + + +# don't lose any comments (both magic) +def foo( # 1 + a, # 2 + b, +) -> list[ # 3 + a, # 4 + b, +]: # 5 + ... # 6 + + +# real life example +def SimplePyFn( + context: hl.GeneratorContext, + buffer_input: Buffer[UInt8, 2], + func_input: Buffer[Int32, 2], + float_arg: Scalar[Float32], + offset: int = 0, +) -> tuple[ + Buffer[UInt8, 2], + Buffer[UInt8, 2], +]: ... diff --git a/tests/data/simple_cases/function.py b/tests/data/cases/function.py similarity index 100% rename from tests/data/simple_cases/function.py rename to tests/data/cases/function.py diff --git a/tests/data/simple_cases/function2.py b/tests/data/cases/function2.py similarity index 100% rename from tests/data/simple_cases/function2.py rename to tests/data/cases/function2.py diff --git a/tests/data/simple_cases/function_trailing_comma.py b/tests/data/cases/function_trailing_comma.py similarity index 100% rename from tests/data/simple_cases/function_trailing_comma.py rename to tests/data/cases/function_trailing_comma.py diff --git a/tests/data/simple_cases/ignore_pyi.py b/tests/data/cases/ignore_pyi.py similarity index 97% rename from tests/data/simple_cases/ignore_pyi.py rename to tests/data/cases/ignore_pyi.py index 3ef61079bfe..4fae7530eb9 100644 --- a/tests/data/simple_cases/ignore_pyi.py +++ b/tests/data/cases/ignore_pyi.py @@ -1,3 +1,4 @@ +# flags: --pyi def f(): # type: ignore ... diff --git a/tests/data/simple_cases/import_spacing.py b/tests/data/cases/import_spacing.py similarity index 100% rename from tests/data/simple_cases/import_spacing.py rename to tests/data/cases/import_spacing.py diff --git a/tests/data/miscellaneous/linelength6.py b/tests/data/cases/linelength6.py similarity index 80% rename from tests/data/miscellaneous/linelength6.py rename to tests/data/cases/linelength6.py index 4fb342726f5..158038bf960 100644 --- a/tests/data/miscellaneous/linelength6.py +++ b/tests/data/cases/linelength6.py @@ -1,3 +1,4 @@ +# flags: --line-length=6 # Regression test for #3427, which reproes only with line length <= 6 def f(): """ diff --git a/tests/data/miscellaneous/long_strings_flag_disabled.py b/tests/data/cases/long_strings_flag_disabled.py similarity index 100% rename from tests/data/miscellaneous/long_strings_flag_disabled.py rename to tests/data/cases/long_strings_flag_disabled.py diff --git a/tests/data/cases/module_docstring_1.py b/tests/data/cases/module_docstring_1.py new file mode 100644 index 00000000000..d5897b4db60 --- /dev/null +++ b/tests/data/cases/module_docstring_1.py @@ -0,0 +1,26 @@ +# flags: --preview +"""Single line module-level docstring should be followed by single newline.""" + + + + +a = 1 + + +"""I'm just a string so should be followed by 2 newlines.""" + + + + +b = 2 + +# output +"""Single line module-level docstring should be followed by single newline.""" + +a = 1 + + +"""I'm just a string so should be followed by 2 newlines.""" + + +b = 2 diff --git a/tests/data/cases/module_docstring_2.py b/tests/data/cases/module_docstring_2.py new file mode 100644 index 00000000000..e1f81b4d76b --- /dev/null +++ b/tests/data/cases/module_docstring_2.py @@ -0,0 +1,68 @@ +# flags: --preview +"""I am a very helpful module docstring. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, +quis nostrud exercitation ullamco laboris +nisi ut aliquip ex ea commodo consequat. +Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur. +Excepteur sint occaecat cupidatat non proident, +sunt in culpa qui officia deserunt mollit anim id est laborum. +""" + + + + +a = 1 + + +"""Look at me I'm a docstring... + +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +........................................................NOT! +""" + + + + +b = 2 + +# output +"""I am a very helpful module docstring. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, +quis nostrud exercitation ullamco laboris +nisi ut aliquip ex ea commodo consequat. +Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur. +Excepteur sint occaecat cupidatat non proident, +sunt in culpa qui officia deserunt mollit anim id est laborum. +""" + +a = 1 + + +"""Look at me I'm a docstring... + +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +............................................................ +........................................................NOT! +""" + + +b = 2 diff --git a/tests/data/cases/module_docstring_3.py b/tests/data/cases/module_docstring_3.py new file mode 100644 index 00000000000..0631e136a3d --- /dev/null +++ b/tests/data/cases/module_docstring_3.py @@ -0,0 +1,8 @@ +# flags: --preview +"""Single line module-level docstring should be followed by single newline.""" +a = 1 + +# output +"""Single line module-level docstring should be followed by single newline.""" + +a = 1 diff --git a/tests/data/cases/module_docstring_4.py b/tests/data/cases/module_docstring_4.py new file mode 100644 index 00000000000..515174dcc04 --- /dev/null +++ b/tests/data/cases/module_docstring_4.py @@ -0,0 +1,9 @@ +# flags: --preview +"""Single line module-level docstring should be followed by single newline.""" + +a = 1 + +# output +"""Single line module-level docstring should be followed by single newline.""" + +a = 1 diff --git a/tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py b/tests/data/cases/multiline_consecutive_open_parentheses_ignore.py similarity index 100% rename from tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py rename to tests/data/cases/multiline_consecutive_open_parentheses_ignore.py diff --git a/tests/data/miscellaneous/nested_stub.pyi b/tests/data/cases/nested_stub.py similarity index 97% rename from tests/data/miscellaneous/nested_stub.pyi rename to tests/data/cases/nested_stub.py index 15e69d854db..b81549ec115 100644 --- a/tests/data/miscellaneous/nested_stub.pyi +++ b/tests/data/cases/nested_stub.py @@ -1,3 +1,4 @@ +# flags: --pyi --preview import sys class Outer: diff --git a/tests/data/py_36/numeric_literals.py b/tests/data/cases/numeric_literals.py similarity index 91% rename from tests/data/py_36/numeric_literals.py rename to tests/data/cases/numeric_literals.py index 254da68d330..99669328744 100644 --- a/tests/data/py_36/numeric_literals.py +++ b/tests/data/cases/numeric_literals.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3.6 - x = 123456789 x = 123456 x = .1 @@ -21,9 +19,6 @@ # output - -#!/usr/bin/env python3.6 - x = 123456789 x = 123456 x = 0.1 diff --git a/tests/data/py_36/numeric_literals_skip_underscores.py b/tests/data/cases/numeric_literals_skip_underscores.py similarity index 80% rename from tests/data/py_36/numeric_literals_skip_underscores.py rename to tests/data/cases/numeric_literals_skip_underscores.py index e345bb90276..6d60bdbb34d 100644 --- a/tests/data/py_36/numeric_literals_skip_underscores.py +++ b/tests/data/cases/numeric_literals_skip_underscores.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3.6 - x = 123456789 x = 1_2_3_4_5_6_7 x = 1E+1 @@ -11,8 +9,6 @@ # output -#!/usr/bin/env python3.6 - x = 123456789 x = 1_2_3_4_5_6_7 x = 1e1 diff --git a/tests/data/simple_cases/one_element_subscript.py b/tests/data/cases/one_element_subscript.py similarity index 100% rename from tests/data/simple_cases/one_element_subscript.py rename to tests/data/cases/one_element_subscript.py diff --git a/tests/data/py_310/parenthesized_context_managers.py b/tests/data/cases/parenthesized_context_managers.py similarity index 95% rename from tests/data/py_310/parenthesized_context_managers.py rename to tests/data/cases/parenthesized_context_managers.py index 1ef09a1bd34..16645a18baa 100644 --- a/tests/data/py_310/parenthesized_context_managers.py +++ b/tests/data/cases/parenthesized_context_managers.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.10 with (CtxManager() as example): ... diff --git a/tests/data/py_310/pattern_matching_complex.py b/tests/data/cases/pattern_matching_complex.py similarity index 96% rename from tests/data/py_310/pattern_matching_complex.py rename to tests/data/cases/pattern_matching_complex.py index 97ee194fd39..10b4d26e289 100644 --- a/tests/data/py_310/pattern_matching_complex.py +++ b/tests/data/cases/pattern_matching_complex.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.10 # Cases sampled from Lib/test/test_patma.py # case black_test_patma_098 @@ -142,3 +143,7 @@ y = 1 case []: y = 2 +# issue 3790 +match (X.type, Y): + case _: + pass diff --git a/tests/data/py_310/pattern_matching_extras.py b/tests/data/cases/pattern_matching_extras.py similarity index 98% rename from tests/data/py_310/pattern_matching_extras.py rename to tests/data/cases/pattern_matching_extras.py index 0242d264e5b..1e1481d7bbe 100644 --- a/tests/data/py_310/pattern_matching_extras.py +++ b/tests/data/cases/pattern_matching_extras.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.10 import match match something: diff --git a/tests/data/py_310/pattern_matching_generic.py b/tests/data/cases/pattern_matching_generic.py similarity index 98% rename from tests/data/py_310/pattern_matching_generic.py rename to tests/data/cases/pattern_matching_generic.py index 00a0e4a677d..4b4d45f0bff 100644 --- a/tests/data/py_310/pattern_matching_generic.py +++ b/tests/data/cases/pattern_matching_generic.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.10 re.match() match = a with match() as match: diff --git a/tests/data/py_310/pattern_matching_simple.py b/tests/data/cases/pattern_matching_simple.py similarity index 98% rename from tests/data/py_310/pattern_matching_simple.py rename to tests/data/cases/pattern_matching_simple.py index 5ed62415a4b..6fa2000f0de 100644 --- a/tests/data/py_310/pattern_matching_simple.py +++ b/tests/data/cases/pattern_matching_simple.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.10 # Cases sampled from PEP 636 examples match command.split(): diff --git a/tests/data/py_310/pattern_matching_style.py b/tests/data/cases/pattern_matching_style.py similarity index 97% rename from tests/data/py_310/pattern_matching_style.py rename to tests/data/cases/pattern_matching_style.py index 8e18ce2ada6..2ee6ea2b6e9 100644 --- a/tests/data/py_310/pattern_matching_style.py +++ b/tests/data/cases/pattern_matching_style.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.10 match something: case b(): print(1+1) case c( diff --git a/tests/data/cases/pep604_union_types_line_breaks.py b/tests/data/cases/pep604_union_types_line_breaks.py new file mode 100644 index 00000000000..fee2b840494 --- /dev/null +++ b/tests/data/cases/pep604_union_types_line_breaks.py @@ -0,0 +1,188 @@ +# flags: --preview --minimum-version=3.10 +# This has always worked +z= Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong + +# "AnnAssign"s now also work +z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong +z: (Short + | Short2 + | Short3 + | Short4) +z: (int) +z: ((int)) + + +z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong = 7 +z: (Short + | Short2 + | Short3 + | Short4) = 8 +z: (int) = 2.3 +z: ((int)) = foo() + +# In case I go for not enforcing parantheses, this might get improved at the same time +x = ( + z + == 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999, + y + == 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999, +) + +x = ( + z == (9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999), + y == (9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999), +) + +# handle formatting of "tname"s in parameter list + +# remove unnecessary paren +def foo(i: (int)) -> None: ... + + +# this is a syntax error in the type annotation according to mypy, but it's not invalid *python* code, so make sure we don't mess with it and make it so. +def foo(i: (int,)) -> None: ... + +def foo( + i: int, + x: Loooooooooooooooooooooooong + | Looooooooooooooooong + | Looooooooooooooooooooong + | Looooooong, + *, + s: str, +) -> None: + pass + + +@app.get("/path/") +async def foo( + q: str + | None = Query(None, title="Some long title", description="Some long description") +): + pass + + +def f( + max_jobs: int + | None = Option( + None, help="Maximum number of jobs to launch. And some additional text." + ), + another_option: bool = False + ): + ... + + +# output +# This has always worked +z = ( + Loooooooooooooooooooooooong + | Loooooooooooooooooooooooong + | Loooooooooooooooooooooooong + | Loooooooooooooooooooooooong +) + +# "AnnAssign"s now also work +z: ( + Loooooooooooooooooooooooong + | Loooooooooooooooooooooooong + | Loooooooooooooooooooooooong + | Loooooooooooooooooooooooong +) +z: Short | Short2 | Short3 | Short4 +z: int +z: int + + +z: ( + Loooooooooooooooooooooooong + | Loooooooooooooooooooooooong + | Loooooooooooooooooooooooong + | Loooooooooooooooooooooooong +) = 7 +z: Short | Short2 | Short3 | Short4 = 8 +z: int = 2.3 +z: int = foo() + +# In case I go for not enforcing parantheses, this might get improved at the same time +x = ( + z + == 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999, + y + == 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999, +) + +x = ( + z + == ( + 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999 + | 9999999999999999999999999999999999999999 + ), + y + == ( + 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999 + + 9999999999999999999999999999999999999999 + ), +) + +# handle formatting of "tname"s in parameter list + + +# remove unnecessary paren +def foo(i: int) -> None: ... + + +# this is a syntax error in the type annotation according to mypy, but it's not invalid *python* code, so make sure we don't mess with it and make it so. +def foo(i: (int,)) -> None: ... + + +def foo( + i: int, + x: ( + Loooooooooooooooooooooooong + | Looooooooooooooooong + | Looooooooooooooooooooong + | Looooooong + ), + *, + s: str, +) -> None: + pass + + +@app.get("/path/") +async def foo( + q: str | None = Query( + None, title="Some long title", description="Some long description" + ) +): + pass + + +def f( + max_jobs: int | None = Option( + None, help="Maximum number of jobs to launch. And some additional text." + ), + another_option: bool = False, +): ... diff --git a/tests/data/py_38/pep_570.py b/tests/data/cases/pep_570.py similarity index 95% rename from tests/data/py_38/pep_570.py rename to tests/data/cases/pep_570.py index ca8f7ab1d95..2641c2b970e 100644 --- a/tests/data/py_38/pep_570.py +++ b/tests/data/cases/pep_570.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.8 def positional_only_arg(a, /): pass diff --git a/tests/data/py_38/pep_572.py b/tests/data/cases/pep_572.py similarity index 96% rename from tests/data/py_38/pep_572.py rename to tests/data/cases/pep_572.py index d41805f1cb1..742b6d5b7e4 100644 --- a/tests/data/py_38/pep_572.py +++ b/tests/data/cases/pep_572.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.8 (a := 1) (a := a) if (match := pattern.search(data)) is None: diff --git a/tests/data/fast/pep_572_do_not_remove_parens.py b/tests/data/cases/pep_572_do_not_remove_parens.py similarity index 96% rename from tests/data/fast/pep_572_do_not_remove_parens.py rename to tests/data/cases/pep_572_do_not_remove_parens.py index 05619ddcc2b..08dba3ffdf9 100644 --- a/tests/data/fast/pep_572_do_not_remove_parens.py +++ b/tests/data/cases/pep_572_do_not_remove_parens.py @@ -1,3 +1,4 @@ +# flags: --fast # Most of the following examples are really dumb, some of them aren't even accepted by Python, # we're fixing them only so fuzzers (which follow the grammar which actually allows these # examples matter of fact!) don't yell at us :p diff --git a/tests/data/py_310/pep_572_py310.py b/tests/data/cases/pep_572_py310.py similarity index 93% rename from tests/data/py_310/pep_572_py310.py rename to tests/data/cases/pep_572_py310.py index cb82b2d23f8..9f999deeb89 100644 --- a/tests/data/py_310/pep_572_py310.py +++ b/tests/data/cases/pep_572_py310.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.10 # Unparenthesized walruses are now allowed in indices since Python 3.10. x[a:=0] x[a:=0, b:=1] diff --git a/tests/data/py_39/pep_572_py39.py b/tests/data/cases/pep_572_py39.py similarity index 89% rename from tests/data/py_39/pep_572_py39.py rename to tests/data/cases/pep_572_py39.py index b8b081b8c45..d1614624d99 100644 --- a/tests/data/py_39/pep_572_py39.py +++ b/tests/data/cases/pep_572_py39.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.9 # Unparenthesized walruses are now allowed in set literals & set comprehensions # since Python 3.9 {x := 1, 2, 3} diff --git a/tests/data/py_38/pep_572_remove_parens.py b/tests/data/cases/pep_572_remove_parens.py similarity index 98% rename from tests/data/py_38/pep_572_remove_parens.py rename to tests/data/cases/pep_572_remove_parens.py index b952b2940c5..24f1ac29168 100644 --- a/tests/data/py_38/pep_572_remove_parens.py +++ b/tests/data/cases/pep_572_remove_parens.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.8 if (foo := 0): pass diff --git a/tests/data/simple_cases/pep_604.py b/tests/data/cases/pep_604.py similarity index 100% rename from tests/data/simple_cases/pep_604.py rename to tests/data/cases/pep_604.py diff --git a/tests/data/py_311/pep_646.py b/tests/data/cases/pep_646.py similarity index 98% rename from tests/data/py_311/pep_646.py rename to tests/data/cases/pep_646.py index e843ecf39d8..92b568a379c 100644 --- a/tests/data/py_311/pep_646.py +++ b/tests/data/cases/pep_646.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.11 A[*b] A[*b] = 1 A diff --git a/tests/data/py_311/pep_654.py b/tests/data/cases/pep_654.py similarity index 96% rename from tests/data/py_311/pep_654.py rename to tests/data/cases/pep_654.py index 387c0816f4b..12e49180e41 100644 --- a/tests/data/py_311/pep_654.py +++ b/tests/data/cases/pep_654.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.11 try: raise OSError("blah") except* ExceptionGroup as e: diff --git a/tests/data/py_311/pep_654_style.py b/tests/data/cases/pep_654_style.py similarity index 98% rename from tests/data/py_311/pep_654_style.py rename to tests/data/cases/pep_654_style.py index 9fc7c0c84db..0d34650e098 100644 --- a/tests/data/py_311/pep_654_style.py +++ b/tests/data/cases/pep_654_style.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.11 try: raise OSError("blah") except * ExceptionGroup as e: diff --git a/tests/data/miscellaneous/power_op_newline.py b/tests/data/cases/power_op_newline.py similarity index 73% rename from tests/data/miscellaneous/power_op_newline.py rename to tests/data/cases/power_op_newline.py index 85d434d63f6..d9b31403c9d 100644 --- a/tests/data/miscellaneous/power_op_newline.py +++ b/tests/data/cases/power_op_newline.py @@ -1,3 +1,4 @@ +# flags: --line-length=0 importA;()<<0**0# # output diff --git a/tests/data/simple_cases/power_op_spacing.py b/tests/data/cases/power_op_spacing.py similarity index 95% rename from tests/data/simple_cases/power_op_spacing.py rename to tests/data/cases/power_op_spacing.py index c95fa788fc3..b3ef0aae084 100644 --- a/tests/data/simple_cases/power_op_spacing.py +++ b/tests/data/cases/power_op_spacing.py @@ -29,6 +29,13 @@ def function_dont_replace_spaces(): p = {(k, k**2): v**2 for k, v in pairs} q = [10**i for i in range(6)] r = x**y +s = 1 ** 1 +t = ( + 1 + ** 1 + **1 + ** 1 +) a = 5.0**~4.0 b = 5.0 ** f() @@ -47,6 +54,13 @@ def function_dont_replace_spaces(): o = settings(max_examples=10**6.0) p = {(k, k**2): v**2.0 for k, v in pairs} q = [10.5**i for i in range(6)] +s = 1.0 ** 1.0 +t = ( + 1.0 + ** 1.0 + **1.0 + ** 1.0 +) # WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873) @@ -97,6 +111,8 @@ def function_dont_replace_spaces(): p = {(k, k**2): v**2 for k, v in pairs} q = [10**i for i in range(6)] r = x**y +s = 1**1 +t = 1**1**1**1 a = 5.0**~4.0 b = 5.0 ** f() @@ -115,6 +131,8 @@ def function_dont_replace_spaces(): o = settings(max_examples=10**6.0) p = {(k, k**2): v**2.0 for k, v in pairs} q = [10.5**i for i in range(6)] +s = 1.0**1.0 +t = 1.0**1.0**1.0**1.0 # WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873) diff --git a/tests/data/simple_cases/prefer_rhs_split_reformatted.py b/tests/data/cases/prefer_rhs_split_reformatted.py similarity index 100% rename from tests/data/simple_cases/prefer_rhs_split_reformatted.py rename to tests/data/cases/prefer_rhs_split_reformatted.py diff --git a/tests/data/preview/async_stmts.py b/tests/data/cases/preview_async_stmts.py similarity index 93% rename from tests/data/preview/async_stmts.py rename to tests/data/cases/preview_async_stmts.py index fe9594b2164..0a7671be5a6 100644 --- a/tests/data/preview/async_stmts.py +++ b/tests/data/cases/preview_async_stmts.py @@ -1,3 +1,4 @@ +# flags: --preview async def func() -> (int): return 0 diff --git a/tests/data/preview/cantfit.py b/tests/data/cases/preview_cantfit.py similarity index 99% rename from tests/data/preview/cantfit.py rename to tests/data/cases/preview_cantfit.py index 0849374f776..d5da6654f0c 100644 --- a/tests/data/preview/cantfit.py +++ b/tests/data/cases/preview_cantfit.py @@ -1,3 +1,4 @@ +# flags: --preview # long variable name this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = 0 this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = 1 # with a comment diff --git a/tests/data/preview/comments7.py b/tests/data/cases/preview_comments7.py similarity index 92% rename from tests/data/preview/comments7.py rename to tests/data/cases/preview_comments7.py index 8b1224017e5..006d4f7266f 100644 --- a/tests/data/preview/comments7.py +++ b/tests/data/cases/preview_comments7.py @@ -1,3 +1,4 @@ +# flags: --preview from .config import ( Any, Bool, @@ -131,6 +132,18 @@ def test_fails_invalid_post_data( square = Square(4) # type: Optional[Square] +# Regression test for https://github.com/psf/black/issues/3756. +[ + ( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ), +] +[ + ( # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ), +] + # output from .config import ( @@ -282,3 +295,15 @@ def test_fails_invalid_post_data( square = Square(4) # type: Optional[Square] + +# Regression test for https://github.com/psf/black/issues/3756. +[ + ( # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ), +] +[ + ( # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ), +] diff --git a/tests/data/preview_context_managers/targeting_py38.py b/tests/data/cases/preview_context_managers_38.py similarity index 96% rename from tests/data/preview_context_managers/targeting_py38.py rename to tests/data/cases/preview_context_managers_38.py index f125cdffb8a..719d94fdcc5 100644 --- a/tests/data/preview_context_managers/targeting_py38.py +++ b/tests/data/cases/preview_context_managers_38.py @@ -1,3 +1,4 @@ +# flags: --preview --minimum-version=3.8 with \ make_context_manager1() as cm1, \ make_context_manager2() as cm2, \ diff --git a/tests/data/preview_context_managers/targeting_py39.py b/tests/data/cases/preview_context_managers_39.py similarity index 98% rename from tests/data/preview_context_managers/targeting_py39.py rename to tests/data/cases/preview_context_managers_39.py index c9fcf9c8ba2..589e00ad187 100644 --- a/tests/data/preview_context_managers/targeting_py39.py +++ b/tests/data/cases/preview_context_managers_39.py @@ -1,3 +1,4 @@ +# flags: --preview --minimum-version=3.9 with \ make_context_manager1() as cm1, \ make_context_manager2() as cm2, \ diff --git a/tests/data/preview_context_managers/auto_detect/features_3_10.py b/tests/data/cases/preview_context_managers_autodetect_310.py similarity index 93% rename from tests/data/preview_context_managers/auto_detect/features_3_10.py rename to tests/data/cases/preview_context_managers_autodetect_310.py index 1458df1cb41..a9e31076f03 100644 --- a/tests/data/preview_context_managers/auto_detect/features_3_10.py +++ b/tests/data/cases/preview_context_managers_autodetect_310.py @@ -1,3 +1,4 @@ +# flags: --preview --minimum-version=3.10 # This file uses pattern matching introduced in Python 3.10. diff --git a/tests/data/preview_context_managers/auto_detect/features_3_11.py b/tests/data/cases/preview_context_managers_autodetect_311.py similarity index 92% rename from tests/data/preview_context_managers/auto_detect/features_3_11.py rename to tests/data/cases/preview_context_managers_autodetect_311.py index f83c5330ab3..af1e83fe74c 100644 --- a/tests/data/preview_context_managers/auto_detect/features_3_11.py +++ b/tests/data/cases/preview_context_managers_autodetect_311.py @@ -1,3 +1,4 @@ +# flags: --preview --minimum-version=3.11 # This file uses except* clause in Python 3.11. diff --git a/tests/data/preview_context_managers/auto_detect/features_3_8.py b/tests/data/cases/preview_context_managers_autodetect_38.py similarity index 98% rename from tests/data/preview_context_managers/auto_detect/features_3_8.py rename to tests/data/cases/preview_context_managers_autodetect_38.py index 79e438b995e..25217a40604 100644 --- a/tests/data/preview_context_managers/auto_detect/features_3_8.py +++ b/tests/data/cases/preview_context_managers_autodetect_38.py @@ -1,3 +1,4 @@ +# flags: --preview # This file doesn't use any Python 3.9+ only grammars. diff --git a/tests/data/preview_context_managers/auto_detect/features_3_9.py b/tests/data/cases/preview_context_managers_autodetect_39.py similarity index 93% rename from tests/data/preview_context_managers/auto_detect/features_3_9.py rename to tests/data/cases/preview_context_managers_autodetect_39.py index 0d28f993108..3f72e48db9d 100644 --- a/tests/data/preview_context_managers/auto_detect/features_3_9.py +++ b/tests/data/cases/preview_context_managers_autodetect_39.py @@ -1,3 +1,4 @@ +# flags: --preview --minimum-version=3.9 # This file uses parenthesized context managers introduced in Python 3.9. diff --git a/tests/data/preview/dummy_implementations.py b/tests/data/cases/preview_dummy_implementations.py similarity index 98% rename from tests/data/preview/dummy_implementations.py rename to tests/data/cases/preview_dummy_implementations.py index e07c25ed129..98b69bf87b2 100644 --- a/tests/data/preview/dummy_implementations.py +++ b/tests/data/cases/preview_dummy_implementations.py @@ -1,3 +1,4 @@ +# flags: --preview from typing import NoReturn, Protocol, Union, overload diff --git a/tests/data/preview/format_unicode_escape_seq.py b/tests/data/cases/preview_format_unicode_escape_seq.py similarity index 96% rename from tests/data/preview/format_unicode_escape_seq.py rename to tests/data/cases/preview_format_unicode_escape_seq.py index 3440696c303..65c3d8d166e 100644 --- a/tests/data/preview/format_unicode_escape_seq.py +++ b/tests/data/cases/preview_format_unicode_escape_seq.py @@ -1,3 +1,4 @@ +# flags: --preview x = "\x1F" x = "\\x1B" x = "\\\x1B" diff --git a/tests/data/preview/long_dict_values.py b/tests/data/cases/preview_long_dict_values.py similarity index 99% rename from tests/data/preview/long_dict_values.py rename to tests/data/cases/preview_long_dict_values.py index 4c515180028..fbbacd13d1d 100644 --- a/tests/data/preview/long_dict_values.py +++ b/tests/data/cases/preview_long_dict_values.py @@ -1,3 +1,4 @@ +# flags: --preview my_dict = { "something_something": r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t" diff --git a/tests/data/preview/long_strings.py b/tests/data/cases/preview_long_strings.py similarity index 99% rename from tests/data/preview/long_strings.py rename to tests/data/cases/preview_long_strings.py index 059148729d5..5519f098774 100644 --- a/tests/data/preview/long_strings.py +++ b/tests/data/cases/preview_long_strings.py @@ -1,3 +1,4 @@ +# flags: --preview x = "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." diff --git a/tests/data/preview/long_strings__east_asian_width.py b/tests/data/cases/preview_long_strings__east_asian_width.py similarity index 96% rename from tests/data/preview/long_strings__east_asian_width.py rename to tests/data/cases/preview_long_strings__east_asian_width.py index fb66a78ed8b..d190f422a60 100644 --- a/tests/data/preview/long_strings__east_asian_width.py +++ b/tests/data/cases/preview_long_strings__east_asian_width.py @@ -1,3 +1,4 @@ +# flags: --preview # The following strings do not have not-so-many chars, but are long enough # when these are rendered in a monospace font (if the renderer respects # Unicode East Asian Width properties). diff --git a/tests/data/preview/long_strings__edge_case.py b/tests/data/cases/preview_long_strings__edge_case.py similarity index 99% rename from tests/data/preview/long_strings__edge_case.py rename to tests/data/cases/preview_long_strings__edge_case.py index 2bc0b6ed328..a8e8971968c 100644 --- a/tests/data/preview/long_strings__edge_case.py +++ b/tests/data/cases/preview_long_strings__edge_case.py @@ -1,3 +1,4 @@ +# flags: --preview some_variable = "This string is long but not so long that it needs to be split just yet" some_variable = 'This string is long but not so long that it needs to be split just yet' some_variable = "This string is long, just long enough that it needs to be split, u get?" diff --git a/tests/data/preview/long_strings__regression.py b/tests/data/cases/preview_long_strings__regression.py similarity index 99% rename from tests/data/preview/long_strings__regression.py rename to tests/data/cases/preview_long_strings__regression.py index 5f0646e6029..40d5e745cc8 100644 --- a/tests/data/preview/long_strings__regression.py +++ b/tests/data/cases/preview_long_strings__regression.py @@ -1,3 +1,4 @@ +# flags: --preview class A: def foo(): result = type(message)("") diff --git a/tests/data/preview/long_strings__type_annotations.py b/tests/data/cases/preview_long_strings__type_annotations.py similarity index 95% rename from tests/data/preview/long_strings__type_annotations.py rename to tests/data/cases/preview_long_strings__type_annotations.py index 41d7ee2b67b..8beb877bdd1 100644 --- a/tests/data/preview/long_strings__type_annotations.py +++ b/tests/data/cases/preview_long_strings__type_annotations.py @@ -1,3 +1,4 @@ +# flags: --preview def func( arg1, arg2, @@ -54,6 +55,6 @@ def func( def func( - argument: ("int |" "str"), + argument: "int |" "str", ) -> Set["int |" " str"]: pass diff --git a/tests/data/preview/multiline_strings.py b/tests/data/cases/preview_multiline_strings.py similarity index 99% rename from tests/data/preview/multiline_strings.py rename to tests/data/cases/preview_multiline_strings.py index bb517d128e2..dec4ef2e548 100644 --- a/tests/data/preview/multiline_strings.py +++ b/tests/data/cases/preview_multiline_strings.py @@ -1,3 +1,4 @@ +# flags: --preview """cow say""", call(3, "dogsay", textwrap.dedent("""dove diff --git a/tests/data/preview/no_blank_line_before_docstring.py b/tests/data/cases/preview_no_blank_line_before_docstring.py similarity index 97% rename from tests/data/preview/no_blank_line_before_docstring.py rename to tests/data/cases/preview_no_blank_line_before_docstring.py index a37362de100..303035a7efb 100644 --- a/tests/data/preview/no_blank_line_before_docstring.py +++ b/tests/data/cases/preview_no_blank_line_before_docstring.py @@ -1,3 +1,4 @@ +# flags: --preview def line_before_docstring(): """Please move me up""" diff --git a/tests/data/preview/pep_572.py b/tests/data/cases/preview_pep_572.py similarity index 75% rename from tests/data/preview/pep_572.py rename to tests/data/cases/preview_pep_572.py index a50e130ad9c..8e801ff6cdc 100644 --- a/tests/data/preview/pep_572.py +++ b/tests/data/cases/preview_pep_572.py @@ -1,3 +1,4 @@ +# flags: --preview x[(a:=0):] x[:(a:=0)] diff --git a/tests/data/preview/percent_precedence.py b/tests/data/cases/preview_percent_precedence.py similarity index 96% rename from tests/data/preview/percent_precedence.py rename to tests/data/cases/preview_percent_precedence.py index b895443fb46..aeaf450ff5e 100644 --- a/tests/data/preview/percent_precedence.py +++ b/tests/data/cases/preview_percent_precedence.py @@ -1,3 +1,4 @@ +# flags: --preview ("" % a) ** 2 ("" % a)[0] ("" % a)() diff --git a/tests/data/cases/preview_power_op_spacing.py b/tests/data/cases/preview_power_op_spacing.py new file mode 100644 index 00000000000..650c6fecb20 --- /dev/null +++ b/tests/data/cases/preview_power_op_spacing.py @@ -0,0 +1,97 @@ +# flags: --preview +a = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1 +b = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1 +c = 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 +d = 1**1 ** 1**1 ** 1**1 ** 1**1 ** 1**1**1 ** 1 ** 1**1 ** 1**1**1**1**1 ** 1 ** 1**1**1 **1**1** 1 ** 1 ** 1 +e = 𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟 +f = 𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟 + +a = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0 +b = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0 +c = 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 +d = 1.0**1.0 ** 1.0**1.0 ** 1.0**1.0 ** 1.0**1.0 ** 1.0**1.0**1.0 ** 1.0 ** 1.0**1.0 ** 1.0**1.0**1.0 + +# output +a = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1 +b = ( + 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 + ** 1 +) +c = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1 +d = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1 +e = 𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟**𨉟 +f = ( + 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 + ** 𨉟 +) + +a = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0 +b = ( + 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 + ** 1.0 +) +c = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0 +d = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0 diff --git a/tests/data/preview/prefer_rhs_split.py b/tests/data/cases/preview_prefer_rhs_split.py similarity index 99% rename from tests/data/preview/prefer_rhs_split.py rename to tests/data/cases/preview_prefer_rhs_split.py index a809eacc773..c732c33b53a 100644 --- a/tests/data/preview/prefer_rhs_split.py +++ b/tests/data/cases/preview_prefer_rhs_split.py @@ -1,3 +1,4 @@ +# flags: --preview first_item, second_item = ( some_looooooooong_module.some_looooooooooooooong_function_name( first_argument, second_argument, third_argument diff --git a/tests/data/cases/preview_return_annotation_brackets_string.py b/tests/data/cases/preview_return_annotation_brackets_string.py new file mode 100644 index 00000000000..fea0ea6839a --- /dev/null +++ b/tests/data/cases/preview_return_annotation_brackets_string.py @@ -0,0 +1,24 @@ +# flags: --preview +# Long string example +def frobnicate() -> "ThisIsTrulyUnreasonablyExtremelyLongClassName | list[ThisIsTrulyUnreasonablyExtremelyLongClassName]": + pass + +# splitting the string breaks if there's any parameters +def frobnicate(a) -> "ThisIsTrulyUnreasonablyExtremelyLongClassName | list[ThisIsTrulyUnreasonablyExtremelyLongClassName]": + pass + +# output + +# Long string example +def frobnicate() -> ( + "ThisIsTrulyUnreasonablyExtremelyLongClassName |" + " list[ThisIsTrulyUnreasonablyExtremelyLongClassName]" +): + pass + + +# splitting the string breaks if there's any parameters +def frobnicate( + a, +) -> "ThisIsTrulyUnreasonablyExtremelyLongClassName | list[ThisIsTrulyUnreasonablyExtremelyLongClassName]": + pass diff --git a/tests/data/preview/trailing_comma.py b/tests/data/cases/preview_trailing_comma.py similarity index 97% rename from tests/data/preview/trailing_comma.py rename to tests/data/cases/preview_trailing_comma.py index 5b09c664606..bba7e7ad16d 100644 --- a/tests/data/preview/trailing_comma.py +++ b/tests/data/cases/preview_trailing_comma.py @@ -1,3 +1,4 @@ +# flags: --preview e = { "a": fun(msg, "ts"), "longggggggggggggggid": ..., diff --git a/tests/data/preview_py_310/pep_572.py b/tests/data/cases/py310_pep572.py similarity index 77% rename from tests/data/preview_py_310/pep_572.py rename to tests/data/cases/py310_pep572.py index 78d4e9e4506..172be3898d6 100644 --- a/tests/data/preview_py_310/pep_572.py +++ b/tests/data/cases/py310_pep572.py @@ -1,3 +1,4 @@ +# flags: --preview --minimum-version=3.10 x[a:=0] x[a := 0] x[a := 0, b := 1] diff --git a/tests/data/pyink/empty_lines.py b/tests/data/cases/pyink_empty_lines.py similarity index 78% rename from tests/data/pyink/empty_lines.py rename to tests/data/cases/pyink_empty_lines.py index 577462f7f94..93aac32c56f 100644 --- a/tests/data/pyink/empty_lines.py +++ b/tests/data/cases/pyink_empty_lines.py @@ -1,3 +1,4 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 class MyClass: def first_method(self): diff --git a/tests/data/pyink/imports.py b/tests/data/cases/pyink_imports.py similarity index 78% rename from tests/data/pyink/imports.py rename to tests/data/cases/pyink_imports.py index 01b98c8e8a9..9dd124b3d23 100644 --- a/tests/data/pyink/imports.py +++ b/tests/data/cases/pyink_imports.py @@ -1,2 +1,3 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 from typing import Any, Optional, Literal, Callable, Union, TypeAlias, ClassVar, Final, Annotate, TypeGuard, Generic, TypeVar from google3.some.very.very.long_package_names.aaaaaaaaaaa.bbbbbbbbbbbb.ccccccccccc.dddddddddd import eeeeee diff --git a/tests/data/pyink/indent.py b/tests/data/cases/pyink_indent.py similarity index 92% rename from tests/data/pyink/indent.py rename to tests/data/cases/pyink_indent.py index 9cc1a7dcfe9..c8ac22a9054 100644 --- a/tests/data/pyink/indent.py +++ b/tests/data/cases/pyink_indent.py @@ -1,3 +1,4 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 def abc(): if True: some_very_long_function( diff --git a/tests/data/pyink/module_docstring_1.py b/tests/data/cases/pyink_module_docstring_1.py similarity index 83% rename from tests/data/pyink/module_docstring_1.py rename to tests/data/cases/pyink_module_docstring_1.py index 6dfc2dc39e4..2527f29a254 100644 --- a/tests/data/pyink/module_docstring_1.py +++ b/tests/data/cases/pyink_module_docstring_1.py @@ -1,3 +1,4 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 """Module docstrings in Pyink will be formatted. Just like other docstrings. @@ -21,7 +22,6 @@ Just like other docstrings. """ - """Note that this isn't a module docstring! Some indented text. diff --git a/tests/data/pyink/module_docstring_2.py b/tests/data/cases/pyink_module_docstring_2.py similarity index 63% rename from tests/data/pyink/module_docstring_2.py rename to tests/data/cases/pyink_module_docstring_2.py index 4de7c04f246..4f5952cba26 100644 --- a/tests/data/pyink/module_docstring_2.py +++ b/tests/data/cases/pyink_module_docstring_2.py @@ -1,3 +1,4 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 """This is the first line. This is another paragraph line that perfectly fits in the line length---- limit. diff --git a/tests/data/pyink/module_docstring_3.py b/tests/data/cases/pyink_module_docstring_3.py similarity index 70% rename from tests/data/pyink/module_docstring_3.py rename to tests/data/cases/pyink_module_docstring_3.py index 106d624877d..dbc5f6b0637 100644 --- a/tests/data/pyink/module_docstring_3.py +++ b/tests/data/cases/pyink_module_docstring_3.py @@ -1,3 +1,4 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 # Regression test for https://github.com/google/pyink/issues/8. """Module docstrings with trailing quotes on their own line. diff --git a/tests/data/pyink/module_docstring_4.py b/tests/data/cases/pyink_module_docstring_4.py similarity index 61% rename from tests/data/pyink/module_docstring_4.py rename to tests/data/cases/pyink_module_docstring_4.py index 17e52607a97..341da9eb03c 100644 --- a/tests/data/pyink/module_docstring_4.py +++ b/tests/data/cases/pyink_module_docstring_4.py @@ -1,3 +1,4 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 # Regression test for https://github.com/google/pyink/issues/8. """ Module docstring on its own line. diff --git a/tests/data/pyink/nested_brackets.py b/tests/data/cases/pyink_nested_brackets.py similarity index 97% rename from tests/data/pyink/nested_brackets.py rename to tests/data/cases/pyink_nested_brackets.py index b8f2547de6a..34988286a64 100644 --- a/tests/data/pyink/nested_brackets.py +++ b/tests/data/cases/pyink_nested_brackets.py @@ -1,3 +1,4 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 WITH_SET = frozenset({ 1001, 1002, diff --git a/tests/data/pyink/nested_brackets_explodes.py b/tests/data/cases/pyink_nested_brackets_explodes.py similarity index 96% rename from tests/data/pyink/nested_brackets_explodes.py rename to tests/data/cases/pyink_nested_brackets_explodes.py index 94d4aefec07..4554c5fc44f 100644 --- a/tests/data/pyink/nested_brackets_explodes.py +++ b/tests/data/cases/pyink_nested_brackets_explodes.py @@ -1,3 +1,4 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 # Literal list/tuple/set/dict will always be exploded on their own, immediately # nested brackets should match this behavior. literal_inner_set = [{ diff --git a/tests/data/pyink/pragma_comments.py b/tests/data/cases/pyink_pragma_comments.py similarity index 93% rename from tests/data/pyink/pragma_comments.py rename to tests/data/cases/pyink_pragma_comments.py index 1e212f4b807..09943421250 100644 --- a/tests/data/pyink/pragma_comments.py +++ b/tests/data/cases/pyink_pragma_comments.py @@ -1,3 +1,4 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 a_very_long_library_name._private_method(and_a_long_arg) # pylint: disable=protected-access a_very_long_library_name._private_method(and_a_long_arg_that_just_fits_limit_) # pylint: disable=protected-access a_very_long_library_name._private_method(and_a_long_arg_that_just_fits_limit___) # pylint: disable=protected-access diff --git a/tests/data/pyink/str_concat_in_func_args.py b/tests/data/cases/pyink_str_concat_in_func_args.py similarity index 97% rename from tests/data/pyink/str_concat_in_func_args.py rename to tests/data/cases/pyink_str_concat_in_func_args.py index 58c97ee3542..9b63b4009ab 100644 --- a/tests/data/pyink/str_concat_in_func_args.py +++ b/tests/data/cases/pyink_str_concat_in_func_args.py @@ -1,3 +1,4 @@ +# flags: --preview --line-length=80 --pyink --pyink-indentation=2 # long arguments normal_name = normal_function_name( "but with super long string arguments that on their own exceed the line limit so there's no way it can ever fit", diff --git a/tests/data/py_37/python37.py b/tests/data/cases/python37.py similarity index 95% rename from tests/data/py_37/python37.py rename to tests/data/cases/python37.py index dab8b404a73..3f61106c45d 100644 --- a/tests/data/py_37/python37.py +++ b/tests/data/cases/python37.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3.7 +# flags: --minimum-version=3.7 def f(): @@ -33,9 +33,6 @@ def make_arange(n): # output -#!/usr/bin/env python3.7 - - def f(): return (i * 2 async for i in arange(42)) diff --git a/tests/data/py_38/python38.py b/tests/data/cases/python38.py similarity index 93% rename from tests/data/py_38/python38.py rename to tests/data/cases/python38.py index 63b0588bc27..919ea6aeed4 100644 --- a/tests/data/py_38/python38.py +++ b/tests/data/cases/python38.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3.8 +# flags: --minimum-version=3.8 def starred_return(): @@ -22,9 +22,6 @@ def t(): # output -#!/usr/bin/env python3.8 - - def starred_return(): my_list = ["value2", "value3"] return "value1", *my_list diff --git a/tests/data/py_39/python39.py b/tests/data/cases/python39.py similarity index 92% rename from tests/data/py_39/python39.py rename to tests/data/cases/python39.py index ae67c2257eb..1b9536c1529 100644 --- a/tests/data/py_39/python39.py +++ b/tests/data/cases/python39.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3.9 +# flags: --minimum-version=3.9 @relaxed_decorator[0] def f(): @@ -14,10 +14,6 @@ def f(): # output - -#!/usr/bin/env python3.9 - - @relaxed_decorator[0] def f(): ... diff --git a/tests/data/simple_cases/remove_await_parens.py b/tests/data/cases/remove_await_parens.py similarity index 100% rename from tests/data/simple_cases/remove_await_parens.py rename to tests/data/cases/remove_await_parens.py diff --git a/tests/data/simple_cases/remove_except_parens.py b/tests/data/cases/remove_except_parens.py similarity index 100% rename from tests/data/simple_cases/remove_except_parens.py rename to tests/data/cases/remove_except_parens.py diff --git a/tests/data/simple_cases/remove_for_brackets.py b/tests/data/cases/remove_for_brackets.py similarity index 100% rename from tests/data/simple_cases/remove_for_brackets.py rename to tests/data/cases/remove_for_brackets.py diff --git a/tests/data/simple_cases/remove_newline_after_code_block_open.py b/tests/data/cases/remove_newline_after_code_block_open.py similarity index 100% rename from tests/data/simple_cases/remove_newline_after_code_block_open.py rename to tests/data/cases/remove_newline_after_code_block_open.py diff --git a/tests/data/py_310/remove_newline_after_match.py b/tests/data/cases/remove_newline_after_match.py similarity index 94% rename from tests/data/py_310/remove_newline_after_match.py rename to tests/data/cases/remove_newline_after_match.py index f7bcfbf27a2..fe6592b664d 100644 --- a/tests/data/py_310/remove_newline_after_match.py +++ b/tests/data/cases/remove_newline_after_match.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.10 def http_status(status): match status: diff --git a/tests/data/simple_cases/remove_parens.py b/tests/data/cases/remove_parens.py similarity index 100% rename from tests/data/simple_cases/remove_parens.py rename to tests/data/cases/remove_parens.py diff --git a/tests/data/py_39/remove_with_brackets.py b/tests/data/cases/remove_with_brackets.py similarity index 98% rename from tests/data/py_39/remove_with_brackets.py rename to tests/data/cases/remove_with_brackets.py index ea58ab93a16..3ee64902a30 100644 --- a/tests/data/py_39/remove_with_brackets.py +++ b/tests/data/cases/remove_with_brackets.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.9 with (open("bla.txt")): pass diff --git a/tests/data/simple_cases/return_annotation_brackets.py b/tests/data/cases/return_annotation_brackets.py similarity index 92% rename from tests/data/simple_cases/return_annotation_brackets.py rename to tests/data/cases/return_annotation_brackets.py index 265c30220d8..8509ecdb92c 100644 --- a/tests/data/simple_cases/return_annotation_brackets.py +++ b/tests/data/cases/return_annotation_brackets.py @@ -87,6 +87,11 @@ def foo() -> tuple[loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo def foo() -> tuple[int, int, int,]: return 2 +# Magic trailing comma example, with params +# this is broken - the trailing comma is transferred to the param list. Fixed in preview +def foo(a,b) -> tuple[int, int, int,]: + return 2 + # output # Control def double(a: int) -> int: @@ -208,3 +213,11 @@ def foo() -> ( ] ): return 2 + + +# Magic trailing comma example, with params +# this is broken - the trailing comma is transferred to the param list. Fixed in preview +def foo( + a, b +) -> tuple[int, int, int,]: + return 2 diff --git a/tests/data/simple_cases/skip_magic_trailing_comma.py b/tests/data/cases/skip_magic_trailing_comma.py similarity index 97% rename from tests/data/simple_cases/skip_magic_trailing_comma.py rename to tests/data/cases/skip_magic_trailing_comma.py index c020db79864..4dda5df40f0 100644 --- a/tests/data/simple_cases/skip_magic_trailing_comma.py +++ b/tests/data/cases/skip_magic_trailing_comma.py @@ -1,3 +1,4 @@ +# flags: --skip-magic-trailing-comma # We should not remove the trailing comma in a single-element subscript. a: tuple[int,] b = tuple[int,] diff --git a/tests/data/simple_cases/slices.py b/tests/data/cases/slices.py similarity index 100% rename from tests/data/simple_cases/slices.py rename to tests/data/cases/slices.py diff --git a/tests/data/py_310/starred_for_target.py b/tests/data/cases/starred_for_target.py similarity index 92% rename from tests/data/py_310/starred_for_target.py rename to tests/data/cases/starred_for_target.py index 8fc8e059ed3..13e517816d6 100644 --- a/tests/data/py_310/starred_for_target.py +++ b/tests/data/cases/starred_for_target.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.10 for x in *a, *b: print(x) diff --git a/tests/data/simple_cases/string_prefixes.py b/tests/data/cases/string_prefixes.py similarity index 100% rename from tests/data/simple_cases/string_prefixes.py rename to tests/data/cases/string_prefixes.py diff --git a/tests/data/miscellaneous/stub.pyi b/tests/data/cases/stub.py similarity index 99% rename from tests/data/miscellaneous/stub.pyi rename to tests/data/cases/stub.py index af2cd2c2c02..f3828d55ba2 100644 --- a/tests/data/miscellaneous/stub.pyi +++ b/tests/data/cases/stub.py @@ -1,3 +1,4 @@ +# flags: --pyi X: int def f(): ... diff --git a/tests/data/simple_cases/torture.py b/tests/data/cases/torture.py similarity index 100% rename from tests/data/simple_cases/torture.py rename to tests/data/cases/torture.py diff --git a/tests/data/simple_cases/trailing_comma_optional_parens1.py b/tests/data/cases/trailing_comma_optional_parens1.py similarity index 100% rename from tests/data/simple_cases/trailing_comma_optional_parens1.py rename to tests/data/cases/trailing_comma_optional_parens1.py diff --git a/tests/data/simple_cases/trailing_comma_optional_parens2.py b/tests/data/cases/trailing_comma_optional_parens2.py similarity index 100% rename from tests/data/simple_cases/trailing_comma_optional_parens2.py rename to tests/data/cases/trailing_comma_optional_parens2.py diff --git a/tests/data/simple_cases/trailing_comma_optional_parens3.py b/tests/data/cases/trailing_comma_optional_parens3.py similarity index 100% rename from tests/data/simple_cases/trailing_comma_optional_parens3.py rename to tests/data/cases/trailing_comma_optional_parens3.py diff --git a/tests/data/simple_cases/trailing_commas_in_leading_parts.py b/tests/data/cases/trailing_commas_in_leading_parts.py similarity index 100% rename from tests/data/simple_cases/trailing_commas_in_leading_parts.py rename to tests/data/cases/trailing_commas_in_leading_parts.py diff --git a/tests/data/simple_cases/tricky_unicode_symbols.py b/tests/data/cases/tricky_unicode_symbols.py similarity index 100% rename from tests/data/simple_cases/tricky_unicode_symbols.py rename to tests/data/cases/tricky_unicode_symbols.py diff --git a/tests/data/simple_cases/tupleassign.py b/tests/data/cases/tupleassign.py similarity index 100% rename from tests/data/simple_cases/tupleassign.py rename to tests/data/cases/tupleassign.py diff --git a/tests/data/cases/type_aliases.py b/tests/data/cases/type_aliases.py new file mode 100644 index 00000000000..7c2009e8202 --- /dev/null +++ b/tests/data/cases/type_aliases.py @@ -0,0 +1,30 @@ +# flags: --minimum-version=3.12 + +type A=int +type Gen[T]=list[T] +type Alias[T]=lambda: T +type And[T]=T and T +type IfElse[T]=T if T else T +type One = int; type Another = str +class X: type InClass = int + +type = aliased +print(type(42)) + +# output + +type A = int +type Gen[T] = list[T] +type Alias[T] = lambda: T +type And[T] = T and T +type IfElse[T] = T if T else T +type One = int +type Another = str + + +class X: + type InClass = int + + +type = aliased +print(type(42)) diff --git a/tests/data/type_comments/type_comment_syntax_error.py b/tests/data/cases/type_comment_syntax_error.py similarity index 100% rename from tests/data/type_comments/type_comment_syntax_error.py rename to tests/data/cases/type_comment_syntax_error.py diff --git a/tests/data/py_312/type_params.py b/tests/data/cases/type_params.py similarity index 97% rename from tests/data/py_312/type_params.py rename to tests/data/cases/type_params.py index 5f8ec43267c..720a775ef31 100644 --- a/tests/data/py_312/type_params.py +++ b/tests/data/cases/type_params.py @@ -1,3 +1,4 @@ +# flags: --minimum-version=3.12 def func [T ](): pass async def func [ T ] (): pass class C[ T ] : pass diff --git a/tests/data/simple_cases/whitespace.py b/tests/data/cases/whitespace.py similarity index 100% rename from tests/data/simple_cases/whitespace.py rename to tests/data/cases/whitespace.py diff --git a/tests/data/miscellaneous/force_pyi.py b/tests/data/miscellaneous/force_pyi.py index 07ed93c6879..40caf30a983 100644 --- a/tests/data/miscellaneous/force_pyi.py +++ b/tests/data/miscellaneous/force_pyi.py @@ -1,3 +1,4 @@ +# flags: --pyi from typing import Union @bird diff --git a/tests/data/miscellaneous/string_quotes.py b/tests/data/miscellaneous/string_quotes.py index 3384241f4ad..6ec088ac79b 100644 --- a/tests/data/miscellaneous/string_quotes.py +++ b/tests/data/miscellaneous/string_quotes.py @@ -1,4 +1,5 @@ '''''' + '\'' '"' "'" @@ -59,6 +60,7 @@ # output """""" + "'" '"' "'" diff --git a/tests/data/preview/return_annotation_brackets_string.py b/tests/data/preview/return_annotation_brackets_string.py deleted file mode 100644 index 6978829fd5c..00000000000 --- a/tests/data/preview/return_annotation_brackets_string.py +++ /dev/null @@ -1,12 +0,0 @@ -# Long string example -def frobnicate() -> "ThisIsTrulyUnreasonablyExtremelyLongClassName | list[ThisIsTrulyUnreasonablyExtremelyLongClassName]": - pass - -# output - -# Long string example -def frobnicate() -> ( - "ThisIsTrulyUnreasonablyExtremelyLongClassName |" - " list[ThisIsTrulyUnreasonablyExtremelyLongClassName]" -): - pass diff --git a/tests/data/py_312/type_aliases.py b/tests/data/py_312/type_aliases.py deleted file mode 100644 index 84e07e50fe2..00000000000 --- a/tests/data/py_312/type_aliases.py +++ /dev/null @@ -1,13 +0,0 @@ -type A=int -type Gen[T]=list[T] - -type = aliased -print(type(42)) - -# output - -type A = int -type Gen[T] = list[T] - -type = aliased -print(type(42)) diff --git a/tests/data/raw_docstring.py b/tests/data/raw_docstring.py new file mode 100644 index 00000000000..751fd3201df --- /dev/null +++ b/tests/data/raw_docstring.py @@ -0,0 +1,32 @@ +# flags: --preview --skip-string-normalization +class C: + + r"""Raw""" + +def f(): + + r"""Raw""" + +class SingleQuotes: + + + r'''Raw''' + +class UpperCaseR: + R"""Raw""" + +# output +class C: + r"""Raw""" + + +def f(): + r"""Raw""" + + +class SingleQuotes: + r'''Raw''' + + +class UpperCaseR: + R"""Raw""" diff --git a/tests/optional.py b/tests/optional.py index 8a39cc440a6..3f5277b6b03 100644 --- a/tests/optional.py +++ b/tests/optional.py @@ -26,7 +26,7 @@ from pytest import StashKey except ImportError: # pytest < 7 - from _pytest.store import StoreKey as StashKey # type: ignore[no-redef] + from _pytest.store import StoreKey as StashKey # type: ignore[import, no-redef] log = logging.getLogger(__name__) diff --git a/tests/test_black.py b/tests/test_black.py index 7272412fa07..4013c0b35e5 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -9,7 +9,6 @@ import re import sys import types -import unittest from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager, redirect_stderr from dataclasses import replace @@ -188,7 +187,9 @@ def test_experimental_string_processing_warns(self) -> None: ) def test_piping(self) -> None: - source, expected = read_data_from_file(PROJECT_ROOT / "src/pyink/__init__.py") + _, source, expected = read_data_from_file( + PROJECT_ROOT / "src/pyink/__init__.py" + ) result = BlackRunner().invoke( pyink.main, [ @@ -210,8 +211,8 @@ def test_piping_diff(self) -> None: r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d" r"\+\d\d:\d\d" ) - source, _ = read_data("simple_cases", "expression.py") - expected, _ = read_data("simple_cases", "expression.diff") + source, _ = read_data("cases", "expression.py") + expected, _ = read_data("cases", "expression.diff") args = [ "-", "--fast", @@ -228,7 +229,7 @@ def test_piping_diff(self) -> None: self.assertEqual(expected, actual) def test_piping_diff_with_color(self) -> None: - source, _ = read_data("simple_cases", "expression.py") + source, _ = read_data("cases", "expression.py") args = [ "-", "--fast", @@ -264,7 +265,7 @@ def _test_wip(self) -> None: pyink.assert_stable(source, actual, pyink.FileMode()) def test_pep_572_version_detection(self) -> None: - source, _ = read_data("py_38", "pep_572") + source, _ = read_data("cases", "pep_572") root = pyink.lib2to3_parse(source) features = pyink.get_features_used(root) self.assertIn(pyink.Feature.ASSIGNMENT_EXPRESSIONS, features) @@ -273,7 +274,7 @@ def test_pep_572_version_detection(self) -> None: def test_pep_695_version_detection(self) -> None: for file in ("type_aliases", "type_params"): - source, _ = read_data("py_312", file) + source, _ = read_data("cases", file) root = pyink.lib2to3_parse(source) features = pyink.get_features_used(root) self.assertIn(pyink.Feature.TYPE_PARAMS, features) @@ -281,7 +282,7 @@ def test_pep_695_version_detection(self) -> None: self.assertIn(pyink.TargetVersion.PY312, versions) def test_expression_ff(self) -> None: - source, expected = read_data("simple_cases", "expression.py") + source, expected = read_data("cases", "expression.py") tmp_file = Path(pyink.dump_to_file(source)) try: self.assertTrue(ff(tmp_file, write_back=pyink.WriteBack.YES)) @@ -294,8 +295,8 @@ def test_expression_ff(self) -> None: pyink.assert_stable(source, actual, DEFAULT_MODE) def test_expression_diff(self) -> None: - source, _ = read_data("simple_cases", "expression.py") - expected, _ = read_data("simple_cases", "expression.diff") + source, _ = read_data("cases", "expression.py") + expected, _ = read_data("cases", "expression.diff") tmp_file = Path(pyink.dump_to_file(source)) diff_header = re.compile( rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d " @@ -320,8 +321,8 @@ def test_expression_diff(self) -> None: self.assertEqual(expected, actual, msg) def test_expression_diff_with_color(self) -> None: - source, _ = read_data("simple_cases", "expression.py") - expected, _ = read_data("simple_cases", "expression.diff") + source, _ = read_data("cases", "expression.py") + expected, _ = read_data("cases", "expression.diff") tmp_file = Path(pyink.dump_to_file(source)) try: result = BlackRunner().invoke( @@ -340,7 +341,7 @@ def test_expression_diff_with_color(self) -> None: self.assertIn("\033[0m", actual) def test_detect_pos_only_arguments(self) -> None: - source, _ = read_data("py_38", "pep_570") + source, _ = read_data("cases", "pep_570") root = pyink.lib2to3_parse(source) features = pyink.get_features_used(root) self.assertIn(pyink.Feature.POS_ONLY_ARGUMENTS, features) @@ -402,7 +403,7 @@ def test_skip_source_first_line_when_mixing_newlines(self) -> None: self.assertEqual(test_file.read_bytes(), expected) def test_skip_magic_trailing_comma(self) -> None: - source, _ = read_data("simple_cases", "expression") + source, _ = read_data("cases", "expression") expected, _ = read_data( "miscellaneous", "expression_skip_magic_trailing_comma.diff" ) @@ -434,7 +435,7 @@ def test_skip_magic_trailing_comma(self) -> None: @patch("pyink.dump_to_file", dump_to_stderr) def test_async_as_identifier(self) -> None: source_path = get_case_path("miscellaneous", "async_as_identifier") - source, expected = read_data_from_file(source_path) + _, source, expected = read_data_from_file(source_path) actual = fs(source) self.assertFormatEqual(expected, actual) major, minor = sys.version_info[:2] @@ -448,8 +449,8 @@ def test_async_as_identifier(self) -> None: @patch("pyink.dump_to_file", dump_to_stderr) def test_python37(self) -> None: - source_path = get_case_path("py_37", "python37") - source, expected = read_data_from_file(source_path) + source_path = get_case_path("cases", "python37") + _, source, expected = read_data_from_file(source_path) actual = fs(source) self.assertFormatEqual(expected, actual) major, minor = sys.version_info[:2] @@ -885,7 +886,7 @@ def test_get_features_used(self) -> None: self.assertEqual(pyink.get_features_used(node), {Feature.NUMERIC_UNDERSCORES}) node = pyink.lib2to3_parse("123456\n") self.assertEqual(pyink.get_features_used(node), set()) - source, expected = read_data("simple_cases", "function") + source, expected = read_data("cases", "function") node = pyink.lib2to3_parse(source) expected_features = { Feature.TRAILING_COMMA_IN_CALL, @@ -895,7 +896,7 @@ def test_get_features_used(self) -> None: self.assertEqual(pyink.get_features_used(node), expected_features) node = pyink.lib2to3_parse(expected) self.assertEqual(pyink.get_features_used(node), expected_features) - source, expected = read_data("simple_cases", "expression") + source, expected = read_data("cases", "expression") node = pyink.lib2to3_parse(source) self.assertEqual(pyink.get_features_used(node), set()) node = pyink.lib2to3_parse(expected) @@ -1047,9 +1048,10 @@ def test_endmarker(self) -> None: self.assertEqual(len(n.children), 1) self.assertEqual(n.children[0].type, pyink.token.ENDMARKER) + @patch("tests.conftest.PRINT_FULL_TREE", True) + @patch("tests.conftest.PRINT_TREE_DIFF", False) @pytest.mark.incompatible_with_mypyc - @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT") - def test_assertFormatEqual(self) -> None: + def test_assertFormatEqual_print_full_tree(self) -> None: out_lines = [] err_lines = [] @@ -1068,6 +1070,29 @@ def err(msg: str, **kwargs: Any) -> None: self.assertIn("Actual tree:", out_str) self.assertEqual("".join(err_lines), "") + @patch("tests.conftest.PRINT_FULL_TREE", False) + @patch("tests.conftest.PRINT_TREE_DIFF", True) + @pytest.mark.incompatible_with_mypyc + def test_assertFormatEqual_print_tree_diff(self) -> None: + out_lines = [] + err_lines = [] + + def out(msg: str, **kwargs: Any) -> None: + out_lines.append(msg) + + def err(msg: str, **kwargs: Any) -> None: + err_lines.append(msg) + + with patch("pyink.output._out", out), patch("pyink.output._err", err): + with self.assertRaises(AssertionError): + self.assertFormatEqual("j = [1, 2, 3]\n", "j = [1, 2, 3,]\n") + + out_str = "".join(out_lines) + self.assertIn("Tree Diff:", out_str) + self.assertIn("+ COMMA", out_str) + self.assertIn("+ ','", out_str) + self.assertEqual("".join(err_lines), "") + @event_loop() @patch("concurrent.futures.ProcessPoolExecutor", MagicMock(side_effect=OSError)) def test_works_in_mono_process_only_environment(self) -> None: @@ -1086,7 +1111,7 @@ def test_check_diff_use_together(self) -> None: src1 = get_case_path("miscellaneous", "string_quotes") self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1) # Files which will not be reformatted. - src2 = get_case_path("simple_cases", "composition") + src2 = get_case_path("cases", "composition") self.invokeBlack([str(src2), "--diff", "--check"]) # Multi file command. self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1) @@ -1309,7 +1334,7 @@ def test_reformat_one_with_stdin_and_existing_path(self) -> None: report = MagicMock() # Even with an existing file, since we are forcing stdin, black # should output to stdout and not modify the file inplace - p = THIS_DIR / "data" / "simple_cases" / "collections.py" + p = THIS_DIR / "data" / "cases" / "collections.py" # Make sure is_file actually returns True self.assertTrue(p.is_file()) path = Path(f"__PYINK_STDIN_FILENAME__{p}") @@ -1962,11 +1987,11 @@ def test_get_cache_dir( # If PYINK_CACHE_DIR is not set, use user_cache_dir monkeypatch.delenv("PYINK_CACHE_DIR", raising=False) with patch_user_cache_dir: - assert get_cache_dir() == workspace1 + assert get_cache_dir().parent == workspace1 # If it is set, use the path provided in the env var. monkeypatch.setenv("PYINK_CACHE_DIR", str(workspace2)) - assert get_cache_dir() == workspace2 + assert get_cache_dir().parent == workspace2 def test_cache_broken_file(self) -> None: mode = DEFAULT_MODE diff --git a/tests/test_format.py b/tests/test_format.py index 8f41183281a..96fc522a513 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -1,4 +1,3 @@ -import re from dataclasses import replace from typing import Any, Iterator from unittest.mock import patch @@ -6,13 +5,13 @@ import pytest import pyink +from pyink.mode import TargetVersion from tests.util import ( - DEFAULT_MODE, - PY36_VERSIONS, all_data_cases, assert_format, dump_to_stderr, read_data, + read_data_with_mode, ) @@ -22,70 +21,33 @@ def patch_dump_to_file(request: Any) -> Iterator[None]: yield -def check_file( - subdir: str, filename: str, mode: pyink.Mode, *, data: bool = True -) -> None: - source, expected = read_data(subdir, filename, data=data) - assert_format(source, expected, mode, fast=False) +def check_file(subdir: str, filename: str, *, data: bool = True) -> None: + args, source, expected = read_data_with_mode(subdir, filename, data=data) + assert_format( + source, + expected, + args.mode, + fast=args.fast, + minimum_version=args.minimum_version, + ) + if args.minimum_version is not None: + major, minor = args.minimum_version + target_version = TargetVersion[f"PY{major}{minor}"] + mode = replace(args.mode, target_versions={target_version}) + assert_format( + source, expected, mode, fast=args.fast, minimum_version=args.minimum_version + ) @pytest.mark.filterwarnings("ignore:invalid escape sequence.*:DeprecationWarning") -@pytest.mark.parametrize("filename", all_data_cases("simple_cases")) +@pytest.mark.parametrize("filename", all_data_cases("cases")) def test_simple_format(filename: str) -> None: - magic_trailing_comma = filename != "skip_magic_trailing_comma" - mode = pyink.Mode( - magic_trailing_comma=magic_trailing_comma, is_pyi=filename.endswith("_pyi") - ) - check_file("simple_cases", filename, mode) - - -@pytest.mark.parametrize("filename", all_data_cases("preview")) -def test_preview_format(filename: str) -> None: - check_file("preview", filename, pyink.Mode(preview=True)) - - -@pytest.mark.parametrize("filename", all_data_cases("pyink")) -def test_pyink_format(filename: str) -> None: - check_file( - "pyink", - filename, - pyink.Mode(preview=True, line_length=80, is_pyink=True, pyink_indentation=2), - ) - - -def test_preview_context_managers_targeting_py38() -> None: - source, expected = read_data("preview_context_managers", "targeting_py38.py") - mode = pyink.Mode(preview=True, target_versions={pyink.TargetVersion.PY38}) - assert_format(source, expected, mode, minimum_version=(3, 8)) - - -def test_preview_context_managers_targeting_py39() -> None: - source, expected = read_data("preview_context_managers", "targeting_py39.py") - mode = pyink.Mode(preview=True, target_versions={pyink.TargetVersion.PY39}) - assert_format(source, expected, mode, minimum_version=(3, 9)) - - -@pytest.mark.parametrize("filename", all_data_cases("preview_py_310")) -def test_preview_python_310(filename: str) -> None: - source, expected = read_data("preview_py_310", filename) - mode = pyink.Mode(target_versions={pyink.TargetVersion.PY310}, preview=True) - assert_format(source, expected, mode, minimum_version=(3, 10)) - - -@pytest.mark.parametrize( - "filename", all_data_cases("preview_context_managers/auto_detect") -) -def test_preview_context_managers_auto_detect(filename: str) -> None: - match = re.match(r"features_3_(\d+)", filename) - assert match is not None, "Unexpected filename format: %s" % filename - source, expected = read_data("preview_context_managers/auto_detect", filename) - mode = pyink.Mode(preview=True) - assert_format(source, expected, mode, minimum_version=(3, int(match.group(1)))) + check_file("cases", filename) # =============== # -# Complex cases -# ============= # +# Unusual cases +# =============== # def test_empty() -> None: @@ -93,48 +55,6 @@ def test_empty() -> None: assert_format(source, expected) -@pytest.mark.parametrize("filename", all_data_cases("py_36")) -def test_python_36(filename: str) -> None: - source, expected = read_data("py_36", filename) - mode = pyink.Mode(target_versions=PY36_VERSIONS) - assert_format(source, expected, mode, minimum_version=(3, 6)) - - -@pytest.mark.parametrize("filename", all_data_cases("py_37")) -def test_python_37(filename: str) -> None: - source, expected = read_data("py_37", filename) - mode = pyink.Mode(target_versions={pyink.TargetVersion.PY37}) - assert_format(source, expected, mode, minimum_version=(3, 7)) - - -@pytest.mark.parametrize("filename", all_data_cases("py_38")) -def test_python_38(filename: str) -> None: - source, expected = read_data("py_38", filename) - mode = pyink.Mode(target_versions={pyink.TargetVersion.PY38}) - assert_format(source, expected, mode, minimum_version=(3, 8)) - - -@pytest.mark.parametrize("filename", all_data_cases("py_39")) -def test_python_39(filename: str) -> None: - source, expected = read_data("py_39", filename) - mode = pyink.Mode(target_versions={pyink.TargetVersion.PY39}) - assert_format(source, expected, mode, minimum_version=(3, 9)) - - -@pytest.mark.parametrize("filename", all_data_cases("py_310")) -def test_python_310(filename: str) -> None: - source, expected = read_data("py_310", filename) - mode = pyink.Mode(target_versions={pyink.TargetVersion.PY310}) - assert_format(source, expected, mode, minimum_version=(3, 10)) - - -@pytest.mark.parametrize("filename", all_data_cases("py_310")) -def test_python_310_without_target_version(filename: str) -> None: - source, expected = read_data("py_310", filename) - mode = pyink.Mode() - assert_format(source, expected, mode, minimum_version=(3, 10)) - - def test_patma_invalid() -> None: source, expected = read_data("miscellaneous", "pattern_matching_invalid") mode = pyink.Mode(target_versions={pyink.TargetVersion.PY310}) @@ -142,88 +62,3 @@ def test_patma_invalid() -> None: assert_format(source, expected, mode, minimum_version=(3, 10)) exc_info.match("Cannot parse: 10:11") - - -@pytest.mark.parametrize("filename", all_data_cases("py_311")) -def test_python_311(filename: str) -> None: - source, expected = read_data("py_311", filename) - mode = pyink.Mode(target_versions={pyink.TargetVersion.PY311}) - assert_format(source, expected, mode, minimum_version=(3, 11)) - - -@pytest.mark.parametrize("filename", all_data_cases("py_312")) -def test_python_312(filename: str) -> None: - source, expected = read_data("py_312", filename) - mode = pyink.Mode(target_versions={pyink.TargetVersion.PY312}) - assert_format(source, expected, mode, minimum_version=(3, 12)) - - -@pytest.mark.parametrize("filename", all_data_cases("fast")) -def test_fast_cases(filename: str) -> None: - source, expected = read_data("fast", filename) - assert_format(source, expected, fast=True) - - -def test_python_2_hint() -> None: - with pytest.raises(pyink.parsing.InvalidInput) as exc_info: - assert_format("print 'daylily'", "print 'daylily'") - exc_info.match(pyink.parsing.PY2_HINT) - - -@pytest.mark.filterwarnings("ignore:invalid escape sequence.*:DeprecationWarning") -def test_docstring_no_string_normalization() -> None: - """Like test_docstring but with string normalization off.""" - source, expected = read_data("miscellaneous", "docstring_no_string_normalization") - mode = replace(DEFAULT_MODE, string_normalization=False) - assert_format(source, expected, mode) - - -def test_docstring_line_length_6() -> None: - """Like test_docstring but with line length set to 6.""" - source, expected = read_data("miscellaneous", "linelength6") - mode = pyink.Mode(line_length=6) - assert_format(source, expected, mode) - - -def test_preview_docstring_no_string_normalization() -> None: - """ - Like test_docstring but with string normalization off *and* the preview style - enabled. - """ - source, expected = read_data( - "miscellaneous", "docstring_preview_no_string_normalization" - ) - mode = replace(DEFAULT_MODE, string_normalization=False, preview=True) - assert_format(source, expected, mode) - - -def test_long_strings_flag_disabled() -> None: - """Tests for turning off the string processing logic.""" - source, expected = read_data("miscellaneous", "long_strings_flag_disabled") - mode = replace(DEFAULT_MODE, experimental_string_processing=False) - assert_format(source, expected, mode) - - -def test_stub() -> None: - mode = replace(DEFAULT_MODE, is_pyi=True) - source, expected = read_data("miscellaneous", "stub.pyi") - assert_format(source, expected, mode) - - -def test_nested_stub() -> None: - mode = replace(DEFAULT_MODE, is_pyi=True, preview=True) - source, expected = read_data("miscellaneous", "nested_stub.pyi") - assert_format(source, expected, mode) - - -def test_power_op_newline() -> None: - # requires line_length=0 - source, expected = read_data("miscellaneous", "power_op_newline") - assert_format(source, expected, mode=pyink.Mode(line_length=0)) - - -def test_type_comment_syntax_error() -> None: - """Test that black is able to format python code with type comment syntax errors.""" - source, expected = read_data("type_comments", "type_comment_syntax_error") - assert_format(source, expected) - pyink.assert_equivalent(source, expected) diff --git a/tests/util.py b/tests/util.py index a8a963eaf28..3fbd3cfa397 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,17 +1,23 @@ +import argparse +import functools import os +import shlex import sys import unittest from contextlib import contextmanager -from dataclasses import replace +from dataclasses import dataclass, field, replace from functools import partial from pathlib import Path from typing import Any, Iterator, List, Optional, Tuple import pyink +from pyink.const import DEFAULT_LINE_LENGTH from pyink.debug import DebugVisitor from pyink.mode import TargetVersion from pyink.output import diff, err, out +from . import conftest + PYTHON_SUFFIX = ".py" ALLOWED_SUFFIXES = (PYTHON_SUFFIX, ".pyi", ".out", ".diff", ".ipynb") @@ -33,23 +39,42 @@ fs = partial(pyink.format_str, mode=DEFAULT_MODE) +@dataclass +class TestCaseArgs: + mode: pyink.Mode = field(default_factory=pyink.Mode) + fast: bool = False + minimum_version: Optional[Tuple[int, int]] = None + + def _assert_format_equal(expected: str, actual: str) -> None: - if actual != expected and not os.environ.get("SKIP_AST_PRINT"): + if actual != expected and (conftest.PRINT_FULL_TREE or conftest.PRINT_TREE_DIFF): bdv: DebugVisitor[Any] - out("Expected tree:", fg="green") + actual_out: str = "" + expected_out: str = "" + if conftest.PRINT_FULL_TREE: + out("Expected tree:", fg="green") try: exp_node = pyink.lib2to3_parse(expected) - bdv = DebugVisitor() + bdv = DebugVisitor(print_output=conftest.PRINT_FULL_TREE) list(bdv.visit(exp_node)) + expected_out = "\n".join(bdv.list_output) except Exception as ve: err(str(ve)) - out("Actual tree:", fg="red") + if conftest.PRINT_FULL_TREE: + out("Actual tree:", fg="red") try: exp_node = pyink.lib2to3_parse(actual) - bdv = DebugVisitor() + bdv = DebugVisitor(print_output=conftest.PRINT_FULL_TREE) list(bdv.visit(exp_node)) + actual_out = "\n".join(bdv.list_output) except Exception as ve: err(str(ve)) + if conftest.PRINT_TREE_DIFF: + out("Tree Diff:") + out( + diff(expected_out, actual_out, "expected tree", "actual tree") + or "Trees do not differ" + ) if actual != expected: out(diff(expected, actual, "expected", "actual")) @@ -164,18 +189,89 @@ def get_case_path( return case_path +def read_data_with_mode( + subdir_name: str, name: str, data: bool = True +) -> Tuple[TestCaseArgs, str, str]: + """read_data_with_mode('test_name') -> Mode(), 'input', 'output'""" + return read_data_from_file(get_case_path(subdir_name, name, data)) + + def read_data(subdir_name: str, name: str, data: bool = True) -> Tuple[str, str]: """read_data('test_name') -> 'input', 'output'""" - return read_data_from_file(get_case_path(subdir_name, name, data)) + _, input, output = read_data_with_mode(subdir_name, name, data) + return input, output + + +def _parse_minimum_version(version: str) -> Tuple[int, int]: + major, minor = version.split(".") + return int(major), int(minor) -def read_data_from_file(file_name: Path) -> Tuple[str, str]: +@functools.lru_cache() +def get_flags_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser() + parser.add_argument( + "--target-version", + action="append", + type=lambda val: TargetVersion[val.upper()], + default=(), + ) + parser.add_argument("--line-length", default=DEFAULT_LINE_LENGTH, type=int) + parser.add_argument( + "--skip-string-normalization", default=False, action="store_true" + ) + parser.add_argument("--pyi", default=False, action="store_true") + parser.add_argument("--ipynb", default=False, action="store_true") + parser.add_argument( + "--skip-magic-trailing-comma", default=False, action="store_true" + ) + parser.add_argument("--preview", default=False, action="store_true") + parser.add_argument("--fast", default=False, action="store_true") + parser.add_argument( + "--minimum-version", + type=_parse_minimum_version, + default=None, + help=( + "Minimum version of Python where this test case is parseable. If this is" + " set, the test case will be run twice: once with the specified" + " --target-version, and once with --target-version set to exactly the" + " specified version. This ensures that Black's autodetection of the target" + " version works correctly." + ), + ) + parser.add_argument("--pyink", default=False, action="store_true") + parser.add_argument("--pyink-indentation", default=4, type=int, choices=[2, 4]) + return parser + + +def parse_mode(flags_line: str) -> TestCaseArgs: + parser = get_flags_parser() + args = parser.parse_args(shlex.split(flags_line)) + mode = pyink.Mode( + target_versions=set(args.target_version), + line_length=args.line_length, + string_normalization=not args.skip_string_normalization, + is_pyi=args.pyi, + is_ipynb=args.ipynb, + magic_trailing_comma=not args.skip_magic_trailing_comma, + preview=args.preview, + is_pyink=args.pyink, + pyink_indentation=args.pyink_indentation, + ) + return TestCaseArgs(mode=mode, fast=args.fast, minimum_version=args.minimum_version) + + +def read_data_from_file(file_name: Path) -> Tuple[TestCaseArgs, str, str]: with open(file_name, "r", encoding="utf8") as test: lines = test.readlines() _input: List[str] = [] _output: List[str] = [] result = _input + mode = TestCaseArgs() for line in lines: + if not _input and line.startswith("# flags: "): + mode = parse_mode(line[len("# flags: ") :]) + continue line = line.replace(EMPTY_LINE, "") if line.rstrip() == "# output": result = _output @@ -185,7 +281,7 @@ def read_data_from_file(file_name: Path) -> Tuple[str, str]: if _input and not _output: # If there's no output marker, treat the entire file as already pre-formatted. _output = _input[:] - return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n" + return mode, "".join(_input).strip() + "\n", "".join(_output).strip() + "\n" def read_jupyter_notebook(subdir_name: str, name: str, data: bool = True) -> str: diff --git a/tox.ini b/tox.ini index ff039f075ce..d67db2d08fa 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] isolated_build = true -envlist = {,ci-}py{37,38,39,310,311,py3},fuzz,run_self +envlist = {,ci-}py{38,39,310,311,py3},fuzz,run_self [testenv] setenv =