Skip to content

Commit 6ddcdcd

Browse files
author
Andy
authored
Merge pull request #10593 from Microsoft/goto_definition_super
Make goto-definition go to a signature declaration if possible
2 parents 2961d97 + 3eadbf6 commit 6ddcdcd

10 files changed

+167
-30
lines changed

src/compiler/utilities.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,18 @@ namespace ts {
10351035
return undefined;
10361036
}
10371037

1038+
export function isCallLikeExpression(node: Node): node is CallLikeExpression {
1039+
switch (node.kind) {
1040+
case SyntaxKind.CallExpression:
1041+
case SyntaxKind.NewExpression:
1042+
case SyntaxKind.TaggedTemplateExpression:
1043+
case SyntaxKind.Decorator:
1044+
return true;
1045+
default:
1046+
return false;
1047+
}
1048+
}
1049+
10381050
export function getInvokedExpression(node: CallLikeExpression): Expression {
10391051
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
10401052
return (<TaggedTemplateExpression>node).tag;

src/harness/fourslash.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,7 +1564,7 @@ namespace FourSlash {
15641564
public goToDefinition(definitionIndex: number) {
15651565
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
15661566
if (!definitions || !definitions.length) {
1567-
this.raiseError("goToDefinition failed - expected to at least one definition location but got 0");
1567+
this.raiseError("goToDefinition failed - expected to find at least one definition location but got 0");
15681568
}
15691569

15701570
if (definitionIndex >= definitions.length) {
@@ -1579,7 +1579,7 @@ namespace FourSlash {
15791579
public goToTypeDefinition(definitionIndex: number) {
15801580
const definitions = this.languageService.getTypeDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
15811581
if (!definitions || !definitions.length) {
1582-
this.raiseError("goToTypeDefinition failed - expected to at least one definition location but got 0");
1582+
this.raiseError("goToTypeDefinition failed - expected to find at least one definition location but got 0");
15831583
}
15841584

15851585
if (definitionIndex >= definitions.length) {
@@ -1600,7 +1600,7 @@ namespace FourSlash {
16001600
this.raiseError(`goToDefinition - expected to 0 definition locations but got ${definitions.length}`);
16011601
}
16021602
else if (!foundDefinitions && !negative) {
1603-
this.raiseError("goToDefinition - expected to at least one definition location but got 0");
1603+
this.raiseError("goToDefinition - expected to find at least one definition location but got 0");
16041604
}
16051605
}
16061606

src/services/services.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2792,20 +2792,36 @@ namespace ts {
27922792
return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node;
27932793
}
27942794

2795+
function climbPastPropertyAccess(node: Node) {
2796+
return isRightSideOfPropertyAccess(node) ? node.parent : node;
2797+
}
2798+
2799+
function climbPastManyPropertyAccesses(node: Node): Node {
2800+
return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node;
2801+
}
2802+
27952803
function isCallExpressionTarget(node: Node): boolean {
2796-
if (isRightSideOfPropertyAccess(node)) {
2797-
node = node.parent;
2798-
}
2804+
node = climbPastPropertyAccess(node);
27992805
return node && node.parent && node.parent.kind === SyntaxKind.CallExpression && (<CallExpression>node.parent).expression === node;
28002806
}
28012807

28022808
function isNewExpressionTarget(node: Node): boolean {
2803-
if (isRightSideOfPropertyAccess(node)) {
2804-
node = node.parent;
2805-
}
2809+
node = climbPastPropertyAccess(node);
28062810
return node && node.parent && node.parent.kind === SyntaxKind.NewExpression && (<CallExpression>node.parent).expression === node;
28072811
}
28082812

2813+
/** Returns a CallLikeExpression where `node` is the target being invoked. */
2814+
function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined {
2815+
const target = climbPastManyPropertyAccesses(node);
2816+
const callLike = target.parent;
2817+
return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target && callLike;
2818+
}
2819+
2820+
function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
2821+
const callLike = getAncestorCallLikeExpression(node);
2822+
return callLike && typeChecker.getResolvedSignature(callLike).declaration;
2823+
}
2824+
28092825
function isNameOfModuleDeclaration(node: Node) {
28102826
return node.parent.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>node.parent).name === node;
28112827
}
@@ -5072,14 +5088,25 @@ namespace ts {
50725088
};
50735089
}
50745090

5091+
function getSymbolInfo(typeChecker: TypeChecker, symbol: Symbol, node: Node) {
5092+
return {
5093+
symbolName: typeChecker.symbolToString(symbol), // Do not get scoped name, just the name of the symbol
5094+
symbolKind: getSymbolKind(symbol, node),
5095+
containerName: symbol.parent ? typeChecker.symbolToString(symbol.parent, node) : ""
5096+
};
5097+
}
5098+
5099+
function createDefinitionFromSignatureDeclaration(decl: SignatureDeclaration): DefinitionInfo {
5100+
const typeChecker = program.getTypeChecker();
5101+
const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, decl.symbol, decl);
5102+
return createDefinitionInfo(decl, symbolKind, symbolName, containerName);
5103+
}
5104+
50755105
function getDefinitionFromSymbol(symbol: Symbol, node: Node): DefinitionInfo[] {
50765106
const typeChecker = program.getTypeChecker();
50775107
const result: DefinitionInfo[] = [];
50785108
const declarations = symbol.getDeclarations();
5079-
const symbolName = typeChecker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol
5080-
const symbolKind = getSymbolKind(symbol, node);
5081-
const containerSymbol = symbol.parent;
5082-
const containerName = containerSymbol ? typeChecker.symbolToString(containerSymbol, node) : "";
5109+
const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, symbol, node);
50835110

50845111
if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) &&
50855112
!tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) {
@@ -5205,6 +5232,12 @@ namespace ts {
52055232
}
52065233

52075234
const typeChecker = program.getTypeChecker();
5235+
5236+
const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node);
5237+
if (calledDeclaration) {
5238+
return [createDefinitionFromSignatureDeclaration(calledDeclaration)];
5239+
}
5240+
52085241
let symbol = typeChecker.getSymbolAtLocation(node);
52095242

