Skip to content

Commit e01c7d2

Browse files
authored
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
1 parent 5bf6e30 commit e01c7d2

File tree

4 files changed

+105
-42
lines changed

4 files changed

+105
-42
lines changed

src/compiler/checker.ts

+63-42
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ namespace ts {
401401
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
402402
const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
403403
const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
404+
const resolvingSignaturesArray = [resolvingSignature];
404405

405406
const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);
406407
const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
@@ -15359,8 +15360,17 @@ namespace ts {
1535915360
}
1536015361
}
1536115362

15362-
if (context.typeArguments) {
15363-
signatures = mapDefined(signatures, s => getJsxSignatureTypeArgumentInstantiation(s, context, isJs));
15363+
const links = getNodeLinks(context);
15364+
if (!links.resolvedSignatures) {
15365+
links.resolvedSignatures = createMap();
15366+
}
15367+
const cacheKey = "" + getTypeId(valueType);
15368+
if (links.resolvedSignatures.get(cacheKey) && links.resolvedSignatures.get(cacheKey) !== resolvingSignaturesArray) {
15369+
signatures = links.resolvedSignatures.get(cacheKey);
15370+
}
15371+
else if (!links.resolvedSignatures.get(cacheKey)) {
15372+
links.resolvedSignatures.set(cacheKey, resolvingSignaturesArray);
15373+
links.resolvedSignatures.set(cacheKey, signatures = instantiateJsxSignatures(context, signatures));
1536415374
}
1536515375

1536615376
return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromClassType(t, isJs, context, /*reportErrors*/ false) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None);
@@ -16341,6 +16351,40 @@ namespace ts {
1634116351
return undefined;
1634216352
}
1634316353

16354+
function getInstantiatedJsxSignatures(openingLikeElement: JsxOpeningLikeElement, elementType: Type, reportErrors?: boolean) {
16355+
const links = getNodeLinks(openingLikeElement);
16356+
if (!links.resolvedSignatures) {
16357+
links.resolvedSignatures = createMap();
16358+
}
16359+
const cacheKey = "" + getTypeId(elementType);
16360+
if (links.resolvedSignatures.get(cacheKey) && links.resolvedSignatures.get(cacheKey) === resolvingSignaturesArray) {
16361+
return;
16362+
}
16363+
else if (links.resolvedSignatures.get(cacheKey)) {
16364+
return links.resolvedSignatures.get(cacheKey);
16365+
}
16366+
16367+
links.resolvedSignatures.set(cacheKey, resolvingSignaturesArray);
16368+
// Resolve the signatures, preferring constructor
16369+
let signatures = getSignaturesOfType(elementType, SignatureKind.Construct);
16370+
if (signatures.length === 0) {
16371+
// No construct signatures, try call signatures
16372+
signatures = getSignaturesOfType(elementType, SignatureKind.Call);
16373+
if (signatures.length === 0) {
16374+
// We found no signatures at all, which is an error
16375+
if (reportErrors) {
16376+
error(openingLikeElement.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(openingLikeElement.tagName));
16377+
}
16378+
return;
16379+
}
16380+
}
16381+
16382+
// Instantiate in context of source type
16383+
const results = instantiateJsxSignatures(openingLikeElement, signatures);
16384+
links.resolvedSignatures.set(cacheKey, results);
16385+
return results;
16386+
}
16387+
1634416388
/**
1634516389
* Resolve attributes type of the given opening-like element. The attributes type is a type of attributes associated with the given elementType.
1634616390
* For instance:
@@ -16403,20 +16447,10 @@ namespace ts {
1640316447

1640416448
// Get the element instance type (the result of newing or invoking this tag)
1640516449

16406-
// Resolve the signatures, preferring constructor
16407-
let signatures = getSignaturesOfType(elementType, SignatureKind.Construct);
16408-
if (signatures.length === 0) {
16409-
// No construct signatures, try call signatures
16410-
signatures = getSignaturesOfType(elementType, SignatureKind.Call);
16411-
if (signatures.length === 0) {
16412-
// We found no signatures at all, which is an error
16413-
error(openingLikeElement.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(openingLikeElement.tagName));
16414-
return unknownType;
16415-
}
16450+
const instantiatedSignatures = getInstantiatedJsxSignatures(openingLikeElement, elementType, /*reportErrors*/ true);
16451+
if (!length(instantiatedSignatures)) {
16452+
return unknownType;
1641616453
}
16417-
16418-
// Instantiate in context of source type
16419-
const instantiatedSignatures = instantiateJsxSignatures(openingLikeElement, signatures);
1642016454
const elemInstanceType = getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype);
1642116455

