Skip to content

Commit 9cdcbfa

Browse files
committed
fix: preserve comments between node and comma in separated lists (#684)
`gen_node_with_separator` emitted the auto-inserted comma immediately after the node, then ran the regular trailing-comments path which only emits same-line comments. Multi-line comments living between the node end and the comma — e.g. foo( a // comment , b); — were therefore captured by `trailing_comments_with_previous` but filtered out by `get_trailing_comments_same_line` and lost. Collect the comma token's leading comments that sit on lines after the node end, mark them handled via `gen_comment_collection`, and emit them after the separator. Result: foo( a, // comment b, ); Applies to anything routed through `gen_node_with_separator`: call arguments, array literals, object literals, function parameters, etc.
1 parent 215f100 commit 9cdcbfa

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

src/generation/generate.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8028,16 +8028,27 @@ fn gen_node_with_separator<'a>(value: Node<'a>, generated_separator: PrintItems,
80288028
PrintItems::new()
80298029
};
80308030

8031+
// comments that sit between the node and the comma on a subsequent line —
8032+
// the default trailing-comments path only emits same-line comments, so these
8033+
// would otherwise be dropped (issue #684).
8034+
let between_node_and_comma = if let Some(comma_token) = comma_token {
8035+
gen_comments_between_node_and_comma(value, comma_token, context)
8036+
} else {
8037+
PrintItems::new()
8038+
};
8039+
80318040
// if the current node is ignored and already has a semi-colon, then skip adding a separator
80328041
let is_ignored_with_semi_colon =
80338042
value.text_fast(context.program).ends_with(';') && get_has_ignore_comment(&value.leading_comments_fast(context.program), value, context);
80348043
if is_ignored_with_semi_colon {
80358044
items.extend(gen_node(value, context));
80368045
} else {
80378046
let generated_separator = generated_separator.into_rc_path();
8047+
let between_node_and_comma = between_node_and_comma.into_rc_path();
80388048
items.extend(gen_node_with_inner_gen(value, context, move |mut items, _| {
80398049
// this Rc clone is necessary because we can't move the captured generated_separator out of this closure
80408050
items.push_optional_path(generated_separator);
8051+
items.push_optional_path(between_node_and_comma);
80418052
items
80428053
}));
80438054
}
@@ -8054,6 +8065,38 @@ fn gen_node_with_separator<'a>(value: Node<'a>, generated_separator: PrintItems,
80548065
}
80558066
}
80568067

8068+
fn gen_comments_between_node_and_comma<'a>(value: Node<'a>, comma_token: &TokenAndSpan, context: &mut Context<'a>) -> PrintItems {
8069+
// collect comments between the node end and the comma — these would be classified
8070+
// as trailing comments of the node, but the existing path only emits ones on the
8071+
// same line, dropping anything that sits on a subsequent line.
8072+
let node_end = value.range().end;
8073+
let node_end_line = node_end.end_line_fast(context.program);
8074+
let mut comments_to_emit = Vec::new();
8075+
for comment in comma_token.range().start.leading_comments_fast(context.program) {
8076+
if context.has_handled_comment(&comment) {
8077+
continue;
8078+
}
8079+
if comment.start() < node_end {
8080+
continue;
8081+
}
8082+
// skip same-line trailing comments — those flow through the regular trailing path
8083+
if comment.start_line_fast(context.program) <= node_end_line {
8084+
continue;
8085+
}
8086+
comments_to_emit.push(comment);
8087+
}
8088+
8089+
if comments_to_emit.is_empty() {
8090+
return PrintItems::new();
8091+
}
8092+
8093+
let comma_range = comma_token.range();
8094+
let mut items = PrintItems::new();
8095+
items.push_signal(Signal::NewLine);
8096+
items.extend(gen_comment_collection(comments_to_emit.into_iter(), None, Some(&comma_range), context));
8097+
items
8098+
}
8099+
80578100
/// Some nodes don't have a TsTypeAnn, but instead a Box<TsType>
80588101
fn gen_type_ann_with_colon_if_exists_for_type<'a>(type_ann: Option<TsType<'a>>, context: &mut Context<'a>) -> PrintItems {
80598102
if let Some(type_ann) = type_ann {

tests/specs/issues/issue0684.txt

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
== should preserve comment between argument and following comma (issue #684) ==
2+
const x = useMemo(() =>
3+
doSomething()
4+
// eslint-disable-next-line react-hooks/exhaustive-deps
5+
, [dep]);
6+
7+
[expect]
8+
const x = useMemo(
9+
() => doSomething(),
10+
// eslint-disable-next-line react-hooks/exhaustive-deps
11+
[dep],
12+
);
13+
14+
== should preserve multiple comments between argument and comma ==
15+
foo(
16+
a
17+
// c1
18+
/* c2 */
19+
// c3
20+
, b);
21+
22+
[expect]
23+
foo(
24+
a,
25+
// c1
26+
/* c2 */
27+
// c3
28+
b,
29+
);
30+
31+
== should preserve comment before comma in array literal ==
32+
[
33+
1
34+
// c
35+
, 2
36+
];
37+
38+
[expect]
39+
[
40+
1,
41+
// c
42+
2,
43+
];
44+
45+
== should preserve comment before comma in object literal ==
46+
({
47+
a: 1
48+
// c
49+
, b: 2
50+
});
51+
52+
[expect]
53+
({
54+
a: 1,
55+
// c
56+
b: 2,
57+
});
58+
59+
== should preserve comment before comma in function parameters ==
60+
function f(
61+
a
62+
// c
63+
, b
64+
) {}
65+
66+
[expect]
67+
function f(
68+
a,
69+
// c
70+
b,
71+
) {}

0 commit comments

Comments
 (0)