52105243
// Could not find a symbol e.g. node is string or number keyword,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @Target: ES6
2+
// @experimentaldecorators: true
3+
4+
////async function f() {}
5+
////
6+
/////*defDecString*/function dec(target: any, propertyKey: string): void;
7+
/////*defDecSymbol*/function dec(target: any, propertyKey: symbol): void;
8+
////function dec(target: any, propertyKey: string | symbol) {}
9+
////
10+
////declare const s: symbol;
11+
////class C {
12+
//// @/*useDecString*/dec f() {}
13+
//// @/*useDecSymbol*/dec [s]() {}
14+
////}
15+
16+
goTo.marker("useDecString");
17+
goTo.definition();
18+
verify.caretAtMarker("defDecString");
19+
20+
goTo.marker("useDecSymbol");
21+
goTo.definition();
22+
verify.caretAtMarker("defDecSymbol");

tests/cases/fourslash/goToDefinitionConstructorOverloads.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111

1212
goTo.marker('constructorOverloadReference1');
1313
goTo.definition();
14-
verify.caretAtMarker('constructorDefinition');
14+
verify.caretAtMarker('constructorOverload1');
1515

1616
goTo.marker('constructorOverloadReference2');
1717
goTo.definition();
18-
verify.caretAtMarker('constructorDefinition');
18+
verify.caretAtMarker('constructorOverload2');
1919

2020
goTo.marker('constructorOverload1');
2121
goTo.definition();

tests/cases/fourslash/goToDefinitionFunctionOverloads.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
/// <reference path='fourslash.ts' />
22

3-
/////*functionOverload1*/function /*functionOverload*/functionOverload();
3+
/////*functionOverload1*/function /*functionOverload*/functionOverload(value: number);
44
/////*functionOverload2*/function functionOverload(value: string);
55
/////*functionOverloadDefinition*/function functionOverload() {}
66
////
7-
/////*functionOverloadReference1*/functionOverload();
7+
/////*functionOverloadReference1*/functionOverload(123);
88
/////*functionOverloadReference2*/functionOverload("123");
9+
/////*brokenOverload*/functionOverload({});
910

1011
goTo.marker('functionOverloadReference1');
1112
goTo.definition();
12-
verify.caretAtMarker('functionOverloadDefinition');
13+
verify.caretAtMarker('functionOverload1');
1314

1415
goTo.marker('functionOverloadReference2');
1516
goTo.definition();
16-
verify.caretAtMarker('functionOverloadDefinition');
17+
verify.caretAtMarker('functionOverload2');
18+
19+
goTo.marker('brokenOverload');
20+
goTo.definition();
21+
verify.caretAtMarker('functionOverload1');
1722

