Skip to content

Commit 3bc05fa

Browse files
committed
feat(transformer): implement jsx spread child (#8763)
closes #8690
1 parent 2b83b71 commit 3bc05fa

File tree

10 files changed

+76
-18
lines changed

10 files changed

+76
-18
lines changed

crates/oxc_transformer/src/jsx/diagnostics.rs

-4
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,3 @@ pub fn valueless_key(span: Span) -> OxcDiagnostic {
3030
OxcDiagnostic::warn("Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.")
3131
.with_label(span)
3232
}
33-
34-
pub fn spread_children_are_not_supported(span: Span) -> OxcDiagnostic {
35-
OxcDiagnostic::warn("Spread children are not supported in React.").with_label(span)
36-
}

crates/oxc_transformer/src/jsx/jsx_impl.rs

+37-9
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ impl<'a> JsxImpl<'a, '_> {
625625
// Append children to object properties in automatic mode
626626
if is_automatic {
627627
let mut children = ctx.ast.vec_from_iter(
628-
children.iter().filter_map(|child| self.transform_jsx_child(child, ctx)),
628+
children.iter().filter_map(|child| self.transform_jsx_child_automatic(child, ctx)),
629629
);
630630
children_len = children.len();
631631
if children_len != 0 {
@@ -750,10 +750,7 @@ impl<'a> JsxImpl<'a, '_> {
750750
// React.createElement(type, arguments, ...children)
751751
// ^^^^^^^^^^^
752752
arguments.extend(
753-
children
754-
.iter()
755-
.filter_map(|child| self.transform_jsx_child(child, ctx))
756-
.map(Argument::from),
753+
children.iter().filter_map(|child| self.transform_jsx_child_classic(child, ctx)),
757754
);
758755
}
759756

@@ -883,6 +880,40 @@ impl<'a> JsxImpl<'a, '_> {
883880
}
884881
}
885882

883+
fn transform_jsx_child_automatic(
884+
&mut self,
885+
child: &JSXChild<'a>,
886+
ctx: &mut TraverseCtx<'a>,
887+
) -> Option<Expression<'a>> {
888+
// Align spread child behavior with esbuild.
889+
// Instead of Babel throwing `Spread children are not supported in React.`
890+
// `<>{...foo}</>` -> `jsxs(Fragment, { children: [ ...foo ] })`
891+
if let JSXChild::Spread(e) = child {
892+
// SAFETY: `ast.copy` is unsound! We need to fix.
893+
let argument = unsafe { ctx.ast.copy(&e.expression) };
894+
let spread_element = ctx.ast.array_expression_element_spread_element(e.span, argument);
895+
let elements = ctx.ast.vec1(spread_element);
896+
return Some(ctx.ast.expression_array(e.span, elements, None));
897+
}
898+
self.transform_jsx_child(child, ctx)
899+
}
900+
901+
fn transform_jsx_child_classic(
902+
&mut self,
903+
child: &JSXChild<'a>,
904+
ctx: &mut TraverseCtx<'a>,
905+
) -> Option<Argument<'a>> {
906+
// Align spread child behavior with esbuild.
907+
// Instead of Babel throwing `Spread children are not supported in React.`
908+
// `<>{...foo}</>` -> `React.createElement(React.Fragment, null, ...foo)`
909+
if let JSXChild::Spread(e) = child {
910+
// SAFETY: `ast.copy` is unsound! We need to fix.
911+
let argument = unsafe { ctx.ast.copy(&e.expression) };
912+
return Some(ctx.ast.argument_spread_element(e.span, argument));
913+
}
914+
self.transform_jsx_child(child, ctx).map(Argument::from)
915+
}
916+
886917
fn transform_jsx_child(
887918
&mut self,
888919
child: &JSXChild<'a>,
@@ -903,10 +934,7 @@ impl<'a> JsxImpl<'a, '_> {
903934
JSXChild::Fragment(e) => {
904935
Some(self.transform_jsx(&JSXElementOrFragment::Fragment(e), ctx))
905936
}
906-
JSXChild::Spread(e) => {
907-
self.ctx.error(diagnostics::spread_children_are_not_supported(e.span));
908-
None
909-
}
937+
JSXChild::Spread(_) => unreachable!(),
910938
}
911939
}
912940

