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+

0 commit comments

Comments
 (0)