@@ -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
@@ -712,6 +727,30 @@ def _needs_blank_line_before(child: Node, indent: int, line_length: int) -> bool
712727 if not prev :
713728 return False
714729
730+ # Preserve blank lines from the input source for transitions inside
731+ # body/bundle/promise block bodies. Only applies when entering a new
732+ # commented or class-guarded section — blank lines between bare
733+ # attributes are stripped, and class guards with attached leading
734+ # comments take their blank line before the comment.
735+ parent = child .parent
736+ if parent and parent .type in BLOCK_BODY_TYPES :
737+ if child .type == "comment" and not _is_empty_comment (child ):
738+ next_sib = _skip_comments (child .next_named_sibling , "next" )
739+ # First comment of a chain leading into a class-guarded group:
740+ # take the input blank line from anywhere along the transition.
741+ if (
742+ prev .type != "comment"
743+ and next_sib is not None
744+ and next_sib .type in CLASS_GUARD_TYPES
745+ and next_sib .start_point [0 ] - prev .end_point [0 ] >= 2
746+ ):
747+ return True
748+ if child .start_point [0 ] - prev .end_point [0 ] >= 2 :
749+ return True
750+ elif child .type in CLASS_GUARD_TYPES and prev .type != "comment" :
751+ if child .start_point [0 ] - prev .end_point [0 ] >= 2 :
752+ return True
753+
715754 if child .type == "bundle_section" :
716755 return prev .type == "bundle_section"
717756
@@ -740,9 +779,13 @@ def _needs_blank_line_before(child: Node, indent: int, line_length: int) -> bool
740779 return prev .type in {"promise" , "half_promise" , "class_guarded_promises" }
741780
742781 if child .type == "comment" :
782+ if _is_empty_comment (child ):
783+ return False
784+ # Top-level comment after a complete block — visually separates them
785+ if prev .type in BLOCK_TYPES :
786+ return True
743787 if prev .type not in {"promise" , "half_promise" } | CLASS_GUARD_TYPES :
744788 return False
745- parent = child .parent
746789 if parent and parent .type in {"bundle_section" , "class_guarded_promises" }:
747790 return True
748791 # Inside a body/promise block, a comment between two class guards
@@ -790,13 +833,14 @@ def _skip_comments(sibling: Node | None, direction: str = "next") -> Node | None
790833def _comment_indent (node : Node , indent : int ) -> int :
791834 """Compute indentation for a leaf comment based on its nearest non-comment neighbor."""
792835 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 .
836+ # A trailing comment with no next sibling aligns with the deepest
837+ # content at the end of the previous sibling subtree, so that comments
838+ # appended after a class-guarded block visually belong to that block .
796839 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
840+ prev = _skip_comments (node .prev_named_sibling , "prev" )
841+ if prev and prev .type in INDENTED_TYPES :
842+ return _trailing_comment_indent (prev , indent )
843+ nearest = prev
800844 if nearest and nearest .type in INDENTED_TYPES :
801845 return indent + 2
802846 # No indented sibling found — if we're directly inside a block body,
@@ -806,6 +850,21 @@ def _comment_indent(node: Node, indent: int) -> int:
806850 return indent
807851
808852
853+ def _trailing_comment_indent (prev : Node , indent : int ) -> int :
854+ """Walk down the end of a sibling subtree to compute the deepest indent."""
855+ while prev .type in INDENTED_TYPES :
856+ indent += 2
857+ deeper = None
858+ for child in reversed (prev .children ):
859+ if child .type in INDENTED_TYPES :
860+ deeper = child
861+ break
862+ if deeper is None :
863+ break
864+ prev = deeper
865+ return indent
866+
867+
809868# ---------------------------------------------------------------------------
810869# Main recursive formatter
811870# ---------------------------------------------------------------------------
0 commit comments