Skip to content

Commit 73f237e

Browse files
olehermanseclaude
andcommitted
cfengine format: Consistently separate class guarded attributes in bodies, and fixed comment indentation
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Ole Herman Schumacher Elgesem <ole@northern.tech>
1 parent 3cb3600 commit 73f237e

1 file changed

Lines changed: 61 additions & 18 deletions

File tree

src/cfengine_cli/format.py

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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
790817
def _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

Comments
 (0)