From 90aa6c831890bf3978d9e6298a003e0652b27dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 25 Nov 2022 00:21:51 +0900 Subject: [PATCH] fix(es/react): Allow spread children (#6505) **Related issue:** - Closes https://github.com/swc-project/swc/issues/2037. --- .../issues-2xxx/2037/case1/output/index.js | 5 +- ...xFactoryDeclarationsLocalTypes.1.normal.js | 120 +++++++++++++++--- ...actoryDeclarationsLocalTypes.2.minified.js | 65 +++++++--- .../tsxSpreadChildren.1.normal.js | 25 +++- .../tsxSpreadChildren.2.minified.js | 10 +- ...drenInvalidType(target=es2015).1.normal.js | 31 +++-- ...enInvalidType(target=es2015).2.minified.js | 15 +-- ...hildrenInvalidType(target=es5).1.normal.js | 40 ++++-- ...ldrenInvalidType(target=es5).2.minified.js | 16 +-- .../swc_ecma_transforms_react/src/jsx/mod.rs | 12 +- .../tests/jsx/fixture/{vercel => }/3/input.js | 0 .../jsx/fixture/{vercel => }/3/output.mjs | 0 .../tests/jsx/fixture/issue-2037/input.js | 3 + .../tests/jsx/fixture/issue-2037/output.mjs | 3 + .../output.mjs | 2 +- .../output.stderr | 6 - .../output.mjs | 2 +- .../output.stderr | 6 - 18 files changed, 242 insertions(+), 119 deletions(-) rename crates/swc_ecma_transforms_react/tests/jsx/fixture/{vercel => }/3/input.js (100%) rename crates/swc_ecma_transforms_react/tests/jsx/fixture/{vercel => }/3/output.mjs (100%) create mode 100644 crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-2037/input.js create mode 100644 crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-2037/output.mjs delete mode 100644 crates/swc_ecma_transforms_react/tests/jsx/fixture/react-automatic/should-disallow-spread-children/output.stderr delete mode 100644 crates/swc_ecma_transforms_react/tests/jsx/fixture/react/should-disallow-spread-children/output.stderr diff --git a/crates/swc/tests/fixture/issues-2xxx/2037/case1/output/index.js b/crates/swc/tests/fixture/issues-2xxx/2037/case1/output/index.js index a3dd991f4775..2981991490b9 100644 --- a/crates/swc/tests/fixture/issues-2xxx/2037/case1/output/index.js +++ b/crates/swc/tests/fixture/issues-2xxx/2037/case1/output/index.js @@ -1,3 +1,6 @@ var A = function() { - return /*#__PURE__*/ React.createElement("div", null); + return /*#__PURE__*/ React.createElement.apply(React, [ + "div", + null + ]); }; diff --git a/crates/swc/tests/tsc-references/inlineJsxFactoryDeclarationsLocalTypes.1.normal.js b/crates/swc/tests/tsc-references/inlineJsxFactoryDeclarationsLocalTypes.1.normal.js index 630c51b0e2cf..71ab051f7c91 100644 --- a/crates/swc/tests/tsc-references/inlineJsxFactoryDeclarationsLocalTypes.1.normal.js +++ b/crates/swc/tests/tsc-references/inlineJsxFactoryDeclarationsLocalTypes.1.normal.js @@ -3,22 +3,106 @@ export { }; //// [renderer2.d.ts] export { }; //// [component.tsx] -//! -//! x Spread children are not supported in React. -//! ,---- -//! 4 | export const MySFC = (props: {x: number, y: number, children?: predom.JSX.Element[]}) =>

{props.x} + {props.y} = {props.x + props.y}{...this.props.children}

; -//! : ^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- -//! -//! x Spread children are not supported in React. -//! ,---- -//! 12 | {...this.props.children} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- +/** @jsx predom */ import _class_call_check from "@swc/helpers/src/_class_call_check.mjs"; +import _to_consumable_array from "@swc/helpers/src/_to_consumable_array.mjs"; +var _this = this; +import { predom } from "./renderer2"; +export var MySFC = function(props) { + return /*#__PURE__*/ predom.apply(void 0, [ + "p", + null, + props.x, + " + ", + props.y, + " = ", + props.x + props.y + ].concat(_to_consumable_array(_this.props.children))); +}; +export var MyClass = /*#__PURE__*/ function() { + "use strict"; + function MyClass(props) { + _class_call_check(this, MyClass); + this.props = props; + } + var _proto = MyClass.prototype; + _proto.render = function render() { + return /*#__PURE__*/ predom.apply(void 0, [ + "p", + null, + this.props.x, + " + ", + this.props.y, + " = ", + this.props.x + this.props.y + ].concat(_to_consumable_array(this.props.children))); + }; + return MyClass; +}(); +export var tree = /*#__PURE__*/ predom(MySFC, { + x: 1, + y: 2 +}, /*#__PURE__*/ predom(MyClass, { + x: 3, + y: 4 +}), /*#__PURE__*/ predom(MyClass, { + x: 5, + y: 6 +})); +export default /*#__PURE__*/ predom("h", null); //// [index.tsx] -//! -//! x Spread children are not supported in React. -//! ,---- -//! 13 | return

{this.props.x} + {this.props.y} = {this.props.x + this.props.y}{...this.props.children}

; -//! : ^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- +/** @jsx dom */ import _class_call_check from "@swc/helpers/src/_class_call_check.mjs"; +import _to_consumable_array from "@swc/helpers/src/_to_consumable_array.mjs"; +import { dom } from "./renderer"; +import prerendered, { MySFC, MyClass, tree } from "./component"; +var elem = prerendered; +elem = /*#__PURE__*/ dom("h", null); // Expect assignability error here +var DOMSFC = function(props) { + return /*#__PURE__*/ dom("p", null, props.x, " + ", props.y, " = ", props.x + props.y, props.children); +}; +var DOMClass = /*#__PURE__*/ function() { + "use strict"; + function DOMClass(props) { + _class_call_check(this, DOMClass); + this.props = props; + } + var _proto = DOMClass.prototype; + _proto.render = function render() { + return /*#__PURE__*/ dom.apply(void 0, [ + "p", + null, + this.props.x, + " + ", + this.props.y, + " = ", + this.props.x + this.props.y + ].concat(_to_consumable_array(this.props.children))); + }; + return DOMClass; +}(); +// Should work, everything is a DOM element +var _tree = /*#__PURE__*/ dom(DOMSFC, { + x: 1, + y: 2 +}, /*#__PURE__*/ dom(DOMClass, { + x: 3, + y: 4 +}), /*#__PURE__*/ dom(DOMClass, { + x: 5, + y: 6 +})); +// Should fail, no dom elements +var _brokenTree = /*#__PURE__*/ dom(MySFC, { + x: 1, + y: 2 +}, /*#__PURE__*/ dom(MyClass, { + x: 3, + y: 4 +}), /*#__PURE__*/ dom(MyClass, { + x: 5, + y: 6 +})); +// Should fail, nondom isn't allowed as children of dom +var _brokenTree2 = /*#__PURE__*/ dom(DOMSFC, { + x: 1, + y: 2 +}, tree, tree); diff --git a/crates/swc/tests/tsc-references/inlineJsxFactoryDeclarationsLocalTypes.2.minified.js b/crates/swc/tests/tsc-references/inlineJsxFactoryDeclarationsLocalTypes.2.minified.js index 630c51b0e2cf..cb4c7cd86638 100644 --- a/crates/swc/tests/tsc-references/inlineJsxFactoryDeclarationsLocalTypes.2.minified.js +++ b/crates/swc/tests/tsc-references/inlineJsxFactoryDeclarationsLocalTypes.2.minified.js @@ -3,22 +3,51 @@ export { }; //// [renderer2.d.ts] export { }; //// [component.tsx] -//! -//! x Spread children are not supported in React. -//! ,---- -//! 4 | export const MySFC = (props: {x: number, y: number, children?: predom.JSX.Element[]}) =>

{props.x} + {props.y} = {props.x + props.y}{...this.props.children}

; -//! : ^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- -//! -//! x Spread children are not supported in React. -//! ,---- -//! 12 | {...this.props.children} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- +import _class_call_check from "@swc/helpers/src/_class_call_check.mjs"; +import _to_consumable_array from "@swc/helpers/src/_to_consumable_array.mjs"; +var _this = this; +import { predom } from "./renderer2"; +export var MySFC = function(props) { + return predom.apply(void 0, [ + "p", + null, + props.x, + " + ", + props.y, + " = ", + props.x + props.y + ].concat(_to_consumable_array(_this.props.children))); +}; +export var MyClass = function() { + "use strict"; + function MyClass(props) { + _class_call_check(this, MyClass), this.props = props; + } + return MyClass.prototype.render = function() { + return predom.apply(void 0, [ + "p", + null, + this.props.x, + " + ", + this.props.y, + " = ", + this.props.x + this.props.y + ].concat(_to_consumable_array(this.props.children))); + }, MyClass; +}(); +export var tree = predom(MySFC, { + x: 1, + y: 2 +}, predom(MyClass, { + x: 3, + y: 4 +}), predom(MyClass, { + x: 5, + y: 6 +})); +export default predom("h", null); //// [index.tsx] -//! -//! x Spread children are not supported in React. -//! ,---- -//! 13 | return

{this.props.x} + {this.props.y} = {this.props.x + this.props.y}{...this.props.children}

; -//! : ^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- +import _class_call_check from "@swc/helpers/src/_class_call_check.mjs"; +import _to_consumable_array from "@swc/helpers/src/_to_consumable_array.mjs"; +import { dom } from "./renderer"; +import prerendered, { MySFC, MyClass, tree } from "./component"; diff --git a/crates/swc/tests/tsc-references/tsxSpreadChildren.1.normal.js b/crates/swc/tests/tsc-references/tsxSpreadChildren.1.normal.js index b019991955f9..b44b53a6deb2 100644 --- a/crates/swc/tests/tsc-references/tsxSpreadChildren.1.normal.js +++ b/crates/swc/tests/tsc-references/tsxSpreadChildren.1.normal.js @@ -1,7 +1,20 @@ //// [tsxSpreadChildren.tsx] -//! -//! x Spread children are not supported in React. -//! ,---- -//! 22 | {...todos.map(todo => )} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- +import _extends from "@swc/helpers/src/_extends.mjs"; +import _to_consumable_array from "@swc/helpers/src/_to_consumable_array.mjs"; +function Todo(prop) { + return /*#__PURE__*/ React.createElement("div", null, prop.key.toString() + prop.todo); +} +function TodoList(param) { + var todos = param.todos; + return /*#__PURE__*/ React.createElement.apply(React, [ + "div", + null + ].concat(_to_consumable_array(todos.map(function(todo) { + return /*#__PURE__*/ React.createElement(Todo, { + key: todo.id, + todo: todo.todo + }); + })))); +} +var x; +/*#__PURE__*/ React.createElement(TodoList, _extends({}, x)); diff --git a/crates/swc/tests/tsc-references/tsxSpreadChildren.2.minified.js b/crates/swc/tests/tsc-references/tsxSpreadChildren.2.minified.js index b019991955f9..9febbcac7e62 100644 --- a/crates/swc/tests/tsc-references/tsxSpreadChildren.2.minified.js +++ b/crates/swc/tests/tsc-references/tsxSpreadChildren.2.minified.js @@ -1,7 +1,5 @@ //// [tsxSpreadChildren.tsx] -//! -//! x Spread children are not supported in React. -//! ,---- -//! 22 | {...todos.map(todo => )} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- +var x; +import _extends from "@swc/helpers/src/_extends.mjs"; +import _to_consumable_array from "@swc/helpers/src/_to_consumable_array.mjs"; +_extends({}, x); diff --git a/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es2015).1.normal.js b/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es2015).1.normal.js index 49c09e75c679..ec30394b2e5d 100644 --- a/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es2015).1.normal.js +++ b/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es2015).1.normal.js @@ -1,13 +1,20 @@ //// [tsxSpreadChildrenInvalidType.tsx] -//! -//! x Spread children are not supported in React. -//! ,---- -//! 21 | {...} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- -//! -//! x Spread children are not supported in React. -//! ,---- -//! 27 | {...( as any)} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- +import _extends from "@swc/helpers/src/_extends.mjs"; +function Todo(prop) { + return /*#__PURE__*/ React.createElement("div", null, prop.key.toString() + prop.todo); +} +function TodoList({ todos }) { + return /*#__PURE__*/ React.createElement("div", null, .../*#__PURE__*/ React.createElement(Todo, { + key: todos[0].id, + todo: todos[0].todo + })); +} +function TodoListNoError({ todos }) { + // any is not checked + return /*#__PURE__*/ React.createElement("div", null, .../*#__PURE__*/ React.createElement(Todo, { + key: todos[0].id, + todo: todos[0].todo + })); +} +let x; +/*#__PURE__*/ React.createElement(TodoList, _extends({}, x)); diff --git a/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es2015).2.minified.js b/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es2015).2.minified.js index 49c09e75c679..47b53e474548 100644 --- a/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es2015).2.minified.js +++ b/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es2015).2.minified.js @@ -1,13 +1,4 @@ //// [tsxSpreadChildrenInvalidType.tsx] -//! -//! x Spread children are not supported in React. -//! ,---- -//! 21 | {...} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- -//! -//! x Spread children are not supported in React. -//! ,---- -//! 27 | {...( as any)} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- +let x; +import _extends from "@swc/helpers/src/_extends.mjs"; +_extends({}, x); diff --git a/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es5).1.normal.js b/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es5).1.normal.js index 49c09e75c679..60479af70f58 100644 --- a/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es5).1.normal.js +++ b/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es5).1.normal.js @@ -1,13 +1,29 @@ //// [tsxSpreadChildrenInvalidType.tsx] -//! -//! x Spread children are not supported in React. -//! ,---- -//! 21 | {...} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- -//! -//! x Spread children are not supported in React. -//! ,---- -//! 27 | {...( as any)} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- +import _extends from "@swc/helpers/src/_extends.mjs"; +import _to_consumable_array from "@swc/helpers/src/_to_consumable_array.mjs"; +function Todo(prop) { + return /*#__PURE__*/ React.createElement("div", null, prop.key.toString() + prop.todo); +} +function TodoList(param) { + var todos = param.todos; + return /*#__PURE__*/ React.createElement.apply(React, [ + "div", + null + ].concat(_to_consumable_array(/*#__PURE__*/ React.createElement(Todo, { + key: todos[0].id, + todo: todos[0].todo + })))); +} +function TodoListNoError(param) { + var todos = param.todos; + // any is not checked + return /*#__PURE__*/ React.createElement.apply(React, [ + "div", + null + ].concat(_to_consumable_array(/*#__PURE__*/ React.createElement(Todo, { + key: todos[0].id, + todo: todos[0].todo + })))); +} +var x; +/*#__PURE__*/ React.createElement(TodoList, _extends({}, x)); diff --git a/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es5).2.minified.js b/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es5).2.minified.js index 49c09e75c679..fd4b63d90b27 100644 --- a/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es5).2.minified.js +++ b/crates/swc/tests/tsc-references/tsxSpreadChildrenInvalidType(target=es5).2.minified.js @@ -1,13 +1,5 @@ //// [tsxSpreadChildrenInvalidType.tsx] -//! -//! x Spread children are not supported in React. -//! ,---- -//! 21 | {...} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- -//! -//! x Spread children are not supported in React. -//! ,---- -//! 27 | {...( as any)} -//! : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//! `---- +var x; +import _extends from "@swc/helpers/src/_extends.mjs"; +import _to_consumable_array from "@swc/helpers/src/_to_consumable_array.mjs"; +_extends({}, x); diff --git a/crates/swc_ecma_transforms_react/src/jsx/mod.rs b/crates/swc_ecma_transforms_react/src/jsx/mod.rs index 5d2152b8190f..11b9135863f6 100644 --- a/crates/swc_ecma_transforms_react/src/jsx/mod.rs +++ b/crates/swc_ecma_transforms_react/src/jsx/mod.rs @@ -769,14 +769,10 @@ where }) => return None, JSXElementChild::JSXElement(el) => self.jsx_elem_to_expr(*el).as_arg(), JSXElementChild::JSXFragment(el) => self.jsx_frag_to_expr(el).as_arg(), - JSXElementChild::JSXSpreadChild(JSXSpreadChild { span, .. }) => { - HANDLER.with(|handler| { - handler - .struct_span_err(span, "Spread children are not supported in React.") - .emit(); - }); - return None; - } + JSXElementChild::JSXSpreadChild(JSXSpreadChild { span, expr, .. }) => ExprOrSpread { + spread: Some(span), + expr, + }, }) } diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/vercel/3/input.js b/crates/swc_ecma_transforms_react/tests/jsx/fixture/3/input.js similarity index 100% rename from crates/swc_ecma_transforms_react/tests/jsx/fixture/vercel/3/input.js rename to crates/swc_ecma_transforms_react/tests/jsx/fixture/3/input.js diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/vercel/3/output.mjs b/crates/swc_ecma_transforms_react/tests/jsx/fixture/3/output.mjs similarity index 100% rename from crates/swc_ecma_transforms_react/tests/jsx/fixture/vercel/3/output.mjs rename to crates/swc_ecma_transforms_react/tests/jsx/fixture/3/output.mjs diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-2037/input.js b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-2037/input.js new file mode 100644 index 000000000000..9a314e322623 --- /dev/null +++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-2037/input.js @@ -0,0 +1,3 @@ +const A = () => { + return
{...[]}
; +}; \ No newline at end of file diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-2037/output.mjs b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-2037/output.mjs new file mode 100644 index 000000000000..9ebb0e7369d8 --- /dev/null +++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-2037/output.mjs @@ -0,0 +1,3 @@ +const A = ()=>{ + return /*#__PURE__*/ React.createElement("div", null, ...[]); +}; diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/react-automatic/should-disallow-spread-children/output.mjs b/crates/swc_ecma_transforms_react/tests/jsx/fixture/react-automatic/should-disallow-spread-children/output.mjs index 33ec0c22bfb9..b8d3602b8bbd 100644 --- a/crates/swc_ecma_transforms_react/tests/jsx/fixture/react-automatic/should-disallow-spread-children/output.mjs +++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/react-automatic/should-disallow-spread-children/output.mjs @@ -1 +1 @@ -/*#__PURE__*/ React.createElement("div", null); +/*#__PURE__*/ React.createElement("div", null, ...children); diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/react-automatic/should-disallow-spread-children/output.stderr b/crates/swc_ecma_transforms_react/tests/jsx/fixture/react-automatic/should-disallow-spread-children/output.stderr deleted file mode 100644 index e5ad4ab15390..000000000000 --- a/crates/swc_ecma_transforms_react/tests/jsx/fixture/react-automatic/should-disallow-spread-children/output.stderr +++ /dev/null @@ -1,6 +0,0 @@ - - x Spread children are not supported in React. - ,-[input.js:1:1] - 1 |
{...children}
; - : ^^^^^^^^^^^^^ - `---- diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/react/should-disallow-spread-children/output.mjs b/crates/swc_ecma_transforms_react/tests/jsx/fixture/react/should-disallow-spread-children/output.mjs index 33ec0c22bfb9..b8d3602b8bbd 100644 --- a/crates/swc_ecma_transforms_react/tests/jsx/fixture/react/should-disallow-spread-children/output.mjs +++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/react/should-disallow-spread-children/output.mjs @@ -1 +1 @@ -/*#__PURE__*/ React.createElement("div", null); +/*#__PURE__*/ React.createElement("div", null, ...children); diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/react/should-disallow-spread-children/output.stderr b/crates/swc_ecma_transforms_react/tests/jsx/fixture/react/should-disallow-spread-children/output.stderr deleted file mode 100644 index e5ad4ab15390..000000000000 --- a/crates/swc_ecma_transforms_react/tests/jsx/fixture/react/should-disallow-spread-children/output.stderr +++ /dev/null @@ -1,6 +0,0 @@ - - x Spread children are not supported in React. - ,-[input.js:1:1] - 1 |
{...children}
; - : ^^^^^^^^^^^^^ - `----