Skip to content

Commit b47194b

Browse files
authored
Fix contextual types for a single jsx child (microsoft#31040)
1 parent 54fa950 commit b47194b

7 files changed

+212
-8
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11773,6 +11773,10 @@ namespace ts {
1177311773
}
1177411774
}
1177511775

11776+
function getSemanticJsxChildren(children: NodeArray<JsxChild>) {
11777+
return filter(children, i => !isJsxText(i) || !i.containsOnlyTriviaWhiteSpaces);
11778+
}
11779+
1177611780
function elaborateJsxComponents(node: JsxAttributes, source: Type, target: Type, relation: Map<RelationComparisonResult>) {
1177711781
let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation);
1177811782
let invalidTextDiagnostic: DiagnosticMessage | undefined;
@@ -11782,7 +11786,7 @@ namespace ts {
1178211786
const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName);
1178311787
const childrenNameType = getLiteralType(childrenPropName);
1178411788
const childrenTargetType = getIndexedAccessType(target, childrenNameType);
11785-
const validChildren = filter(containingElement.children, i => !isJsxText(i) || !i.containsOnlyTriviaWhiteSpaces);
11789+
const validChildren = getSemanticJsxChildren(containingElement.children);
1178611790
if (!length(validChildren)) {
1178711791
return result;
1178811792
}
@@ -18193,16 +18197,17 @@ namespace ts {
1819318197
if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) {
1819418198
return undefined;
1819518199
}
18196-
const childIndex = node.children.indexOf(child);
18200+
const realChildren = getSemanticJsxChildren(node.children);
18201+
const childIndex = realChildren.indexOf(child);
1819718202
const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName);
18198-
return childFieldType && mapType(childFieldType, t => {
18203+
return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => {
1819918204
if (isArrayLikeType(t)) {
1820018205
return getIndexedAccessType(t, getLiteralType(childIndex));
1820118206
}
1820218207
else {
1820318208
return t;
1820418209
}
18205-
}, /*noReductions*/ true);
18210+
}, /*noReductions*/ true));
1820618211
}
1820718212

