From f04d28d422b5e4d2ac6a860c64ff097da773d007 Mon Sep 17 00:00:00 2001 From: Evan Sebastian Date: Thu, 19 May 2016 05:08:00 +0700 Subject: [PATCH 1/7] Recursively check union type of react components for constructor/call --- src/compiler/checker.ts | 43 +++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1a2e0a9e5d2b9..580a19f5b2eda 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9499,6 +9499,34 @@ namespace ts { return links.resolvedSymbol; } + /** + * Given a type, resolve its constructor signatures. + * If no construct signatures, call signatures are resolved. + * If the type is a union type, recursively resolve the signatures using + * the same method. + * Return collected construct/call signature. + */ + function getConstructOrCallSignature(type: Type): Signature[] { + if (type.flags & TypeFlags.Union) { + let signatures: Signature[] = []; + forEach(( type).types, (childType) => { + let childSignatures = getConstructOrCallSignature(childType); + forEach(childSignatures, (signature) => { + signatures.push(signature); + }) + }) + return signatures; + } else { + let signatures = getSignaturesOfType(type, SignatureKind.Construct); + if (signatures.length === 0) { + // No construct signatures, try call signatures + return getSignaturesOfType(type, SignatureKind.Call); + } else { + return signatures; + } + } + } + /** * Given a JSX element that is a class element, finds the Element Instance Type. If the * element is not a class element, or the class element type cannot be determined, returns 'undefined'. @@ -9512,17 +9540,12 @@ namespace ts { return anyType; } - // Resolve the signatures, preferring constructors - let signatures = getSignaturesOfType(valueType, SignatureKind.Construct); + // Resolve the signatures + let signatures = getConstructOrCallSignature(valueType); if (signatures.length === 0) { - // No construct signatures, try call signatures - signatures = getSignaturesOfType(valueType, SignatureKind.Call); - - if (signatures.length === 0) { - // We found no signatures at all, which is an error - error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); - return unknownType; - } + // We found no signatures at all, which is an error + error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); + return unknownType; } return getUnionType(signatures.map(getReturnTypeOfSignature)); From c808d444036b8c41c2bf3bbe725e279bc0481487 Mon Sep 17 00:00:00 2001 From: Evan Sebastian Date: Thu, 19 May 2016 05:46:07 +0700 Subject: [PATCH 2/7] Requires the construct/call signature for each type in the union instead of whole. --- src/compiler/checker.ts | 64 ++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 580a19f5b2eda..c9b69ddd33e5c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9499,34 +9499,6 @@ namespace ts { return links.resolvedSymbol; } - /** - * Given a type, resolve its constructor signatures. - * If no construct signatures, call signatures are resolved. - * If the type is a union type, recursively resolve the signatures using - * the same method. - * Return collected construct/call signature. - */ - function getConstructOrCallSignature(type: Type): Signature[] { - if (type.flags & TypeFlags.Union) { - let signatures: Signature[] = []; - forEach(( type).types, (childType) => { - let childSignatures = getConstructOrCallSignature(childType); - forEach(childSignatures, (signature) => { - signatures.push(signature); - }) - }) - return signatures; - } else { - let signatures = getSignaturesOfType(type, SignatureKind.Construct); - if (signatures.length === 0) { - // No construct signatures, try call signatures - return getSignaturesOfType(type, SignatureKind.Call); - } else { - return signatures; - } - } - } - /** * Given a JSX element that is a class element, finds the Element Instance Type. If the * element is not a class element, or the class element type cannot be determined, returns 'undefined'. @@ -9540,11 +9512,43 @@ namespace ts { return anyType; } + /** + * Given a type, resolve its constructor signatures. + * If no construct signatures, it should have call signature, + * otherwise it's not a valid React component. + */ + function getConstructOrCallSignature(type: Type): Signature[] { + // If the type is a union type, recursively resolve the signatures. + if (type.flags & TypeFlags.Union) { + const signatures: Signature[] = []; + forEach(( type).types, (childType) => { + const childSignatures = getConstructOrCallSignature(childType); + forEach(childSignatures, (signature) => { + signatures.push(signature); + }) + }) + return signatures; + } else { + const constructSignatures = getSignaturesOfType(type, SignatureKind.Construct); + if (constructSignatures.length === 0) { + // No construct signatures, try call signatures + const callSignatures = getSignaturesOfType(type, SignatureKind.Call); + if (callSignatures.length === 0) { + // We found no signatures at all, which is an error + error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); + return []; + } else { + return callSignatures; + } + } else { + return constructSignatures; + } + } + } + // Resolve the signatures let signatures = getConstructOrCallSignature(valueType); if (signatures.length === 0) { - // We found no signatures at all, which is an error - error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); return unknownType; } From f1643c7a8a22bec42c846c61ba961f06907d0e02 Mon Sep 17 00:00:00 2001 From: Evan Sebastian Date: Thu, 19 May 2016 06:00:35 +0700 Subject: [PATCH 3/7] Fix lint errors --- src/compiler/checker.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c9b69ddd33e5c..f1d90c00bdedc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9525,10 +9525,11 @@ namespace ts { const childSignatures = getConstructOrCallSignature(childType); forEach(childSignatures, (signature) => { signatures.push(signature); - }) - }) + }); + }); return signatures; - } else { + } + else { const constructSignatures = getSignaturesOfType(type, SignatureKind.Construct); if (constructSignatures.length === 0) { // No construct signatures, try call signatures @@ -9537,17 +9538,19 @@ namespace ts { // We found no signatures at all, which is an error error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); return []; - } else { + } + else { return callSignatures; } - } else { + } + else { return constructSignatures; } } } // Resolve the signatures - let signatures = getConstructOrCallSignature(valueType); + const signatures = getConstructOrCallSignature(valueType); if (signatures.length === 0) { return unknownType; } From 44ace668888ab7da54a379cf351e3b94f4b0268b Mon Sep 17 00:00:00 2001 From: Evan Sebastian Date: Thu, 19 May 2016 21:53:11 +0700 Subject: [PATCH 4/7] Handles resolving JSX type for union typed element class --- src/compiler/checker.ts | 258 +++++++++++++++++++++------------------- 1 file changed, 133 insertions(+), 125 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f1d90c00bdedc..dcea5667eb90e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9499,59 +9499,51 @@ namespace ts { return links.resolvedSymbol; } + /** + * Given a type, resolve its constructor signatures. + * If no construct signatures, it should have call signature, + * otherwise it's not a valid React component. + */ + function getConstructOrCallSignature(type: Type): Signature[] { + // If the type is a union type, recursively resolve the signatures. + if (type.flags & TypeFlags.Union) { + const signatures: Signature[] = []; + forEach(( type).types, (childType) => { + const childSignatures = getConstructOrCallSignature(childType); + forEach(childSignatures, (signature) => { + signatures.push(signature); + }); + }); + return signatures; + } + else { + const constructSignatures = getSignaturesOfType(type, SignatureKind.Construct); + if (constructSignatures.length === 0) { + // No construct signatures, try call signatures + return getSignaturesOfType(type, SignatureKind.Call); + } + else { + return constructSignatures; + } + } + } + /** * Given a JSX element that is a class element, finds the Element Instance Type. If the * element is not a class element, or the class element type cannot be determined, returns 'undefined'. * For example, in the element , the element instance type is `MyClass` (not `typeof MyClass`). */ - function getJsxElementInstanceType(node: JsxOpeningLikeElement) { - const valueType = checkExpression(node.tagName); - + function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type) { if (isTypeAny(valueType)) { // Short-circuit if the class tag is using an element type 'any' return anyType; } - /** - * Given a type, resolve its constructor signatures. - * If no construct signatures, it should have call signature, - * otherwise it's not a valid React component. - */ - function getConstructOrCallSignature(type: Type): Signature[] { - // If the type is a union type, recursively resolve the signatures. - if (type.flags & TypeFlags.Union) { - const signatures: Signature[] = []; - forEach(( type).types, (childType) => { - const childSignatures = getConstructOrCallSignature(childType); - forEach(childSignatures, (signature) => { - signatures.push(signature); - }); - }); - return signatures; - } - else { - const constructSignatures = getSignaturesOfType(type, SignatureKind.Construct); - if (constructSignatures.length === 0) { - // No construct signatures, try call signatures - const callSignatures = getSignaturesOfType(type, SignatureKind.Call); - if (callSignatures.length === 0) { - // We found no signatures at all, which is an error - error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); - return []; - } - else { - return callSignatures; - } - } - else { - return constructSignatures; - } - } - } - // Resolve the signatures const signatures = getConstructOrCallSignature(valueType); if (signatures.length === 0) { + // We found no signatures at all, which is an error + error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); return unknownType; } @@ -9595,6 +9587,103 @@ namespace ts { } } + /** + * Given React element instance type and the class type, resolve the Jsx type + * Pass elemType to handle individual type in the union typed element type. + */ + function getResolvedJsxType(node: JsxOpeningLikeElement, elemType?: Type, elemClassType?: Type): Type { + if (!elemType) { + elemType = checkExpression(node.tagName); + } + if (elemType.flags & TypeFlags.Union) { + const types = ( elemType).types; + return getUnionType(types.map(type => { + return getResolvedJsxType(node, type, elemClassType); + })); + } + + // Get the element instance type (the result of newing or invoking this tag) + const elemInstanceType = getJsxElementInstanceType(node, elemType); + + if (!elemClassType || !isTypeAssignableTo(elemInstanceType, elemClassType)) { + // Is this is a stateless function component? See if its single signature's return type is + // assignable to the JSX Element Type + if (jsxElementType) { + const callSignatures = elemType && getSignaturesOfType(elemType, SignatureKind.Call); + const callSignature = callSignatures && callSignatures.length > 0 && callSignatures[0]; + const callReturnType = callSignature && getReturnTypeOfSignature(callSignature); + let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0])); + if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType)) { + // Intersect in JSX.IntrinsicAttributes if it exists + const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes); + if (intrinsicAttributes !== unknownType) { + paramType = intersectTypes(intrinsicAttributes, paramType); + } + return paramType; + } + } + } + + // Issue an error if this return type isn't assignable to JSX.ElementClass + if (elemClassType) { + checkTypeRelatedTo(elemInstanceType, elemClassType, assignableRelation, node, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); + } + + if (isTypeAny(elemInstanceType)) { + return elemInstanceType; + } + + const propsName = getJsxElementPropertiesName(); + if (propsName === undefined) { + // There is no type ElementAttributesProperty, return 'any' + return anyType; + } + else if (propsName === "") { + // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead + return elemInstanceType; + } + else { + const attributesType = getTypeOfPropertyOfType(elemInstanceType, propsName); + + if (!attributesType) { + // There is no property named 'props' on this instance type + return emptyObjectType; + } + else if (isTypeAny(attributesType) || (attributesType === unknownType)) { + // Props is of type 'any' or unknown + return attributesType; + } + else if (attributesType.flags & TypeFlags.Union) { + // Props cannot be a union type + error(node.tagName, Diagnostics.JSX_element_attributes_type_0_may_not_be_a_union_type, typeToString(attributesType)); + return anyType; + } + else { + // Normal case -- add in IntrinsicClassElements and IntrinsicElements + let apparentAttributesType = attributesType; + const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes); + if (intrinsicClassAttribs !== unknownType) { + const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); + if (typeParams) { + if (typeParams.length === 1) { + apparentAttributesType = intersectTypes(createTypeReference(intrinsicClassAttribs, [elemInstanceType]), apparentAttributesType); + } + } + else { + apparentAttributesType = intersectTypes(attributesType, intrinsicClassAttribs); + } + } + + const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes); + if (intrinsicAttribs !== unknownType) { + apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); + } + + return apparentAttributesType; + } + } + } + /** * Given an opening/self-closing element, get the 'element attributes type', i.e. the type that tells * us which attributes are valid on a given element. @@ -9610,96 +9699,15 @@ namespace ts { else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) { return links.resolvedJsxType = getIndexInfoOfSymbol(symbol, IndexKind.String).type; } + else { + return links.resolvedJsxType = unknownType; + } } else { - // Get the element instance type (the result of newing or invoking this tag) - const elemInstanceType = getJsxElementInstanceType(node); - const elemClassType = getJsxGlobalElementClassType(); - - if (!elemClassType || !isTypeAssignableTo(elemInstanceType, elemClassType)) { - // Is this is a stateless function component? See if its single signature's return type is - // assignable to the JSX Element Type - if (jsxElementType) { - const elemType = checkExpression(node.tagName); - const callSignatures = elemType && getSignaturesOfType(elemType, SignatureKind.Call); - const callSignature = callSignatures && callSignatures.length > 0 && callSignatures[0]; - const callReturnType = callSignature && getReturnTypeOfSignature(callSignature); - let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0])); - if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType)) { - // Intersect in JSX.IntrinsicAttributes if it exists - const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes); - if (intrinsicAttributes !== unknownType) { - paramType = intersectTypes(intrinsicAttributes, paramType); - } - return links.resolvedJsxType = paramType; - } - } - } - - // Issue an error if this return type isn't assignable to JSX.ElementClass - if (elemClassType) { - checkTypeRelatedTo(elemInstanceType, elemClassType, assignableRelation, node, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); - } - - if (isTypeAny(elemInstanceType)) { - return links.resolvedJsxType = elemInstanceType; - } - - const propsName = getJsxElementPropertiesName(); - if (propsName === undefined) { - // There is no type ElementAttributesProperty, return 'any' - return links.resolvedJsxType = anyType; - } - else if (propsName === "") { - // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead - return links.resolvedJsxType = elemInstanceType; - } - else { - const attributesType = getTypeOfPropertyOfType(elemInstanceType, propsName); - - if (!attributesType) { - // There is no property named 'props' on this instance type - return links.resolvedJsxType = emptyObjectType; - } - else if (isTypeAny(attributesType) || (attributesType === unknownType)) { - // Props is of type 'any' or unknown - return links.resolvedJsxType = attributesType; - } - else if (attributesType.flags & TypeFlags.Union) { - // Props cannot be a union type - error(node.tagName, Diagnostics.JSX_element_attributes_type_0_may_not_be_a_union_type, typeToString(attributesType)); - return links.resolvedJsxType = anyType; - } - else { - // Normal case -- add in IntrinsicClassElements and IntrinsicElements - let apparentAttributesType = attributesType; - const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes); - if (intrinsicClassAttribs !== unknownType) { - const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); - if (typeParams) { - if (typeParams.length === 1) { - apparentAttributesType = intersectTypes(createTypeReference(intrinsicClassAttribs, [elemInstanceType]), apparentAttributesType); - } - } - else { - apparentAttributesType = intersectTypes(attributesType, intrinsicClassAttribs); - } - } - - const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes); - if (intrinsicAttribs !== unknownType) { - apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); - } - - return links.resolvedJsxType = apparentAttributesType; - } - } + return links.resolvedJsxType = getResolvedJsxType(node, undefined, elemClassType); } - - return links.resolvedJsxType = unknownType; } - return links.resolvedJsxType; } From 9d1922a3a59d8a62a6f4864d4353e4fa2f700474 Mon Sep 17 00:00:00 2001 From: Evan Sebastian Date: Thu, 19 May 2016 22:22:53 +0700 Subject: [PATCH 5/7] Add test for #8657 --- .../tsxUnionTypeComponent.errors.txt | 35 +++++++++ .../reference/tsxUnionTypeComponent.js | 71 +++++++++++++++++++ .../conformance/jsx/tsxUnionTypeComponent.tsx | 32 +++++++++ 3 files changed, 138 insertions(+) create mode 100644 tests/baselines/reference/tsxUnionTypeComponent.errors.txt create mode 100644 tests/baselines/reference/tsxUnionTypeComponent.js create mode 100644 tests/cases/conformance/jsx/tsxUnionTypeComponent.tsx diff --git a/tests/baselines/reference/tsxUnionTypeComponent.errors.txt b/tests/baselines/reference/tsxUnionTypeComponent.errors.txt new file mode 100644 index 0000000000000..6650db4533b37 --- /dev/null +++ b/tests/baselines/reference/tsxUnionTypeComponent.errors.txt @@ -0,0 +1,35 @@ +tests/cases/conformance/jsx/file.tsx(28,2): error TS2604: JSX element type 'X' does not have any construct or call signatures. + + +==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== + + import React = require('react'); + + interface ComponentProps { + AnyComponent: React.StatelessComponent | React.ComponentClass; + } + + class MyComponent extends React.Component { + render() { + const { AnyComponent } = this.props; + const someProps = {}; + const button = + return (
{button}
); + } + } + + }/> + + class MyButtonComponent extends React.Component<{},{}> { + } + + + + type Invalid = string | React.ComponentClass; + + var X: Invalid = ""; + + // Should fail + ~ +!!! error TS2604: JSX element type 'X' does not have any construct or call signatures. + \ No newline at end of file diff --git a/tests/baselines/reference/tsxUnionTypeComponent.js b/tests/baselines/reference/tsxUnionTypeComponent.js new file mode 100644 index 0000000000000..5b105abcb83fc --- /dev/null +++ b/tests/baselines/reference/tsxUnionTypeComponent.js @@ -0,0 +1,71 @@ +//// [file.tsx] + +import React = require('react'); + +interface ComponentProps { + AnyComponent: React.StatelessComponent | React.ComponentClass; +} + +class MyComponent extends React.Component { + render() { + const { AnyComponent } = this.props; + const someProps = {}; + const button = + return (
{button}
); + } +} + + }/> + +class MyButtonComponent extends React.Component<{},{}> { +} + + + +type Invalid = string | React.ComponentClass; + +var X: Invalid = ""; + + // Should fail + + +//// [file.js] +"use strict"; +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var __assign = (this && this.__assign) || Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; +}; +var React = require('react'); +var MyComponent = (function (_super) { + __extends(MyComponent, _super); + function MyComponent() { + _super.apply(this, arguments); + } + MyComponent.prototype.render = function () { + var AnyComponent = this.props.AnyComponent; + var someProps = {}; + var button = React.createElement(AnyComponent, __assign({}, someProps)); + return (React.createElement("div", null, button)); + }; + return MyComponent; +}(React.Component)); +React.createElement(MyComponent, {AnyComponent: function () { return React.createElement("button", null, "test"); }}); +var MyButtonComponent = (function (_super) { + __extends(MyButtonComponent, _super); + function MyButtonComponent() { + _super.apply(this, arguments); + } + return MyButtonComponent; +}(React.Component)); +React.createElement(MyComponent, {AnyComponent: MyButtonComponent}); +var X = ""; +React.createElement(X, null); // Should fail diff --git a/tests/cases/conformance/jsx/tsxUnionTypeComponent.tsx b/tests/cases/conformance/jsx/tsxUnionTypeComponent.tsx new file mode 100644 index 0000000000000..073d4f3720062 --- /dev/null +++ b/tests/cases/conformance/jsx/tsxUnionTypeComponent.tsx @@ -0,0 +1,32 @@ +// @filename: file.tsx +// @jsx: react +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +interface ComponentProps { + AnyComponent: React.StatelessComponent | React.ComponentClass; +} + +class MyComponent extends React.Component { + render() { + const { AnyComponent } = this.props; + const someProps = {}; + const button = + return (
{button}
); + } +} + + }/> + +class MyButtonComponent extends React.Component<{},{}> { +} + + + +type Invalid = string | React.ComponentClass; + +var X: Invalid = ""; + + // Should fail From 8ef350c76244af643731a20da556cb77d4308a2a Mon Sep 17 00:00:00 2001 From: Evan Sebastian Date: Thu, 19 May 2016 22:41:03 +0700 Subject: [PATCH 6/7] Simplify the checker, no recursive checking for signature is needed anymore. --- src/compiler/checker.ts | 44 ++++++++++------------------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dcea5667eb90e..12f68f7b41d56 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9499,52 +9499,28 @@ namespace ts { return links.resolvedSymbol; } - /** - * Given a type, resolve its constructor signatures. - * If no construct signatures, it should have call signature, - * otherwise it's not a valid React component. - */ - function getConstructOrCallSignature(type: Type): Signature[] { - // If the type is a union type, recursively resolve the signatures. - if (type.flags & TypeFlags.Union) { - const signatures: Signature[] = []; - forEach(( type).types, (childType) => { - const childSignatures = getConstructOrCallSignature(childType); - forEach(childSignatures, (signature) => { - signatures.push(signature); - }); - }); - return signatures; - } - else { - const constructSignatures = getSignaturesOfType(type, SignatureKind.Construct); - if (constructSignatures.length === 0) { - // No construct signatures, try call signatures - return getSignaturesOfType(type, SignatureKind.Call); - } - else { - return constructSignatures; - } - } - } - /** * Given a JSX element that is a class element, finds the Element Instance Type. If the * element is not a class element, or the class element type cannot be determined, returns 'undefined'. * For example, in the element , the element instance type is `MyClass` (not `typeof MyClass`). */ function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type) { + Debug.assert(!(valueType.flags & TypeFlags.Union)); if (isTypeAny(valueType)) { // Short-circuit if the class tag is using an element type 'any' return anyType; } - // Resolve the signatures - const signatures = getConstructOrCallSignature(valueType); + // Resolve the signatures, preferring constructor + let signatures = getSignaturesOfType(valueType, SignatureKind.Construct); if (signatures.length === 0) { - // We found no signatures at all, which is an error - error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); - return unknownType; + // No construct signatures, try call signatures + signatures = getSignaturesOfType(valueType, SignatureKind.Call); + if (signatures.length === 0) { + // We found no signatures at all, which is an error + error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); + return unknownType; + } } return getUnionType(signatures.map(getReturnTypeOfSignature)); From 01b541dbe2f63a427e24550cdd15eebd68a1f3c1 Mon Sep 17 00:00:00 2001 From: Evan Sebastian Date: Fri, 20 May 2016 01:19:35 +0700 Subject: [PATCH 7/7] Simplify and split + and - test --- .../tsxUnionTypeComponent.errors.txt | 35 ---------- ...Component.js => tsxUnionTypeComponent1.js} | 27 ++------ .../reference/tsxUnionTypeComponent1.symbols | 58 +++++++++++++++++ .../reference/tsxUnionTypeComponent1.types | 64 +++++++++++++++++++ .../tsxUnionTypeComponent2.errors.txt | 17 +++++ .../reference/tsxUnionTypeComponent2.js | 18 ++++++ ...mponent.tsx => tsxUnionTypeComponent1.tsx} | 11 +--- .../jsx/tsxUnionTypeComponent2.tsx | 14 ++++ 8 files changed, 180 insertions(+), 64 deletions(-) delete mode 100644 tests/baselines/reference/tsxUnionTypeComponent.errors.txt rename tests/baselines/reference/{tsxUnionTypeComponent.js => tsxUnionTypeComponent1.js} (66%) create mode 100644 tests/baselines/reference/tsxUnionTypeComponent1.symbols create mode 100644 tests/baselines/reference/tsxUnionTypeComponent1.types create mode 100644 tests/baselines/reference/tsxUnionTypeComponent2.errors.txt create mode 100644 tests/baselines/reference/tsxUnionTypeComponent2.js rename tests/cases/conformance/jsx/{tsxUnionTypeComponent.tsx => tsxUnionTypeComponent1.tsx} (71%) create mode 100644 tests/cases/conformance/jsx/tsxUnionTypeComponent2.tsx diff --git a/tests/baselines/reference/tsxUnionTypeComponent.errors.txt b/tests/baselines/reference/tsxUnionTypeComponent.errors.txt deleted file mode 100644 index 6650db4533b37..0000000000000 --- a/tests/baselines/reference/tsxUnionTypeComponent.errors.txt +++ /dev/null @@ -1,35 +0,0 @@ -tests/cases/conformance/jsx/file.tsx(28,2): error TS2604: JSX element type 'X' does not have any construct or call signatures. - - -==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== - - import React = require('react'); - - interface ComponentProps { - AnyComponent: React.StatelessComponent | React.ComponentClass; - } - - class MyComponent extends React.Component { - render() { - const { AnyComponent } = this.props; - const someProps = {}; - const button = - return (
{button}
); - } - } - - }/> - - class MyButtonComponent extends React.Component<{},{}> { - } - - - - type Invalid = string | React.ComponentClass; - - var X: Invalid = ""; - - // Should fail - ~ -!!! error TS2604: JSX element type 'X' does not have any construct or call signatures. - \ No newline at end of file diff --git a/tests/baselines/reference/tsxUnionTypeComponent.js b/tests/baselines/reference/tsxUnionTypeComponent1.js similarity index 66% rename from tests/baselines/reference/tsxUnionTypeComponent.js rename to tests/baselines/reference/tsxUnionTypeComponent1.js index 5b105abcb83fc..11b41a2aa3d1b 100644 --- a/tests/baselines/reference/tsxUnionTypeComponent.js +++ b/tests/baselines/reference/tsxUnionTypeComponent1.js @@ -9,24 +9,19 @@ interface ComponentProps { class MyComponent extends React.Component { render() { const { AnyComponent } = this.props; - const someProps = {}; - const button = - return (
{button}
); + return (); } } +// Stateless Component As Props }/> +// Component Class as Props class MyButtonComponent extends React.Component<{},{}> { } -type Invalid = string | React.ComponentClass; - -var X: Invalid = ""; - - // Should fail //// [file.js] @@ -36,14 +31,6 @@ var __extends = (this && this.__extends) || function (d, b) { function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; -var __assign = (this && this.__assign) || Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; -}; var React = require('react'); var MyComponent = (function (_super) { __extends(MyComponent, _super); @@ -52,13 +39,13 @@ var MyComponent = (function (_super) { } MyComponent.prototype.render = function () { var AnyComponent = this.props.AnyComponent; - var someProps = {}; - var button = React.createElement(AnyComponent, __assign({}, someProps)); - return (React.createElement("div", null, button)); + return (React.createElement(AnyComponent, null)); }; return MyComponent; }(React.Component)); +// Stateless Component As Props React.createElement(MyComponent, {AnyComponent: function () { return React.createElement("button", null, "test"); }}); +// Component Class as Props var MyButtonComponent = (function (_super) { __extends(MyButtonComponent, _super); function MyButtonComponent() { @@ -67,5 +54,3 @@ var MyButtonComponent = (function (_super) { return MyButtonComponent; }(React.Component)); React.createElement(MyComponent, {AnyComponent: MyButtonComponent}); -var X = ""; -React.createElement(X, null); // Should fail diff --git a/tests/baselines/reference/tsxUnionTypeComponent1.symbols b/tests/baselines/reference/tsxUnionTypeComponent1.symbols new file mode 100644 index 0000000000000..73f7180f3b8cd --- /dev/null +++ b/tests/baselines/reference/tsxUnionTypeComponent1.symbols @@ -0,0 +1,58 @@ +=== tests/cases/conformance/jsx/file.tsx === + +import React = require('react'); +>React : Symbol(React, Decl(file.tsx, 0, 0)) + +interface ComponentProps { +>ComponentProps : Symbol(ComponentProps, Decl(file.tsx, 1, 32)) + + AnyComponent: React.StatelessComponent | React.ComponentClass; +>AnyComponent : Symbol(ComponentProps.AnyComponent, Decl(file.tsx, 3, 26)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>StatelessComponent : Symbol(React.StatelessComponent, Decl(react.d.ts, 139, 5)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>ComponentClass : Symbol(React.ComponentClass, Decl(react.d.ts, 150, 5)) +} + +class MyComponent extends React.Component { +>MyComponent : Symbol(MyComponent, Decl(file.tsx, 5, 1)) +>React.Component : Symbol(React.Component, Decl(react.d.ts, 114, 55)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>Component : Symbol(React.Component, Decl(react.d.ts, 114, 55)) +>ComponentProps : Symbol(ComponentProps, Decl(file.tsx, 1, 32)) + + render() { +>render : Symbol(MyComponent.render, Decl(file.tsx, 7, 63)) + + const { AnyComponent } = this.props; +>AnyComponent : Symbol(AnyComponent, Decl(file.tsx, 9, 15)) +>this.props : Symbol(React.Component.props, Decl(react.d.ts, 122, 30)) +>this : Symbol(MyComponent, Decl(file.tsx, 5, 1)) +>props : Symbol(React.Component.props, Decl(react.d.ts, 122, 30)) + + return (); +>AnyComponent : Symbol(AnyComponent, Decl(file.tsx, 9, 15)) + } +} + +// Stateless Component As Props + }/> +>MyComponent : Symbol(MyComponent, Decl(file.tsx, 5, 1)) +>AnyComponent : Symbol(ComponentProps.AnyComponent, Decl(file.tsx, 3, 26)) +>button : Symbol(JSX.IntrinsicElements.button, Decl(react.d.ts, 913, 43)) +>button : Symbol(JSX.IntrinsicElements.button, Decl(react.d.ts, 913, 43)) + +// Component Class as Props +class MyButtonComponent extends React.Component<{},{}> { +>MyButtonComponent : Symbol(MyButtonComponent, Decl(file.tsx, 15, 57)) +>React.Component : Symbol(React.Component, Decl(react.d.ts, 114, 55)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>Component : Symbol(React.Component, Decl(react.d.ts, 114, 55)) +} + + +>MyComponent : Symbol(MyComponent, Decl(file.tsx, 5, 1)) +>AnyComponent : Symbol(ComponentProps.AnyComponent, Decl(file.tsx, 3, 26)) +>MyButtonComponent : Symbol(MyButtonComponent, Decl(file.tsx, 15, 57)) + + diff --git a/tests/baselines/reference/tsxUnionTypeComponent1.types b/tests/baselines/reference/tsxUnionTypeComponent1.types new file mode 100644 index 0000000000000..ce6e3bbeecc6c --- /dev/null +++ b/tests/baselines/reference/tsxUnionTypeComponent1.types @@ -0,0 +1,64 @@ +=== tests/cases/conformance/jsx/file.tsx === + +import React = require('react'); +>React : typeof React + +interface ComponentProps { +>ComponentProps : ComponentProps + + AnyComponent: React.StatelessComponent | React.ComponentClass; +>AnyComponent : React.StatelessComponent | React.ComponentClass +>React : any +>StatelessComponent : React.StatelessComponent

