From b90d2910fbe8cf95b7703a59da6f1df5b54f201b Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 15 Nov 2018 13:46:32 -0800 Subject: [PATCH] Strongly typecheck unions of intrinsic tag names (#28557) --- src/compiler/checker.ts | 42 ++++++++++--------- .../baselines/reference/jsxIntrinsicUnions.js | 17 ++++++++ .../reference/jsxIntrinsicUnions.symbols | 19 +++++++++ .../reference/jsxIntrinsicUnions.types | 25 +++++++++++ .../reference/tsxDynamicTagName3.errors.txt | 7 +++- tests/cases/compiler/jsxIntrinsicUnions.tsx | 8 ++++ 6 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 tests/baselines/reference/jsxIntrinsicUnions.js create mode 100644 tests/baselines/reference/jsxIntrinsicUnions.symbols create mode 100644 tests/baselines/reference/jsxIntrinsicUnions.types create mode 100644 tests/cases/compiler/jsxIntrinsicUnions.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6f35378959a..e88da2c261c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18355,16 +18355,31 @@ namespace ts { return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace); } - function getUninstantiatedJsxSignaturesOfType(elementType: Type): ReadonlyArray { + function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxOpeningLikeElement): ReadonlyArray { + if (elementType.flags & TypeFlags.String) { + return [anySignature]; + } + else if (elementType.flags & TypeFlags.StringLiteral) { + const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType(elementType as StringLiteralType, caller); + if (!intrinsicType) { + error(caller, Diagnostics.Property_0_does_not_exist_on_type_1, (elementType as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements); + return emptyArray; + } + else { + const fakeSignature = createSignatureForJSXIntrinsic(caller, intrinsicType); + return [fakeSignature]; + } + } + const apparentElemType = getApparentType(elementType); // Resolve the signatures, preferring constructor - let signatures = getSignaturesOfType(elementType, SignatureKind.Construct); + let signatures = getSignaturesOfType(apparentElemType, SignatureKind.Construct); if (signatures.length === 0) { // No construct signatures, try call signatures - signatures = getSignaturesOfType(elementType, SignatureKind.Call); + signatures = getSignaturesOfType(apparentElemType, SignatureKind.Call); } - if (signatures.length === 0 && elementType.flags & TypeFlags.Union) { + if (signatures.length === 0 && apparentElemType.flags & TypeFlags.Union) { // If each member has some combination of new/call signatures; make a union signature list for those - signatures = getUnionSignatures(map((elementType as UnionType).types, getUninstantiatedJsxSignaturesOfType)); + signatures = getUnionSignatures(map((apparentElemType as UnionType).types, t => getUninstantiatedJsxSignaturesOfType(t, caller))); } return signatures; } @@ -20547,21 +20562,8 @@ namespace ts { return resolveErrorCall(node); } - if (exprTypes.flags & TypeFlags.StringLiteral) { - const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType(exprTypes as StringLiteralType, node); - if (!intrinsicType) { - error(node, Diagnostics.Property_0_does_not_exist_on_type_1, (exprTypes as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements); - return resolveUntypedCall(node); - } - else { - const fakeSignature = createSignatureForJSXIntrinsic(node, intrinsicType); - checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined), intrinsicType, node.tagName, node.attributes); - return fakeSignature; - } - } - - const signatures = getUninstantiatedJsxSignaturesOfType(apparentType); - if (exprTypes.flags & TypeFlags.String || isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { + const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); + if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { return resolveUntypedCall(node); } diff --git a/tests/baselines/reference/jsxIntrinsicUnions.js b/tests/baselines/reference/jsxIntrinsicUnions.js new file mode 100644 index 00000000000..7b5a8fdbbc3 --- /dev/null +++ b/tests/baselines/reference/jsxIntrinsicUnions.js @@ -0,0 +1,17 @@ +//// [jsxIntrinsicUnions.tsx] +/// + +import * as React from "react"; + +const El = Math.random() ? 'h1' : 'h2'; + +const tag = {"Title"}; + + +//// [jsxIntrinsicUnions.js] +"use strict"; +/// +exports.__esModule = true; +var React = require("react"); +var El = Math.random() ? 'h1' : 'h2'; +var tag = React.createElement(El, { className: "ok", key: "key" }, "Title"); diff --git a/tests/baselines/reference/jsxIntrinsicUnions.symbols b/tests/baselines/reference/jsxIntrinsicUnions.symbols new file mode 100644 index 00000000000..4e13fa9e6ba --- /dev/null +++ b/tests/baselines/reference/jsxIntrinsicUnions.symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/jsxIntrinsicUnions.tsx === +/// + +import * as React from "react"; +>React : Symbol(React, Decl(jsxIntrinsicUnions.tsx, 2, 6)) + +const El = Math.random() ? 'h1' : 'h2'; +>El : Symbol(El, Decl(jsxIntrinsicUnions.tsx, 4, 5)) +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + +const tag = {"Title"}; +>tag : Symbol(tag, Decl(jsxIntrinsicUnions.tsx, 6, 5)) +>El : Symbol(El, Decl(jsxIntrinsicUnions.tsx, 4, 5)) +>className : Symbol(className, Decl(jsxIntrinsicUnions.tsx, 6, 15)) +>key : Symbol(key, Decl(jsxIntrinsicUnions.tsx, 6, 30)) +>El : Symbol(El, Decl(jsxIntrinsicUnions.tsx, 4, 5)) + diff --git a/tests/baselines/reference/jsxIntrinsicUnions.types b/tests/baselines/reference/jsxIntrinsicUnions.types new file mode 100644 index 00000000000..5d6a8d79f35 --- /dev/null +++ b/tests/baselines/reference/jsxIntrinsicUnions.types @@ -0,0 +1,25 @@ +=== tests/cases/compiler/jsxIntrinsicUnions.tsx === +/// + +import * as React from "react"; +>React : typeof React + +const El = Math.random() ? 'h1' : 'h2'; +>El : "h1" | "h2" +>Math.random() ? 'h1' : 'h2' : "h1" | "h2" +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>'h1' : "h1" +>'h2' : "h2" + +const tag = {"Title"}; +>tag : JSX.Element +>{"Title"} : JSX.Element +>El : "h1" | "h2" +>className : string +>key : string +>"Title" : "Title" +>El : "h1" | "h2" + diff --git a/tests/baselines/reference/tsxDynamicTagName3.errors.txt b/tests/baselines/reference/tsxDynamicTagName3.errors.txt index 406a12bd14f..56b86d6ffaa 100644 --- a/tests/baselines/reference/tsxDynamicTagName3.errors.txt +++ b/tests/baselines/reference/tsxDynamicTagName3.errors.txt @@ -1,7 +1,8 @@ tests/cases/conformance/jsx/tsxDynamicTagName3.tsx(9,1): error TS2339: Property 'h1' does not exist on type 'JSX.IntrinsicElements'. +tests/cases/conformance/jsx/tsxDynamicTagName3.tsx(9,2): error TS2604: JSX element type 'CustomTag' does not have any construct or call signatures. -==== tests/cases/conformance/jsx/tsxDynamicTagName3.tsx (1 errors) ==== +==== tests/cases/conformance/jsx/tsxDynamicTagName3.tsx (2 errors) ==== declare module JSX { interface Element { } interface IntrinsicElements { @@ -12,4 +13,6 @@ tests/cases/conformance/jsx/tsxDynamicTagName3.tsx(9,1): error TS2339: Property var CustomTag: "h1" = "h1"; Hello World // This should be an error. we will try look up string literal type in JSX.IntrinsicElements ~~~~~~~~~~~ -!!! error TS2339: Property 'h1' does not exist on type 'JSX.IntrinsicElements'. \ No newline at end of file +!!! error TS2339: Property 'h1' does not exist on type 'JSX.IntrinsicElements'. + ~~~~~~~~~ +!!! error TS2604: JSX element type 'CustomTag' does not have any construct or call signatures. \ No newline at end of file diff --git a/tests/cases/compiler/jsxIntrinsicUnions.tsx b/tests/cases/compiler/jsxIntrinsicUnions.tsx new file mode 100644 index 00000000000..68d84a38e22 --- /dev/null +++ b/tests/cases/compiler/jsxIntrinsicUnions.tsx @@ -0,0 +1,8 @@ +// @jsx: react +/// + +import * as React from "react"; + +const El = Math.random() ? 'h1' : 'h2'; + +const tag = {"Title"};