@@ -66,6 +66,9 @@ def __init__(self) -> None:
6666 self .empty : bool = True
6767 self .previous : Node | None = None
6868 self .buffer : str = ""
69+ # Indentation level saved when @if is encountered, restored after
70+ # @else so that both branches are formatted at the same depth.
71+ self .macro_indent : int = 0
6972
7073 def _write (self , message : str , end : str = "\n " ) -> None :
7174 """Append text to the buffer with the given line ending."""
@@ -432,6 +435,8 @@ def can_single_line_promise(node: Node, indent: int, line_length: int) -> bool:
432435 children = node .children
433436 attrs = [c for c in children if c .type == "attribute" ]
434437 next_sib = node .next_named_sibling
438+ while next_sib and next_sib .type == "macro" :
439+ next_sib = next_sib .next_named_sibling
435440 if len (attrs ) > 1 :
436441 # We always want to multiline a promise with multiple attributes
437442 # even if it would fit on one line, i.e this should be split:
@@ -479,7 +484,6 @@ def _format_promise(
479484 fmt : Formatter ,
480485 indent : int ,
481486 line_length : int ,
482- macro_indent : int ,
483487) -> bool :
484488 """Format a promise node. Returns True if handled, False to fall through."""
485489 # Single-line promise
@@ -513,7 +517,7 @@ def _format_promise(
513517 close_indent = indent + 2
514518 if attrs :
515519 fmt .print ("}" , close_indent )
516- _format_remaining_children (children , fmt , indent , line_length , macro_indent )
520+ _format_remaining_children (children , fmt , indent , line_length )
517521 else :
518522 fmt .print ("};" , close_indent )
519523 return True
@@ -522,7 +526,7 @@ def _format_promise(
522526 prefix = _promiser_line_with_stakeholder (children )
523527 if prefix :
524528 fmt .print (prefix , indent )
525- _format_remaining_children (children , fmt , indent , line_length , macro_indent )
529+ _format_remaining_children (children , fmt , indent , line_length )
526530 return True
527531
528532 return False
@@ -533,13 +537,12 @@ def _format_remaining_children(
533537 fmt : Formatter ,
534538 indent : int ,
535539 line_length : int ,
536- macro_indent : int ,
537540) -> None :
538541 """Format promise children, skipping promiser/arrow/stakeholder parts."""
539542 for child in children :
540543 if child .type in PROMISER_PARTS :
541544 continue
542- autoformat (child , fmt , line_length , macro_indent , indent )
545+ autoformat (child , fmt , line_length , indent )
543546
544547
545548# ---------------------------------------------------------------------------
@@ -570,7 +573,10 @@ def _format_block_header(node: Node, fmt: Formatter) -> list[Node]:
570573 # Skip over preceding empty comments since they will be removed
571574 while prev_sib and prev_sib .type == "comment" and _is_empty_comment (prev_sib ):
572575 prev_sib = prev_sib .prev_named_sibling
573- if not (prev_sib and prev_sib .type == "comment" ):
576+ is_macro_wrapped = (
577+ prev_sib and prev_sib .type == "macro" and text (prev_sib ).startswith ("@if" )
578+ )
579+ if not (prev_sib and prev_sib .type == "comment" ) and not is_macro_wrapped :
574580 fmt .blank_line ()
575581 fmt .print (line , 0 )
576582 for i , comment in enumerate (header_comments ):
@@ -599,14 +605,20 @@ def _needs_blank_line_before(child: Node, indent: int, line_length: int) -> bool
599605 if child .type == "bundle_section" :
600606 return prev .type == "bundle_section"
601607
602- if child .type == "promise" and prev .type in {"promise" , "half_promise" }:
603- promise_indent = indent + 2
604- both_single = (
605- prev .type == "promise"
606- and can_single_line_promise (prev , promise_indent , line_length )
607- and can_single_line_promise (child , promise_indent , line_length )
608- )
609- return not both_single
608+ if child .type == "promise" :
609+ # Skip past macros to find the content-bearing previous sibling,
610+ # so we detect promise groups separated by macro-split halves.
611+ prev_content = prev
612+ while prev_content and prev_content .type == "macro" :
613+ prev_content = prev_content .prev_named_sibling
614+ if prev_content and prev_content .type in {"promise" , "half_promise" }:
615+ promise_indent = indent + 2
616+ both_single = (
617+ prev_content .type == "promise"
618+ and can_single_line_promise (prev_content , promise_indent , line_length )
619+ and can_single_line_promise (child , promise_indent , line_length )
620+ )
621+ return not both_single
610622
611623 if child .type in CLASS_GUARD_TYPES :
612624 return prev .type in {"promise" , "half_promise" , "class_guarded_promises" }
@@ -637,8 +649,8 @@ def _is_empty_comment(node: Node) -> bool:
637649
638650
639651def _skip_comments (sibling : Node | None , direction : str = "next" ) -> Node | None :
640- """Walk past adjacent comment siblings to find the nearest non-comment ."""
641- while sibling and sibling .type == "comment" :
652+ """Walk past adjacent comment and macro siblings to find the nearest content node ."""
653+ while sibling and sibling .type in ( "comment" , "macro" ) :
642654 sibling = (
643655 sibling .next_named_sibling
644656 if direction == "next"
@@ -670,21 +682,25 @@ def autoformat(
670682 node : Node ,
671683 fmt : Formatter ,
672684 line_length : int ,
673- macro_indent : int ,
674685 indent : int = 0 ,
675686) -> None :
676687 """Recursively format a tree-sitter node tree into the Formatter buffer."""
677688 previous = fmt .update_previous (node )
678689
679690 # Macro handling
680691 if previous and previous .type == "macro" and text (previous ).startswith ("@else" ):
681- indent = macro_indent
692+ if node .type != "half_promise" :
693+ indent = fmt .macro_indent
682694 if node .type == "macro" :
683- fmt .print (node , 0 )
684695 if text (node ).startswith ("@if" ):
685- macro_indent = indent
696+ # Add blank line before @if at top level if preceded by a block
697+ prev_sib = node .prev_named_sibling
698+ if prev_sib and prev_sib .type in BLOCK_TYPES and not fmt .empty :
699+ fmt .blank_line ()
700+ fmt .macro_indent = indent
686701 elif text (node ).startswith ("@else" ):
687- indent = macro_indent
702+ indent = fmt .macro_indent
703+ fmt .print (node , 0 )
688704 return
689705
690706 # Block header (bundle/body/promise blocks)
@@ -703,15 +719,15 @@ def autoformat(
703719
704720 # Promise — delegate to promise formatter
705721 if node .type == "promise" :
706- if _format_promise (node , children , fmt , indent , line_length , macro_indent ):
722+ if _format_promise (node , children , fmt , indent , line_length ):
707723 return
708724
709725 # Interior node with children — recurse
710726 if children :
711727 for child in children :
712728 if _needs_blank_line_before (child , indent , line_length ):
713729 fmt .blank_line ()
714- autoformat (child , fmt , line_length , macro_indent , indent )
730+ autoformat (child , fmt , line_length , indent )
715731 return
716732
717733 # Leaf nodes
@@ -739,15 +755,14 @@ def format_policy_file(filename: str, line_length: int, check: bool) -> int:
739755 PY_LANGUAGE = Language (tscfengine .language ())
740756 parser = Parser (PY_LANGUAGE )
741757
742- macro_indent = 0
743758 fmt = Formatter ()
744759 with open (filename , "rb" ) as f :
745760 original_data = f .read ()
746761 tree = parser .parse (original_data )
747762
748763 root_node = tree .root_node
749764 assert root_node .type == "source_file"
750- autoformat (root_node , fmt , line_length , macro_indent )
765+ autoformat (root_node , fmt , line_length )
751766
752767 new_data = fmt .buffer + "\n "
753768 if new_data != original_data .decode ("utf-8" ):
@@ -768,14 +783,13 @@ def format_policy_fin_fout(
768783 PY_LANGUAGE = Language (tscfengine .language ())
769784 parser = Parser (PY_LANGUAGE )
770785
771- macro_indent = 0
772786 fmt = Formatter ()
773787 original_data = fin .read ().encode ("utf-8" )
774788 tree = parser .parse (original_data )
775789
776790 root_node = tree .root_node
777791 assert root_node .type == "source_file"
778- autoformat (root_node , fmt , line_length , macro_indent )
792+ autoformat (root_node , fmt , line_length )
779793
780794 new_data = fmt .buffer + "\n "
781795 fout .write (new_data )
0 commit comments