Skip to content

Commit 43b36d2

Browse files
committed
Merge pull request #8674 from evansb/union-type-react
Fixes #8657: Handles union typed React component.
2 parents c9ab20c + 01b541d commit 43b36d2

8 files changed

+358
-90
lines changed

src/compiler/checker.ts

Lines changed: 104 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -9509,20 +9509,18 @@ namespace ts {
95099509
* element is not a class element, or the class element type cannot be determined, returns 'undefined'.
95109510
* For example, in the element <MyClass>, the element instance type is `MyClass` (not `typeof MyClass`).
95119511
*/
9512-
function getJsxElementInstanceType(node: JsxOpeningLikeElement) {
9513-
const valueType = checkExpression(node.tagName);
9514-
9512+
function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type) {
9513+
Debug.assert(!(valueType.flags & TypeFlags.Union));
95159514
if (isTypeAny(valueType)) {
95169515
// Short-circuit if the class tag is using an element type 'any'
95179516
return anyType;
95189517
}
95199518

9520-
// Resolve the signatures, preferring constructors
9519+
// Resolve the signatures, preferring constructor
95219520
let signatures = getSignaturesOfType(valueType, SignatureKind.Construct);
95229521
if (signatures.length === 0) {
95239522
// No construct signatures, try call signatures
95249523
signatures = getSignaturesOfType(valueType, SignatureKind.Call);
9525-
95269524
if (signatures.length === 0) {
95279525
// We found no signatures at all, which is an error
95289526
error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName));
@@ -9570,6 +9568,103 @@ namespace ts {
95709568
}
95719569
}
95729570

9571+
/**
9572+
* Given React element instance type and the class type, resolve the Jsx type
9573+
* Pass elemType to handle individual type in the union typed element type.
9574+
*/
9575+
function getResolvedJsxType(node: JsxOpeningLikeElement, elemType?: Type, elemClassType?: Type): Type {
9576+
if (!elemType) {
9577+
elemType = checkExpression(node.tagName);
9578+
}
9579+
if (elemType.flags & TypeFlags.Union) {
9580+
const types = (<UnionOrIntersectionType> elemType).types;
9581+
return getUnionType(types.map(type => {
9582+
return getResolvedJsxType(node, type, elemClassType);
9583+
}));
9584+
}
9585+
9586+
// Get the element instance type (the result of newing or invoking this tag)
9587+
const elemInstanceType = getJsxElementInstanceType(node, elemType);
9588+
9589+
if (!elemClassType || !isTypeAssignableTo(elemInstanceType, elemClassType)) {
9590+
// Is this is a stateless function component? See if its single signature's return type is
9591+
// assignable to the JSX Element Type
9592+
if (jsxElementType) {
9593+
const callSignatures = elemType && getSignaturesOfType(elemType, SignatureKind.Call);
9594+
const callSignature = callSignatures && callSignatures.length > 0 && callSignatures[0];
9595+
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature);
9596+
let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0]));
9597+
if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType)) {
9598+
// Intersect in JSX.IntrinsicAttributes if it exists
9599+
const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes);
9600+
if (intrinsicAttributes !== unknownType) {
9601+
paramType = intersectTypes(intrinsicAttributes, paramType);
9602+
}
9603+
return paramType;
9604+
}
9605+
}
9606+
}
9607+
9608+
// Issue an error if this return type isn't assignable to JSX.ElementClass
9609+
if (elemClassType) {
9610+
checkTypeRelatedTo(elemInstanceType, elemClassType, assignableRelation, node, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
9611+
}
9612+
9613+
if (isTypeAny(elemInstanceType)) {
9614+
return elemInstanceType;
9615+
}
9616+
9617+
const propsName = getJsxElementPropertiesName();
9618+
if (propsName === undefined) {
9619+
// There is no type ElementAttributesProperty, return 'any'
9620+
return anyType;
9621+
}
9622+
else if (propsName === "") {
9623+
// If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead
9624+
return elemInstanceType;
9625+
}
9626+
else {
9627+
const attributesType = getTypeOfPropertyOfType(elemInstanceType, propsName);
9628+
9629+
if (!attributesType) {
9630+
// There is no property named 'props' on this instance type
9631+
return emptyObjectType;
9632+
}
9633+
else if (isTypeAny(attributesType) || (attributesType === unknownType)) {
9634+
// Props is of type 'any' or unknown
9635+
return attributesType;
9636+
}
9637+
else if (attributesType.flags & TypeFlags.Union) {
9638+
// Props cannot be a union type
9639+
error(node.tagName, Diagnostics.JSX_element_attributes_type_0_may_not_be_a_union_type, typeToString(attributesType));
9640+
return anyType;
9641+
}
9642+
else {
9643+
// Normal case -- add in IntrinsicClassElements<T> and IntrinsicElements
9644+
let apparentAttributesType = attributesType;
9645+
const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes);
9646+
if (intrinsicClassAttribs !== unknownType) {
9647+
const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol);
9648+
if (typeParams) {
9649+
if (typeParams.length === 1) {
9650+
apparentAttributesType = intersectTypes(createTypeReference(<GenericType>intrinsicClassAttribs, [elemInstanceType]), apparentAttributesType);
9651+
}
9652+
}
9653+
else {
9654+
apparentAttributesType = intersectTypes(attributesType, intrinsicClassAttribs);
9655+
}
9656+
}
9657+
9658+
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes);
9659+
if (intrinsicAttribs !== unknownType) {
9660+
apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType);
9661+
}
9662+
9663+
return apparentAttributesType;
9664+
}
9665+
}
9666+
}
9667+
95739668
/**
95749669
* Given an opening/self-closing element, get the 'element attributes type', i.e. the type that tells
95759670
* us which attributes are valid on a given element.
@@ -9585,96 +9680,15 @@ namespace ts {
95859680
else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) {
95869681
return links.resolvedJsxType = getIndexInfoOfSymbol(symbol, IndexKind.String).type;
95879682
}
9683+
else {
9684+
return links.resolvedJsxType = unknownType;
9685+
}
95889686
}
95899687
else {
9590-
// Get the element instance type (the result of newing or invoking this tag)
9591-
const elemInstanceType = getJsxElementInstanceType(node);
9592-
95939688
const elemClassType = getJsxGlobalElementClassType();
9594-
9595-
if (!elemClassType || !isTypeAssignableTo(elemInstanceType, elemClassType)) {
9596-
// Is this is a stateless function component? See if its single signature's return type is
9597-
// assignable to the JSX Element Type
9598-
if (jsxElementType) {
9599-
const elemType = checkExpression(node.tagName);
9600-
const callSignatures = elemType && getSignaturesOfType(elemType, SignatureKind.Call);
9601-
const callSignature = callSignatures && callSignatures.length > 0 && callSignatures[0];
9602-
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature);
9603-
let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0]));
9604-
if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType)) {
9605-
// Intersect in JSX.IntrinsicAttributes if it exists
9606-
const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes);
9607-
if (intrinsicAttributes !== unknownType) {
9608-
paramType = intersectTypes(intrinsicAttributes, paramType);
9609-
}
9610-
return links.resolvedJsxType = paramType;
9611-
}
9612-
}
9613-
}
9614-
9615-
// Issue an error if this return type isn't assignable to JSX.ElementClass
9616-
if (elemClassType) {
9617-
checkTypeRelatedTo(elemInstanceType, elemClassType, assignableRelation, node, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
9618-
}
9619-
9620-
if (isTypeAny(elemInstanceType)) {
9621-
return links.resolvedJsxType = elemInstanceType;
9622-
}
9623-
9624-
const propsName = getJsxElementPropertiesName();
9625-
if (propsName === undefined) {
9626-
// There is no type ElementAttributesProperty, return 'any'
9627-
return links.resolvedJsxType = anyType;
9628-
}
9629-
else if (propsName === "") {
9630-
// If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead
9631-
return links.resolvedJsxType = elemInstanceType;
9632-
}
9633-
else {
9634-
const attributesType = getTypeOfPropertyOfType(elemInstanceType, propsName);
9635-
9636-
if (!attributesType) {
9637-
// There is no property named 'props' on this instance type
9638-
return links.resolvedJsxType = emptyObjectType;
9639-
}
9640-
else if (isTypeAny(attributesType) || (attributesType === unknownType)) {
9641-
// Props is of type 'any' or unknown
9642-
return links.resolvedJsxType = attributesType;
9643-
}
9644-
else if (attributesType.flags & TypeFlags.Union) {
9645-
// Props cannot be a union type
9646-
error(node.tagName, Diagnostics.JSX_element_attributes_type_0_may_not_be_a_union_type, typeToString(attributesType));
9647-
return links.resolvedJsxType = anyType;
9648-
}
9649-
else {
9650-
// Normal case -- add in IntrinsicClassElements<T> and IntrinsicElements
9651-
let apparentAttributesType = attributesType;
9652-
const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes);
9653-
if (intrinsicClassAttribs !== unknownType) {
9654-
const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol);
9655-
if (typeParams) {
9656-
if (typeParams.length === 1) {
9657-
apparentAttributesType = intersectTypes(createTypeReference(<GenericType>intrinsicClassAttribs, [elemInstanceType]), apparentAttributesType);
9658-
}
9659-
}
9660-
else {
9661-
apparentAttributesType = intersectTypes(attributesType, intrinsicClassAttribs);
9662-
}
9663-
}
9664-
9665-
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes);
9666-
if (intrinsicAttribs !== unknownType) {
9667-
apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType);
9668-
}
9669-
9670-
return links.resolvedJsxType = apparentAttributesType;
9671-
}
9672-
}
9689+
return links.resolvedJsxType = getResolvedJsxType(node, undefined, elemClassType);
96739690
}
9674-
9675-
return links.resolvedJsxType = unknownType;
96769691
}
9677-
96789692
return links.resolvedJsxType;
96799693
}
96809694

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//// [file.tsx]
2+
3+
import React = require('react');
4+
5+
interface ComponentProps {
6+
AnyComponent: React.StatelessComponent<any> | React.ComponentClass<any>;
7+
}
8+
9+
class MyComponent extends React.Component<ComponentProps, {}> {
10+
render() {
11+
const { AnyComponent } = this.props;
12+
return (<AnyComponent />);
13+
}
14+
}
15+
16+
// Stateless Component As Props
17+
<MyComponent AnyComponent={() => <button>test</button>}/>
18+
19+
// Component Class as Props
20+
class MyButtonComponent extends React.Component<{},{}> {
21+
}
22+
23+
<MyComponent AnyComponent={MyButtonComponent} />
24+
25+
26+
27+
//// [file.js]
28+
"use strict";
29+
var __extends = (this && this.__extends) || function (d, b) {
30+
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
31+
function __() { this.constructor = d; }
32+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
33+
};
34+
var React = require('react');
35+
var MyComponent = (function (_super) {
36+
__extends(MyComponent, _super);
37+
function MyComponent() {
38+
_super.apply(this, arguments);
39+
}
40+
MyComponent.prototype.render = function () {
41+
var AnyComponent = this.props.AnyComponent;
42+
return (React.createElement(AnyComponent, null));
43+
};
44+
return MyComponent;
45+
}(React.Component));
46+
// Stateless Component As Props
47+
React.createElement(MyComponent, {AnyComponent: function () { return React.createElement("button", null, "test"); }});
48+
// Component Class as Props
49+
var MyButtonComponent = (function (_super) {
50+
__extends(MyButtonComponent, _super);
51+
function MyButtonComponent() {
52+
_super.apply(this, arguments);
53+
}
54+
return MyButtonComponent;
55+
}(React.Component));
56+
React.createElement(MyComponent, {AnyComponent: MyButtonComponent});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
3+
import React = require('react');
4+
>React : Symbol(React, Decl(file.tsx, 0, 0))
5+
6+
interface ComponentProps {
7+
>ComponentProps : Symbol(ComponentProps, Decl(file.tsx, 1, 32))
8+
9+
AnyComponent: React.StatelessComponent<any> | React.ComponentClass<any>;
10+
>AnyComponent : Symbol(ComponentProps.AnyComponent, Decl(file.tsx, 3, 26))
11+
>React : Symbol(React, Decl(file.tsx, 0, 0))
12+
>StatelessComponent : Symbol(React.StatelessComponent, Decl(react.d.ts, 139, 5))
13+
>React : Symbol(React, Decl(file.tsx, 0, 0))
14+
>ComponentClass : Symbol(React.ComponentClass, Decl(react.d.ts, 150, 5))
15+
}
16+
17+
class MyComponent extends React.Component<ComponentProps, {}> {
18+
>MyComponent : Symbol(MyComponent, Decl(file.tsx, 5, 1))
19+
>React.Component : Symbol(React.Component, Decl(react.d.ts, 114, 55))
20+
>React : Symbol(React, Decl(file.tsx, 0, 0))
21+
>Component : Symbol(React.Component, Decl(react.d.ts, 114, 55))
22+
>ComponentProps : Symbol(ComponentProps, Decl(file.tsx, 1, 32))
23+
24+
render() {
25+
>render : Symbol(MyComponent.render, Decl(file.tsx, 7, 63))
26+
27+
const { AnyComponent } = this.props;
28+
>AnyComponent : Symbol(AnyComponent, Decl(file.tsx, 9, 15))
29+
>this.props : Symbol(React.Component.props, Decl(react.d.ts, 122, 30))
30+
>this : Symbol(MyComponent, Decl(file.tsx, 5, 1))
31+
>props : Symbol(React.Component.props, Decl(react.d.ts, 122, 30))
32+
33+
return (<AnyComponent />);
34+
>AnyComponent : Symbol(AnyComponent, Decl(file.tsx, 9, 15))
35+
}
36+
}
37+
38+
// Stateless Component As Props
39+
<MyComponent AnyComponent={() => <button>test</button>}/>
40+
>MyComponent : Symbol(MyComponent, Decl(file.tsx, 5, 1))
41+
>AnyComponent : Symbol(ComponentProps.AnyComponent, Decl(file.tsx, 3, 26))
42+
>button : Symbol(JSX.IntrinsicElements.button, Decl(react.d.ts, 913, 43))
43+
>button : Symbol(JSX.IntrinsicElements.button, Decl(react.d.ts, 913, 43))
44+
45+
// Component Class as Props
46+
class MyButtonComponent extends React.Component<{},{}> {
47+
>MyButtonComponent : Symbol(MyButtonComponent, Decl(file.tsx, 15, 57))
48+
>React.Component : Symbol(React.Component, Decl(react.d.ts, 114, 55))
49+
>React : Symbol(React, Decl(file.tsx, 0, 0))
50+
>Component : Symbol(React.Component, Decl(react.d.ts, 114, 55))
51+
}
52+
53+
<MyComponent AnyComponent={MyButtonComponent} />
54+
>MyComponent : Symbol(MyComponent, Decl(file.tsx, 5, 1))
55+
>AnyComponent : Symbol(ComponentProps.AnyComponent, Decl(file.tsx, 3, 26))
56+
>MyButtonComponent : Symbol(MyButtonComponent, Decl(file.tsx, 15, 57))
57+
58+

0 commit comments

Comments
 (0)