Skip to content

Commit 21c04bd

Browse files
authored
Merge pull request #11591 from Microsoft/spread-jsx-expression-children
Add spread syntax to JsxExpression
2 parents b227cf0 + 88ef816 commit 21c04bd

13 files changed

+394
-4
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12187,7 +12187,11 @@ namespace ts {
1218712187

1218812188
function checkJsxExpression(node: JsxExpression) {
1218912189
if (node.expression) {
12190-
return checkExpression(node.expression);
12190+
const type = checkExpression(node.expression);
12191+
if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) {
12192+
error(node, Diagnostics.JSX_spread_child_must_be_an_array_type, node.toString(), typeToString(type));
12193+
}
12194+
return type;
1219112195
}
1219212196
else {
1219312197
return unknownType;

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,10 @@
18111811
"category": "Error",
18121812
"code": 2608
18131813
},
1814+
"JSX spread child must be an array type.": {
1815+
"category": "Error",
1816+
"code": 2609
1817+
},
18141818
"Cannot emit namespaced JSX elements in React": {
18151819
"category": "Error",
18161820
"code": 2650

src/compiler/emitter.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1854,6 +1854,9 @@ namespace ts {
18541854
function emitJsxExpression(node: JsxExpression) {
18551855
if (node.expression) {
18561856
write("{");
1857+
if (node.dotDotDotToken) {
1858+
write("...");
1859+
}
18571860
emitExpression(node.expression);
18581861
write("}");
18591862
}

src/compiler/factory.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,15 +1317,16 @@ namespace ts {
13171317
return node;
13181318
}
13191319

1320-
export function createJsxExpression(expression: Expression, location?: TextRange) {
1320+
export function createJsxExpression(expression: Expression, dotDotDotToken: Token<SyntaxKind.DotDotDotToken>, location?: TextRange) {
13211321
const node = <JsxExpression>createNode(SyntaxKind.JsxExpression, location);
1322+
node.dotDotDotToken = dotDotDotToken;
13221323
node.expression = expression;
13231324
return node;
13241325
}
13251326

13261327
export function updateJsxExpression(node: JsxExpression, expression: Expression) {
13271328
if (node.expression !== expression) {
1328-
return updateNode(createJsxExpression(expression, node), node);
1329+
return updateNode(createJsxExpression(expression, node.dotDotDotToken, node), node);
13291330
}
13301331
return node;
13311332
}

src/compiler/parser.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,8 @@ namespace ts {
373373
case SyntaxKind.JsxSpreadAttribute:
374374
return visitNode(cbNode, (<JsxSpreadAttribute>node).expression);
375375
case SyntaxKind.JsxExpression:
376-
return visitNode(cbNode, (<JsxExpression>node).expression);
376+
return visitNode(cbNode, (node as JsxExpression).dotDotDotToken) ||
377+
visitNode(cbNode, (node as JsxExpression).expression);
377378
case SyntaxKind.JsxClosingElement:
378379
return visitNode(cbNode, (<JsxClosingElement>node).tagName);
379380

@@ -3914,6 +3915,7 @@ namespace ts {
39143915

39153916
parseExpected(SyntaxKind.OpenBraceToken);
39163917
if (token() !== SyntaxKind.CloseBraceToken) {
3918+
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
39173919
node.expression = parseAssignmentExpressionOrHigher();
39183920
}
39193921
if (inExpressionContext) {

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,7 @@ namespace ts {
14921492

14931493
export interface JsxExpression extends Expression {
14941494
kind: SyntaxKind.JsxExpression;
1495+
dotDotDotToken?: Token<SyntaxKind.DotDotDotToken>;
14951496
expression?: Expression;
14961497
}
14971498

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//// [tsxSpreadChildren.tsx]
2+
3+
declare module JSX {
4+
interface Element { }
5+
interface IntrinsicElements {
6+
[s: string]: any;
7+
}
8+
}
9+
declare var React: any;
10+
11+
interface TodoProp {
12+
id: number;
13+
todo: string;
14+
}
15+
interface TodoListProps {
16+
todos: TodoProp[];
17+
}
18+
function Todo(prop: { key: number, todo: string }) {
19+
return <div>{prop.key.toString() + prop.todo}</div>;
20+
}
21+
function TodoList({ todos }: TodoListProps) {
22+
return <div>
23+
{...todos.map(todo => <Todo key={todo.id} todo={todo.todo}/>)}
24+
</div>;
25+
}
26+
let x: TodoListProps;
27+
<TodoList {...x}/>
28+
29+
30+
//// [tsxSpreadChildren.jsx]
31+
function Todo(prop) {
32+
return <div>{prop.key.toString() + prop.todo}</div>;
33+
}
34+
function TodoList(_a) {
35+
var todos = _a.todos;
36+
return <div>
37+
{...todos.map(function (todo) { return <Todo key={todo.id} todo={todo.todo}/>; })}
38+
</div>;
39+
}
40+
var x;
41+
<TodoList {...x}/>;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
=== tests/cases/conformance/jsx/tsxSpreadChildren.tsx ===
2+
3+
declare module JSX {
4+
>JSX : Symbol(JSX, Decl(tsxSpreadChildren.tsx, 0, 0))
5+
6+
interface Element { }
7+
>Element : Symbol(Element, Decl(tsxSpreadChildren.tsx, 1, 20))
8+
9+
interface IntrinsicElements {
10+
>IntrinsicElements : Symbol(IntrinsicElements, Decl(tsxSpreadChildren.tsx, 2, 22))
11+
12+
[s: string]: any;
13+
>s : Symbol(s, Decl(tsxSpreadChildren.tsx, 4, 3))
14+
}
15+
}
16+
declare var React: any;
17+
>React : Symbol(React, Decl(tsxSpreadChildren.tsx, 7, 11))
18+
19+
interface TodoProp {
20+
>TodoProp : Symbol(TodoProp, Decl(tsxSpreadChildren.tsx, 7, 23))
21+
22+
id: number;
23+
>id : Symbol(TodoProp.id, Decl(tsxSpreadChildren.tsx, 9, 20))
24+
25+
todo: string;
26+
>todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildren.tsx, 10, 15))
27+
}
28+
interface TodoListProps {
29+
>TodoListProps : Symbol(TodoListProps, Decl(tsxSpreadChildren.tsx, 12, 1))
30+
31+
todos: TodoProp[];
32+
>todos : Symbol(TodoListProps.todos, Decl(tsxSpreadChildren.tsx, 13, 25))
33+
>TodoProp : Symbol(TodoProp, Decl(tsxSpreadChildren.tsx, 7, 23))
34+
}
35+
function Todo(prop: { key: number, todo: string }) {
36+
>Todo : Symbol(Todo, Decl(tsxSpreadChildren.tsx, 15, 1))
37+
>prop : Symbol(prop, Decl(tsxSpreadChildren.tsx, 16, 14))
38+
>key : Symbol(key, Decl(tsxSpreadChildren.tsx, 16, 21))
39+
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 16, 34))
40+
41+
return <div>{prop.key.toString() + prop.todo}</div>;
42+
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildren.tsx, 2, 22))
43+
>prop.key.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
44+
>prop.key : Symbol(key, Decl(tsxSpreadChildren.tsx, 16, 21))
45+
>prop : Symbol(prop, Decl(tsxSpreadChildren.tsx, 16, 14))
46+
>key : Symbol(key, Decl(tsxSpreadChildren.tsx, 16, 21))
47+
>toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
48+
>prop.todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 16, 34))
49+
>prop : Symbol(prop, Decl(tsxSpreadChildren.tsx, 16, 14))
50+
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 16, 34))
51+
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildren.tsx, 2, 22))
52+
}
53+
function TodoList({ todos }: TodoListProps) {
54+
>TodoList : Symbol(TodoList, Decl(tsxSpreadChildren.tsx, 18, 1))
55+
>todos : Symbol(todos, Decl(tsxSpreadChildren.tsx, 19, 19))
56+
>TodoListProps : Symbol(TodoListProps, Decl(tsxSpreadChildren.tsx, 12, 1))
57+
58+
return <div>
59+
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildren.tsx, 2, 22))
60+
61+
{...todos.map(todo => <Todo key={todo.id} todo={todo.todo}/>)}
62+
>todos.map : Symbol(Array.map, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
63+
>todos : Symbol(todos, Decl(tsxSpreadChildren.tsx, 19, 19))
64+
>map : Symbol(Array.map, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
65+
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 21, 22))
66+
>Todo : Symbol(Todo, Decl(tsxSpreadChildren.tsx, 15, 1))
67+
>key : Symbol(key, Decl(tsxSpreadChildren.tsx, 16, 21))
68+
>todo.id : Symbol(TodoProp.id, Decl(tsxSpreadChildren.tsx, 9, 20))
69+
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 21, 22))
70+
>id : Symbol(TodoProp.id, Decl(tsxSpreadChildren.tsx, 9, 20))
71+
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 16, 34))
72+
>todo.todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildren.tsx, 10, 15))
73+
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 21, 22))
74+
>todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildren.tsx, 10, 15))
75+
76+
</div>;
77+
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildren.tsx, 2, 22))
78+
}
79+
let x: TodoListProps;
80+
>x : Symbol(x, Decl(tsxSpreadChildren.tsx, 24, 3))
81+
>TodoListProps : Symbol(TodoListProps, Decl(tsxSpreadChildren.tsx, 12, 1))
82+
83+
<TodoList {...x}/>
84+
>TodoList : Symbol(TodoList, Decl(tsxSpreadChildren.tsx, 18, 1))
85+
>x : Symbol(x, Decl(tsxSpreadChildren.tsx, 24, 3))
86+
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
=== tests/cases/conformance/jsx/tsxSpreadChildren.tsx ===
2+
3+
declare module JSX {
4+
>JSX : any
5+
6+
interface Element { }
7+
>Element : Element
8+
9+
interface IntrinsicElements {
10+
>IntrinsicElements : IntrinsicElements
11+
12+
[s: string]: any;
13+
>s : string
14+
}
15+
}
16+
declare var React: any;
17+
>React : any
18+
19+
interface TodoProp {
20+
>TodoProp : TodoProp
21+
22+
id: number;
23+
>id : number
24+
25+
todo: string;
26+
>todo : string
27+
}
28+
interface TodoListProps {
29+
>TodoListProps : TodoListProps
30+
31+
todos: TodoProp[];
32+
>todos : TodoProp[]
33+
>TodoProp : TodoProp
34+
}
35+
function Todo(prop: { key: number, todo: string }) {
36+
>Todo : (prop: { key: number; todo: string; }) => JSX.Element
37+
>prop : { key: number; todo: string; }
38+
>key : number
39+
>todo : string
40+
41+
return <div>{prop.key.toString() + prop.todo}</div>;
42+
><div>{prop.key.toString() + prop.todo}</div> : JSX.Element
43+
>div : any
44+
>prop.key.toString() + prop.todo : string
45+
>prop.key.toString() : string
46+
>prop.key.toString : (radix?: number) => string
47+
>prop.key : number
48+
>prop : { key: number; todo: string; }
49+
>key : number
50+
>toString : (radix?: number) => string
51+
>prop.todo : string
52+
>prop : { key: number; todo: string; }
53+
>todo : string
54+
>div : any
55+
}
56+
function TodoList({ todos }: TodoListProps) {
57+
>TodoList : ({todos}: TodoListProps) => JSX.Element
58+
>todos : TodoProp[]
59+
>TodoListProps : TodoListProps
60+
61+
return <div>
62+
><div> {...todos.map(todo => <Todo key={todo.id} todo={todo.todo}/>)} </div> : JSX.Element
63+
>div : any
64+
65+
{...todos.map(todo => <Todo key={todo.id} todo={todo.todo}/>)}
66+
>todos.map(todo => <Todo key={todo.id} todo={todo.todo}/>) : JSX.Element[]
67+
>todos.map : { <U>(this: [TodoProp, TodoProp, TodoProp, TodoProp, TodoProp], callbackfn: (value: TodoProp, index: number, array: TodoProp[]) => U, thisArg?: any): [U, U, U, U, U]; <U>(this: [TodoProp, TodoProp, TodoProp, TodoProp], callbackfn: (value: TodoProp, index: number, array: TodoProp[]) => U, thisArg?: any): [U, U, U, U]; <U>(this: [TodoProp, TodoProp, TodoProp], callbackfn: (value: TodoProp, index: number, array: TodoProp[]) => U, thisArg?: any): [U, U, U]; <U>(this: [TodoProp, TodoProp], callbackfn: (value: TodoProp, index: number, array: TodoProp[]) => U, thisArg?: any): [U, U]; <U>(callbackfn: (value: TodoProp, index: number, array: TodoProp[]) => U, thisArg?: any): U[]; }
68+
>todos : TodoProp[]
69+
>map : { <U>(this: [TodoProp, TodoProp, TodoProp, TodoProp, TodoProp], callbackfn: (value: TodoProp, index: number, array: TodoProp[]) => U, thisArg?: any): [U, U, U, U, U]; <U>(this: [TodoProp, TodoProp, TodoProp, TodoProp], callbackfn: (value: TodoProp, index: number, array: TodoProp[]) => U, thisArg?: any): [U, U, U, U]; <U>(this: [TodoProp, TodoProp, TodoProp], callbackfn: (value: TodoProp, index: number, array: TodoProp[]) => U, thisArg?: any): [U, U, U]; <U>(this: [TodoProp, TodoProp], callbackfn: (value: TodoProp, index: number, array: TodoProp[]) => U, thisArg?: any): [U, U]; <U>(callbackfn: (value: TodoProp, index: number, array: TodoProp[]) => U, thisArg?: any): U[]; }
70+
>todo => <Todo key={todo.id} todo={todo.todo}/> : (todo: TodoProp) => JSX.Element
71+
>todo : TodoProp
72+
><Todo key={todo.id} todo={todo.todo}/> : JSX.Element
73+
>Todo : (prop: { key: number; todo: string; }) => JSX.Element
74+
>key : any
75+
>todo.id : number
76+
>todo : TodoProp
77+
>id : number
78+
>todo : any
79+
>todo.todo : string
80+
>todo : TodoProp
81+
>todo : string
82+
83+
</div>;
84+
>div : any
85+
}
86+
let x: TodoListProps;
87+
>x : TodoListProps
88+
>TodoListProps : TodoListProps
89+
90+
<TodoList {...x}/>
91+
><TodoList {...x}/> : JSX.Element
92+
>TodoList : ({todos}: TodoListProps) => JSX.Element
93+
>x : TodoListProps
94+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
tests/cases/conformance/jsx/tsxSpreadChildrenInvalidType.tsx(21,9): error TS2609: JSX spread child must be an array type.
2+
3+
4+
==== tests/cases/conformance/jsx/tsxSpreadChildrenInvalidType.tsx (1 errors) ====
5+
declare module JSX {
6+
interface Element { }
7+
interface IntrinsicElements {
8+
[s: string]: any;
9+
}
10+
}
11+
declare var React: any;
12+
13+
interface TodoProp {
14+
id: number;
15+
todo: string;
16+
}
17+
interface TodoListProps {
18+
todos: TodoProp[];
19+
}
20+
function Todo(prop: { key: number, todo: string }) {
21+
return <div>{prop.key.toString() + prop.todo}</div>;
22+
}
23+
function TodoList({ todos }: TodoListProps) {
24+
return <div>
25+
{...<Todo key={todos[0].id} todo={todos[0].todo} />}
26+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27+
!!! error TS2609: JSX spread child must be an array type.
28+
</div>;
29+
}
30+
function TodoListNoError({ todos }: TodoListProps) {
31+
// any is not checked
32+
return <div>
33+
{...(<Todo key={todos[0].id} todo={todos[0].todo} /> as any)}
34+
</div>;
35+
}
36+
let x: TodoListProps;
37+
<TodoList {...x}/>
38+

0 commit comments

Comments
 (0)