Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
magic trailing commas and intentional multiline formatting (#4865)
- Fix `fix_fmt_skip_in_one_liners` crashing on `with` statements (#4853)
- Fix `fix_fmt_skip_in_one_liners` crashing on annotated parameters (#4854)
- Always wrap `in` clauses of comprehensions if they have delimiters (##4881)

### Configuration

Expand Down
8 changes: 7 additions & 1 deletion src/black/brackets.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,14 @@ def max_delimiter_priority_in_atom(node: LN) -> Priority:
if not (first.type == token.LPAR and last.type == token.RPAR):
return 0

return max_delimiter_priority(node.children[1:-1])


def max_delimiter_priority(children: list[LN]) -> Priority:
"""Return maximum delimiter priority in a list of leaves and nodes."""

bt = BracketTracker()
for c in node.children[1:-1]:
for c in children:
if isinstance(c, Leaf):
bt.mark(c)
else:
Expand Down
43 changes: 39 additions & 4 deletions src/black/linegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
DOT_PRIORITY,
STRING_PRIORITY,
get_leaves_inside_matching_brackets,
max_delimiter_priority,
max_delimiter_priority_in_atom,
)
from black.comments import (
Expand Down Expand Up @@ -639,9 +640,43 @@ def visit_tstring(self, node: Node) -> Iterator[Line]:

def visit_comp_for(self, node: Node) -> Iterator[Line]:
if Preview.wrap_comprehension_in in self.mode:
normalize_invisible_parens(
node, parens_after={"in"}, mode=self.mode, features=self.features
)
check_lpar = False
for child in node.children:
if not check_lpar:
check_lpar = isinstance(child, Leaf) and child.value == "in"
continue

check_lpar = isinstance(child, Leaf) and child.value == "in"

is_wrapped = (
len(child.children) == 3
and is_lpar_token(child.children[0])
and is_rpar_token(child.children[-1])
)
max_delimiter = max_delimiter_priority(
(child.children[1] if is_wrapped else child).children
)

if child.type == syms.atom:
if is_wrapped and child.children[1].type == syms.test:
continue
if max_delimiter > DOT_PRIORITY:
if not is_wrapped and maybe_make_parens_invisible_in_atom(
child,
parent=node,
mode=self.mode,
features=self.features,
):
wrap_in_parentheses(node, child, visible=True)
elif maybe_make_parens_invisible_in_atom(
child, parent=node, mode=self.mode, features=self.features
):
wrap_in_parentheses(node, child, visible=False)
else:
wrap_in_parentheses(
node, child, visible=max_delimiter > DOT_PRIORITY
)

yield from self.visit_default(node)

def visit_old_comp_for(self, node: Node) -> Iterator[Line]:
Expand Down Expand Up @@ -1012,7 +1047,7 @@ def _first_right_hand_split(
else:
line_length = line.mode.line_length - sum(
len(str(leaf))
for leaf in hugged_opening_leaves + hugged_closing_leaves
for leaf in (hugged_opening_leaves + hugged_closing_leaves)
)
if is_line_short_enough(
inner_body, mode=replace(line.mode, line_length=line_length)
Expand Down
16 changes: 16 additions & 0 deletions tests/data/cases/preview_wrap_comprehension_in.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
)
}

# Short `in`s with delimiters
filtered_trait_instructions: list[str] = [
trait for trait in (traits_instructions or []) if trait
]
filtered_trait_instructions: list[str] = [
trait for trait in traits_instructions or [] if trait
]

# output
[
a
Expand Down Expand Up @@ -159,3 +167,11 @@
key_with_super_really_long_name: key_with_super_really_long_name
for key in dictionary
}

# Short `in`s with delimiters
filtered_trait_instructions: list[str] = [
trait for trait in (traits_instructions or []) if trait
]
filtered_trait_instructions: list[str] = [
trait for trait in (traits_instructions or []) if trait
]
2 changes: 1 addition & 1 deletion tests/optional.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def pytest_configure(config: "Config") -> None:
passed_args = config.getoption("run_optional")
if passed_args:
ot_run.update(itertools.chain.from_iterable(a.split(",") for a in passed_args))
ot_run |= {no(excluded) for excluded in ot_markers - ot_run}
ot_run |= {no(excluded) for excluded in (ot_markers - ot_run)}
ot_markers |= {no(m) for m in ot_markers}

log.info("optional tests to run: %s", ot_run)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def test_feature_lists_are_up_to_date() -> None:
future_style = f.readlines()
preview_error = check_feature_list(
future_style,
{feature.name for feature in set(Preview) - UNSTABLE_FEATURES},
{feature.name for feature in (set(Preview) - UNSTABLE_FEATURES)},
"preview",
)
assert preview_error is None, preview_error
Expand Down
Loading