1823
goTo.marker('functionOverload');
1924
goTo.definition();
Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/// <reference path='fourslash.ts' />
22

33
////class MethodOverload {
4-
//// static me/*staticMethodOverload1*/thod();
5-
//// static me/*staticMethodOverload2*/thod(foo: string);
6-
/////*staticMethodDefinition*/static method(foo?: any) { }
7-
//// public met/*instanceMethodOverload1*/hod(): any;
8-
//// public met/*instanceMethodOverload2*/hod(foo: string);
4+
//// /*staticMethodOverload1*/static /*staticMethodOverload1Name*/method();
5+
//// /*staticMethodOverload2*/static method(foo: string);
6+
//// /*staticMethodDefinition*/static method(foo?: any) { }
7+
//// /*instanceMethodOverload1*/public /*instanceMethodOverload1Name*/method(): any;
8+
//// /*instanceMethodOverload2*/public method(foo: string);
99
/////*instanceMethodDefinition*/public method(foo?: any) { return "foo" }
1010
////}
1111

@@ -20,25 +20,25 @@
2020

2121
goTo.marker('staticMethodReference1');
2222
goTo.definition();
23-
verify.caretAtMarker('staticMethodDefinition');
23+
verify.caretAtMarker('staticMethodOverload1');
2424

2525
goTo.marker('staticMethodReference2');
2626
goTo.definition();
27-
verify.caretAtMarker('staticMethodDefinition');
27+
verify.caretAtMarker('staticMethodOverload2');
2828

2929
goTo.marker('instanceMethodReference1');
3030
goTo.definition();
31-
verify.caretAtMarker('instanceMethodDefinition');
31+
verify.caretAtMarker('instanceMethodOverload1');
3232

3333
goTo.marker('instanceMethodReference2');
3434
goTo.definition();
35-
verify.caretAtMarker('instanceMethodDefinition');
35+
verify.caretAtMarker('instanceMethodOverload2');
3636

37-
goTo.marker('staticMethodOverload1');
37+
goTo.marker('staticMethodOverload1Name');
3838
goTo.definition();
3939
verify.caretAtMarker('staticMethodDefinition');
4040

41-
goTo.marker('instanceMethodOverload1');
41+
goTo.marker('instanceMethodOverload1Name');
4242
goTo.definition();
4343
verify.caretAtMarker('instanceMethodDefinition');
4444

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// Test that we can climb past more than one property access to reach a call expression.
4+
5+
////namespace A {
6+
//// export namespace B {
7+
//// export function f(value: number): void;
8+
//// /*1*/export function f(value: string): void;
9+
//// export function f(value: number | string) {}
10+
//// }
11+
////}
12+
////A.B./*2*/f("");
13+
14+
goTo.marker("2");
15+
goTo.definition();
16+
verify.caretAtMarker("1");
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
/////*defFNumber*/function f(strs: TemplateStringsArray, x: number): void;
4+
/////*defFBool*/function f(strs: TemplateStringsArray, x: boolean): void;
5+
////function f(strs: TemplateStringsArray, x: number | boolean) {}
6+
////
7+
/////*useFNumber*/f`${0}`;
8+
/////*useFBool*/f`${false}`;
9+
10+
goTo.marker("useFNumber");
11+
goTo.definition();
12+
verify.caretAtMarker("defFNumber");
13+
14+
goTo.marker("useFBool");
15+
goTo.definition();
16+
verify.caretAtMarker("defFBool");
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
///<reference path="fourslash.ts"/>
2+
3+
////class A {
4+
//// /*ctr*/constructor() {}
5+
//// x() {}
6+
////}
7+
/////*B*/class B extends A {}
8+
////class C extends B {
9+
//// constructor() {
10+
//// /*super*/super();
11+
//// }
12+
//// method() {
13+
//// /*superExpression*/super.x();
14+
//// }
15+
////}
16+
////class D {
17+
//// constructor() {
18+
//// /*superBroken*/super();
19+
//// }
20+
////}
21+
22+
// Super in call position goes to constructor.
23+
goTo.marker("super");
24+
goTo.definition();
25+
verify.caretAtMarker("ctr");
26+
27+
// Super in any other position goes to the superclass.
28+
goTo.marker("superExpression");
29+
goTo.definition();
30+
verify.caretAtMarker("B");
31+
32+
goTo.marker("superBroken");
33+
verify.definitionCountIs(0);

0 commit comments

Comments
 (0)