Skip to content

Add spread syntax to JsxExpression #11591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12187,7 +12187,11 @@ namespace ts {

function checkJsxExpression(node: JsxExpression) {
if (node.expression) {
return checkExpression(node.expression);
const type = checkExpression(node.expression);
if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) {
error(node, Diagnostics.JSX_spread_child_must_be_an_array_type, node.toString(), typeToString(type));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These arguments are unused :(

}
return type;
}
else {
return unknownType;
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1811,6 +1811,10 @@
"category": "Error",
"code": 2608
},
"JSX spread child must be an array type.": {
"category": "Error",
"code": 2609
},
"Cannot emit namespaced JSX elements in React": {
"category": "Error",
"code": 2650
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1854,6 +1854,9 @@ namespace ts {
function emitJsxExpression(node: JsxExpression) {
if (node.expression) {
write("{");
if (node.dotDotDotToken) {
write("...");
}
emitExpression(node.expression);
write("}");
}
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1317,15 +1317,16 @@ namespace ts {
return node;
}

export function createJsxExpression(expression: Expression, location?: TextRange) {
export function createJsxExpression(expression: Expression, dotDotDotToken: Token<SyntaxKind.DotDotDotToken>, location?: TextRange) {
const node = <JsxExpression>createNode(SyntaxKind.JsxExpression, location);
node.dotDotDotToken = dotDotDotToken;
node.expression = expression;
return node;
}

export function updateJsxExpression(node: JsxExpression, expression: Expression) {
if (node.expression !== expression) {
return updateNode(createJsxExpression(expression, node), node);
return updateNode(createJsxExpression(expression, node.dotDotDotToken, node), node);
}
return node;
}
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@ namespace ts {
case SyntaxKind.JsxSpreadAttribute:
return visitNode(cbNode, (<JsxSpreadAttribute>node).expression);
case SyntaxKind.JsxExpression:
return visitNode(cbNode, (<JsxExpression>node).expression);
return visitNode(cbNode, (node as JsxExpression).dotDotDotToken) ||
visitNode(cbNode, (node as JsxExpression).expression);
case SyntaxKind.JsxClosingElement:
return visitNode(cbNode, (<JsxClosingElement>node).tagName);

Expand Down Expand Up @@ -3914,6 +3915,7 @@ namespace ts {

parseExpected(SyntaxKind.OpenBraceToken);
if (token() !== SyntaxKind.CloseBraceToken) {
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
node.expression = parseAssignmentExpressionOrHigher();
}
if (inExpressionContext) {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1492,6 +1492,7 @@ namespace ts {

export interface JsxExpression extends Expression {
kind: SyntaxKind.JsxExpression;
dotDotDotToken?: Token<SyntaxKind.DotDotDotToken>;
expression?: Expression;
}

Expand Down
41 changes: 41 additions & 0 deletions tests/baselines/reference/tsxSpreadChildren.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//// [tsxSpreadChildren.tsx]

declare module JSX {
interface Element { }
interface IntrinsicElements {
[s: string]: any;
}
}
declare var React: any;

interface TodoProp {
id: number;
todo: string;
}
interface TodoListProps {
todos: TodoProp[];
}
function Todo(prop: { key: number, todo: string }) {
return <div>{prop.key.toString() + prop.todo}</div>;
}
function TodoList({ todos }: TodoListProps) {
return <div>
{...todos.map(todo => <Todo key={todo.id} todo={todo.todo}/>)}
</div>;
}
let x: TodoListProps;
<TodoList {...x}/>


//// [tsxSpreadChildren.jsx]
function Todo(prop) {
return <div>{prop.key.toString() + prop.todo}</div>;
}
function TodoList(_a) {
var todos = _a.todos;
return <div>
{...todos.map(function (todo) { return <Todo key={todo.id} todo={todo.todo}/>; })}
</div>;
}
var x;
<TodoList {...x}/>;
86 changes: 86 additions & 0 deletions tests/baselines/reference/tsxSpreadChildren.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
=== tests/cases/conformance/jsx/tsxSpreadChildren.tsx ===

declare module JSX {
>JSX : Symbol(JSX, Decl(tsxSpreadChildren.tsx, 0, 0))

interface Element { }
>Element : Symbol(Element, Decl(tsxSpreadChildren.tsx, 1, 20))

interface IntrinsicElements {
>IntrinsicElements : Symbol(IntrinsicElements, Decl(tsxSpreadChildren.tsx, 2, 22))

[s: string]: any;
>s : Symbol(s, Decl(tsxSpreadChildren.tsx, 4, 3))
}
}
declare var React: any;
>React : Symbol(React, Decl(tsxSpreadChildren.tsx, 7, 11))

interface TodoProp {
>TodoProp : Symbol(TodoProp, Decl(tsxSpreadChildren.tsx, 7, 23))

id: number;
>id : Symbol(TodoProp.id, Decl(tsxSpreadChildren.tsx, 9, 20))

todo: string;
>todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildren.tsx, 10, 15))
}
interface TodoListProps {
>TodoListProps : Symbol(TodoListProps, Decl(tsxSpreadChildren.tsx, 12, 1))

todos: TodoProp[];
>todos : Symbol(TodoListProps.todos, Decl(tsxSpreadChildren.tsx, 13, 25))
>TodoProp : Symbol(TodoProp, Decl(tsxSpreadChildren.tsx, 7, 23))
}
function Todo(prop: { key: number, todo: string }) {
>Todo : Symbol(Todo, Decl(tsxSpreadChildren.tsx, 15, 1))
>prop : Symbol(prop, Decl(tsxSpreadChildren.tsx, 16, 14))
>key : Symbol(key, Decl(tsxSpreadChildren.tsx, 16, 21))
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 16, 34))

return <div>{prop.key.toString() + prop.todo}</div>;
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildren.tsx, 2, 22))
>prop.key.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
>prop.key : Symbol(key, Decl(tsxSpreadChildren.tsx, 16, 21))
>prop : Symbol(prop, Decl(tsxSpreadChildren.tsx, 16, 14))
>key : Symbol(key, Decl(tsxSpreadChildren.tsx, 16, 21))
>toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
>prop.todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 16, 34))
>prop : Symbol(prop, Decl(tsxSpreadChildren.tsx, 16, 14))
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 16, 34))
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildren.tsx, 2, 22))
}
function TodoList({ todos }: TodoListProps) {
>TodoList : Symbol(TodoList, Decl(tsxSpreadChildren.tsx, 18, 1))
>todos : Symbol(todos, Decl(tsxSpreadChildren.tsx, 19, 19))
>TodoListProps : Symbol(TodoListProps, Decl(tsxSpreadChildren.tsx, 12, 1))

return <div>
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildren.tsx, 2, 22))

