@@ -536,6 +536,9 @@ def _can_single_line_promise(node: Node, indent: int, line_length: int) -> bool:
536536 return False
537537 if _contains_list_with_comment (children ):
538538 return False
539+ if any (c .type == "comment" for c in children ):
540+ # Comments between promiser and attribute force multi-line layout
541+ return False
539542 attrs = [c for c in children if c .type == "attribute" ]
540543 next_sib = node .next_named_sibling
541544 while next_sib and next_sib .type == "macro" :
@@ -647,9 +650,21 @@ def _format_remaining_children(
647650 line_length : int ,
648651) -> None :
649652 """Format promise children, skipping promiser/arrow/stakeholder parts."""
653+ # When the promise was multi-lined because of comments between the
654+ # promiser and the attribute, preserve the attribute's original spacing
655+ # rather than running it through the spacing-normalizing stringifier.
656+ # Only applies when the comments and attribute aren't separated by a
657+ # macro — half-promise patterns still use the normal renderer.
658+ verbatim_attr = any (c .type == "comment" for c in children ) and not any (
659+ c .type == "macro" for c in children
660+ )
650661 for child in children :
651662 if child .type in PROMISER_PARTS :
652663 continue
664+ if verbatim_attr and child .type == "attribute" :
665+ fmt .print (text (child ), indent + 2 )
666+ fmt .update_previous (child )
667+ continue
653668 _autoformat (child , fmt , line_length , indent )
654669
655670
@@ -736,27 +751,39 @@ def _needs_blank_line_before(child: Node, indent: int, line_length: int) -> bool
736751 if child .type == "class_guarded_promise_block_attributes" :
737752 return prev .type in {"attribute" , "class_guarded_promise_block_attributes" }
738753
754+ if child .type == "class_guarded_body_attributes" :
755+ return prev .type in {"attribute" , "class_guarded_body_attributes" }
756+
739757 if child .type in CLASS_GUARD_TYPES :
740758 return prev .type in {"promise" , "half_promise" , "class_guarded_promises" }
741759
742760 if child .type == "comment" :
743- if prev . type not in { "promise" , "half_promise" } | CLASS_GUARD_TYPES :
761+ if _is_empty_comment ( child ) :
744762 return False
763+ # Top-level comment after a complete block — visually separates them
764+ if prev .type in BLOCK_TYPES :
765+ return True
745766 parent = child .parent
746- if parent and parent .type in {"bundle_section" , "class_guarded_promises" }:
767+ # Trailing comment at the end of a bundle body lands deeply
768+ # indented (aligned with the deepest attribute); insert a blank
769+ # line so it doesn't run into the preceding section.
770+ if (
771+ prev .type == "bundle_section"
772+ and parent
773+ and parent .type == "bundle_block_body"
774+ and _skip_comments (child .next_named_sibling , "next" ) is None
775+ ):
747776 return True
748- # Inside a body/promise block, a comment between two class guards
749- # only gets a blank-line separator when the preceding class guard
750- # already has interior comments (i.e. the visual block is rich
751- # enough that running it together with the next would look dense).
777+ if parent and parent .type in {"bundle_section" , "class_guarded_promises" }:
778+ return prev .type in {"promise" , "half_promise" } | CLASS_GUARD_TYPES
752779 if parent and parent .type in {"body_block_body" , "promise_block_body" }:
753- if _skip_comments (child .next_named_sibling , "next" ) is None :
780+ next_sib = _skip_comments (child .next_named_sibling , "next" )
781+ if next_sib is None :
754782 return False
755- if prev .type in CLASS_GUARD_TYPES and any (
756- c .type == "comment" for c in prev .children
757- ):
758- return True
759- return False
783+ # Leading comment for a class-guarded section preceded by
784+ # content above it.
785+ if next_sib .type in CLASS_GUARD_TYPES :
786+ return prev .type in CLASS_GUARD_TYPES | {"attribute" }
760787 return False
761788
762789 return False
@@ -790,13 +817,14 @@ def _skip_comments(sibling: Node | None, direction: str = "next") -> Node | None
790817def _comment_indent (node : Node , indent : int ) -> int :
791818 """Compute indentation for a leaf comment based on its nearest non-comment neighbor."""
792819 nearest = _skip_comments (node .next_named_sibling , "next" )
793- # A trailing comment whose previous sibling is a class-guarded group
794- # lines up with that group's contents (one extra indent level), as if
795- # it were the last comment inside the class guard .
820+ # A trailing comment with no next sibling aligns with the deepest
821+ # content at the end of the previous sibling subtree, so that comments
822+ # appended after a class-guarded block visually belong to that block .
796823 if nearest is None :
797- nearest = _skip_comments (node .prev_named_sibling , "prev" )
798- if nearest and nearest .type in CLASS_GUARD_TYPES :
799- return indent + 4
824+ prev = _skip_comments (node .prev_named_sibling , "prev" )
825+ if prev and prev .type in INDENTED_TYPES :
826+ return _trailing_comment_indent (prev , indent )
827+ nearest = prev
800828 if nearest and nearest .type in INDENTED_TYPES :
801829 return indent + 2
802830 # No indented sibling found — if we're directly inside a block body,
@@ -806,6 +834,21 @@ def _comment_indent(node: Node, indent: int) -> int:
806834 return indent
807835
808836
837+ def _trailing_comment_indent (prev : Node , indent : int ) -> int :
838+ """Walk down the end of a sibling subtree to compute the deepest indent."""
839+ while prev .type in INDENTED_TYPES :
840+ indent += 2
841+ deeper = None
842+ for child in reversed (prev .children ):
843+ if child .type in INDENTED_TYPES :
844+ deeper = child
845+ break
846+ if deeper is None :
847+ break
848+ prev = deeper
849+ return indent
850+
851+
809852# ---------------------------------------------------------------------------
810853# Main recursive formatter
811854# ---------------------------------------------------------------------------
0 commit comments