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:
parent
5bf6e30f8e
commit
e01c7d23e1
4 changed files with 105 additions and 42 deletions
|
@ -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));
|
||||
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.");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
22
tests/cases/fourslash/jsxGenericQuickInfo.tsx
Normal file
22
tests/cases/fourslash/jsxGenericQuickInfo.tsx
Normal 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");
|
|
@ -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");
|
Loading…
Reference in a new issue