{...todos.map(todo => <Todo key={todo.id} todo={todo.todo}/>)}
>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, --, --))
>todos : Symbol(todos, Decl(tsxSpreadChildren.tsx, 19, 19))
>map : Symbol(Array.map, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 21, 22))
>Todo : Symbol(Todo, Decl(tsxSpreadChildren.tsx, 15, 1))
>key : Symbol(key, Decl(tsxSpreadChildren.tsx, 16, 21))
>todo.id : Symbol(TodoProp.id, Decl(tsxSpreadChildren.tsx, 9, 20))
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 21, 22))
>id : Symbol(TodoProp.id, Decl(tsxSpreadChildren.tsx, 9, 20))
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 16, 34))
>todo.todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildren.tsx, 10, 15))
>todo : Symbol(todo, Decl(tsxSpreadChildren.tsx, 21, 22))
>todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildren.tsx, 10, 15))

</div>;
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildren.tsx, 2, 22))
}
let x: TodoListProps;
>x : Symbol(x, Decl(tsxSpreadChildren.tsx, 24, 3))
>TodoListProps : Symbol(TodoListProps, Decl(tsxSpreadChildren.tsx, 12, 1))

<TodoList {...x}/>
>TodoList : Symbol(TodoList, Decl(tsxSpreadChildren.tsx, 18, 1))
>x : Symbol(x, Decl(tsxSpreadChildren.tsx, 24, 3))