tasks/coverage/snapshots/semantic_typescript.snap

+12-3
Original file line numberDiff line numberDiff line change
@@ -39578,11 +39578,20 @@ after transform: ScopeId(0): [ScopeId(1), ScopeId(2)]
3957839578
rebuilt : ScopeId(0): [ScopeId(1)]
3957939579

3958039580
tasks/coverage/typescript/tests/cases/conformance/jsx/tsxSpreadChildren.tsx
39581-
semantic error: Spread children are not supported in React.
39581+
semantic error: Bindings mismatch:
39582+
after transform: ScopeId(0): ["JSX", "React", "Todo", "TodoList", "_jsxFileName", "_objectSpread", "_reactJsxRuntime", "x"]
39583+
rebuilt : ScopeId(0): ["Todo", "TodoList", "_jsxFileName", "_objectSpread", "_reactJsxRuntime", "x"]
39584+
Scope children mismatch:
39585+
after transform: ScopeId(0): [ScopeId(1), ScopeId(4), ScopeId(5), ScopeId(6), ScopeId(7)]
39586+
rebuilt : ScopeId(0): [ScopeId(1), ScopeId(2)]
3958239587

3958339588
tasks/coverage/typescript/tests/cases/conformance/jsx/tsxSpreadChildrenInvalidType.tsx
39584-
semantic error: Spread children are not supported in React.
39585-
Spread children are not supported in React.
39589+
semantic error: Bindings mismatch:
39590+
after transform: ScopeId(0): ["JSX", "React", "Todo", "TodoList", "TodoListNoError", "_jsxFileName", "_objectSpread", "_reactJsxRuntime", "x"]
39591+
rebuilt : ScopeId(0): ["Todo", "TodoList", "TodoListNoError", "_jsxFileName", "_objectSpread", "_reactJsxRuntime", "x"]
39592+
Scope children mismatch:
39593+
after transform: ScopeId(0): [ScopeId(1), ScopeId(4), ScopeId(5), ScopeId(6), ScopeId(7), ScopeId(8)]
39594+
rebuilt : ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3)]
3958639595

3958739596
tasks/coverage/typescript/tests/cases/conformance/jsx/tsxStatelessFunctionComponentOverload2.tsx
3958839597
semantic error: Scope children mismatch:

tasks/transform_conformance/snapshots/oxc.snap.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
commit: acbc09a8
22

3-
Passed: 133/155
3+
Passed: 135/157
44

55
# All Passed:
66
* babel-plugin-transform-class-static-block
@@ -308,7 +308,7 @@ rebuilt : SymbolId(2): []
308308
x Output mismatch
309309

310310

311-
# babel-plugin-transform-react-jsx (35/38)
311+
# babel-plugin-transform-react-jsx (37/40)
312312
* refresh/does-not-transform-it-because-it-is-not-used-in-the-AST/input.jsx
313313
x Output mismatch
314314

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<>{...foo}</>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"plugins": [
3+
[
4+
"transform-react-jsx",
5+
{
6+
"runtime": "automatic"
7+
}
8+
]
9+
]
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
var _reactJsxRuntime = require("react/jsx-runtime");
2+
_reactJsxRuntime.jsx(_reactJsxRuntime.Fragment, { children: [...foo] });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<>{...foo}</>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"plugins": [
3+
[
4+
"transform-react-jsx",
5+
{
6+
"runtime": "classic"
7+
}
8+
]
9+
]
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
React.createElement(React.Fragment, null, ...foo);

0 commit comments

Comments
 (0)