fix(codegen): print comments in JSX expression containers and spread attributes (#19701)

## Summary

Closes #17266

- Print leading comments attached to expressions inside `JSXExpressionContainer` (e.g. `{/*before*/ x}`, `{/*before*/ "y"}`, `{/*before*/ true}`)
- Print comments between `{` and `...` in `JSXSpreadAttribute` (e.g. `{/*before*/ ...x}`)
- Add `print_comments_in_range` helper for finding comments attached to positions not directly represented in the AST (needed for the `...` token in spread attributes)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Boshen committed 2026-02-25 09:04:15 +00:00
1 parent b40e5629c5
commit 3b96f41211
3 files changed
+41 -4

No files matched your search

+16
View File
@@ -64,6 +64,22 @@ impl Codegen<'_> {
}
}
/// Print comments attached to any position in the given range `(start, end)` (exclusive).
/// Returns `true` if any comments were printed.
pub(crate) fn print_comments_in_range(&mut self, start: u32, end: u32) -> bool {
if self.comments.is_empty() {
return false;
}
// Find and remove the first key in the range.
let key = self.comments.keys().find(|&&k| k > start && k < end).copied();
if let Some(key) = key {
let comments = self.comments.remove(&key).unwrap();
self.print_comments(&comments);
return true;
}
false
}
pub(crate) fn print_expr_comments(&mut self, start: u32) -> bool {
if self.comments.is_empty() {
return false;
+15 -4
View File
@@ -2482,9 +2482,16 @@ impl Gen for JSXEmptyExpression {
impl Gen for JSXExpression<'_> {
fn r#gen(&self, p: &mut Codegen, ctx: Context) {
match self {
Self::EmptyExpression(expr) => expr.print(p, ctx),
_ => p.print_expression(self.to_expression()),
if let Self::EmptyExpression(expr) = self {
expr.print(p, ctx);
} else {
let expr = self.to_expression();
let start = expr.span().start;
if p.has_comment(start) {
p.print_comments_at(start);
p.print_indent();
}
p.print_expression(expr);
}
}
}
@@ -2515,7 +2522,11 @@ impl Gen for JSXAttributeValue<'_> {
impl Gen for JSXSpreadAttribute<'_> {
fn r#gen(&self, p: &mut Codegen, _ctx: Context) {
p.print_str("{...");
p.print_ascii_byte(b'{');
if p.print_comments_in_range(self.span.start, self.argument.span().start) {
p.print_indent();
}
p.print_str("...");
self.argument.print_expr(p, Precedence::Comma, Context::empty());
p.print_ascii_byte(b'}');
}
@@ -20,6 +20,16 @@ fn test_comment_at_top_of_file() {
#[test]
fn unit() {
test_same("<div>{/* Hello */}</div>;\n");
// https://github.com/oxc-project/oxc/issues/17266
test("console.log(<div x={/*before*/ x} />)", "console.log(<div x={/*before*/ x} />);\n");
test(
"console.log(<div x={/*before*/ \"y\"} />)",
"console.log(<div x={/*before*/ \"y\"} />);\n",
);
test("console.log(<div x={/*before*/ true} />)", "console.log(<div x={/*before*/ true} />);\n");
test("console.log(<div {/*before*/ ...x} />)", "console.log(<div {/*before*/ ...x} />);\n");
test("console.log(<div>{/*before*/ x}</div>)", "console.log(<div>{/*before*/ x}</div>);\n");
test("console.log(<>{/*before*/ x}</>)", "console.log(<>{/*before*/ x}</>);\n");
// https://lingui.dev/ref/macro#definemessage
test("const message = /*i18n*/{};", "const message = (/*i18n*/ {});\n");
test(