Skip to content

Commit 50870c1

Browse files
committed
Add child typechecking.
1 parent 007751b commit 50870c1

File tree

6 files changed

+312
-28
lines changed

6 files changed

+312
-28
lines changed

src/compiler/checker.ts

+49
Original file line numberDiff line numberDiff line change
@@ -11825,6 +11825,54 @@ namespace ts {
1182511825
return jsxElementType || anyType;
1182611826
}
1182711827

11828+
function findJsxElementParent(node: JsxOpeningLikeElement) {
11829+
let parent = node.parent;
11830+
11831+
while (parent) {
11832+
if (parent.kind == SyntaxKind.JsxElement && (<JsxElement>parent).openingElement !== node) {
11833+
return (<JsxElement>parent).openingElement;
11834+
}
11835+
11836+
parent = parent.parent;
11837+
}
11838+
11839+
return;
11840+
}
11841+
11842+
/**
11843+
* Validates a JsxElement against the type of the 'children' attribute of its parent
11844+
*/
11845+
function checkJsxElementAgainstParent(node: JsxOpeningLikeElement) {
11846+
const parent = findJsxElementParent(node);
11847+
if (!parent) {
11848+
return;
11849+
}
11850+
11851+
const parentAttributesType = getJsxElementAttributesType(parent);
11852+
if (!parentAttributesType || isTypeAny(parentAttributesType)) {
11853+
return;
11854+
}
11855+
11856+
const childrenPropSymbol = getPropertyOfType(parentAttributesType, "children");
11857+
if (!childrenPropSymbol) {
11858+
return;
11859+
}
11860+
11861+
let childrenPropType = getTypeOfSymbol(childrenPropSymbol);
11862+
if (isArrayLikeType(childrenPropType)) {
11863+
childrenPropType = getIndexTypeOfType(childrenPropType, IndexKind.Number);
11864+
}
11865+
11866+
const intrinsicTagType = isJsxIntrinsicIdentifier(node.tagName) ? getTypeOfSymbol(getIntrinsicTagSymbol(node)) : undefined;
11867+
11868+
const elementType = intrinsicTagType || getJsxElementInstanceType(node, getTypeOfNode(node.tagName));
11869+
if (!elementType) {
11870+
return;
11871+
}
11872+
11873+
checkTypeAssignableTo(elementType, childrenPropType, node);
11874+
}
11875+
1182811876
function checkJsxElement(node: JsxElement) {
1182911877
// Check attributes
1183011878
checkJsxOpeningLikeElement(node.openingElement);
@@ -12240,6 +12288,7 @@ namespace ts {
1224012288
function checkJsxOpeningLikeElement(node: JsxOpeningLikeElement) {
1224112289
checkGrammarJsxElement(node);
1224212290
checkJsxPreconditions(node);
12291+
checkJsxElementAgainstParent(node);
1224312292
// The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import.
1224412293
// And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error.
1224512294
const reactRefErr = compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
tests/cases/conformance/jsx/file.tsx(8,15): error TS2339: Property 'srce' does not exist on type 'HTMLProps<HTMLImageElement>'.
2+
tests/cases/conformance/jsx/file.tsx(12,15): error TS2322: Type '{ oops: number; }' is not assignable to type 'string'.
3+
tests/cases/conformance/jsx/file.tsx(15,10): error TS2339: Property 'imag' does not exist on type 'JSX.IntrinsicElements'.
4+
tests/cases/conformance/jsx/file.tsx(32,10): error TS2324: Property 'reqd' is missing in type 'IntrinsicAttributes & IntrinsicClassAttributes<MyClass> & { pt?: { x: number; y: number; }; name?: string; reqd: boolean; }'.
5+
tests/cases/conformance/jsx/file.tsx(32,19): error TS2322: Type '{ x: number; y: string; }' is not assignable to type '{ x: number; y: number; }'.
6+
Types of property 'y' are incompatible.
7+
Type 'string' is not assignable to type 'number'.
8+
tests/cases/conformance/jsx/file.tsx(44,25): error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
9+
Property 'setState' is missing in type 'HTMLProps<HTMLDivElement>'.
10+
tests/cases/conformance/jsx/file.tsx(44,32): error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
11+
12+
13+
==== tests/cases/conformance/jsx/file.tsx (7 errors) ====
14+
15+
import React = require('react');
16+
17+
// A built-in element (OK)
18+
var a1 = <div id="foo" />;
19+
20+
// A built-in element with a mistyped property (error)
21+
var a2 = <img srce="foo.jpg" />
22+
~~~~
23+
!!! error TS2339: Property 'srce' does not exist on type 'HTMLProps<HTMLImageElement>'.
24+
25+
// A built-in element with a badly-typed attribute value (error)
26+
var thing = { oops: 100 };
27+
var a3 = <div id={thing} />
28+
~~~~~~~~~~
29+
!!! error TS2322: Type '{ oops: number; }' is not assignable to type 'string'.
30+
31+
// Mistyped html name (error)
32+
var e1 = <imag src="bar.jpg" />
33+
~~~~~~~~~~~~~~~~~~~~~~
34+
!!! error TS2339: Property 'imag' does not exist on type 'JSX.IntrinsicElements'.
35+
36+
class MyClass extends React.Component<{
37+
pt?: { x: number; y: number; };
38+
name?: string;
39+
reqd: boolean;
40+
}, any> {
41+
}
42+
43+
// Let's use it
44+
// TODO: Error on missing 'reqd'
45+
var b1 = <MyClass reqd={true} />;
46+
47+
// Mistyped attribute member
48+
// sample.tsx(23,22): error TS2322: Type '{ x: number; y: string; }' is not assignable to type '{ x: number; y: number; }'.
49+
// Types of property 'y' are incompatible.
50+
// Type 'string' is not assignable to type 'number'.
51+
var b2 = <MyClass pt={{x: 4, y: 'oops'}} />;
52+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53+
!!! error TS2324: Property 'reqd' is missing in type 'IntrinsicAttributes & IntrinsicClassAttributes<MyClass> & { pt?: { x: number; y: number; }; name?: string; reqd: boolean; }'.
54+
~~~~~~~~~~~~~~~~~~~~~~
55+
!!! error TS2322: Type '{ x: number; y: string; }' is not assignable to type '{ x: number; y: number; }'.
56+
!!! error TS2322: Types of property 'y' are incompatible.
57+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
58+
59+
// A custom element type with an explicit children prop attribute type
60+
61+
class MyParentClass extends React.Component<{
62+
children?: MyClass[];
63+
}, any> {
64+
}
65+
66+
// OK - Child element matches the children prop
67+
var d1 = <MyParentClass><MyClass reqd={true} /><MyClass reqd={true}></MyClass></MyParentClass>
68+
// Error - Incorrect child element type
69+
var d2 = <MyParentClass><div /><div></div><MyClass reqd={true} /><MyClass reqd={true}></MyClass></MyParentClass>
70+
~~~~~~~
71+
!!! error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
72+
!!! error TS2322: Property 'setState' is missing in type 'HTMLProps<HTMLDivElement>'.
73+
~~~~~
74+
!!! error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
+48-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
//// [tsxTypeErrors.tsx]
1+
//// [file.tsx]
2+
3+
import React = require('react');
24

35
// A built-in element (OK)
46
var a1 = <div id="foo" />;
@@ -13,28 +15,48 @@ var a3 = <div id={thing} />
1315
// Mistyped html name (error)
1416
var e1 = <imag src="bar.jpg" />
1517

16-
// A custom type
17-
class MyClass {
18-
props: {
18+
class MyClass extends React.Component<{
1919
pt?: { x: number; y: number; };
20-
name?: string;
21-
reqd: boolean;
22-
}
20+
name?: string;
21+
reqd: boolean;
22+
}, any> {
2323
}
2424

2525
// Let's use it
2626
// TODO: Error on missing 'reqd'
27-
var b1 = <MyClass reqd={true} />;
27+
var b1 = <MyClass reqd={true} />;
2828

2929
// Mistyped attribute member
3030
// sample.tsx(23,22): error TS2322: Type '{ x: number; y: string; }' is not assignable to type '{ x: number; y: number; }'.
3131
// Types of property 'y' are incompatible.
3232
// Type 'string' is not assignable to type 'number'.
3333
var b2 = <MyClass pt={{x: 4, y: 'oops'}} />;
3434

35+
// A custom element type with an explicit children prop attribute type
36+
37+
class MyParentClass extends React.Component<{
38+
children?: MyClass[];
39+
}, any> {
40+
}
41+
42+
// OK - Child element matches the children prop
43+
var d1 = <MyParentClass><MyClass reqd={true} /><MyClass reqd={true}></MyClass></MyParentClass>
44+
// Error - Incorrect child element type
45+
var d2 = <MyParentClass><div /><div></div><MyClass reqd={true} /><MyClass reqd={true}></MyClass></MyParentClass>
3546

36-
37-
//// [tsxTypeErrors.jsx]
47+
//// [file.jsx]
48+
"use strict";
49+
var __extends = (this && this.__extends) || (function () {
50+
var extendStatics = Object.setPrototypeOf ||
51+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
52+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
53+
return function (d, b) {
54+
extendStatics(d, b);
55+
function __() { this.constructor = d; }
56+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
57+
};
58+
})();
59+
var React = require("react");
3860
// A built-in element (OK)
3961
var a1 = <div id="foo"/>;
4062
// A built-in element with a mistyped property (error)
@@ -44,12 +66,13 @@ var thing = { oops: 100 };
4466
var a3 = <div id={thing}/>;
4567
// Mistyped html name (error)
4668
var e1 = <imag src="bar.jpg"/>;
47-
// A custom type
48-
var MyClass = (function () {
69+
var MyClass = (function (_super) {
70+
__extends(MyClass, _super);
4971
function MyClass() {
72+
return _super !== null && _super.apply(this, arguments) || this;
5073
}
5174
return MyClass;
52-
}());
75+
}(React.Component));
5376
// Let's use it
5477
// TODO: Error on missing 'reqd'
5578
var b1 = <MyClass reqd={true}/>;
@@ -58,3 +81,15 @@ var b1 = <MyClass reqd={true}/>;
5881
// Types of property 'y' are incompatible.
5982
// Type 'string' is not assignable to type 'number'.
6083
var b2 = <MyClass pt={{ x: 4, y: 'oops' }}/>;
84+
// A custom element type with an explicit children prop attribute type
85+
var MyParentClass = (function (_super) {
86+
__extends(MyParentClass, _super);
87+
function MyParentClass() {
88+
return _super !== null && _super.apply(this, arguments) || this;
89+
}
90+
return MyParentClass;
91+
}(React.Component));
92+
// OK - Child element matches the children prop
93+
var d1 = <MyParentClass><MyClass reqd={true}/><MyClass reqd={true}></MyClass></MyParentClass>;
94+
// Error - Incorrect child element type
95+
var d2 = <MyParentClass><div /><div></div><MyClass reqd={true}/><MyClass reqd={true}></MyClass></MyParentClass>;

tests/baselines/reference/tsxTypeErrors.symbols

+53-4
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,17 @@ class MyClass {
4141
>x : Symbol(x, Decl(tsxTypeErrors.tsx, 17, 10))
4242
>y : Symbol(y, Decl(tsxTypeErrors.tsx, 17, 21))
4343

44-
name?: string;
44+
name?: string;
4545
>name : Symbol(name, Decl(tsxTypeErrors.tsx, 17, 35))
4646

47-
reqd: boolean;
48-
>reqd : Symbol(reqd, Decl(tsxTypeErrors.tsx, 18, 15))
47+
reqd: boolean;
48+
>reqd : Symbol(reqd, Decl(tsxTypeErrors.tsx, 18, 18))
4949
}
5050
}
5151

5252
// Let's use it
5353
// TODO: Error on missing 'reqd'
54-
var b1 = <MyClass reqd={true} />;
54+
var b1 = <MyClass reqd={true} />;
5555
>b1 : Symbol(b1, Decl(tsxTypeErrors.tsx, 25, 3))
5656
>MyClass : Symbol(MyClass, Decl(tsxTypeErrors.tsx, 12, 31))
5757
>reqd : Symbol(unknown)
@@ -67,4 +67,53 @@ var b2 = <MyClass pt={{x: 4, y: 'oops'}} />;
6767
>x : Symbol(x, Decl(tsxTypeErrors.tsx, 31, 23))
6868
>y : Symbol(y, Decl(tsxTypeErrors.tsx, 31, 28))
6969

70+
// A custom element type with an explicit children prop attribute type
71+
class MyParentClass {
72+
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
73+
74+
props: {
75+
>props : Symbol(MyParentClass.props, Decl(tsxTypeErrors.tsx, 34, 21))
76+
77+
children?: MyClass[];
78+
>children : Symbol(children, Decl(tsxTypeErrors.tsx, 35, 10))
79+
>MyClass : Symbol(MyClass, Decl(tsxTypeErrors.tsx, 12, 31))
80+
}
81+
}
82+
83+
// Correct child element type
84+
// Child element matches the children prop (OK)
85+
var d1 = <MyParentClass><MyClass reqd={true} /></MyParentClass>
86+
>d1 : Symbol(d1, Decl(tsxTypeErrors.tsx, 42, 3))
87+
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
88+
>MyClass : Symbol(MyClass, Decl(tsxTypeErrors.tsx, 12, 31))
89+
>reqd : Symbol(unknown)
90+
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
91+
92+
// Conforms with type checking on element attribute (OK)
93+
var d2 = <MyParentClass children={[<MyClass reqd={true} />]}></MyParentClass>
94+
>d2 : Symbol(d2, Decl(tsxTypeErrors.tsx, 44, 3))
95+
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
96+
>children : Symbol(unknown)
97+
>MyClass : Symbol(MyClass, Decl(tsxTypeErrors.tsx, 12, 31))
98+
>reqd : Symbol(unknown)
99+
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
100+
101+
// Incorrect child element type
102+
// sample.tsx(23,22): error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
103+
var d3 = <MyParentClass><div></div><MyClass reqd={true} /></MyParentClass>
104+
>d3 : Symbol(d3, Decl(tsxTypeErrors.tsx, 48, 3))
105+
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
106+
>div : Symbol(unknown)
107+
>div : Symbol(unknown)
108+
>MyClass : Symbol(MyClass, Decl(tsxTypeErrors.tsx, 12, 31))
109+
>reqd : Symbol(unknown)
110+
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
111+
112+
// sample.tsx(23,22): error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
113+
var d4 = <MyParentClass children={[<div />]}></MyParentClass>
114+
>d4 : Symbol(d4, Decl(tsxTypeErrors.tsx, 50, 3))
115+
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
116+
>children : Symbol(unknown)
117+
>div : Symbol(unknown)
118+
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
70119

0 commit comments

Comments
 (0)