+>React : any +>ComponentClass : React.ComponentClass

+} + +class MyComponent extends React.Component { +>MyComponent : MyComponent +>React.Component : React.Component +>React : typeof React +>Component : typeof React.Component +>ComponentProps : ComponentProps + + render() { +>render : () => JSX.Element + + const { AnyComponent } = this.props; +>AnyComponent : React.StatelessComponent | React.ComponentClass +>this.props : ComponentProps +>this : this +>props : ComponentProps + + return (); +>() : JSX.Element +> : JSX.Element +>AnyComponent : React.StatelessComponent | React.ComponentClass + } +} + +// Stateless Component As Props + }/> +> }/> : JSX.Element +>MyComponent : typeof MyComponent +>AnyComponent : any +>() => : () => JSX.Element +> : JSX.Element +>button : any +>button : any + +// Component Class as Props +class MyButtonComponent extends React.Component<{},{}> { +>MyButtonComponent : MyButtonComponent +>React.Component : React.Component<{}, {}> +>React : typeof React +>Component : typeof React.Component +} + + +> : JSX.Element +>MyComponent : typeof MyComponent +>AnyComponent : any +>MyButtonComponent : typeof MyButtonComponent + + diff --git a/tests/baselines/reference/tsxUnionTypeComponent2.errors.txt b/tests/baselines/reference/tsxUnionTypeComponent2.errors.txt new file mode 100644 index 0000000000000..e5a5c77da4c47 --- /dev/null +++ b/tests/baselines/reference/tsxUnionTypeComponent2.errors.txt @@ -0,0 +1,17 @@ +tests/cases/conformance/jsx/file.tsx(8,2): error TS2604: JSX element type 'X' does not have any construct or call signatures. + + +==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== + + import React = require('react'); + + type Invalid1 = React.ComponentClass | string; + + const X: Invalid1 = "Should fail to construct"; + + ; + ~ +!!! error TS2604: JSX element type 'X' does not have any construct or call signatures. + + + \ No newline at end of file diff --git a/tests/baselines/reference/tsxUnionTypeComponent2.js b/tests/baselines/reference/tsxUnionTypeComponent2.js new file mode 100644 index 0000000000000..18e86eb8b4c7a --- /dev/null +++ b/tests/baselines/reference/tsxUnionTypeComponent2.js @@ -0,0 +1,18 @@ +//// [file.tsx] + +import React = require('react'); + +type Invalid1 = React.ComponentClass | string; + +const X: Invalid1 = "Should fail to construct"; + +; + + + + +//// [file.js] +"use strict"; +var React = require('react'); +var X = "Should fail to construct"; +React.createElement(X, null); diff --git a/tests/cases/conformance/jsx/tsxUnionTypeComponent.tsx b/tests/cases/conformance/jsx/tsxUnionTypeComponent1.tsx similarity index 71% rename from tests/cases/conformance/jsx/tsxUnionTypeComponent.tsx rename to tests/cases/conformance/jsx/tsxUnionTypeComponent1.tsx index 073d4f3720062..b9e50357185a8 100644 --- a/tests/cases/conformance/jsx/tsxUnionTypeComponent.tsx +++ b/tests/cases/conformance/jsx/tsxUnionTypeComponent1.tsx @@ -12,21 +12,16 @@ interface ComponentProps { class MyComponent extends React.Component { render() { const { AnyComponent } = this.props; - const someProps = {}; - const button = - return (

{button}
); + return (); } } +// Stateless Component As Props }/> +// Component Class as Props class MyButtonComponent extends React.Component<{},{}> { } -type Invalid = string | React.ComponentClass; - -var X: Invalid = ""; - - // Should fail diff --git a/tests/cases/conformance/jsx/tsxUnionTypeComponent2.tsx b/tests/cases/conformance/jsx/tsxUnionTypeComponent2.tsx new file mode 100644 index 0000000000000..3980721966115 --- /dev/null +++ b/tests/cases/conformance/jsx/tsxUnionTypeComponent2.tsx @@ -0,0 +1,14 @@ +// @filename: file.tsx +// @jsx: react +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +type Invalid1 = React.ComponentClass | string; + +const X: Invalid1 = "Should fail to construct"; + +; + +