Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
- Fix crash on an assignment whose target is split over lines inside redundant
parentheses and doesn't fit on one line with the value, such as `(a.b(c)` ...
`.d[e]) = f` (#5606, #5621)
- Fix crash when `# fmt: skip` is on an opening bracket that starts an operand of a
longer expression after `with` or `yield` (#5620)
- Fix crash on a method chain with multiple `# type: ignore` comments when another
comment sits between them, which put a `# type: ignore` after the other comment on one
line, where it no longer applies (#5618)
Expand Down
29 changes: 11 additions & 18 deletions src/black/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@ def _generate_ignored_nodes_from_fmt_skip(
current_node = current_node.parent

ignored_nodes = [current_node]
if current_node.prev_sibling is None and current_node.parent is not None:
while current_node.prev_sibling is None and current_node.parent is not None:
current_node = current_node.parent

# Track seen nodes to detect cycles that can occur after tree modifications
Expand Down Expand Up @@ -979,32 +979,25 @@ def _generate_ignored_nodes_from_fmt_skip(

ignored_nodes.insert(0, current_node)

if current_node.prev_sibling is None and current_node.parent is not None:
while current_node.prev_sibling is None and current_node.parent is not None:
current_node = current_node.parent
# Every child of the node we are climbing out of is now ignored, so
# take the node itself instead. Converting only its leaves would
# leave the emptied node in the tree, and visitors that expect
# children, like the one for PEP 695 type parameters, crash on it.
# Children collapsed on an earlier climb are already nodes here, so
# compare against children rather than leaves.
# The collapsed node can be the first child of a parent that is now
# fully ignored as well (`a + b` in `a + b if c else d`), so keep
# climbing and collapse that parent too. Stopping one level short
# leaves the parent's children in the list next to a sibling from
# the enclosing node, and the standalone comment then lands inside
# the parent while that sibling is removed from around it, turning
# e.g. a tuple into a call. Climbing also has to go on past parents
# that aren't fully ignored, or the walk stops before reaching the
# start of the line (`with` in `with not ( # fmt: skip`).
if _children_lead_ignored_nodes(current_node, ignored_nodes):
ignored_nodes[: len(current_node.children)] = [current_node]
# The collapsed node can be the first child of a parent that
# is now fully ignored as well (`a + b` in `a + b if c else d`).
# Collapse that parent too and keep walking from it. Stopping
# one level short leaves the parent's children in the list next
# to a sibling from the enclosing node, and the standalone
# comment then lands inside the parent while that sibling is
# removed from around it, turning e.g. a tuple into a call.
while (
current_node.prev_sibling is None
and current_node.parent is not None
and _children_lead_ignored_nodes(
current_node.parent, ignored_nodes
)
):
current_node = current_node.parent
ignored_nodes[: len(current_node.children)] = [current_node]

# Special handling for compound statements with semicolon-separated bodies
if isinstance(parent, Node):
Expand Down
8 changes: 6 additions & 2 deletions src/black/linegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,13 @@ def visit_factor(self, node: Node) -> Iterator[Line]:

-2 ** 8 -> -(2 ** 8)
"""
_operator, operand = node.children
# The operator is gone when a `# fmt: skip` on the operand's opening bracket
# hides it and the operand goes on after the closing bracket, as in
# `x = -( # fmt: skip` ... `).y`.
operand = node.children[-1]
if (
operand.type == syms.power
len(node.children) == 2
and operand.type == syms.power
and len(operand.children) == 3
and operand.children[1].type == token.DOUBLESTAR
):
Expand Down
95 changes: 95 additions & 0 deletions tests/data/cases/fmtskip_opening_bracket_operand.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# `# fmt: skip` on an opening bracket that starts an operand of a longer expression
# hides the whole line, including the code before the bracket.

with ( # fmt: skip
a
) == 1 and b:
x = 1

with not ( # fmt: skip
a
) or b as c:
pass

with -( # fmt: skip
a
).real and b:
pass

x = ( # fmt: skip
a
) + 1 or b

y = not [ # fmt: skip
1, 2] and c

assert ( # fmt: skip
a
) * 2 + 1 == b


async def f():
async with ( # fmt: skip
a
) in b and c:
pass


def g():
yield ( # fmt: skip
a
) == 1 and b
yield not ( # fmt: skip
a
) or b
return -{ # fmt: skip
"a": 1}.keys or b

# output

# `# fmt: skip` on an opening bracket that starts an operand of a longer expression
# hides the whole line, including the code before the bracket.

with ( # fmt: skip
a
) == 1 and b:
x = 1

with not ( # fmt: skip
a
) or b as c:
pass

with -( # fmt: skip
a
).real and b:
pass

x = ( # fmt: skip
a
) + 1 or b

y = not [ # fmt: skip
1, 2] and c

assert ( # fmt: skip
a
) * 2 + 1 == b


async def f():
async with ( # fmt: skip
a
) in b and c:
pass


def g():
yield ( # fmt: skip
a
) == 1 and b
yield not ( # fmt: skip
a
) or b
return -{ # fmt: skip
"a": 1}.keys or b
Loading