Skip to content

Commit

Permalink
fix #2245: preserve ... before JSX children
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed May 11, 2022
1 parent 1bd0094 commit 7e2d75c
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 8 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(<a>{...b}</a>)

// 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))
Expand Down
19 changes: 13 additions & 6 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4444,6 +4444,7 @@ func TestJSX(t *testing.T) {
expectPrintedJSX(t, "<a>&lt;&gt;</a>", "/* @__PURE__ */ React.createElement(\"a\", null, \"<>\");\n")
expectPrintedJSX(t, "<a>&wrong;</a>", "/* @__PURE__ */ React.createElement(\"a\", null, \"&wrong;\");\n")
expectPrintedJSX(t, "<a>🙂</a>", "/* @__PURE__ */ React.createElement(\"a\", null, \"🙂\");\n")
expectPrintedJSX(t, "<a>{...children}</a>", "/* @__PURE__ */ React.createElement(\"a\", null, ...children);\n")

// Note: The TypeScript compiler and Babel disagree. This matches TypeScript.
expectPrintedJSX(t, "<a b=\" c\"/>", "/* @__PURE__ */ React.createElement(\"a\", {\n b: \" c\"\n});\n")
Expand Down Expand Up @@ -4538,7 +4539,6 @@ func TestJSX(t *testing.T) {
"<stdin>: ERROR: Expected closing tag \"c.d\" to match opening tag \"a.b\"\n<stdin>: NOTE: The opening tag \"a.b\" is here:\n")
expectParseErrorJSX(t, "<a-b.c>", "<stdin>: ERROR: Expected \">\" but found \".\"\n")
expectParseErrorJSX(t, "<a.b-c>", "<stdin>: ERROR: Unexpected \"-\"\n")
expectParseErrorJSX(t, "<a>{...children}</a>", "<stdin>: ERROR: Unexpected \"...\"\n")

expectPrintedJSX(t, "< /**/ a/>", "/* @__PURE__ */ React.createElement(\"a\", null);\n")
expectPrintedJSX(t, "< //\n a/>", "/* @__PURE__ */ React.createElement(\"a\", null);\n")
Expand Down
2 changes: 1 addition & 1 deletion internal/js_parser/ts_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2225,7 +2225,7 @@ func TestTSJSX(t *testing.T) {

expectPrintedTSX(t, "<x>a{}c</x>", "/* @__PURE__ */ React.createElement(\"x\", null, \"a\", \"c\");\n")
expectPrintedTSX(t, "<x>a{b}c</x>", "/* @__PURE__ */ React.createElement(\"x\", null, \"a\", b, \"c\");\n")
expectPrintedTSX(t, "<x>a{...b}c</x>", "/* @__PURE__ */ React.createElement(\"x\", null, \"a\", b, \"c\");\n")
expectPrintedTSX(t, "<x>a{...b}c</x>", "/* @__PURE__ */ React.createElement(\"x\", null, \"a\", ...b, \"c\");\n")

expectPrintedTSX(t, "const x = <Foo<T>></Foo>", "const x = /* @__PURE__ */ React.createElement(Foo, null);\n")
expectPrintedTSX(t, "const x = <Foo<T> data-foo></Foo>", "const x = /* @__PURE__ */ React.createElement(Foo, {\n \"data-foo\": true\n});\n")
Expand Down

0 comments on commit 7e2d75c

Please sign in to comment.