From 11da02da72ed437facde3658bb61ddebce21e7a4 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Sat, 18 Nov 2023 11:47:05 -0800 Subject: [PATCH] Handle more huggable immediately nested parens/brackets. (#4012) Fixes #4011 --- CHANGES.md | 3 + docs/conf.py | 36 +++--- docs/the_black_code_style/future_style.md | 17 ++- src/black/cache.py | 6 +- src/black/handle_ipynb_magics.py | 54 ++++---- src/black/linegen.py | 69 ++++++++-- ..._parens_with_braces_and_square_brackets.py | 122 ++++++++++++++++-- .../cases/preview_long_strings__regression.py | 15 +-- 8 files changed, 233 insertions(+), 89 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 13b6c7bdb21..29f037b4767 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,9 @@ +- Additional cases of immediately nested tuples, lists, and dictionaries are now + indented less (#4012) + ### Configuration diff --git a/docs/conf.py b/docs/conf.py index 6b645435325..52a849d06a4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -149,15 +149,13 @@ def make_pypi_svg(version: str) -> None: # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). -latex_documents = [ - ( - master_doc, - "black.tex", - "Documentation for Black", - "Łukasz Langa and contributors to Black", - "manual", - ) -] +latex_documents = [( + master_doc, + "black.tex", + "Documentation for Black", + "Łukasz Langa and contributors to Black", + "manual", +)] # -- Options for manual page output ------------------------------------------ @@ -172,17 +170,15 @@ def make_pypi_svg(version: str) -> None: # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - "Black", - "Documentation for Black", - author, - "Black", - "The uncompromising Python code formatter", - "Miscellaneous", - ) -] +texinfo_documents = [( + master_doc, + "Black", + "Documentation for Black", + author, + "Black", + "The uncompromising Python code formatter", + "Miscellaneous", +)] # -- Options for Epub output ------------------------------------------------- diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 944ffad033e..428bd87ab50 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -116,8 +116,7 @@ my_dict = { ### Improved multiline dictionary and list indentation for sole function parameter For better readability and less verticality, _Black_ now pairs parentheses ("(", ")") -with braces ("{", "}") and square brackets ("[", "]") on the same line for single -parameter function calls. For example: +with braces ("{", "}") and square brackets ("[", "]") on the same line. For example: ```python foo( @@ -127,6 +126,14 @@ foo( 3, ] ) + +nested_array = [ + [ + 1, + 2, + 3, + ] +] ``` will be changed to: @@ -137,6 +144,12 @@ foo([ 2, 3, ]) + +nested_array = [[ + 1, + 2, + 3, +]] ``` This also applies to list and dictionary unpacking: diff --git a/src/black/cache.py b/src/black/cache.py index 6a332304981..6baa096baca 100644 --- a/src/black/cache.py +++ b/src/black/cache.py @@ -124,9 +124,9 @@ def filtered_cached(self, sources: Iterable[Path]) -> Tuple[Set[Path], Set[Path] def write(self, sources: Iterable[Path]) -> None: """Update the cache file data and write a new cache file.""" - self.file_data.update(**{ - str(src.resolve()): Cache.get_file_data(src) for src in sources - }) + self.file_data.update( + **{str(src.resolve()): Cache.get_file_data(src) for src in sources} + ) try: CACHE_DIR.mkdir(parents=True, exist_ok=True) with tempfile.NamedTemporaryFile( diff --git a/src/black/handle_ipynb_magics.py b/src/black/handle_ipynb_magics.py index 55ef2267df8..5b2847cb0c4 100644 --- a/src/black/handle_ipynb_magics.py +++ b/src/black/handle_ipynb_magics.py @@ -17,36 +17,30 @@ from black.output import out from black.report import NothingChanged -TRANSFORMED_MAGICS = frozenset( - ( - "get_ipython().run_cell_magic", - "get_ipython().system", - "get_ipython().getoutput", - "get_ipython().run_line_magic", - ) -) -TOKENS_TO_IGNORE = frozenset( - ( - "ENDMARKER", - "NL", - "NEWLINE", - "COMMENT", - "DEDENT", - "UNIMPORTANT_WS", - "ESCAPED_NL", - ) -) -PYTHON_CELL_MAGICS = frozenset( - ( - "capture", - "prun", - "pypy", - "python", - "python3", - "time", - "timeit", - ) -) +TRANSFORMED_MAGICS = frozenset(( + "get_ipython().run_cell_magic", + "get_ipython().system", + "get_ipython().getoutput", + "get_ipython().run_line_magic", +)) +TOKENS_TO_IGNORE = frozenset(( + "ENDMARKER", + "NL", + "NEWLINE", + "COMMENT", + "DEDENT", + "UNIMPORTANT_WS", + "ESCAPED_NL", +)) +PYTHON_CELL_MAGICS = frozenset(( + "capture", + "prun", + "pypy", + "python", + "python3", + "time", + "timeit", +)) TOKEN_HEX = secrets.token_hex diff --git a/src/black/linegen.py b/src/black/linegen.py index e2c961d7a01..8a2cd4710b9 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -817,25 +817,66 @@ def _first_right_hand_split( body_leaves.reverse() head_leaves.reverse() - if Preview.hug_parens_with_braces_and_square_brackets in line.mode: - is_unpacking = 1 if body_leaves[0].type in [token.STAR, token.DOUBLESTAR] else 0 - if ( - tail_leaves[0].type == token.RPAR - and tail_leaves[0].value - and tail_leaves[0].opening_bracket is head_leaves[-1] - and body_leaves[-1].type in [token.RBRACE, token.RSQB] - and body_leaves[-1].opening_bracket is body_leaves[is_unpacking] + body: Optional[Line] = None + if ( + Preview.hug_parens_with_braces_and_square_brackets in line.mode + and tail_leaves[0].value + and tail_leaves[0].opening_bracket is head_leaves[-1] + ): + inner_body_leaves = list(body_leaves) + hugged_opening_leaves: List[Leaf] = [] + hugged_closing_leaves: List[Leaf] = [] + is_unpacking = body_leaves[0].type in [token.STAR, token.DOUBLESTAR] + unpacking_offset: int = 1 if is_unpacking else 0 + while ( + len(inner_body_leaves) >= 2 + unpacking_offset + and inner_body_leaves[-1].type in CLOSING_BRACKETS + and inner_body_leaves[-1].opening_bracket + is inner_body_leaves[unpacking_offset] ): - head_leaves = head_leaves + body_leaves[: 1 + is_unpacking] - tail_leaves = body_leaves[-1:] + tail_leaves - body_leaves = body_leaves[1 + is_unpacking : -1] + if unpacking_offset: + hugged_opening_leaves.append(inner_body_leaves.pop(0)) + unpacking_offset = 0 + hugged_opening_leaves.append(inner_body_leaves.pop(0)) + hugged_closing_leaves.insert(0, inner_body_leaves.pop()) + + if hugged_opening_leaves and inner_body_leaves: + inner_body = bracket_split_build_line( + inner_body_leaves, + line, + hugged_opening_leaves[-1], + component=_BracketSplitComponent.body, + ) + if ( + line.mode.magic_trailing_comma + and inner_body_leaves[-1].type == token.COMMA + ): + should_hug = True + else: + line_length = line.mode.line_length - sum( + len(str(leaf)) + for leaf in hugged_opening_leaves + hugged_closing_leaves + ) + if is_line_short_enough( + inner_body, mode=replace(line.mode, line_length=line_length) + ): + # Do not hug if it fits on a single line. + should_hug = False + else: + should_hug = True + if should_hug: + body_leaves = inner_body_leaves + head_leaves.extend(hugged_opening_leaves) + tail_leaves = hugged_closing_leaves + tail_leaves + body = inner_body # No need to re-calculate the body again later. head = bracket_split_build_line( head_leaves, line, opening_bracket, component=_BracketSplitComponent.head ) - body = bracket_split_build_line( - body_leaves, line, opening_bracket, component=_BracketSplitComponent.body - ) + if body is None: + body = bracket_split_build_line( + body_leaves, line, opening_bracket, component=_BracketSplitComponent.body + ) tail = bracket_split_build_line( tail_leaves, line, opening_bracket, component=_BracketSplitComponent.tail ) diff --git a/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py b/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py index 97b5b2e8dd1..9e5c9eb8546 100644 --- a/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py +++ b/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py @@ -128,6 +128,19 @@ def foo_square_brackets(request): func({"short line"}) func({"long line", "long long line", "long long long line", "long long long long line", "long long long long long line"}) func({{"long line", "long long line", "long long long line", "long long long long line", "long long long long long line"}}) +func(("long line", "long long line", "long long long line", "long long long long line", "long long long long long line")) +func((("long line", "long long line", "long long long line", "long long long long line", "long long long long long line"))) +func([["long line", "long long line", "long long long line", "long long long long line", "long long long long long line"]]) + +# Do not hug if the argument fits on a single line. +func({"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"}) +func(("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line")) +func(["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"]) +func(**{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit---"}) +func(*("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit----")) +array = [{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"}] +array = [("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line")] +array = [["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"]] foooooooooooooooooooo( [{c: n + 1 for c in range(256)} for n in range(100)] + [{}], {size} @@ -137,6 +150,13 @@ def foo_square_brackets(request): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], {x}, "a string", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ) +nested_mapping = {"key": [{"a very long key 1": "with a very long value", "a very long key 2": "with a very long value"}]} +nested_array = [[["long line", "long long line", "long long long line", "long long long long line", "long long long long long line"]]] +explicit_exploding = [[["short", "line",],],] +single_item_do_not_explode = Context({ + "version": get_docs_version(), +}) + foo(*["long long long long long line", "long long long long long line", "long long long long long line"]) foo(*[str(i) for i in range(100000000000000000000000000000000000000000000000000000000000)]) @@ -152,6 +172,9 @@ def foo_square_brackets(request): foo(**{x: y for x, y in enumerate(["long long long long line","long long long long line"])}) +# Edge case when deciding whether to hug the brackets without inner content. +very_very_very_long_variable = very_very_very_long_module.VeryVeryVeryVeryLongClassName([[]]) + for foo in ["a", "b"]: output.extend([ individual @@ -276,9 +299,9 @@ def foo_square_brackets(request): ) func([x for x in "short line"]) -func([ - x for x in "long line long line long line long line long line long line long line" -]) +func( + [x for x in "long line long line long line long line long line long line long line"] +) func([ x for x in [ @@ -295,15 +318,60 @@ def foo_square_brackets(request): "long long long long line", "long long long long long line", }) -func({ - { - "long line", - "long long line", - "long long long line", - "long long long long line", - "long long long long long line", - } -}) +func({{ + "long line", + "long long line", + "long long long line", + "long long long long line", + "long long long long long line", +}}) +func(( + "long line", + "long long line", + "long long long line", + "long long long long line", + "long long long long long line", +)) +func((( + "long line", + "long long line", + "long long long line", + "long long long long line", + "long long long long long line", +))) +func([[ + "long line", + "long long line", + "long long long line", + "long long long long line", + "long long long long long line", +]]) + +# Do not hug if the argument fits on a single line. +func( + {"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"} +) +func( + ("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line") +) +func( + ["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"] +) +func( + **{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit---"} +) +func( + *("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit----") +) +array = [ + {"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"} +] +array = [ + ("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line") +] +array = [ + ["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"] +] foooooooooooooooooooo( [{c: n + 1 for c in range(256)} for n in range(100)] + [{}], {size} @@ -313,6 +381,31 @@ def foo_square_brackets(request): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], {x}, "a string", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ) +nested_mapping = { + "key": [{ + "a very long key 1": "with a very long value", + "a very long key 2": "with a very long value", + }] +} +nested_array = [[[ + "long line", + "long long line", + "long long long line", + "long long long long line", + "long long long long long line", +]]] +explicit_exploding = [ + [ + [ + "short", + "line", + ], + ], +] +single_item_do_not_explode = Context({ + "version": get_docs_version(), +}) + foo(*[ "long long long long long line", "long long long long long line", @@ -334,6 +427,11 @@ def foo_square_brackets(request): x: y for x, y in enumerate(["long long long long line", "long long long long line"]) }) +# Edge case when deciding whether to hug the brackets without inner content. +very_very_very_long_variable = very_very_very_long_module.VeryVeryVeryVeryLongClassName( + [[]] +) + for foo in ["a", "b"]: output.extend([ individual diff --git a/tests/data/cases/preview_long_strings__regression.py b/tests/data/cases/preview_long_strings__regression.py index 313d898cd83..5e76a8cf61c 100644 --- a/tests/data/cases/preview_long_strings__regression.py +++ b/tests/data/cases/preview_long_strings__regression.py @@ -611,14 +611,13 @@ def foo(): class A: def foo(): - XXXXXXXXXXXX.append( - ( - "xxx_xxxxxxxxxx(xxxxx={}, xxxx={}, xxxxx, xxxx_xxxx_xxxxxxxxxx={})" - .format(xxxxx, xxxx, xxxx_xxxx_xxxxxxxxxx), - my_var, - my_other_var, - ) - ) + XXXXXXXXXXXX.append(( + "xxx_xxxxxxxxxx(xxxxx={}, xxxx={}, xxxxx, xxxx_xxxx_xxxxxxxxxx={})".format( + xxxxx, xxxx, xxxx_xxxx_xxxxxxxxxx + ), + my_var, + my_other_var, + )) class A: