From 3800ebd81df6a1c31d1eac8cc15899537b9cbb61 Mon Sep 17 00:00:00 2001 From: Joe Young <80432516+jpy-git@users.noreply.github.com> Date: Thu, 24 Mar 2022 02:16:09 +0000 Subject: [PATCH] Avoid magic-trailing-comma in single-element subscripts (#2942) Closes #2918. --- CHANGES.md | 1 + src/black/linegen.py | 11 ++++++--- src/black/lines.py | 21 ++++++++++++++--- src/black/mode.py | 5 ++-- src/black/nodes.py | 12 +++++++--- tests/data/one_element_subscript.py | 36 +++++++++++++++++++++++++++++ tests/test_format.py | 1 + 7 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 tests/data/one_element_subscript.py diff --git a/CHANGES.md b/CHANGES.md index d0faf7c..d753a24 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ - Code cell separators `#%%` are now standardised to `# %%` (#2919) +- Avoid magic-trailing-comma in single-element subscripts (#2942) ### _Blackd_ diff --git a/src/black/linegen.py b/src/black/linegen.py index 79475a8..5d92011 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -8,7 +8,12 @@ from typing import Collection, Iterator, List, Optional, Set, Union from black.nodes import WHITESPACE, RARROW, STATEMENT, STANDALONE_COMMENT from black.nodes import ASSIGNMENTS, OPENING_BRACKETS, CLOSING_BRACKETS from black.nodes import Visitor, syms, is_arith_like, ensure_visible -from black.nodes import is_docstring, is_empty_tuple, is_one_tuple, is_one_tuple_between +from black.nodes import ( + is_docstring, + is_empty_tuple, + is_one_tuple, + is_one_sequence_between, +) from black.nodes import is_name_token, is_lpar_token, is_rpar_token from black.nodes import is_walrus_assignment, is_yield, is_vararg, is_multiline_string from black.nodes import is_stub_suite, is_stub_body, is_atom_with_invisible_parens @@ -973,7 +978,7 @@ def generate_trailers_to_omit(line: Line, line_length: int) -> Iterator[Set[Leaf prev and prev.type == token.COMMA and leaf.opening_bracket is not None - and not is_one_tuple_between( + and not is_one_sequence_between( leaf.opening_bracket, leaf, line.leaves ) ): @@ -1001,7 +1006,7 @@ def generate_trailers_to_omit(line: Line, line_length: int) -> Iterator[Set[Leaf prev and prev.type == token.COMMA and leaf.opening_bracket is not None - and not is_one_tuple_between(leaf.opening_bracket, leaf, line.leaves) + and not is_one_sequence_between(leaf.opening_bracket, leaf, line.leaves) ): # Never omit bracket pairs with trailing commas. # We need to explode on those. diff --git a/src/black/lines.py b/src/black/lines.py index f35665c..e455a50 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -17,12 +17,12 @@ from blib2to3.pytree import Node, Leaf from blib2to3.pgen2 import token from black.brackets import BracketTracker, DOT_PRIORITY -from black.mode import Mode +from black.mode import Mode, Preview from black.nodes import STANDALONE_COMMENT, TEST_DESCENDANTS from black.nodes import BRACKETS, OPENING_BRACKETS, CLOSING_BRACKETS from black.nodes import syms, whitespace, replace_child, child_towards from black.nodes import is_multiline_string, is_import, is_type_comment -from black.nodes import is_one_tuple_between +from black.nodes import is_one_sequence_between # types T = TypeVar("T") @@ -254,6 +254,7 @@ class Line: """Return True if we have a magic trailing comma, that is when: - there's a trailing comma here - it's not a one-tuple + - it's not a single-element subscript Additionally, if ensure_removable: - it's not from square bracket indexing """ @@ -268,6 +269,20 @@ class Line: return True if closing.type == token.RSQB: + if ( + Preview.one_element_subscript in self.mode + and closing.parent + and closing.parent.type == syms.trailer + and closing.opening_bracket + and is_one_sequence_between( + closing.opening_bracket, + closing, + self.leaves, + brackets=(token.LSQB, token.RSQB), + ) + ): + return False + if not ensure_removable: return True comma = self.leaves[-1] @@ -276,7 +291,7 @@ class Line: if self.is_import: return True - if closing.opening_bracket is not None and not is_one_tuple_between( + if closing.opening_bracket is not None and not is_one_sequence_between( closing.opening_bracket, closing, self.leaves ): return True diff --git a/src/black/mode.py b/src/black/mode.py index 35a072c..77b1cab 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -127,6 +127,7 @@ class Preview(Enum): """Individual preview style features.""" string_processing = auto() + one_element_subscript = auto() class Deprecated(UserWarning): @@ -162,9 +163,7 @@ class Mode: """ if feature is Preview.string_processing: return self.preview or self.experimental_string_processing - # TODO: Remove type ignore comment once preview contains more features - # than just ESP - return self.preview # type: ignore + return self.preview def get_cache_key(self) -> str: if self.target_versions: diff --git a/src/black/nodes.py b/src/black/nodes.py index f130bff..d18d4bd 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -9,6 +9,7 @@ from typing import ( List, Optional, Set, + Tuple, TypeVar, Union, ) @@ -559,9 +560,14 @@ def is_one_tuple(node: LN) -> bool: ) -def is_one_tuple_between(opening: Leaf, closing: Leaf, leaves: List[Leaf]) -> bool: - """Return True if content between `opening` and `closing` looks like a one-tuple.""" - if opening.type != token.LPAR and closing.type != token.RPAR: +def is_one_sequence_between( + opening: Leaf, + closing: Leaf, + leaves: List[Leaf], + brackets: Tuple[int, int] = (token.LPAR, token.RPAR), +) -> bool: + """Return True if content between `opening` and `closing` is a one-sequence.""" + if (opening.type, closing.type) != brackets: return False depth = closing.bracket_depth + 1 diff --git a/tests/data/one_element_subscript.py b/tests/data/one_element_subscript.py new file mode 100644 index 0000000..39205ba --- /dev/null +++ b/tests/data/one_element_subscript.py @@ -0,0 +1,36 @@ +# We should not treat the trailing comma +# in a single-element subscript. +a: tuple[int,] +b = tuple[int,] + +# The magic comma still applies to multi-element subscripts. +c: tuple[int, int,] +d = tuple[int, int,] + +# Magic commas still work as expected for non-subscripts. +small_list = [1,] +list_of_types = [tuple[int,],] + +# output +# We should not treat the trailing comma +# in a single-element subscript. +a: tuple[int,] +b = tuple[int,] + +# The magic comma still applies to multi-element subscripts. +c: tuple[ + int, + int, +] +d = tuple[ + int, + int, +] + +# Magic commas still work as expected for non-subscripts. +small_list = [ + 1, +] +list_of_types = [ + tuple[int,], +] diff --git a/tests/test_format.py b/tests/test_format.py index 667d5c1..4de3170 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -80,6 +80,7 @@ PREVIEW_CASES: List[str] = [ "long_strings__edge_case", "long_strings__regression", "percent_precedence", + "one_element_subscript", ] SOURCES: List[str] = [ -- 2.39.2