Skip to content

Commit 0d4c9fa

Browse files
author
Kanchalai Tanglertsampan
committed
Handle goto-definition of stateless function component
1 parent 30afb33 commit 0d4c9fa

File tree

3 files changed

+72
-17
lines changed

3 files changed

+72
-17
lines changed

src/compiler/checker.ts

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11197,11 +11197,8 @@ namespace ts {
1119711197
if (!elementClassType || !isTypeAssignableTo(elemInstanceType, elementClassType)) {
1119811198
// Is this is a stateless function component? See if its single signature's return type is assignable to the JSX Element Type
1119911199
if (jsxElementType) {
11200-
// Cached the result of trying to solve opening-like JSX element as a stateless function component (similar to how getResolvedSignature)
11201-
// We don't call getResolvedSignature directly because here we have already resolve the type of JSX Element.
11202-
const links = getNodeLinks(openingLikeElement);
11203-
const callSignature = resolvedStateLessJsxOpeningLikeElement(openingLikeElement, elementType, /*candidatesOutArray*/ undefined);
11204-
links.resolvedSignature = callSignature;
11200+
// We don't call getResolvedSignature because here we have already resolve the type of JSX Element.
11201+
const callSignature = getResolvedJSXStatelessFunctionSignature(openingLikeElement, elementType, /*candidatesOutArray*/ undefined);
1120511202
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature);
1120611203
let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0]));
1120711204
if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType)) {
@@ -11232,12 +11229,9 @@ namespace ts {
1123211229
if (!elementClassType || !isTypeAssignableTo(elemInstanceType, elementClassType)) {
1123311230
// Is this is a stateless function component? See if its single signature's return type is assignable to the JSX Element Type
1123411231
if (jsxElementType) {
11235-
// Cached the result of trying to solve opening-like JSX element as a stateless function component (similar to how getResolvedSignature)
11236-
// We don't call getResolvedSignature directly because here we have already resolve the type of JSX Element.
11237-
const links = getNodeLinks(openingLikeElement);
11232+
// We don't call getResolvedSignature because here we have already resolve the type of JSX Element.
1123811233
const candidatesOutArray: Signature[] = [];
11239-
const callSignature = resolvedStateLessJsxOpeningLikeElement(openingLikeElement, elementType, candidatesOutArray);
11240-
links.resolvedSignature = callSignature;
11234+
getResolvedJSXStatelessFunctionSignature(openingLikeElement, elementType, candidatesOutArray);
1124111235
let result: Type;
1124211236
let defaultResult: Type;
1124311237
for (const candidate of candidatesOutArray) {
@@ -12760,9 +12754,11 @@ namespace ts {
1276012754
}
1276112755

1276212756
// Do not report any error if we are doing so for stateless function component as such error will be error will be handle in "resolveCustomJsxElementAttributesType".
12763-
// Just return the latest signature candidate we try so far so that when we report an error we will get better error message.
1276412757
if (isJsxOpeningOrSelfClosingElement) {
12765-
return candidateForArgumentError;
12758+
// If this is a type resolution session, e.g. Language Service, just return undefined as the language service can decide how to proceed with this failure.
12759+
// (see getDefinitionAtPosition which simply get the symbol and return the first declaration of the JSXopeningLikeElement node)
12760+
// otherwise, just return the latest signature candidate we try so far so that when we report an error we will get better error message.
12761+
return produceDiagnostics ? candidateForArgumentError : undefined;
1276612762
}
1276712763

1276812764
// No signatures were applicable. Now report errors based on the last applicable signature with
@@ -13178,6 +13174,38 @@ namespace ts {
1317813174
return resolveCall(node, callSignatures, candidatesOutArray, headMessage);
1317913175
}
1318013176

13177+
/**
13178+
*
13179+
* @param openingLikeElement
13180+
* @param elementType
13181+
* @param candidatesOutArray
13182+
*/
13183+
function getResolvedJSXStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature {
13184+
Debug.assert(!(elementType.flags & TypeFlags.Union));
13185+
const links = getNodeLinks(openingLikeElement);
13186+
// If getResolvedSignature has already been called, we will have cached the resolvedSignature.
13187+
// However, it is possible that either candidatesOutArray was not passed in the first time,
13188+
// or that a different candidatesOutArray was passed in. Therefore, we need to redo the work
13189+
// to correctly fill the candidatesOutArray.
13190+
const cached = links.resolvedSignature;
13191+
if (cached && cached !== resolvingSignature && !candidatesOutArray) {
13192+
return cached;
13193+
}
13194+
links.resolvedSignature = resolvingSignature;
13195+
13196+
let callSignature = resolvedStateLessJsxOpeningLikeElement(openingLikeElement, elementType, candidatesOutArray);
13197+
if (!callSignature || callSignature === unknownSignature) {
13198+
const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call);
13199+
callSignature = callSignatures[callSignatures.length - 1];
13200+
}
13201+
links.resolvedSignature = callSignature;
13202+
// If signature resolution originated in control flow type analysis (for example to compute the
13203+
// assigned type in a flow assignment) we don't cache the result as it may be based on temporary
13204+
// types from the control flow analysis.
13205+
links.resolvedSignature = flowLoopStart === flowLoopCount ? callSignature : cached;
13206+
return callSignature;
13207+
}
13208+
1318113209
/**
1318213210
* Try treating a given opening-like element as stateless function component and try to resolve a signature.
1318313211
* @param openingLikeElement an JsxOpeningLikeElement we want to try resolve its state-less function if possible
@@ -13187,7 +13215,7 @@ namespace ts {
1318713215
* @return a resolved signature if we can find function matching function signature through resolve call or a first signature in the list of functions.
1318813216
* otherwise return undefined if tag-name of the opening-like element doesn't have call signatures
1318913217
*/
13190-
function resolvedStateLessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined {
13218+
function resolvedStateLessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature {
1319113219
if (elementType.flags & TypeFlags.Union) {
1319213220
const types = (elementType as UnionType).types;
1319313221
let result: Signature;
@@ -13202,7 +13230,7 @@ namespace ts {
1320213230
const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call);
1320313231
if (callSignatures && callSignatures.length > 0) {
1320413232
let callSignature: Signature;
13205-
callSignature = resolveCall(openingLikeElement, callSignatures, candidatesOutArray) || callSignatures[0];
13233+
callSignature = resolveCall(openingLikeElement, callSignatures, candidatesOutArray);
1320613234
return callSignature;
1320713235
}
1320813236

src/compiler/utilities.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,8 @@ namespace ts {
10781078

10791079
export function isCallLikeExpression(node: Node): node is CallLikeExpression {
10801080
switch (node.kind) {
1081+
case SyntaxKind.JsxOpeningElement:
1082+
case SyntaxKind.JsxSelfClosingElement:
10811083
case SyntaxKind.CallExpression:
10821084
case SyntaxKind.NewExpression:
10831085
case SyntaxKind.TaggedTemplateExpression:

src/services/goToDefinition.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,31 @@ namespace ts.GoToDefinition {
8787
declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName));
8888
}
8989

90+
if (isJsxOpeningLikeElement(node.parent)) {
91+
// For JSX opening-like element, the tag can be resolved either as stateful component (e.g class) or stateless function component.
92+
// Because if it is a stateless function component with an error while trying to resolve the signature, we don't want to return all
93+
// possible overloads but just the first one.
94+
// For example:
95+
// /*firstSource*/declare function MainButton(buttonProps: ButtonProps): JSX.Element;
96+
// /*secondSource*/declare function MainButton(linkProps: LinkProps): JSX.Element;
97+
// /*thirdSource*/declare function MainButton(props: ButtonProps | LinkProps): JSX.Element;
98+
// let opt = <Main/*firstTarget*/Button />; // We get undefined for resolved signature indicating an error, then just return the first declaration
99+
const {symbolName, symbolKind, containerName} = getSymbolInfo(typeChecker, symbol, node);
100+
return [createDefinitionInfo(symbol.valueDeclaration, symbolKind, symbolName, containerName)];
101+
}
102+
90103
// If the current location we want to find its definition is in an object literal, try to get the contextual type for the
91-
// object literal, lookup the property symbol in the contextual type, and use this for goto-definition
104+
// object literal, lookup the property symbol in the contextual type, and use this for goto-definition.
105+
// For example
106+
// interface Props{
107+
// /first*/prop1: number
108+
// prop2: boolean
109+
// }
110+
// function Foo(arg: Props) {}
111+
// Foo( { pr/*1*/op1: 10, prop2: true })
92112
const container = getContainingObjectLiteralElement(node);
93113
if (container) {
94-
const contextualType = typeChecker.getContextualType(node.parent.parent as JsxOpeningLikeElement);
114+
const contextualType = typeChecker.getContextualType(node.parent.parent as Expression);
95115
if (contextualType) {
96116
let result: DefinitionInfo[] = [];
97117
forEach(getPropertySymbolsFromContextualType(typeChecker, container), contextualSymbol => {
@@ -264,6 +284,11 @@ namespace ts.GoToDefinition {
264284

265285
function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
266286
const callLike = getAncestorCallLikeExpression(node);
267-
return callLike && typeChecker.getResolvedSignature(callLike).declaration;
287+
if (callLike) {
288+
const resolvedSignature = typeChecker.getResolvedSignature(callLike);
289+
// We have to check that resolvedSignature is not undefined because in the case of JSX opening-like element,
290+
// it may not be a stateless function component which then will cause getResolvedSignature to return undefined.
291+
return resolvedSignature && resolvedSignature.declaration;
292+
}
268293
}
269294
}

0 commit comments

Comments
 (0)