diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 62fc1a7cc418f..6e6d5127e6fe9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -401,6 +401,7 @@ namespace ts { const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + const resolvingSignaturesArray = [resolvingSignature]; const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false); @@ -15255,8 +15256,17 @@ namespace ts { } } - if (context.typeArguments) { - signatures = mapDefined(signatures, s => getJsxSignatureTypeArgumentInstantiation(s, context, isJs)); + const links = getNodeLinks(context); + if (!links.resolvedSignatures) { + links.resolvedSignatures = createMap(); + } + const cacheKey = "" + getTypeId(valueType); + if (links.resolvedSignatures.get(cacheKey) && links.resolvedSignatures.get(cacheKey) !== resolvingSignaturesArray) { + signatures = links.resolvedSignatures.get(cacheKey); + } + else if (!links.resolvedSignatures.get(cacheKey)) { + links.resolvedSignatures.set(cacheKey, resolvingSignaturesArray); + links.resolvedSignatures.set(cacheKey, signatures = instantiateJsxSignatures(context, signatures)); } return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromClassType(t, isJs, context, /*reportErrors*/ false) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None); @@ -16237,6 +16247,40 @@ namespace ts { return undefined; } + function getInstantiatedJsxSignatures(openingLikeElement: JsxOpeningLikeElement, elementType: Type, reportErrors?: boolean) { + const links = getNodeLinks(openingLikeElement); + if (!links.resolvedSignatures) { + links.resolvedSignatures = createMap(); + } + const cacheKey = "" + getTypeId(elementType); + if (links.resolvedSignatures.get(cacheKey) && links.resolvedSignatures.get(cacheKey) === resolvingSignaturesArray) { + return; + } + else if (links.resolvedSignatures.get(cacheKey)) { + return links.resolvedSignatures.get(cacheKey); + } + + links.resolvedSignatures.set(cacheKey, resolvingSignaturesArray); + // Resolve the signatures, preferring constructor + let signatures = getSignaturesOfType(elementType, SignatureKind.Construct); + if (signatures.length === 0) { + // No construct signatures, try call signatures + signatures = getSignaturesOfType(elementType, SignatureKind.Call); + if (signatures.length === 0) { + // We found no signatures at all, which is an error + if (reportErrors) { + error(openingLikeElement.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(openingLikeElement.tagName)); + } + return; + } + } + + // Instantiate in context of source type + const results = instantiateJsxSignatures(openingLikeElement, signatures); + links.resolvedSignatures.set(cacheKey, results); + return results; + } + /** * Resolve attributes type of the given opening-like element. The attributes type is a type of attributes associated with the given elementType. * For instance: @@ -16299,20 +16343,10 @@ namespace ts { // Get the element instance type (the result of newing or invoking this tag) - // Resolve the signatures, preferring constructor - let signatures = getSignaturesOfType(elementType, SignatureKind.Construct); - if (signatures.length === 0) { - // No construct signatures, try call signatures - signatures = getSignaturesOfType(elementType, SignatureKind.Call); - if (signatures.length === 0) { - // We found no signatures at all, which is an error - error(openingLikeElement.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(openingLikeElement.tagName)); - return unknownType; - } + const instantiatedSignatures = getInstantiatedJsxSignatures(openingLikeElement, elementType, /*reportErrors*/ true); + if (!length(instantiatedSignatures)) { + return unknownType; } - - // Instantiate in context of source type - const instantiatedSignatures = instantiateJsxSignatures(openingLikeElement, signatures); const elemInstanceType = getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype); // If we should include all stateless attributes type, then get all attributes type from all stateless function signature. @@ -18002,11 +18036,11 @@ namespace ts { let typeArguments: NodeArray; - if (!isDecorator && !isJsxOpeningOrSelfClosingElement) { + if (!isDecorator) { typeArguments = (node).typeArguments; // We already perform checking on the type arguments on the class declaration itself. - if (isTaggedTemplate || (node).expression.kind !== SyntaxKind.SuperKeyword) { + if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node).expression.kind !== SyntaxKind.SuperKeyword) { forEach(typeArguments, checkSourceElement); } } @@ -18595,30 +18629,6 @@ namespace ts { */ function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined { Debug.assert(!(elementType.flags & TypeFlags.Union)); - return resolveStatelessJsxOpeningLikeElement(openingLikeElement, elementType, candidatesOutArray); - } - - /** - * Try treating a given opening-like element as stateless function component and resolve a tagName to a function signature. - * @param openingLikeElement an JSX opening-like element we want to try resolve its stateless function if possible - * @param elementType a type of the opening-like JSX element, a result of resolving tagName in opening-like element. - * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; - * the function will fill it up with appropriate candidate signatures - * @return a resolved signature if we can find function matching function signature through resolve call or a first signature in the list of functions. - * otherwise return undefined if tag-name of the opening-like element doesn't have call signatures - */ - function resolveStatelessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined { - // If this function is called from language service, elementType can be a union type. This is not possible if the function is called from compiler (see: resolveCustomJsxElementAttributesType) - if (elementType.flags & TypeFlags.Union) { - const types = (elementType as UnionType).types; - let result: Signature; - for (const type of types) { - result = result || resolveStatelessJsxOpeningLikeElement(openingLikeElement, type, candidatesOutArray); - } - - return result; - } - const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call); if (callSignatures && callSignatures.length > 0) { return resolveCall(openingLikeElement, callSignatures, candidatesOutArray); @@ -18640,7 +18650,18 @@ namespace ts { case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: // This code-path is called by language service - return resolveStatelessJsxOpeningLikeElement(node, checkExpression(node.tagName), candidatesOutArray) || unknownSignature; + const exprTypes = checkExpression(node.tagName); + return forEachType(exprTypes, exprType => { + const sfcResult = getResolvedJsxStatelessFunctionSignature(node, exprType, candidatesOutArray); + if (sfcResult && sfcResult !== unknownSignature) { + return sfcResult; + } + const sigs = getInstantiatedJsxSignatures(node, exprType); + if (candidatesOutArray && length(sigs)) { + candidatesOutArray.push(...sigs); + } + return length(sigs) ? sigs[0] : unknownSignature; + }) || unknownSignature; } Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d59ea7dfbfd3b..7a37dc750a415 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3560,6 +3560,7 @@ namespace ts { flags?: NodeCheckFlags; // Set of flags specific to Node resolvedType?: Type; // Cached type of type node resolvedSignature?: Signature; // Cached signature of signature node or call expression + resolvedSignatures?: Map; // Cached signatures of jsx node resolvedSymbol?: Symbol; // Cached name resolution result resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate diff --git a/tests/cases/fourslash/jsxGenericQuickInfo.tsx b/tests/cases/fourslash/jsxGenericQuickInfo.tsx new file mode 100644 index 0000000000000..898c7c17790a0 --- /dev/null +++ b/tests/cases/fourslash/jsxGenericQuickInfo.tsx @@ -0,0 +1,22 @@ +/// +//@Filename: file.tsx +//// declare module JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// } +//// interface ElementAttributesProperty { props } +//// } +//// interface Props { +//// items: T[]; +//// renderItem: (item: T) => string; +//// } +//// class Component { +//// constructor(props: Props) {} +//// props: Props; +//// } +//// var b = new Component({items: [0, 1, 2], render/*0*/Item: it/*1*/em => item.toFixed()}); +//// var c = item.toFixed()} +verify.quickInfoAt("0", "(property) Props.renderItem: (item: number) => string"); +verify.quickInfoAt("1", "(parameter) item: number"); +verify.quickInfoAt("2", "(JSX attribute) renderItem: (item: number) => string"); +verify.quickInfoAt("3", "(parameter) item: number"); diff --git a/tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx b/tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx new file mode 100644 index 0000000000000..e317fe338edb8 --- /dev/null +++ b/tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx @@ -0,0 +1,19 @@ +/// + +//// declare namespace JSX { +//// interface Element { +//// render(): Element | string | false; +//// } +//// } +//// +//// function SFC(_props: Record) { +//// return ''; +//// } +//// +//// (); +//// (/>); + +goTo.marker("1"); +verify.currentSignatureHelpIs("SFC(_props: Record): string"); +goTo.marker("2"); +verify.currentSignatureHelpIs("SFC(_props: Record): string");