Skip to content

Commit 9832386

Browse files
Improve fmt:skip handling in nested expressions with defensive checks
1 parent 23b8127 commit 9832386

4 files changed

Lines changed: 66 additions & 11 deletions

File tree

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
- Fix `fix_fmt_skip_in_one_liners` crashing on `with` statements (#4853)
2323
- Fix `fix_fmt_skip_in_one_liners` crashing on annotated parameters (#4854)
2424
- Fix new lines being added after imports with `# fmt: skip` on them (#4894)
25+
- Add defensive checks to prevent `AttributeError` crashes when processing `# fmt: skip`
26+
in nested expressions (#4903)
2527

2628
### Packaging
2729

src/black/comments.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,9 @@ def _generate_ignored_nodes_from_fmt_skip(
645645
return
646646

647647
if Preview.fix_fmt_skip_in_one_liners in mode and not prev_sibling and parent:
648+
# Simple one-level check: if the leaf has no prev_sibling,
649+
# check parent's prev_sibling. This handles some nested cases
650+
# without the complexity and indentation issues of deeper tree climbing
648651
prev_sibling = parent.prev_sibling
649652

650653
if prev_sibling is not None:

src/black/lines.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,10 @@ def is_line_short_enough(line: Line, *, mode: Mode, line_str: str = "") -> bool:
864864
for i, leaf in enumerate(line.leaves):
865865
if max_level_to_update == math.inf:
866866
had_comma: int | None = None
867+
# Skip multiline_string_handling logic for leaves without bracket_depth
868+
# (e.g., newly created leaves not yet processed by bracket tracker)
869+
if not hasattr(leaf, "bracket_depth"):
870+
continue
867871
if leaf.bracket_depth + 1 > len(commas):
868872
commas.append(0)
869873
elif leaf.bracket_depth + 1 < len(commas):
@@ -879,17 +883,22 @@ def is_line_short_enough(line: Line, *, mode: Mode, line_str: str = "") -> bool:
879883
# MLS was in parens with at least one comma - force split
880884
return False
881885

882-
if leaf.bracket_depth <= max_level_to_update and leaf.type == token.COMMA:
883-
# Inside brackets, ignore trailing comma
884-
# directly after MLS/MLS-containing expression
885-
ignore_ctxs: list[LN | None] = [None]
886-
ignore_ctxs += multiline_string_contexts
887-
if (line.inside_brackets or leaf.bracket_depth > 0) and (
888-
i != len(line.leaves) - 1 or leaf.prev_sibling not in ignore_ctxs
889-
):
890-
commas[leaf.bracket_depth] += 1
891-
if max_level_to_update != math.inf:
892-
max_level_to_update = min(max_level_to_update, leaf.bracket_depth)
886+
# Skip bracket-depth-dependent processing for leaves without the attribute
887+
if not hasattr(leaf, "bracket_depth"):
888+
# Still process multiline string detection below
889+
pass
890+
else:
891+
if leaf.bracket_depth <= max_level_to_update and leaf.type == token.COMMA:
892+
# Inside brackets, ignore trailing comma
893+
# directly after MLS/MLS-containing expression
894+
ignore_ctxs: list[LN | None] = [None]
895+
ignore_ctxs += multiline_string_contexts
896+
if (line.inside_brackets or leaf.bracket_depth > 0) and (
897+
i != len(line.leaves) - 1 or leaf.prev_sibling not in ignore_ctxs
898+
):
899+
commas[leaf.bracket_depth] += 1
900+
if max_level_to_update != math.inf:
901+
max_level_to_update = min(max_level_to_update, leaf.bracket_depth)
893902

894903
if is_multiline_string(leaf):
895904
if leaf.parent and (
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# flags: --preview
2+
class ClassWithALongName:
3+
Constant1 = 1
4+
Constant2 = 2
5+
Constant3 = 3
6+
7+
8+
def test():
9+
if (
10+
"cond1" == "cond1"
11+
and "cond2" == "cond2"
12+
and 1 in ( # fmt: skip
13+
ClassWithALongName.Constant1,
14+
ClassWithALongName.Constant2,
15+
ClassWithALongName.Constant3,
16+
)
17+
):
18+
return True
19+
return False
20+
21+
# output
22+
23+
class ClassWithALongName:
24+
Constant1 = 1
25+
Constant2 = 2
26+
Constant3 = 3
27+
28+
29+
def test():
30+
if (
31+
"cond1" == "cond1"
32+
and "cond2" == "cond2"
33+
and 1
34+
in ( # fmt: skip
35+
ClassWithALongName.Constant1,
36+
ClassWithALongName.Constant2,
37+
ClassWithALongName.Constant3,
38+
)
39+
):
40+
return True
41+
return False

0 commit comments

Comments
 (0)