Skip to content

Commit 9b1ba8f

Browse files
Fix react-jsx spread children invalid emit (#46565)
* Fix react-jsx spread children invalid emit * Update Baselines and/or Applied Lint Fixes * Change childrenLength parameter -> isStaticChildren Co-authored-by: TypeScript Bot <[email protected]>
1 parent 3254358 commit 9b1ba8f

18 files changed

+653
-11
lines changed

src/compiler/transformers/jsx.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ namespace ts {
2626
return currentFileState.filenameDeclaration.name;
2727
}
2828

29-
function getJsxFactoryCalleePrimitive(childrenLength: number): "jsx" | "jsxs" | "jsxDEV" {
30-
return compilerOptions.jsx === JsxEmit.ReactJSXDev ? "jsxDEV" : childrenLength > 1 ? "jsxs" : "jsx";
29+
function getJsxFactoryCalleePrimitive(isStaticChildren: boolean): "jsx" | "jsxs" | "jsxDEV" {
30+
return compilerOptions.jsx === JsxEmit.ReactJSXDev ? "jsxDEV" : isStaticChildren ? "jsxs" : "jsx";
3131
}
3232

33-
function getJsxFactoryCallee(childrenLength: number) {
34-
const type = getJsxFactoryCalleePrimitive(childrenLength);
33+
function getJsxFactoryCallee(isStaticChildren: boolean) {
34+
const type = getJsxFactoryCalleePrimitive(isStaticChildren);
3535
return getImplicitImportForName(type);
3636
}
3737

@@ -206,7 +206,7 @@ namespace ts {
206206

207207
function convertJsxChildrenToChildrenPropAssignment(children: readonly JsxChild[]) {
208208
const nonWhitespaceChildren = getSemanticJsxChildren(children);
209-
if (length(nonWhitespaceChildren) === 1) {
209+
if (length(nonWhitespaceChildren) === 1 && !(nonWhitespaceChildren[0] as JsxExpression).dotDotDotToken) {
210210
const result = transformJsxChildToExpression(nonWhitespaceChildren[0]);
211211
return result && factory.createPropertyAssignment("children", result);
212212
}
@@ -221,16 +221,33 @@ namespace ts {
221221
const attrs = keyAttr ? filter(node.attributes.properties, p => p !== keyAttr) : node.attributes.properties;
222222
const objectProperties = length(attrs) ? transformJsxAttributesToObjectProps(attrs, childrenProp) :
223223
factory.createObjectLiteralExpression(childrenProp ? [childrenProp] : emptyArray); // When there are no attributes, React wants {}
224-
return visitJsxOpeningLikeElementOrFragmentJSX(tagName, objectProperties, keyAttr, length(getSemanticJsxChildren(children || emptyArray)), isChild, location);
224+
return visitJsxOpeningLikeElementOrFragmentJSX(
225+
tagName,
226+
objectProperties,
227+
keyAttr,
228+
children || emptyArray,
229+
isChild,
230+
location
231+
);
225232
}
226233

227-
function visitJsxOpeningLikeElementOrFragmentJSX(tagName: Expression, objectProperties: Expression, keyAttr: JsxAttribute | undefined, childrenLength: number, isChild: boolean, location: TextRange) {
234+
function visitJsxOpeningLikeElementOrFragmentJSX(
235+
tagName: Expression,
236+
objectProperties: Expression,
237+
keyAttr: JsxAttribute | undefined,
238+
children: readonly JsxChild[],
239+
isChild: boolean,
240+
location: TextRange
241+
) {
242+
const nonWhitespaceChildren = getSemanticJsxChildren(children);
243+
const isStaticChildren =
244+
length(nonWhitespaceChildren) > 1 || !!(nonWhitespaceChildren[0] as JsxExpression)?.dotDotDotToken;
228245
const args: Expression[] = [tagName, objectProperties, !keyAttr ? factory.createVoidZero() : transformJsxAttributeInitializer(keyAttr.initializer)];
229246
if (compilerOptions.jsx === JsxEmit.ReactJSXDev) {
230247
const originalFile = getOriginalNode(currentSourceFile);
231248
if (originalFile && isSourceFile(originalFile)) {
232249
// isStaticChildren development flag
233-
args.push(childrenLength > 1 ? factory.createTrue() : factory.createFalse());
250+
args.push(isStaticChildren ? factory.createTrue() : factory.createFalse());
234251
// __source development flag
235252
const lineCol = getLineAndCharacterOfPosition(originalFile, location.pos);
236253
args.push(factory.createObjectLiteralExpression([
@@ -242,7 +259,10 @@ namespace ts {
242259
args.push(factory.createThis());
243260
}
244261
}
245-
const element = setTextRange(factory.createCallExpression(getJsxFactoryCallee(childrenLength), /*typeArguments*/ undefined, args), location);
262+
const element = setTextRange(
263+
factory.createCallExpression(getJsxFactoryCallee(isStaticChildren), /*typeArguments*/ undefined, args),
264+
location
265+
);
246266

247267
if (isChild) {
248268
startOnNewLine(element);
@@ -294,7 +314,7 @@ namespace ts {
294314
getImplicitJsxFragmentReference(),
295315
childrenProps || factory.createObjectLiteralExpression([]),
296316
/*keyAttr*/ undefined,
297-
length(getSemanticJsxChildren(children)),
317+
children,
298318
isChild,
299319
location
300320
);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
tests/cases/conformance/jsx/tsxSpreadChildrenInvalidType.tsx(17,12): error TS2792: Cannot find module 'react/jsx-runtime'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?
2+
tests/cases/conformance/jsx/tsxSpreadChildrenInvalidType.tsx(21,9): error TS2609: JSX spread child must be an array type.
3+
4+
5+
==== tests/cases/conformance/jsx/tsxSpreadChildrenInvalidType.tsx (2 errors) ====
6+
declare module JSX {
7+
interface Element { }
8+
interface IntrinsicElements {
9+
[s: string]: any;
10+
}
11+
}
12+
declare var React: any;
13+
14+
interface TodoProp {
15+
id: number;
16+
todo: string;
17+
}
18+
interface TodoListProps {
19+
todos: TodoProp[];
20+
}
21+
function Todo(prop: { key: number, todo: string }) {
22+
return <div>{prop.key.toString() + prop.todo}</div>;
23+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24+
!!! error TS2792: Cannot find module 'react/jsx-runtime'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?
25+
}
26+
function TodoList({ todos }: TodoListProps) {
27+
return <div>
28+
{...<Todo key={todos[0].id} todo={todos[0].todo} />}
29+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30+
!!! error TS2609: JSX spread child must be an array type.
31+
</div>;
32+
}
33+
function TodoListNoError({ todos }: TodoListProps) {
34+
// any is not checked
35+
return <div>
36+
{...(<Todo key={todos[0].id} todo={todos[0].todo} /> as any)}
37+
</div>;
38+
}
39+
let x: TodoListProps;
40+
<TodoList {...x}/>
41+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//// [tsxSpreadChildrenInvalidType.tsx]
2+
declare module JSX {
3+
interface Element { }
4+
interface IntrinsicElements {
5+
[s: string]: any;
6+
}
7+
}
8+
declare var React: any;
9+
10+
interface TodoProp {
11+
id: number;
12+
todo: string;
13+
}
14+
interface TodoListProps {
15+
todos: TodoProp[];
16+
}
17+
function Todo(prop: { key: number, todo: string }) {
18+
return <div>{prop.key.toString() + prop.todo}</div>;
19+
}
20+
function TodoList({ todos }: TodoListProps) {
21+
return <div>
22+
{...<Todo key={todos[0].id} todo={todos[0].todo} />}
23+
</div>;
24+
}
25+
function TodoListNoError({ todos }: TodoListProps) {
26+
// any is not checked
27+
return <div>
28+
{...(<Todo key={todos[0].id} todo={todos[0].todo} /> as any)}
29+
</div>;
30+
}
31+
let x: TodoListProps;
32+
<TodoList {...x}/>
33+
34+
35+
//// [tsxSpreadChildrenInvalidType.js]
36+
function Todo(prop) {
37+
return _jsx("div", { children: prop.key.toString() + prop.todo }, void 0);
38+
}
39+
function TodoList({ todos }) {
40+
return _jsxs("div", { children: [..._jsx(Todo, { todo: todos[0].todo }, todos[0].id)] }, void 0);
41+
}
42+
function TodoListNoError({ todos }) {
43+
// any is not checked
44+
return _jsxs("div", { children: [..._jsx(Todo, { todo: todos[0].todo }, todos[0].id)] }, void 0);
45+
}
46+
let x;
47+
_jsx(TodoList, Object.assign({}, x), void 0);
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
=== tests/cases/conformance/jsx/tsxSpreadChildrenInvalidType.tsx ===
2+
declare module JSX {
3+
>JSX : Symbol(JSX, Decl(tsxSpreadChildrenInvalidType.tsx, 0, 0))
4+
5+
interface Element { }
6+
>Element : Symbol(Element, Decl(tsxSpreadChildrenInvalidType.tsx, 0, 20))
7+
8+
interface IntrinsicElements {
9+
>IntrinsicElements : Symbol(IntrinsicElements, Decl(tsxSpreadChildrenInvalidType.tsx, 1, 22))
10+
11+
[s: string]: any;
12+
>s : Symbol(s, Decl(tsxSpreadChildrenInvalidType.tsx, 3, 3))
13+
}
14+
}
15+
declare var React: any;
16+
>React : Symbol(React, Decl(tsxSpreadChildrenInvalidType.tsx, 6, 11))
17+
18+
interface TodoProp {
19+
>TodoProp : Symbol(TodoProp, Decl(tsxSpreadChildrenInvalidType.tsx, 6, 23))
20+
21+
id: number;
22+
>id : Symbol(TodoProp.id, Decl(tsxSpreadChildrenInvalidType.tsx, 8, 20))
23+
24+
todo: string;
25+
>todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildrenInvalidType.tsx, 9, 15))
26+
}
27+
interface TodoListProps {
28+
>TodoListProps : Symbol(TodoListProps, Decl(tsxSpreadChildrenInvalidType.tsx, 11, 1))
29+
30+
todos: TodoProp[];
31+
>todos : Symbol(TodoListProps.todos, Decl(tsxSpreadChildrenInvalidType.tsx, 12, 25))
32+
>TodoProp : Symbol(TodoProp, Decl(tsxSpreadChildrenInvalidType.tsx, 6, 23))
33+
}
34+
function Todo(prop: { key: number, todo: string }) {
35+
>Todo : Symbol(Todo, Decl(tsxSpreadChildrenInvalidType.tsx, 14, 1))
36+
>prop : Symbol(prop, Decl(tsxSpreadChildrenInvalidType.tsx, 15, 14))
37+
>key : Symbol(key, Decl(tsxSpreadChildrenInvalidType.tsx, 15, 21))
38+
>todo : Symbol(todo, Decl(tsxSpreadChildrenInvalidType.tsx, 15, 34))
39+
40+
return <div>{prop.key.toString() + prop.todo}</div>;
41+
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildrenInvalidType.tsx, 1, 22))
42+
>prop.key.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --))
43+
>prop.key : Symbol(key, Decl(tsxSpreadChildrenInvalidType.tsx, 15, 21))
44+
>prop : Symbol(prop, Decl(tsxSpreadChildrenInvalidType.tsx, 15, 14))
45+
>key : Symbol(key, Decl(tsxSpreadChildrenInvalidType.tsx, 15, 21))
46+
>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --))
47+
>prop.todo : Symbol(todo, Decl(tsxSpreadChildrenInvalidType.tsx, 15, 34))
48+
>prop : Symbol(prop, Decl(tsxSpreadChildrenInvalidType.tsx, 15, 14))
49+
>todo : Symbol(todo, Decl(tsxSpreadChildrenInvalidType.tsx, 15, 34))
50+
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildrenInvalidType.tsx, 1, 22))
51+
}
52+
function TodoList({ todos }: TodoListProps) {
53+
>TodoList : Symbol(TodoList, Decl(tsxSpreadChildrenInvalidType.tsx, 17, 1))
54+
>todos : Symbol(todos, Decl(tsxSpreadChildrenInvalidType.tsx, 18, 19))
55+
>TodoListProps : Symbol(TodoListProps, Decl(tsxSpreadChildrenInvalidType.tsx, 11, 1))
56+
57+
return <div>
58+
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildrenInvalidType.tsx, 1, 22))
59+
60+
{...<Todo key={todos[0].id} todo={todos[0].todo} />}
61+
>Todo : Symbol(Todo, Decl(tsxSpreadChildrenInvalidType.tsx, 14, 1))
62+
>key : Symbol(key, Decl(tsxSpreadChildrenInvalidType.tsx, 20, 17))
63+
>todos[0].id : Symbol(TodoProp.id, Decl(tsxSpreadChildrenInvalidType.tsx, 8, 20))
64+
>todos : Symbol(todos, Decl(tsxSpreadChildrenInvalidType.tsx, 18, 19))
65+
>id : Symbol(TodoProp.id, Decl(tsxSpreadChildrenInvalidType.tsx, 8, 20))
66+
>todo : Symbol(todo, Decl(tsxSpreadChildrenInvalidType.tsx, 20, 35))
67+
>todos[0].todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildrenInvalidType.tsx, 9, 15))
68+
>todos : Symbol(todos, Decl(tsxSpreadChildrenInvalidType.tsx, 18, 19))
69+
>todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildrenInvalidType.tsx, 9, 15))
70+
71+
</div>;
72+
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildrenInvalidType.tsx, 1, 22))
73+
}
74+
function TodoListNoError({ todos }: TodoListProps) {
75+
>TodoListNoError : Symbol(TodoListNoError, Decl(tsxSpreadChildrenInvalidType.tsx, 22, 1))
76+
>todos : Symbol(todos, Decl(tsxSpreadChildrenInvalidType.tsx, 23, 26))
77+
>TodoListProps : Symbol(TodoListProps, Decl(tsxSpreadChildrenInvalidType.tsx, 11, 1))
78+
79+
// any is not checked
80+
return <div>
81+
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildrenInvalidType.tsx, 1, 22))
82+
83+
{...(<Todo key={todos[0].id} todo={todos[0].todo} /> as any)}
84+
>Todo : Symbol(Todo, Decl(tsxSpreadChildrenInvalidType.tsx, 14, 1))
85+
>key : Symbol(key, Decl(tsxSpreadChildrenInvalidType.tsx, 26, 18))
86+
>todos[0].id : Symbol(TodoProp.id, Decl(tsxSpreadChildrenInvalidType.tsx, 8, 20))
87+
>todos : Symbol(todos, Decl(tsxSpreadChildrenInvalidType.tsx, 23, 26))
88+
>id : Symbol(TodoProp.id, Decl(tsxSpreadChildrenInvalidType.tsx, 8, 20))
89+
>todo : Symbol(todo, Decl(tsxSpreadChildrenInvalidType.tsx, 26, 36))
90+
>todos[0].todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildrenInvalidType.tsx, 9, 15))
91+
>todos : Symbol(todos, Decl(tsxSpreadChildrenInvalidType.tsx, 23, 26))
92+
>todo : Symbol(TodoProp.todo, Decl(tsxSpreadChildrenInvalidType.tsx, 9, 15))
93+
94+
</div>;
95+
>div : Symbol(JSX.IntrinsicElements, Decl(tsxSpreadChildrenInvalidType.tsx, 1, 22))
96+
}
97+
let x: TodoListProps;
98+
>x : Symbol(x, Decl(tsxSpreadChildrenInvalidType.tsx, 29, 3))
99+
>TodoListProps : Symbol(TodoListProps, Decl(tsxSpreadChildrenInvalidType.tsx, 11, 1))
100+
101+
<TodoList {...x}/>
102+
>TodoList : Symbol(TodoList, Decl(tsxSpreadChildrenInvalidType.tsx, 17, 1))
103+
>x : Symbol(x, Decl(tsxSpreadChildrenInvalidType.tsx, 29, 3))
104+
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
=== tests/cases/conformance/jsx/tsxSpreadChildrenInvalidType.tsx ===
2+
declare module JSX {
3+
interface Element { }
4+
interface IntrinsicElements {
5+
[s: string]: any;
6+
>s : string
7+
}
8+
}
9+
declare var React: any;
10+
>React : any
11+
12+
interface TodoProp {
13+
id: number;
14+
>id : number
15+
16+
todo: string;
17+
>todo : string
18+
}
19+
interface TodoListProps {
20+
todos: TodoProp[];
21+
>todos : TodoProp[]
22+
}
23+
function Todo(prop: { key: number, todo: string }) {
24+
>Todo : (prop: { key: number; todo: string;}) => JSX.Element
25+
>prop : { key: number; todo: string; }
26+
>key : number
27+
>todo : string
28+
29+
return <div>{prop.key.toString() + prop.todo}</div>;
30+
><div>{prop.key.toString() + prop.todo}</div> : JSX.Element
31+
>div : any
32+
>prop.key.toString() + prop.todo : string
33+
>prop.key.toString() : string
34+
>prop.key.toString : (radix?: number) => string
35+
>prop.key : number
36+
>prop : { key: number; todo: string; }
37+
>key : number
38+
>toString : (radix?: number) => string
39+
>prop.todo : string
40+
>prop : { key: number; todo: string; }
41+
>todo : string
42+
>div : any
43+
}
44+
function TodoList({ todos }: TodoListProps) {
45+
>TodoList : ({ todos }: TodoListProps) => JSX.Element
46+
>todos : TodoProp[]
47+
48+
return <div>
49+
><div> {...<Todo key={todos[0].id} todo={todos[0].todo} />} </div> : JSX.Element
50+
>div : any
51+
52+
{...<Todo key={todos[0].id} todo={todos[0].todo} />}
53+
><Todo key={todos[0].id} todo={todos[0].todo} /> : JSX.Element
54+
>Todo : (prop: { key: number; todo: string; }) => JSX.Element
55+
>key : number
56+
>todos[0].id : number
57+
>todos[0] : TodoProp
58+
>todos : TodoProp[]
59+
>0 : 0
60+
>id : number
61+
>todo : string
62+
>todos[0].todo : string
63+
>todos[0] : TodoProp
64+
>todos : TodoProp[]
65+
>0 : 0
66+
>todo : string
67+
68+
</div>;
69+
>div : any
70+
}
71+
function TodoListNoError({ todos }: TodoListProps) {
72+
>TodoListNoError : ({ todos }: TodoListProps) => JSX.Element
73+
>todos : TodoProp[]
74+
75+
// any is not checked
76+
return <div>
77+
><div> {...(<Todo key={todos[0].id} todo={todos[0].todo} /> as any)} </div> : JSX.Element
78+
>div : any
79+
80+
{...(<Todo key={todos[0].id} todo={todos[0].todo} /> as any)}
81+
>(<Todo key={todos[0].id} todo={todos[0].todo} /> as any) : any
82+
><Todo key={todos[0].id} todo={todos[0].todo} /> as any : any
83+
><Todo key={todos[0].id} todo={todos[0].todo} /> : JSX.Element
84+
>Todo : (prop: { key: number; todo: string; }) => JSX.Element
85+
>key : number
86+
>todos[0].id : number
87+
>todos[0] : TodoProp
88+
>todos : TodoProp[]
89+
>0 : 0
90+
>id : number
91+
>todo : string
92+
>todos[0].todo : string
93+
>todos[0] : TodoProp
94+
>todos : TodoProp[]
95+
>0 : 0
96+
>todo : string
97+
98+
</div>;
99+
>div : any
100+
}
101+
let x: TodoListProps;
102+
>x : TodoListProps
103+
104+
<TodoList {...x}/>
105+
><TodoList {...x}/> : JSX.Element
106+
>TodoList : ({ todos }: TodoListProps) => JSX.Element
107+
>x : TodoListProps
108+

0 commit comments

Comments
 (0)