Skip to content

Commit

Permalink
Add child typechecking.
Browse files Browse the repository at this point in the history
  • Loading branch information
joelday committed Jan 30, 2017
1 parent 007751b commit 50870c1
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 28 deletions.
49 changes: 49 additions & 0 deletions src/compiler/checker.ts
Expand Up @@ -11825,6 +11825,54 @@ namespace ts {
return jsxElementType || anyType;
}

function findJsxElementParent(node: JsxOpeningLikeElement) {
let parent = node.parent;

while (parent) {
if (parent.kind == SyntaxKind.JsxElement && (<JsxElement>parent).openingElement !== node) {
return (<JsxElement>parent).openingElement;
}

parent = parent.parent;
}

return;
}

/**
* Validates a JsxElement against the type of the 'children' attribute of its parent
*/
function checkJsxElementAgainstParent(node: JsxOpeningLikeElement) {
const parent = findJsxElementParent(node);
if (!parent) {
return;
}

const parentAttributesType = getJsxElementAttributesType(parent);
if (!parentAttributesType || isTypeAny(parentAttributesType)) {
return;
}

const childrenPropSymbol = getPropertyOfType(parentAttributesType, "children");
if (!childrenPropSymbol) {
return;
}

let childrenPropType = getTypeOfSymbol(childrenPropSymbol);
if (isArrayLikeType(childrenPropType)) {
childrenPropType = getIndexTypeOfType(childrenPropType, IndexKind.Number);
}

const intrinsicTagType = isJsxIntrinsicIdentifier(node.tagName) ? getTypeOfSymbol(getIntrinsicTagSymbol(node)) : undefined;

const elementType = intrinsicTagType || getJsxElementInstanceType(node, getTypeOfNode(node.tagName));
if (!elementType) {
return;
}

checkTypeAssignableTo(elementType, childrenPropType, node);
}