1642216456
// If we should include all stateless attributes type, then get all attributes type from all stateless function signature.
@@ -18106,11 +18140,11 @@ namespace ts {
1810618140

1810718141
let typeArguments: NodeArray<TypeNode>;
1810818142

18109-
if (!isDecorator && !isJsxOpeningOrSelfClosingElement) {
18143+
if (!isDecorator) {
1811018144
typeArguments = (<CallExpression>node).typeArguments;
1811118145

1811218146
// We already perform checking on the type arguments on the class declaration itself.
18113-
if (isTaggedTemplate || (<CallExpression>node).expression.kind !== SyntaxKind.SuperKeyword) {
18147+
if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (<CallExpression>node).expression.kind !== SyntaxKind.SuperKeyword) {
1811418148
forEach(typeArguments, checkSourceElement);
1811518149
}
1811618150
}
@@ -18699,30 +18733,6 @@ namespace ts {
1869918733
*/
1870018734
function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined {
1870118735
Debug.assert(!(elementType.flags & TypeFlags.Union));
18702-
return resolveStatelessJsxOpeningLikeElement(openingLikeElement, elementType, candidatesOutArray);
18703-
}
18704-
18705-
/**
18706-
* Try treating a given opening-like element as stateless function component and resolve a tagName to a function signature.
18707-
* @param openingLikeElement an JSX opening-like element we want to try resolve its stateless function if possible
18708-
* @param elementType a type of the opening-like JSX element, a result of resolving tagName in opening-like element.
18709-
* @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service;
18710-
* the function will fill it up with appropriate candidate signatures
18711-
* @return a resolved signature if we can find function matching function signature through resolve call or a first signature in the list of functions.
18712-
* otherwise return undefined if tag-name of the opening-like element doesn't have call signatures
18713-
*/
18714-
function resolveStatelessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined {
18715-
// 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)
18716-
if (elementType.flags & TypeFlags.Union) {
18717-
const types = (elementType as UnionType).types;
18718-
let result: Signature;
18719-
for (const type of types) {
18720-
result = result || resolveStatelessJsxOpeningLikeElement(openingLikeElement, type, candidatesOutArray);
18721-
}
18722-
18723-
return result;
18724-
}
18725-
1872618736
const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call);
1872718737
if (callSignatures && callSignatures.length > 0) {
1872818738
return resolveCall(openingLikeElement, callSignatures, candidatesOutArray);
@@ -18744,7 +18754,18 @@ namespace ts {
1874418754
case SyntaxKind.JsxOpeningElement:
1874518755
case SyntaxKind.JsxSelfClosingElement:
1874618756
// This code-path is called by language service
18747-
return resolveStatelessJsxOpeningLikeElement(node, checkExpression(node.tagName), candidatesOutArray) || unknownSignature;
18757+
const exprTypes = checkExpression(node.tagName);
18758+
return forEachType(exprTypes, exprType => {
18759+
const sfcResult = getResolvedJsxStatelessFunctionSignature(node, exprType, candidatesOutArray);
18760+
if (sfcResult && sfcResult !== unknownSignature) {
18761+
return sfcResult;
18762+
}
18763+
const sigs = getInstantiatedJsxSignatures(node, exprType);
18764+
if (candidatesOutArray && length(sigs)) {
18765+
candidatesOutArray.push(...sigs);
18766+
}
18767+
return length(sigs) ? sigs[0] : unknownSignature;
18768+
}) || unknownSignature;
1874818769
}
1874918770
Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable.");
1875018771
}

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3625,6 +3625,7 @@ namespace ts {
36253625
flags?: NodeCheckFlags; // Set of flags specific to Node
36263626
resolvedType?: Type; // Cached type of type node
36273627
resolvedSignature?: Signature; // Cached signature of signature node or call expression
3628+
resolvedSignatures?: Map<Signature[]>; // Cached signatures of jsx node
36283629
resolvedSymbol?: Symbol; // Cached name resolution result
36293630
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
36303631
maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path="fourslash.ts" />
2+
//@Filename: file.tsx
3+
//// declare module JSX {
4+
//// interface Element { }
5+
//// interface IntrinsicElements {
6+
//// }
7+
//// interface ElementAttributesProperty { props }
8+
//// }
9+
//// interface Props<T> {
10+
//// items: T[];
11+
//// renderItem: (item: T) => string;
12+
//// }
13+
//// class Component<T> {
14+
//// constructor(props: Props<T>) {}
15+
//// props: Props<T>;
16+
//// }
17+
//// var b = new Component({items: [0, 1, 2], render/*0*/Item: it/*1*/em => item.toFixed()});
18+
//// var c = <Component items={[0, 1, 2]} render/*2*/Item={it/*3*/em => item.toFixed()}
19+
verify.quickInfoAt("0", "(property) Props<number>.renderItem: (item: number) => string");
20+
verify.quickInfoAt("1", "(parameter) item: number");
21+
verify.quickInfoAt("2", "(JSX attribute) renderItem: (item: number) => string");
22+
verify.quickInfoAt("3", "(parameter) item: number");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// <reference path="./fourslash.ts" />
2+
3+
//// declare namespace JSX {
4+
//// interface Element {
5+
//// render(): Element | string | false;
6+
//// }
7+
//// }
8+
////
9+
//// function SFC<T>(_props: Record<string, T>) {
10+
//// return '';
11+
//// }
12+
////
13+
//// (</*1*/SFC/>);
14+
//// (</*2*/SFC<string>/>);
15+
16+
goTo.marker("1");
17+
verify.currentSignatureHelpIs("SFC(_props: Record<string, {}>): string");
18+
goTo.marker("2");
19+
verify.currentSignatureHelpIs("SFC(_props: Record<string, string>): string");

0 commit comments

Comments
 (0)