Skip to content

Commit 26c4320

Browse files
authored
also return classes when caling goToDef on a constructor call (#59421)
1 parent f025a5b commit 26c4320

9 files changed

+601
-26
lines changed

src/services/goToDefinition.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
isBindingElement,
4747
isCallLikeExpression,
4848
isCallOrNewExpressionTarget,
49+
isClassDeclaration,
4950
isClassElement,
5051
isClassExpression,
5152
isClassLike,
@@ -228,16 +229,20 @@ export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile
228229
// Don't go to the component constructor definition for a JSX element, just go to the component definition.
229230
if (calledDeclaration && !(isJsxOpeningLikeElement(node.parent) && isJsxConstructorLike(calledDeclaration))) {
230231
const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration, failedAliasResolution);
232+
231233
// For a function, if this is the original function definition, return just sigInfo.
232234
// If this is the original constructor definition, parent is the class.
235+
// Here, we filter declarations to not duplicate returned definitions.
236+
let declarationFilter: (d: Declaration) => boolean = d => d !== calledDeclaration;
233237
if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration))) {
234-
return [sigInfo];
235-
}
236-
else {
237-
const defs = getDefinitionFromSymbol(typeChecker, symbol, node, failedAliasResolution, calledDeclaration) || emptyArray;
238-
// For a 'super()' call, put the signature first, else put the variable first.
239-
return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo];
238+
if (!isConstructorDeclaration(calledDeclaration)) return [sigInfo];
239+
240+
// If we found a constructor declaration, we also look for class declarations as definitions
241+
declarationFilter = (d: Declaration) => d !== calledDeclaration && (isClassDeclaration(d) || isClassExpression(d));
240242
}
243+
const defs = getDefinitionFromSymbol(typeChecker, symbol, node, failedAliasResolution, declarationFilter) || emptyArray;
244+
// For a 'super()' call, put the signature first, else put the variable first.
245+
return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo];
241246
}
242247

243248
// Because name in short-hand property assignment has two different meanings: property name and property value,
@@ -584,9 +589,10 @@ function isExpandoDeclaration(node: Declaration): boolean {
584589
return !!containingAssignment && getAssignmentDeclarationKind(containingAssignment) === AssignmentDeclarationKind.Property;
585590
}
586591

587-
function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, failedAliasResolution?: boolean, excludeDeclaration?: Node): DefinitionInfo[] | undefined {
588-
const filteredDeclarations = filter(symbol.declarations, d => d !== excludeDeclaration);
589-
const signatureDefinition = getConstructSignatureDefinition() || getCallSignatureDefinition();
592+
function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, failedAliasResolution?: boolean, declarationFilter?: (d: Declaration) => boolean): DefinitionInfo[] | undefined {
593+
const filteredDeclarations = declarationFilter !== undefined ? filter(symbol.declarations, declarationFilter) : symbol.declarations;
594+
// If we have a declaration filter, we are looking for specific declaration(s), so we should not return prematurely.
595+
const signatureDefinition = !declarationFilter && (getConstructSignatureDefinition() || getCallSignatureDefinition());
590596
if (signatureDefinition) {
591597
return signatureDefinition;
592598
}

tests/baselines/reference/goToDefinitionAmbiants.baseline.jsonc

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@
6464
// === /tests/cases/fourslash/goToDefinitionAmbiants.ts ===
6565
// declare var ambientVar;
6666
// declare function ambientFunction();
67-
// declare class ambientClass {
68-
// [|constructor();|]
67+
// <|declare class [|{| defId: 0 |}ambientClass|] {
68+
// [|{| defId: 1 |}constructor();|]
6969
// static method();
7070
// public method();
71-
// }
71+
// }|>
7272
//
7373
// ambientVar = 1;
7474
// ambientFunction();
@@ -79,6 +79,17 @@
7979
// === Details ===
8080
[
8181
{
82+
"defId": 0,
83+
"kind": "class",
84+
"name": "ambientClass",
85+
"containerName": "",
86+
"isLocal": false,
87+
"isAmbient": true,
88+
"unverified": false,
89+
"failedAliasResolution": false
90+
},
91+
{
92+
"defId": 1,
8293
"kind": "constructor",
8394
"name": "__constructor",
8495
"containerName": "ambientClass",
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// === goToDefinition ===
2+
// === /tests/cases/fourslash/main.ts ===
3+
// <|import { [|Derived|] } from './base'|>
4+
// const derived = new /*GOTO DEF*/Derived(cArg)
5+
6+
// === Details ===
7+
[
8+
{
9+
"kind": "alias",
10+
"name": "Derived",
11+
"containerName": "",
12+
"isLocal": true,
13+
"isAmbient": false,
14+
"unverified": false,
15+
"failedAliasResolution": true
16+
}
17+
]
18+
19+
20+
21+
// === goToDefinition ===
22+
// === /tests/cases/fourslash/defInSameFile.ts ===
23+
// import { Base } from './base'
24+
// <|class [|SameFile|] extends Base {
25+
// readonly name: string = 'SameFile'
26+
// }|>
27+
// const SameFile = new /*GOTO DEF*/SameFile(cArg)
28+
// const wrapper = new Base(cArg)
29+
30+
// === Details ===
31+
[
32+
{
33+
"kind": "class",
34+
"name": "SameFile",
35+
"containerName": "",
36+
"isLocal": true,
37+
"isAmbient": false,
38+
"unverified": false,
39+
"failedAliasResolution": false
40+
}
41+
]
42+
43+
44+
45+
// === goToDefinition ===
46+
// === /tests/cases/fourslash/hasConstructor.ts ===
47+
// import { Base } from './base'
48+
// <|class [|{| defId: 0 |}HasConstructor|] extends Base {
49+
// [|{| defId: 1 |}constructor() {}|]
50+
// readonly name: string = '';
51+
// }|>
52+
// const hasConstructor = new /*GOTO DEF*/HasConstructor(cArg)
53+
54+
// === Details ===
55+
[
56+
{
57+
"defId": 0,
58+
"kind": "class",
59+
"name": "HasConstructor",
60+
"containerName": "",
61+
"isLocal": true,
62+
"isAmbient": false,
63+
"unverified": false,
64+
"failedAliasResolution": false
65+
},
66+
{
67+
"defId": 1,
68+
"kind": "constructor",
69+
"name": "__constructor",
70+
"containerName": "HasConstructor",
71+
"isLocal": true,
72+
"isAmbient": false,
73+
"unverified": false,
74+
"failedAliasResolution": false
75+
}
76+
]
77+
78+
79+
80+
// === goToDefinition ===
81+
// === /tests/cases/fourslash/defInSameFile.ts ===
82+
// <|import { [|Base|] } from './base'|>
83+
// class SameFile extends Base {
84+
// readonly name: string = 'SameFile'
85+
// }
86+
// const SameFile = new SameFile(cArg)
87+
// const wrapper = new /*GOTO DEF*/Base(cArg)
88+
89+
// === Details ===
90+
[
91+
{
92+
"kind": "alias",
93+
"name": "Base",
94+
"containerName": "",
95+
"isLocal": true,
96+
"isAmbient": false,
97+
"unverified": false,
98+
"failedAliasResolution": true
99+
}
100+
]

0 commit comments

Comments
 (0)