Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid magic-trailing-comma in single-element subscripts #2942

Merged
merged 7 commits into from Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -14,6 +14,8 @@

<!-- Changes that affect Black's preview style -->

- Avoid magic-trailing-comma in single tuple type (#2942)

### _Blackd_

<!-- Changes to blackd -->
Expand Down
27 changes: 23 additions & 4 deletions src/black/lines.py
Expand Up @@ -17,12 +17,17 @@
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_multiline_string,
is_import,
is_type_comment,
is_within_annotation,
is_one_tuple_between,
)

# types
T = TypeVar("T")
Expand Down Expand Up @@ -253,7 +258,7 @@ def has_magic_trailing_comma(
) -> bool:
"""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 one-tuple (or the equivalent type hint)
Additionally, if ensure_removable:
- it's not from square bracket indexing
"""
Expand All @@ -268,6 +273,20 @@ def has_magic_trailing_comma(
return True

if closing.type == token.RSQB:
if (
Preview.one_tuple_type in self.mode
and is_within_annotation(closing)
and closing.opening_bracket
and is_one_tuple_between(closing.opening_bracket, closing, self.leaves)
and closing.parent
and closing.parent.prev_sibling
and (
list(closing.parent.prev_sibling.leaves())[-1].value
in ("tuple", "Tuple")
jpy-git marked this conversation as resolved.
Show resolved Hide resolved
)
):
return False

if not ensure_removable:
return True
comma = self.leaves[-1]
Expand Down
5 changes: 2 additions & 3 deletions src/black/mode.py
Expand Up @@ -127,6 +127,7 @@ class Preview(Enum):
"""Individual preview style features."""

string_processing = auto()
one_tuple_type = auto()


class Deprecated(UserWarning):
Expand Down Expand Up @@ -162,9 +163,7 @@ def __contains__(self, feature: Preview) -> bool:
"""
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:
Expand Down
17 changes: 16 additions & 1 deletion src/black/nodes.py
Expand Up @@ -561,7 +561,10 @@ 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:
if not (
(opening.type == token.LPAR and closing.type == token.RPAR)
or (opening.type == token.LSQB and closing.type == token.RSQB)
):
return False

depth = closing.bracket_depth + 1
Expand Down Expand Up @@ -597,6 +600,18 @@ def is_walrus_assignment(node: LN) -> bool:
return inner is not None and inner.type == syms.namedexpr_test


def is_within_annotation(node: LN) -> bool:
"""Return True iff `node` is either `annassign` or child of `annassign`"""
found_annassign = False
current_node = node
while current_node.parent:
if current_node.type == syms.annassign:
found_annassign = True
break
current_node = current_node.parent
return found_annassign


def is_simple_decorator_trailer(node: LN, last: bool = False) -> bool:
"""Return True iff `node` is a trailer valid in a simple decorator"""
return node.type == syms.trailer and (
Expand Down
34 changes: 34 additions & 0 deletions tests/data/one_tuple_annotation.py
@@ -0,0 +1,34 @@
import typing
from typing import List, Tuple

# We should not treat the trailing comma
# in a single-element tuple type as a magic comma.
a: tuple[int,]
b: Tuple[int,]
c: typing.Tuple[int,]

# The magic comma still applies to non tuple types.
d: list[int,]
e: List[int,]
f: typing.List[int,]

# output
import typing
from typing import List, Tuple

# We should not treat the trailing comma
# in a single-element tuple type as a magic comma.
a: tuple[int,]
b: Tuple[int,]
c: typing.Tuple[int,]

# The magic comma still applies to non tuple types.
d: list[
int,
]
e: List[
int,
]
f: typing.List[
int,
]
1 change: 1 addition & 0 deletions tests/test_format.py
Expand Up @@ -79,6 +79,7 @@
"long_strings__edge_case",
"long_strings__regression",
"percent_precedence",
"one_tuple_annotation",
]

SOURCES: List[str] = [
Expand Down