3535
3636PROMISER_PARTS = {"promiser" , "->" , "stakeholder" }
3737
38+ SAMELINE_COMMENT_SEPARATOR = " "
39+
3840
3941def _has_direct_macro (node : Node ) -> bool :
4042 """Check if any direct child of a node is a macro (non-recursive)."""
@@ -241,6 +243,9 @@ def split_generic_list(
241243 # outside the function call, so we do not add trailing commas into function
242244 # call argument lists.
243245 value_end_indices : list [int ] = []
246+ # Same-line (trailing) comments are attached only after trailing commas are
247+ # placed, so the comma lands before the comment instead of after it.
248+ sameline_comments : dict [int , str ] = {}
244249 for element in middle :
245250 if elements and element .type == "," :
246251 elements [- 1 ] = elements [- 1 ] + ","
@@ -249,7 +254,10 @@ def split_generic_list(
249254 elements .append (text (element ))
250255 continue
251256 if element .type == "comment" :
252- elements .append (" " * indent + text (element ))
257+ if _is_sameline_comment (element ) and elements :
258+ sameline_comments [len (elements ) - 1 ] = text (element )
259+ else :
260+ elements .append (" " * indent + text (element ))
253261 continue
254262 line = " " * indent + stringify_single_line_node (element )
255263 # Strict < reserves 1 char for the comma appended after this check
@@ -275,6 +283,10 @@ def split_generic_list(
275283 if not elements [i ].lstrip ().startswith ("#" ):
276284 elements [i ] = _set_trailing_comma (elements [i ], trailing_comma )
277285 break
286+
287+ # Attach same-line comments to the end of their value lines
288+ for i , comment in sameline_comments .items ():
289+ elements [i ] = elements [i ] + SAMELINE_COMMENT_SEPARATOR + comment
278290 return elements
279291
280292
@@ -354,7 +366,10 @@ def _format_attribute_with_macros(node: Node, indent: int) -> list[str]:
354366 if child .type == "macro" :
355367 lines .append (text (child ))
356368 elif child .type == "comment" :
357- lines .append (" " * (indent + 2 ) + text (child ))
369+ if _is_sameline_comment (child ) and lines :
370+ lines [- 1 ] = lines [- 1 ] + SAMELINE_COMMENT_SEPARATOR + text (child )
371+ else :
372+ lines .append (" " * (indent + 2 ) + text (child ))
358373 else :
359374 lines .append (" " * (indent + 2 ) + stringify_single_line_node (child ))
360375
@@ -505,13 +520,19 @@ def _format_stakeholder_elements(
505520 return maybe_split_generic_list (middle , indent , line_length )
506521 # Comments present — format element-by-element to preserve them
507522 elements : list [str ] = []
523+ # Same-line (trailing) comments are attached only after the trailing comma is
524+ # placed, so the comma lands before the comment instead of after it.
525+ sameline_comments : dict [int , str ] = {}
508526 for node in middle :
509527 if node .type == "," :
510528 if elements :
511529 elements [- 1 ] = elements [- 1 ] + ","
512530 continue
513531 if node .type == "comment" :
514- elements .append (" " * indent + text (node ))
532+ if _is_sameline_comment (node ) and elements :
533+ sameline_comments [len (elements ) - 1 ] = text (node )
534+ else :
535+ elements .append (" " * indent + text (node ))
515536 else :
516537 line = " " * indent + stringify_single_line_node (node )
517538 # Strict < reserves 1 char for the comma appended after this check
@@ -528,6 +549,10 @@ def _format_stakeholder_elements(
528549 if not elements [i ].endswith ("," ):
529550 elements [i ] = elements [i ] + ","
530551 break
552+
553+ # Attach inline comments to the end of their value lines
554+ for i , comment in sameline_comments .items ():
555+ elements [i ] = elements [i ] + SAMELINE_COMMENT_SEPARATOR + comment
531556 return elements
532557
533558
@@ -682,9 +707,13 @@ def _format_block_header(node: Node, fmt: Formatter) -> list[Node]:
682707 """Format a block header line and return the body's children for further processing."""
683708 header_parts : list [str ] = []
684709 header_comments : list [str ] = []
710+ # Direct comment children of the block (excludes comments inside a
711+ # parameter list) — candidates for being kept on the header line.
712+ direct_comments : list [Node ] = []
685713 for x in node .children [0 :- 1 ]:
686714 if x .type == "comment" :
687715 header_comments .append (text (x ))
716+ direct_comments .append (x )
688717 elif x .type == "parameter_list" :
689718 parts : list [str ] = []
690719 for p in x .children :
@@ -696,6 +725,16 @@ def _format_block_header(node: Node, fmt: Formatter) -> list[Node]:
696725 else :
697726 header_parts .append (text (x ))
698727 line = " " .join (header_parts )
728+ # A lone same-line comment stays on the header line. When the header has
729+ # more than one comment, keep them together on their own lines below the
730+ # header instead of splitting one off onto the header line.
731+ if (
732+ len (header_comments ) == 1
733+ and len (direct_comments ) == 1
734+ and _is_sameline_comment (direct_comments [0 ])
735+ ):
736+ line += SAMELINE_COMMENT_SEPARATOR + header_comments [0 ]
737+ header_comments = []
699738 if not fmt .empty :
700739 prev_sib = node .prev_named_sibling
701740 # Skip over preceding empty comments since they will be removed
@@ -725,6 +764,11 @@ def _format_block_header(node: Node, fmt: Formatter) -> list[Node]:
725764
726765def _needs_blank_line_before (child : Node , indent : int , line_length : int ) -> bool :
727766 """Check if a blank separator line should precede this child node."""
767+ # Inline (trailing) comments are appended to the preceding line, so they
768+ # must never be preceded by a blank separator line.
769+ if child .type == "comment" and _is_sameline_comment (child ):
770+ return False
771+
728772 prev = child .prev_named_sibling
729773 # Empty comments preceding this child will be dropped — look past them
730774 # so we evaluate against the real prior content.
@@ -798,6 +842,21 @@ def _needs_blank_line_before(child: Node, indent: int, line_length: int) -> bool
798842# ---------------------------------------------------------------------------
799843
800844
845+ def _is_sameline_comment (node : Node ) -> bool :
846+ """Check if a comment sits on the same source line as the element before it.
847+
848+ A trailing comment shares the row of the preceding sibling (which may be
849+ a leaf like ',' or ';', or a whole promise/attribute). Such comments are
850+ kept on the same output line as that element rather than moved to their
851+ own line."""
852+ if node .type != "comment" :
853+ return False
854+ prev = node .prev_sibling
855+ if prev is None :
856+ return False
857+ return prev .end_point [0 ] == node .start_point [0 ]
858+
859+
801860def _is_empty_comment (node : Node ) -> bool :
802861 """Check if a bare '#' comment should be dropped.
803862
@@ -934,7 +993,11 @@ def _autoformat(
934993 else :
935994 fmt .print_same_line (node )
936995 elif node .type == "comment" :
937- if not _is_empty_comment (node ):
996+ if _is_empty_comment (node ):
997+ pass
998+ elif _is_sameline_comment (node ) and not fmt .empty :
999+ fmt .print_same_line (SAMELINE_COMMENT_SEPARATOR + text (node ))
1000+ else :
9381001 fmt .print (node , _comment_indent (node , indent ))
9391002 else :
9401003 fmt .print (node , indent )
0 commit comments