Correctly show instantiated signatures for JSX element signature help and quick info (#23492)

* Correctly show instantiated signatures for JSX element signature help

* Also bundle fix for quickinfo

* Use more complete cache to avoid duplicate errors
This commit is contained in:
Wesley Wigham 2018-05-16 13:09:54 -07:00 committed by GitHub
parent 5bf6e30f8e
commit e01c7d23e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 105 additions and 42 deletions

View file

@ -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);
@ -15359,8 +15360,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);
@ -16341,6 +16351,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:
@ -16403,20 +16447,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.
@ -18106,11 +18140,11 @@ namespace ts {
let typeArguments: NodeArray<TypeNode>;
if (!isDecorator && !isJsxOpeningOrSelfClosingElement) {
if (!isDecorator) {
typeArguments = (<CallExpression>node).typeArguments;
// We already perform checking on the type arguments on the class declaration itself.
if (isTaggedTemplate || (<CallExpression>node).expression.kind !== SyntaxKind.SuperKeyword) {
if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (<CallExpression>node).expression.kind !== SyntaxKind.SuperKeyword) {
forEach(typeArguments, checkSourceElement);
}
}
@ -18699,30 +18733,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);
@ -18744,7 +18754,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.");
}

View file

@ -3625,6 +3625,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<Signature[]>; // 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

View file

@ -0,0 +1,22 @@
/// <reference path="fourslash.ts" />
//@Filename: file.tsx
//// declare module JSX {
//// interface Element { }
//// interface IntrinsicElements {
//// }
//// interface ElementAttributesProperty { props }
//// }
//// interface Props<T> {
//// items: T[];
//// renderItem: (item: T) => string;
//// }
//// class Component<T> {
//// constructor(props: Props<T>) {}
//// props: Props<T>;
//// }
//// var b = new Component({items: [0, 1, 2], render/*0*/Item: it/*1*/em => item.toFixed()});
//// var c = <Component items={[0, 1, 2]} render/*2*/Item={it/*3*/em => item.toFixed()}
verify.quickInfoAt("0", "(property) Props<number>.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");

View file

@ -0,0 +1,19 @@
/// <reference path="./fourslash.ts" />
//// declare namespace JSX {
//// interface Element {
//// render(): Element | string | false;
//// }
//// }
////
//// function SFC<T>(_props: Record<string, T>) {
//// return '';
//// }
////
//// (</*1*/SFC/>);
//// (</*2*/SFC<string>/>);
goTo.marker("1");
verify.currentSignatureHelpIs("SFC(_props: Record<string, {}>): string");
goTo.marker("2");
verify.currentSignatureHelpIs("SFC(_props: Record<string, string>): string");