|
8 | 8 | from black.nodes import ( |
9 | 9 | CLOSING_BRACKETS, |
10 | 10 | STANDALONE_COMMENT, |
| 11 | + STATEMENT, |
11 | 12 | WHITESPACE, |
12 | 13 | container_of, |
13 | 14 | first_leaf_of, |
|
26 | 27 | FMT_SKIP: Final = {"# fmt: skip", "# fmt:skip"} |
27 | 28 | FMT_ON: Final = {"# fmt: on", "# fmt:on", "# yapf: enable"} |
28 | 29 |
|
| 30 | +# Compound statements we care about for fmt: skip handling |
| 31 | +# (excludes except_clause and case_block which aren't standalone compound statements) |
| 32 | +_COMPOUND_STATEMENTS: Final = STATEMENT - {syms.except_clause, syms.case_block} |
| 33 | + |
29 | 34 | COMMENT_EXCEPTIONS = " !:#'" |
30 | 35 | _COMMENT_PREFIX = "# " |
31 | 36 | _COMMENT_LIST_SEPARATOR = ";" |
@@ -322,112 +327,99 @@ def generate_ignored_nodes( |
322 | 327 |
|
323 | 328 |
|
324 | 329 | def _find_compound_statement_context( |
325 | | - parent: Optional[LN], |
326 | | -) -> tuple[Optional[Node], Optional[Node]]: |
327 | | - """Find compound statement and suite nodes for fmt: skip handling.""" |
| 330 | + parent: Optional[LN], mode: Mode |
| 331 | +) -> Optional[Node]: |
| 332 | + """Return the body node of a compound statement if we should respect fmt: skip. |
| 333 | + |
| 334 | + This handles one-line compound statements like: |
| 335 | + if condition: body # fmt: skip |
| 336 | + |
| 337 | + When Black expands such statements, they temporarily look like: |
| 338 | + if condition: |
| 339 | + body # fmt: skip |
| 340 | + |
| 341 | + In both cases, we want to return the body node (either the simple_stmt directly |
| 342 | + or the suite containing it). |
| 343 | + """ |
| 344 | + if Preview.fix_fmt_skip_in_one_liners not in mode: |
| 345 | + return None |
| 346 | + |
328 | 347 | if parent is None or parent.type != syms.simple_stmt: |
329 | | - return None, None |
| 348 | + return None |
| 349 | + |
| 350 | + assert isinstance(parent, Node) |
330 | 351 |
|
331 | | - # Case 1: simple_stmt -> suite -> compound_stmt (after reformatting) |
| 352 | + # Case 1: Expanded form after Black's initial formatting pass. |
| 353 | + # The one-liner has been split across multiple lines: |
| 354 | + # if True: |
| 355 | + # print("a"); print("b") # fmt: skip |
| 356 | + # Structure: compound_stmt -> suite -> simple_stmt |
332 | 357 | if ( |
333 | | - parent.parent is not None |
| 358 | + isinstance(parent.parent, Node) |
334 | 359 | and parent.parent.type == syms.suite |
335 | | - and parent.parent.parent is not None |
| 360 | + and isinstance(parent.parent.parent, Node) |
| 361 | + and parent.parent.parent.type in _COMPOUND_STATEMENTS |
336 | 362 | ): |
337 | | - assert isinstance(parent.parent, Node) |
338 | | - suite_node = parent.parent |
339 | | - assert isinstance(suite_node.parent, Node) |
340 | | - compound_parent = suite_node.parent |
341 | | - return compound_parent, suite_node |
342 | | - |
343 | | - # Case 2: simple_stmt -> compound_stmt (original structure) |
344 | | - compound_types = ( |
345 | | - syms.if_stmt, |
346 | | - syms.while_stmt, |
347 | | - syms.for_stmt, |
348 | | - syms.try_stmt, |
349 | | - syms.with_stmt, |
350 | | - syms.funcdef, |
351 | | - syms.classdef, |
352 | | - ) |
353 | | - if parent.parent is not None and parent.parent.type in compound_types: |
354 | | - assert isinstance(parent.parent, Node) |
355 | | - compound_parent = parent.parent |
356 | | - # In original single-line structure, the simple_stmt IS the body |
357 | | - assert isinstance(parent, Node) # simple_stmt is always a Node |
358 | | - return compound_parent, parent |
359 | | - |
360 | | - return None, None |
361 | | - |
362 | | - |
363 | | -def _get_compound_statement_header( |
364 | | - compound_parent: Node, suite_node: Node, parent: LN |
365 | | -) -> list[LN]: |
366 | | - """Get header nodes for compound statement if it should be preserved.""" |
367 | | - compound_types = ( |
368 | | - syms.if_stmt, |
369 | | - syms.while_stmt, |
370 | | - syms.for_stmt, |
371 | | - syms.try_stmt, |
372 | | - syms.with_stmt, |
373 | | - syms.funcdef, |
374 | | - syms.classdef, |
375 | | - ) |
376 | | - if compound_parent.type not in compound_types: |
377 | | - return [] |
| 363 | + return parent.parent |
378 | 364 |
|
379 | | - # Check if the body contains semicolon-separated statements |
380 | | - has_semicolon = False |
381 | | - if suite_node.type == syms.suite: |
382 | | - # After reformatting, check if the suite contains semicolons |
383 | | - for child in suite_node.children: |
384 | | - if hasattr(child, "children"): |
385 | | - for grandchild in child.children: |
386 | | - if getattr(grandchild, "type", None) == token.SEMI: |
387 | | - has_semicolon = True |
388 | | - break |
389 | | - if has_semicolon: |
390 | | - break |
391 | | - else: |
392 | | - # Original structure - check the simple_stmt for semicolons |
393 | | - for child in suite_node.children: |
394 | | - if getattr(child, "type", None) == token.SEMI: |
395 | | - has_semicolon = True |
396 | | - break |
| 365 | + # Case 2: Original one-line form from the input source. |
| 366 | + # The statement is still on a single line: |
| 367 | + # if True: print("a"); print("b") # fmt: skip |
| 368 | + # Structure: compound_stmt -> simple_stmt |
| 369 | + if ( |
| 370 | + isinstance(parent.parent, Node) |
| 371 | + and parent.parent.type in _COMPOUND_STATEMENTS |
| 372 | + ): |
| 373 | + return parent |
397 | 374 |
|
398 | | - # Only apply our fix for compound statements with semicolon-separated bodies |
399 | | - if not has_semicolon: |
400 | | - return [] |
| 375 | + return None |
401 | 376 |
|
402 | | - # Check if this is a single-line compound statement |
403 | | - if suite_node.type == syms.suite: |
404 | | - # Case 1: After reformatting, body is in a suite |
405 | | - stmt_children = [ |
406 | | - child |
407 | | - for child in suite_node.children |
408 | | - if child.type not in (token.NEWLINE, token.INDENT, token.DEDENT) |
409 | | - ] |
410 | | - single_line_body = len(stmt_children) == 1 and ( |
411 | | - stmt_children[0] == parent |
412 | | - or any( |
413 | | - c == parent |
414 | | - for c in stmt_children[0].children |
415 | | - if hasattr(stmt_children[0], "children") |
416 | | - ) |
417 | | - ) |
418 | | - else: |
419 | | - # Case 2: Original structure, suite_node is the simple_stmt body |
420 | | - single_line_body = suite_node == parent |
421 | 377 |
|
422 | | - if not single_line_body: |
423 | | - return [] |
| 378 | +def _should_keep_compound_statement_inline( |
| 379 | + body_node: Node, simple_stmt_parent: Node |
| 380 | +) -> bool: |
| 381 | + """Check if a compound statement should be kept on one line. |
| 382 | + |
| 383 | + Returns True only for compound statements with semicolon-separated bodies, |
| 384 | + like: if True: print("a"); print("b") # fmt: skip |
| 385 | + """ |
| 386 | + # Check if there are semicolons in the body |
| 387 | + for leaf in body_node.leaves(): |
| 388 | + if leaf.type == token.SEMI: |
| 389 | + # Verify it's a single-line body (one simple_stmt) |
| 390 | + if body_node.type == syms.suite: |
| 391 | + # After formatting: check suite has one simple_stmt child |
| 392 | + simple_stmts = [ |
| 393 | + child |
| 394 | + for child in body_node.children |
| 395 | + if child.type == syms.simple_stmt |
| 396 | + ] |
| 397 | + return len(simple_stmts) == 1 and simple_stmts[0] is simple_stmt_parent |
| 398 | + else: |
| 399 | + # Original form: body_node IS the simple_stmt |
| 400 | + return body_node is simple_stmt_parent |
| 401 | + return False |
| 402 | + |
424 | 403 |
|
425 | | - # Include the compound statement header |
| 404 | +def _get_compound_statement_header(body_node: Node, simple_stmt_parent: Node) -> list[LN]: |
| 405 | + """Get header nodes for a compound statement that should be preserved inline.""" |
| 406 | + if not _should_keep_compound_statement_inline(body_node, simple_stmt_parent): |
| 407 | + return [] |
| 408 | + |
| 409 | + # Get the compound statement (parent of body) |
| 410 | + compound_stmt = body_node.parent |
| 411 | + if compound_stmt is None or compound_stmt.type not in _COMPOUND_STATEMENTS: |
| 412 | + return [] |
| 413 | + |
| 414 | + # Collect all header leaves before the body |
426 | 415 | header_leaves: list[LN] = [] |
427 | | - for child in compound_parent.children: |
428 | | - if child == suite_node: |
| 416 | + for child in compound_stmt.children: |
| 417 | + if child is body_node: |
429 | 418 | break |
430 | | - if child.type not in (token.NEWLINE, token.INDENT): |
| 419 | + if isinstance(child, Leaf): |
| 420 | + if child.type not in (token.NEWLINE, token.INDENT): |
| 421 | + header_leaves.append(child) |
| 422 | + else: |
431 | 423 | header_leaves.extend(child.leaves()) |
432 | 424 | return header_leaves |
433 | 425 |
|
@@ -494,15 +486,9 @@ def _generate_ignored_nodes_from_fmt_skip( |
494 | 486 | if current_node.prev_sibling is None and current_node.parent is not None: |
495 | 487 | current_node = current_node.parent |
496 | 488 | # Special handling for compound statements with semicolon-separated bodies |
497 | | - compound_parent, suite_node = _find_compound_statement_context(parent) |
498 | | - if ( |
499 | | - compound_parent is not None |
500 | | - and suite_node is not None |
501 | | - and parent is not None |
502 | | - ): |
503 | | - header_nodes = _get_compound_statement_header( |
504 | | - compound_parent, suite_node, parent |
505 | | - ) |
| 489 | + body_node = _find_compound_statement_context(parent, mode) |
| 490 | + if body_node is not None and isinstance(parent, Node): |
| 491 | + header_nodes = _get_compound_statement_header(body_node, parent) |
506 | 492 | if header_nodes: |
507 | 493 | ignored_nodes = header_nodes + ignored_nodes |
508 | 494 |
|
|
0 commit comments