Skip to content

Commit 7e2d75c

Browse files
committed
fix #2245: preserve ... before JSX children
1 parent 1bd0094 commit 7e2d75c

File tree

4 files changed

+32
-8
lines changed

4 files changed

+32
-8
lines changed

CHANGELOG.md

+17
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,23 @@
2020
})();
2121
```
2222

23+
* Preserve `...` before JSX child expressions ([#2245](https://github.com/evanw/esbuild/issues/2245))
24+
25+
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:
26+
27+
```jsx
28+
// Original code
29+
console.log(<a>{...b}</a>)
30+
31+
// Old output
32+
console.log(/* @__PURE__ */ React.createElement("a", null, b));
33+
34+
// New output
35+
console.log(/* @__PURE__ */ React.createElement("a", null, ...b));
36+
```
37+
38+
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").
39+
2340
## 0.14.38
2441

2542
* Further fixes to TypeScript 4.7 instantiation expression parsing ([#2201](https://github.com/evanw/esbuild/issues/2201))

internal/js_parser/js_parser.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -4518,14 +4518,21 @@ func (p *parser) parseJSXElement(loc logger.Loc) js_ast.Expr {
45184518
// Use Next() instead of NextJSXElementChild() here since the next token is an expression
45194519
p.lexer.Next()
45204520

4521-
// The "..." here is ignored (it's used to signal an array type in TypeScript)
4522-
if p.lexer.Token == js_lexer.TDotDotDot && p.options.ts.Parse {
4523-
p.lexer.Next()
4524-
}
4525-
45264521
// The expression is optional, and may be absent
45274522
if p.lexer.Token != js_lexer.TCloseBrace {
4528-
children = append(children, p.parseExpr(js_ast.LLowest))
4523+
if p.lexer.Token == js_lexer.TDotDotDot {
4524+
// TypeScript preserves "..." before JSX child expressions here.
4525+
// Babel gives the error "Spread children are not supported in React"
4526+
// instead, so it should be safe to support this TypeScript-specific
4527+
// behavior. Note that TypeScript's behavior changed in TypeScript 4.5.
4528+
// Before that, the "..." was omitted instead of being preserved.
4529+
itemLoc := p.lexer.Loc()
4530+
p.markSyntaxFeature(compat.RestArgument, p.lexer.Range())
4531+
p.lexer.Next()
4532+
children = append(children, js_ast.Expr{Loc: itemLoc, Data: &js_ast.ESpread{Value: p.parseExpr(js_ast.LLowest)}})
4533+
} else {
4534+
children = append(children, p.parseExpr(js_ast.LLowest))
4535+
}
45294536
}
45304537

45314538
// Use ExpectJSXElementChild() so we parse child strings

internal/js_parser/js_parser_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -4444,6 +4444,7 @@ func TestJSX(t *testing.T) {
44444444
expectPrintedJSX(t, "<a>&lt;&gt;</a>", "/* @__PURE__ */ React.createElement(\"a\", null, \"<>\");\n")
44454445
expectPrintedJSX(t, "<a>&wrong;</a>", "/* @__PURE__ */ React.createElement(\"a\", null, \"&wrong;\");\n")
44464446
expectPrintedJSX(t, "<a>🙂</a>", "/* @__PURE__ */ React.createElement(\"a\", null, \"🙂\");\n")
4447+
expectPrintedJSX(t, "<a>{...children}</a>", "/* @__PURE__ */ React.createElement(\"a\", null, ...children);\n")
44474448

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

45434543
expectPrintedJSX(t, "< /**/ a/>", "/* @__PURE__ */ React.createElement(\"a\", null);\n")
45444544
expectPrintedJSX(t, "< //\n a/>", "/* @__PURE__ */ React.createElement(\"a\", null);\n")

internal/js_parser/ts_parser_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2225,7 +2225,7 @@ func TestTSJSX(t *testing.T) {
22252225

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

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

0 commit comments

Comments
 (0)