1820818213
function getContextualTypeForJsxExpression(node: JsxExpression): Type | undefined {

tests/baselines/reference/jsxChildrenIndividualErrorElaborations.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ tests/cases/compiler/index.tsx(14,9): error TS2322: Type 'number' is not assigna
22
tests/cases/compiler/index.tsx(18,15): error TS2747: 'Blah' components don't accept text as child elements. Text in JSX has the type 'string', but the expected type of 'children' is '(x: number) => string'.
33
tests/cases/compiler/index.tsx(23,10): error TS2746: This JSX tag's 'children' prop expects a single child of type '(x: number) => string', but multiple children were provided.
44
tests/cases/compiler/index.tsx(37,10): error TS2745: This JSX tag's 'children' prop expects type '((x: number) => string)[]' which requires multiple children, but only a single child was provided.
5+
tests/cases/compiler/index.tsx(38,4): error TS7006: Parameter 'x' implicitly has an 'any' type.
56
tests/cases/compiler/index.tsx(42,10): error TS2745: This JSX tag's 'children' prop expects type '((x: number) => string)[]' which requires multiple children, but only a single child was provided.
67
tests/cases/compiler/index.tsx(48,9): error TS2322: Type 'number' is not assignable to type 'string'.
78
tests/cases/compiler/index.tsx(49,9): error TS2322: Type 'number' is not assignable to type 'string'.
@@ -13,7 +14,7 @@ tests/cases/compiler/index.tsx(73,9): error TS2322: Type 'number' is not assigna
1314
tests/cases/compiler/index.tsx(74,9): error TS2322: Type 'number' is not assignable to type 'string'.
1415

1516

16-
==== tests/cases/compiler/index.tsx (11 errors) ====
17+
==== tests/cases/compiler/index.tsx (12 errors) ====
1718
/// <reference path="/.lib/react16.d.ts" />
1819
import * as React from "react";
1920

@@ -64,6 +65,8 @@ tests/cases/compiler/index.tsx(74,9): error TS2322: Type 'number' is not assigna
6465
~~~~~
6566
!!! error TS2745: This JSX tag's 'children' prop expects type '((x: number) => string)[]' which requires multiple children, but only a single child was provided.
6667
{x => x}
68+
~
69+
!!! error TS7006: Parameter 'x' implicitly has an 'any' type.
6770
</Blah2>
6871

6972
// Blah2 components don't accept text as child elements

tests/baselines/reference/jsxChildrenIndividualErrorElaborations.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ var a = <Blah2>
8585
>Blah2 : (props: PropsArr) => JSX.Element
8686

8787
{x => x}
88-
>x => x : (x: number) => number
89-
>x : number
90-
>x : number
88+
>x => x : (x: any) => any
89+
>x : any
90+
>x : any
9191

9292
</Blah2>
9393
>Blah2 : (props: PropsArr) => JSX.Element
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//// [jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
4+
import * as React from 'react'
5+
6+
type Tab = [string, React.ReactNode] // [tabName, tabContent]
7+
8+
interface Props {
9+
children: Tab[]
10+
}
11+
12+
function TabLayout(props: Props) {
13+
return <div/>
14+
}
15+
16+
export class App extends React.Component<{}> {
17+
render() {
18+
return <TabLayout>
19+
{[
20+
['Users', <div/>],
21+
['Products', <div/>]
22+
]}
23+
</TabLayout>
24+
}
25+
}
26+
27+
//// [jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.js]
28+
"use strict";
29+
/// <reference path="react16.d.ts" />
30+
var __extends = (this && this.__extends) || (function () {
31+
var extendStatics = function (d, b) {
32+
extendStatics = Object.setPrototypeOf ||
33+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
34+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
35+
return extendStatics(d, b);
36+
};
37+
return function (d, b) {
38+
extendStatics(d, b);
39+
function __() { this.constructor = d; }
40+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
41+
};
42+
})();
43+
exports.__esModule = true;
44+
var React = require("react");
45+
function TabLayout(props) {
46+
return React.createElement("div", null);
47+
}
48+
var App = /** @class */ (function (_super) {
49+
__extends(App, _super);
50+
function App() {
51+
return _super !== null && _super.apply(this, arguments) || this;
52+
}
53+
App.prototype.render = function () {
54+
return React.createElement(TabLayout, null, [
55+
['Users', React.createElement("div", null)],
56+
['Products', React.createElement("div", null)]
57+
]);
58+
};
59+
return App;
60+
}(React.Component));
61+
exports.App = App;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
=== tests/cases/compiler/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
import * as React from 'react'
5+
>React : Symbol(React, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 2, 6))
6+
7+
type Tab = [string, React.ReactNode] // [tabName, tabContent]
8+
>Tab : Symbol(Tab, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 2, 30))
9+
>React : Symbol(React, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 2, 6))
10+
>ReactNode : Symbol(React.ReactNode, Decl(react16.d.ts, 216, 49))
11+
12+
interface Props {
13+
>Props : Symbol(Props, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 4, 36))
14+
15+
children: Tab[]
16+
>children : Symbol(Props.children, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 6, 17))
17+
>Tab : Symbol(Tab, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 2, 30))
18+
}
19+
20+
function TabLayout(props: Props) {
21+
>TabLayout : Symbol(TabLayout, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 8, 1))
22+
>props : Symbol(props, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 10, 19))
23+
>Props : Symbol(Props, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 4, 36))
24+
25+
return <div/>
26+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
27+
}
28+
29+
export class App extends React.Component<{}> {
30+
>App : Symbol(App, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 12, 1))
31+
>React.Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
32+
>React : Symbol(React, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 2, 6))
33+
>Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
34+
35+
render() {
36+
>render : Symbol(App.render, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 14, 46))
37+
38+
return <TabLayout>
39+
>TabLayout : Symbol(TabLayout, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 8, 1))
40+
41+
{[
42+
['Users', <div/>],
43+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
44+
45+
['Products', <div/>]
46+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
47+
48+
]}
49+
</TabLayout>
50+
>TabLayout : Symbol(TabLayout, Decl(jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx, 8, 1))
51+
}
52+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
=== tests/cases/compiler/jsxChildrenSingleChildConfusableWithMultipleChildrenNoError.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
import * as React from 'react'
5+
>React : typeof React
6+
7+
type Tab = [string, React.ReactNode] // [tabName, tabContent]
8+
>Tab : [string, React.ReactNode]
9+
>React : any
10+
11+
interface Props {
12+
children: Tab[]
13+
>children : [string, React.ReactNode][]
14+
}
15+
16+
function TabLayout(props: Props) {
17+
>TabLayout : (props: Props) => JSX.Element
18+
>props : Props
19+
20+
return <div/>
21+
><div/> : JSX.Element
22+
>div : any
23+
}
24+
25+
export class App extends React.Component<{}> {
26+
>App : App
27+
>React.Component : React.Component<{}, {}, any>
28+
>React : typeof React
29+
>Component : typeof React.Component
30+
31+
render() {
32+
>render : () => JSX.Element
33+
34+
return <TabLayout>
35+
><TabLayout> {[ ['Users', <div/>], ['Products', <div/>] ]} </TabLayout> : JSX.Element
36+
>TabLayout : (props: Props) => JSX.Element
37+
38+
{[
39+
>[ ['Users', <div/>], ['Products', <div/>] ] : [string, JSX.Element][]
40+
41+
['Users', <div/>],
42+
>['Users', <div/>] : [string, JSX.Element]
43+
>'Users' : "Users"
44+
><div/> : JSX.Element
45+
>div : any
46+
47+
['Products', <div/>]
48+
>['Products', <div/>] : [string, JSX.Element]
49+
>'Products' : "Products"
50+
><div/> : JSX.Element
51+
>div : any
52+
53+
]}
54+
</TabLayout>
55+
>TabLayout : (props: Props) => JSX.Element
56+
}
57+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// @skipLibCheck: true
2+
// @jsx: react
3+
/// <reference path="/.lib/react16.d.ts" />
4+
5+
import * as React from 'react'
6+
7+
type Tab = [string, React.ReactNode] // [tabName, tabContent]
8+
9+
interface Props {
10+
children: Tab[]
11+
}
12+
13+
function TabLayout(props: Props) {
14+
return <div/>
15+
}
16+
17+
export class App extends React.Component<{}> {
18+
render() {
19+
return <TabLayout>
20+
{[
21+
['Users', <div/>],
22+
['Products', <div/>]
23+
]}
24+
</TabLayout>
25+
}
26+
}

0 commit comments

Comments
 (0)