From 7e2d75c25035362e7d9dd837fcf79bbad8c43a8c Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Wed, 11 May 2022 14:13:32 -0400 Subject: [PATCH] fix #2245: preserve `...` before JSX children --- CHANGELOG.md | 17 +++++++++++++++++ internal/js_parser/js_parser.go | 19 +++++++++++++------ internal/js_parser/js_parser_test.go | 2 +- internal/js_parser/ts_parser_test.go | 2 +- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ee184b038f..003f7ddb1b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,23 @@ })(); ``` +* Preserve `...` before JSX child expressions ([#2245](https://github.com/evanw/esbuild/issues/2245)) + + TypeScript 4.5 changed how JSX child expressions that start with `...` are emitted. Previously the `...` was omitted but starting with TypeScript 4.5, the `...` is now preserved instead. This release updates esbuild to match TypeScript's new output in this case: + + ```jsx + // Original code + console.log({...b}) + + // Old output + console.log(/* @__PURE__ */ React.createElement("a", null, b)); + + // New output + console.log(/* @__PURE__ */ React.createElement("a", null, ...b)); + ``` + + Note that this behavior is TypeScript-specific. Babel doesn't support the `...` token at all (it gives the error "Spread children are not supported in React"). + ## 0.14.38 * Further fixes to TypeScript 4.7 instantiation expression parsing ([#2201](https://github.com/evanw/esbuild/issues/2201)) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 722488f1b50..9ce33b0dbd1 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -4518,14 +4518,21 @@ func (p *parser) parseJSXElement(loc logger.Loc) js_ast.Expr { // Use Next() instead of NextJSXElementChild() here since the next token is an expression p.lexer.Next() - // The "..." here is ignored (it's used to signal an array type in TypeScript) - if p.lexer.Token == js_lexer.TDotDotDot && p.options.ts.Parse { - p.lexer.Next() - } - // The expression is optional, and may be absent if p.lexer.Token != js_lexer.TCloseBrace { - children = append(children, p.parseExpr(js_ast.LLowest)) + if p.lexer.Token == js_lexer.TDotDotDot { + // TypeScript preserves "..." before JSX child expressions here. + // Babel gives the error "Spread children are not supported in React" + // instead, so it should be safe to support this TypeScript-specific + // behavior. Note that TypeScript's behavior changed in TypeScript 4.5. + // Before that, the "..." was omitted instead of being preserved. + itemLoc := p.lexer.Loc() + p.markSyntaxFeature(compat.RestArgument, p.lexer.Range()) + p.lexer.Next() + children = append(children, js_ast.Expr{Loc: itemLoc, Data: &js_ast.ESpread{Value: p.parseExpr(js_ast.LLowest)}}) + } else { + children = append(children, p.parseExpr(js_ast.LLowest)) + } } // Use ExpectJSXElementChild() so we parse child strings diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index 1819c19eff6..ce56aef25bf 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -4444,6 +4444,7 @@ func TestJSX(t *testing.T) { expectPrintedJSX(t, "<>", "/* @__PURE__ */ React.createElement(\"a\", null, \"<>\");\n") expectPrintedJSX(t, "&wrong;", "/* @__PURE__ */ React.createElement(\"a\", null, \"&wrong;\");\n") expectPrintedJSX(t, "🙂", "/* @__PURE__ */ React.createElement(\"a\", null, \"🙂\");\n") + expectPrintedJSX(t, "{...children}", "/* @__PURE__ */ React.createElement(\"a\", null, ...children);\n") // Note: The TypeScript compiler and Babel disagree. This matches TypeScript. expectPrintedJSX(t, "", "/* @__PURE__ */ React.createElement(\"a\", {\n b: \" c\"\n});\n") @@ -4538,7 +4539,6 @@ func TestJSX(t *testing.T) { ": ERROR: Expected closing tag \"c.d\" to match opening tag \"a.b\"\n: NOTE: The opening tag \"a.b\" is here:\n") expectParseErrorJSX(t, "", ": ERROR: Expected \">\" but found \".\"\n") expectParseErrorJSX(t, "", ": ERROR: Unexpected \"-\"\n") - expectParseErrorJSX(t, "{...children}", ": ERROR: Unexpected \"...\"\n") expectPrintedJSX(t, "< /**/ a/>", "/* @__PURE__ */ React.createElement(\"a\", null);\n") expectPrintedJSX(t, "< //\n a/>", "/* @__PURE__ */ React.createElement(\"a\", null);\n") diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index c3212c3d321..9a7a965c044 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -2225,7 +2225,7 @@ func TestTSJSX(t *testing.T) { expectPrintedTSX(t, "a{}c", "/* @__PURE__ */ React.createElement(\"x\", null, \"a\", \"c\");\n") expectPrintedTSX(t, "a{b}c", "/* @__PURE__ */ React.createElement(\"x\", null, \"a\", b, \"c\");\n") - expectPrintedTSX(t, "a{...b}c", "/* @__PURE__ */ React.createElement(\"x\", null, \"a\", b, \"c\");\n") + expectPrintedTSX(t, "a{...b}c", "/* @__PURE__ */ React.createElement(\"x\", null, \"a\", ...b, \"c\");\n") expectPrintedTSX(t, "const x = >", "const x = /* @__PURE__ */ React.createElement(Foo, null);\n") expectPrintedTSX(t, "const x = data-foo>", "const x = /* @__PURE__ */ React.createElement(Foo, {\n \"data-foo\": true\n});\n")