function checkJsxElement(node: JsxElement) {
// Check attributes
checkJsxOpeningLikeElement(node.openingElement);
Expand Down Expand Up @@ -12240,6 +12288,7 @@ namespace ts {
function checkJsxOpeningLikeElement(node: JsxOpeningLikeElement) {
checkGrammarJsxElement(node);
checkJsxPreconditions(node);
checkJsxElementAgainstParent(node);
// The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import.
// And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error.
const reactRefErr = compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined;
Expand Down
74 changes: 74 additions & 0 deletions tests/baselines/reference/tsxTypeErrors.errors.txt
@@ -0,0 +1,74 @@
tests/cases/conformance/jsx/file.tsx(8,15): error TS2339: Property 'srce' does not exist on type 'HTMLProps<HTMLImageElement>'.
tests/cases/conformance/jsx/file.tsx(12,15): error TS2322: Type '{ oops: number; }' is not assignable to type 'string'.
tests/cases/conformance/jsx/file.tsx(15,10): error TS2339: Property 'imag' does not exist on type 'JSX.IntrinsicElements'.
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; }'.
tests/cases/conformance/jsx/file.tsx(32,19): error TS2322: Type '{ x: number; y: string; }' is not assignable to type '{ x: number; y: number; }'.
Types of property 'y' are incompatible.
Type 'string' is not assignable to type 'number'.
tests/cases/conformance/jsx/file.tsx(44,25): error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
Property 'setState' is missing in type 'HTMLProps<HTMLDivElement>'.
tests/cases/conformance/jsx/file.tsx(44,32): error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.


==== tests/cases/conformance/jsx/file.tsx (7 errors) ====

import React = require('react');

// A built-in element (OK)
var a1 = <div id="foo" />;

// A built-in element with a mistyped property (error)
var a2 = <img srce="foo.jpg" />
~~~~
!!! error TS2339: Property 'srce' does not exist on type 'HTMLProps<HTMLImageElement>'.

// A built-in element with a badly-typed attribute value (error)
var thing = { oops: 100 };
var a3 = <div id={thing} />
~~~~~~~~~~
!!! error TS2322: Type '{ oops: number; }' is not assignable to type 'string'.

// Mistyped html name (error)
var e1 = <imag src="bar.jpg" />
~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2339: Property 'imag' does not exist on type 'JSX.IntrinsicElements'.

class MyClass extends React.Component<{
pt?: { x: number; y: number; };
name?: string;
reqd: boolean;
}, any> {
}

// Let's use it
// TODO: Error on missing 'reqd'
var b1 = <MyClass reqd={true} />;

// Mistyped attribute member
// sample.tsx(23,22): error TS2322: Type '{ x: number; y: string; }' is not assignable to type '{ x: number; y: number; }'.
// Types of property 'y' are incompatible.
// Type 'string' is not assignable to type 'number'.
var b2 = <MyClass pt={{x: 4, y: 'oops'}} />;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2324: Property 'reqd' is missing in type 'IntrinsicAttributes & IntrinsicClassAttributes<MyClass> & { pt?: { x: number; y: number; }; name?: string; reqd: boolean; }'.
~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type '{ x: number; y: string; }' is not assignable to type '{ x: number; y: number; }'.
!!! error TS2322: Types of property 'y' are incompatible.
!!! error TS2322: Type 'string' is not assignable to type 'number'.

// A custom element type with an explicit children prop attribute type

class MyParentClass extends React.Component<{
children?: MyClass[];
}, any> {
}

// OK - Child element matches the children prop
var d1 = <MyParentClass><MyClass reqd={true} /><MyClass reqd={true}></MyClass></MyParentClass>
// Error - Incorrect child element type
var d2 = <MyParentClass><div /><div></div><MyClass reqd={true} /><MyClass reqd={true}></MyClass></MyParentClass>
~~~~~~~
!!! error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
!!! error TS2322: Property 'setState' is missing in type 'HTMLProps<HTMLDivElement>'.
~~~~~
!!! error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
61 changes: 48 additions & 13 deletions tests/baselines/reference/tsxTypeErrors.js
@@ -1,4 +1,6 @@
//// [tsxTypeErrors.tsx]
//// [file.tsx]

import React = require('react');

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

// A custom type
class MyClass {
props: {
class MyClass extends React.Component<{
pt?: { x: number; y: number; };
name?: string;
reqd: boolean;
}
name?: string;
reqd: boolean;
}, any> {
}

// Let's use it
// TODO: Error on missing 'reqd'
var b1 = <MyClass reqd={true} />;
var b1 = <MyClass reqd={true} />;

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

// A custom element type with an explicit children prop attribute type

class MyParentClass extends React.Component<{
children?: MyClass[];
}, any> {
}

// OK - Child element matches the children prop
var d1 = <MyParentClass><MyClass reqd={true} /><MyClass reqd={true}></MyClass></MyParentClass>
// Error - Incorrect child element type
var d2 = <MyParentClass><div /><div></div><MyClass reqd={true} /><MyClass reqd={true}></MyClass></MyParentClass>


//// [tsxTypeErrors.jsx]
//// [file.jsx]
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var React = require("react");
// A built-in element (OK)
var a1 = <div id="foo"/>;
// A built-in element with a mistyped property (error)
Expand All @@ -44,12 +66,13 @@ var thing = { oops: 100 };
var a3 = <div id={thing}/>;
// Mistyped html name (error)
var e1 = <imag src="bar.jpg"/>;
// A custom type
var MyClass = (function () {
var MyClass = (function (_super) {
__extends(MyClass, _super);
function MyClass() {
return _super !== null && _super.apply(this, arguments) || this;
}
return MyClass;
}());
}(React.Component));
// Let's use it
// TODO: Error on missing 'reqd'
var b1 = <MyClass reqd={true}/>;
Expand All @@ -58,3 +81,15 @@ var b1 = <MyClass reqd={true}/>;
// Types of property 'y' are incompatible.
// Type 'string' is not assignable to type 'number'.
var b2 = <MyClass pt={{ x: 4, y: 'oops' }}/>;
// A custom element type with an explicit children prop attribute type
var MyParentClass = (function (_super) {
__extends(MyParentClass, _super);
function MyParentClass() {
return _super !== null && _super.apply(this, arguments) || this;
}
return MyParentClass;
}(React.Component));
// OK - Child element matches the children prop
var d1 = <MyParentClass><MyClass reqd={true}/><MyClass reqd={true}></MyClass></MyParentClass>;
// Error - Incorrect child element type
var d2 = <MyParentClass><div /><div></div><MyClass reqd={true}/><MyClass reqd={true}></MyClass></MyParentClass>;
57 changes: 53 additions & 4 deletions tests/baselines/reference/tsxTypeErrors.symbols
Expand Up @@ -41,17 +41,17 @@ class MyClass {
>x : Symbol(x, Decl(tsxTypeErrors.tsx, 17, 10))
>y : Symbol(y, Decl(tsxTypeErrors.tsx, 17, 21))

name?: string;
name?: string;
>name : Symbol(name, Decl(tsxTypeErrors.tsx, 17, 35))

reqd: boolean;
>reqd : Symbol(reqd, Decl(tsxTypeErrors.tsx, 18, 15))
reqd: boolean;
>reqd : Symbol(reqd, Decl(tsxTypeErrors.tsx, 18, 18))
}
}

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

// A custom element type with an explicit children prop attribute type
class MyParentClass {
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))

props: {
>props : Symbol(MyParentClass.props, Decl(tsxTypeErrors.tsx, 34, 21))

children?: MyClass[];
>children : Symbol(children, Decl(tsxTypeErrors.tsx, 35, 10))
>MyClass : Symbol(MyClass, Decl(tsxTypeErrors.tsx, 12, 31))
}
}

// Correct child element type
// Child element matches the children prop (OK)
var d1 = <MyParentClass><MyClass reqd={true} /></MyParentClass>
>d1 : Symbol(d1, Decl(tsxTypeErrors.tsx, 42, 3))
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
>MyClass : Symbol(MyClass, Decl(tsxTypeErrors.tsx, 12, 31))
>reqd : Symbol(unknown)
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))

// Conforms with type checking on element attribute (OK)
var d2 = <MyParentClass children={[<MyClass reqd={true} />]}></MyParentClass>
>d2 : Symbol(d2, Decl(tsxTypeErrors.tsx, 44, 3))
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
>children : Symbol(unknown)
>MyClass : Symbol(MyClass, Decl(tsxTypeErrors.tsx, 12, 31))
>reqd : Symbol(unknown)
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))

// Incorrect child element type
// sample.tsx(23,22): error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
var d3 = <MyParentClass><div></div><MyClass reqd={true} /></MyParentClass>
>d3 : Symbol(d3, Decl(tsxTypeErrors.tsx, 48, 3))
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
>div : Symbol(unknown)
>div : Symbol(unknown)
>MyClass : Symbol(MyClass, Decl(tsxTypeErrors.tsx, 12, 31))
>reqd : Symbol(unknown)
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))

// sample.tsx(23,22): error TS2322: Type 'HTMLProps<HTMLDivElement>' is not assignable to type 'MyClass'.
var d4 = <MyParentClass children={[<div />]}></MyParentClass>
>d4 : Symbol(d4, Decl(tsxTypeErrors.tsx, 50, 3))
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))
>children : Symbol(unknown)
>div : Symbol(unknown)
>MyParentClass : Symbol(MyParentClass, Decl(tsxTypeErrors.tsx, 31, 44))

0 comments on commit 50870c1

Please sign in to comment.