diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7c8a0b7337f9b..36920ba80cac2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11825,6 +11825,54 @@ namespace ts { return jsxElementType || anyType; } + function findJsxElementParent(node: JsxOpeningLikeElement) { + let parent = node.parent; + + while (parent) { + if (parent.kind == SyntaxKind.JsxElement && (parent).openingElement !== node) { + return (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); @@ -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; diff --git a/tests/baselines/reference/tsxTypeErrors.errors.txt b/tests/baselines/reference/tsxTypeErrors.errors.txt new file mode 100644 index 0000000000000..fdd3c5a46acbd --- /dev/null +++ b/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'. +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 & { 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' is not assignable to type 'MyClass'. + Property 'setState' is missing in type 'HTMLProps'. +tests/cases/conformance/jsx/file.tsx(44,32): error TS2322: Type 'HTMLProps' 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 =
; + + // A built-in element with a mistyped property (error) + var a2 = + ~~~~ +!!! error TS2339: Property 'srce' does not exist on type 'HTMLProps'. + + // A built-in element with a badly-typed attribute value (error) + var thing = { oops: 100 }; + var a3 =
+ ~~~~~~~~~~ +!!! error TS2322: Type '{ oops: number; }' is not assignable to type 'string'. + + // Mistyped html name (error) + var e1 = + ~~~~~~~~~~~~~~~~~~~~~~ +!!! 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 = ; + + // 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 = ; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2324: Property 'reqd' is missing in type 'IntrinsicAttributes & IntrinsicClassAttributes & { 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 = + // Error - Incorrect child element type + var d2 =
+ ~~~~~~~ +!!! error TS2322: Type 'HTMLProps' is not assignable to type 'MyClass'. +!!! error TS2322: Property 'setState' is missing in type 'HTMLProps'. + ~~~~~ +!!! error TS2322: Type 'HTMLProps' is not assignable to type 'MyClass'. \ No newline at end of file diff --git a/tests/baselines/reference/tsxTypeErrors.js b/tests/baselines/reference/tsxTypeErrors.js index fd28f9a5bcb63..b24151edb8ebc 100644 --- a/tests/baselines/reference/tsxTypeErrors.js +++ b/tests/baselines/reference/tsxTypeErrors.js @@ -1,4 +1,6 @@ -//// [tsxTypeErrors.tsx] +//// [file.tsx] + +import React = require('react'); // A built-in element (OK) var a1 =
; @@ -13,18 +15,16 @@ var a3 =
// Mistyped html name (error) var e1 = -// 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 = ; +var b1 = ; // Mistyped attribute member // sample.tsx(23,22): error TS2322: Type '{ x: number; y: string; }' is not assignable to type '{ x: number; y: number; }'. @@ -32,9 +32,31 @@ var b1 = ; // Type 'string' is not assignable to type 'number'. var b2 = ; +// 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 = +// Error - Incorrect child element type +var d2 =
- -//// [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 =
; // A built-in element with a mistyped property (error) @@ -44,12 +66,13 @@ var thing = { oops: 100 }; var a3 =
; // Mistyped html name (error) var e1 = ; -// 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 = ; @@ -58,3 +81,15 @@ var b1 = ; // Types of property 'y' are incompatible. // Type 'string' is not assignable to type 'number'. var b2 = ; +// 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 = ; +// Error - Incorrect child element type +var d2 =
; diff --git a/tests/baselines/reference/tsxTypeErrors.symbols b/tests/baselines/reference/tsxTypeErrors.symbols index 062442cda1604..07bad4ef11b65 100644 --- a/tests/baselines/reference/tsxTypeErrors.symbols +++ b/tests/baselines/reference/tsxTypeErrors.symbols @@ -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 = ; +var b1 = ; >b1 : Symbol(b1, Decl(tsxTypeErrors.tsx, 25, 3)) >MyClass : Symbol(MyClass, Decl(tsxTypeErrors.tsx, 12, 31)) >reqd : Symbol(unknown) @@ -67,4 +67,53 @@ var b2 = ; >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 = +>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 = ]}> +>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' is not assignable to type 'MyClass'. +var d3 =
+>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' is not assignable to type 'MyClass'. +var d4 = ]}> +>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)) diff --git a/tests/baselines/reference/tsxTypeErrors.types b/tests/baselines/reference/tsxTypeErrors.types index d64781479fa27..afbd3f72e2dcc 100644 --- a/tests/baselines/reference/tsxTypeErrors.types +++ b/tests/baselines/reference/tsxTypeErrors.types @@ -47,17 +47,17 @@ class MyClass { >x : number >y : number - name?: string; + name?: string; >name : string - reqd: boolean; + reqd: boolean; >reqd : boolean } } // Let's use it // TODO: Error on missing 'reqd' -var b1 = ; +var b1 = ; >b1 : any > : any >MyClass : typeof MyClass @@ -79,4 +79,67 @@ var b2 = ; >y : string >'oops' : "oops" +// A custom element type with an explicit children prop attribute type +class MyParentClass { +>MyParentClass : MyParentClass + + props: { +>props : { children?: MyClass[]; } + + children?: MyClass[]; +>children : MyClass[] +>MyClass : MyClass + } +} + +// Correct child element type +// Child element matches the children prop (OK) +var d1 = +>d1 : any +> : any +>MyParentClass : typeof MyParentClass +> : any +>MyClass : typeof MyClass +>reqd : any +>true : true +>MyParentClass : typeof MyParentClass + +// Conforms with type checking on element attribute (OK) +var d2 = ]}> +>d2 : any +>]}> : any +>MyParentClass : typeof MyParentClass +>children : any +>[] : any[] +> : any +>MyClass : typeof MyClass +>reqd : any +>true : true +>MyParentClass : typeof MyParentClass + +// Incorrect child element type +// sample.tsx(23,22): error TS2322: Type 'HTMLProps' is not assignable to type 'MyClass'. +var d3 =
+>d3 : any +>
: any +>MyParentClass : typeof MyParentClass +>
: any +>div : any +>div : any +> : any +>MyClass : typeof MyClass +>reqd : any +>true : true +>MyParentClass : typeof MyParentClass + +// sample.tsx(23,22): error TS2322: Type 'HTMLProps' is not assignable to type 'MyClass'. +var d4 = ]}> +>d4 : any +>]}> : any +>MyParentClass : typeof MyParentClass +>children : any +>[
] : any[] +>
: any +>div : any +>MyParentClass : typeof MyParentClass diff --git a/tests/cases/conformance/jsx/tsxTypeErrors.tsx b/tests/cases/conformance/jsx/tsxTypeErrors.tsx index f8288b68ce336..96baa5ace9206 100644 --- a/tests/cases/conformance/jsx/tsxTypeErrors.tsx +++ b/tests/cases/conformance/jsx/tsxTypeErrors.tsx @@ -1,4 +1,9 @@ -//@jsx: preserve +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); // A built-in element (OK) var a1 =
; @@ -13,18 +18,16 @@ var a3 =
// Mistyped html name (error) var e1 = -// 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 = ; +var b1 = ; // Mistyped attribute member // sample.tsx(23,22): error TS2322: Type '{ x: number; y: string; }' is not assignable to type '{ x: number; y: number; }'. @@ -32,3 +35,14 @@ var b1 = ; // Type 'string' is not assignable to type 'number'. var b2 = ; +// 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 = +// Error - Incorrect child element type +var d2 =
\ No newline at end of file