94 changes: 94 additions & 0 deletions tests/baselines/reference/tsxSpreadChildren.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
=== tests/cases/conformance/jsx/tsxSpreadChildren.tsx ===

declare module JSX {
>JSX : any

interface Element { }
>Element : Element

interface IntrinsicElements {
>IntrinsicElements : IntrinsicElements

[s: string]: any;
>s : string
}
}
declare var React: any;
>React : any

interface TodoProp {
>TodoProp : TodoProp

id: number;
>id : number

todo: string;
>todo : string
}
interface TodoListProps {
>TodoListProps : TodoListProps

todos: TodoProp[];
>todos : TodoProp[]
>TodoProp : TodoProp
}
function Todo(prop: { key: number, todo: string }) {
>Todo : (prop: { key: number; todo: string; }) => JSX.Element
>prop : { key: number; todo: string; }
>key : number
>todo : string

return <div>{prop.key.toString() + prop.todo}</div>;
><div>{prop.key.toString() + prop.todo}</div> : JSX.Element
>div : any
>prop.key.toString() + prop.todo : string
>prop.key.toString() : string
>prop.key.toString : (radix?: number) => string
>prop.key : number
>prop : { key: number; todo: string; }
>key : number
>toString : (radix?: number) => string
>prop.todo : string
>prop : { key: number; todo: string; }
>todo : string
>div : any
}
function TodoList({ todos }: TodoListProps) {
>TodoList : ({todos}: TodoListProps) => JSX.Element
>todos : TodoProp[]
>TodoListProps : TodoListProps

return <div>
><div> {...todos.map(todo => <Todo key={todo.id} todo={todo.todo}/>)} </div> : JSX.Element
>div : any

{...todos.map(todo => <Todo key={todo.id} todo={todo.todo}/>)}
>todos.map(todo => <Todo key={todo.id} todo={todo.todo}/>) : JSX.Element[]
>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[]; }
>todos : TodoProp[]
>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[]; }
>todo => <Todo key={todo.id} todo={todo.todo}/> : (todo: TodoProp) => JSX.Element
>todo : TodoProp
><Todo key={todo.id} todo={todo.todo}/> : JSX.Element
>Todo : (prop: { key: number; todo: string; }) => JSX.Element
>key : any
>todo.id : number
>todo : TodoProp
>id : number
>todo : any
>todo.todo : string
>todo : TodoProp
>todo : string

</div>;
>div : any
}
let x: TodoListProps;
>x : TodoListProps
>TodoListProps : TodoListProps

<TodoList {...x}/>
><TodoList {...x}/> : JSX.Element
>TodoList : ({todos}: TodoListProps) => JSX.Element
>x : TodoListProps

38 changes: 38 additions & 0 deletions tests/baselines/reference/tsxSpreadChildrenInvalidType.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
tests/cases/conformance/jsx/tsxSpreadChildrenInvalidType.tsx(21,9): error TS2609: JSX spread child must be an array type.


==== tests/cases/conformance/jsx/tsxSpreadChildrenInvalidType.tsx (1 errors) ====
declare module JSX {
interface Element { }
interface IntrinsicElements {
[s: string]: any;
}
}
declare var React: any;

interface TodoProp {
id: number;
todo: string;
}
interface TodoListProps {
todos: TodoProp[];
}
function Todo(prop: { key: number, todo: string }) {
return <div>{prop.key.toString() + prop.todo}</div>;
}
function TodoList({ todos }: TodoListProps) {
return <div>
{...<Todo key={todos[0].id} todo={todos[0].todo} />}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2609: JSX spread child must be an array type.
</div>;
}
function TodoListNoError({ todos }: TodoListProps) {
// any is not checked
return <div>
{...(<Todo key={todos[0].id} todo={todos[0].todo} /> as any)}
</div>;
}
let x: TodoListProps;
<TodoList {...x}/>

Loading