Merge pull request #10593 from Microsoft/goto_definition_super
Make goto-definition go to a signature declaration if possible
This commit is contained in:
commit
6ddcdcd503
10 changed files with 167 additions and 30 deletions
|
@ -1035,6 +1035,18 @@ namespace ts {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
export function isCallLikeExpression(node: Node): node is CallLikeExpression {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
case SyntaxKind.TaggedTemplateExpression:
|
||||
case SyntaxKind.Decorator:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getInvokedExpression(node: CallLikeExpression): Expression {
|
||||
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
|
||||
return (<TaggedTemplateExpression>node).tag;
|
||||
|
|
|
@ -1564,7 +1564,7 @@ namespace FourSlash {
|
|||
public goToDefinition(definitionIndex: number) {
|
||||
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
|
||||
if (!definitions || !definitions.length) {
|
||||
this.raiseError("goToDefinition failed - expected to at least one definition location but got 0");
|
||||
this.raiseError("goToDefinition failed - expected to find at least one definition location but got 0");
|
||||
}
|
||||
|
||||
if (definitionIndex >= definitions.length) {
|
||||
|
@ -1579,7 +1579,7 @@ namespace FourSlash {
|
|||
public goToTypeDefinition(definitionIndex: number) {
|
||||
const definitions = this.languageService.getTypeDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
|
||||
if (!definitions || !definitions.length) {
|
||||
this.raiseError("goToTypeDefinition failed - expected to at least one definition location but got 0");
|
||||
this.raiseError("goToTypeDefinition failed - expected to find at least one definition location but got 0");
|
||||
}
|
||||
|
||||
if (definitionIndex >= definitions.length) {
|
||||
|
@ -1600,7 +1600,7 @@ namespace FourSlash {
|
|||
this.raiseError(`goToDefinition - expected to 0 definition locations but got ${definitions.length}`);
|
||||
}
|
||||
else if (!foundDefinitions && !negative) {
|
||||
this.raiseError("goToDefinition - expected to at least one definition location but got 0");
|
||||
this.raiseError("goToDefinition - expected to find at least one definition location but got 0");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2792,20 +2792,36 @@ namespace ts {
|
|||
return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node;
|
||||
}
|
||||
|
||||
function climbPastPropertyAccess(node: Node) {
|
||||
return isRightSideOfPropertyAccess(node) ? node.parent : node;
|
||||
}
|
||||
|
||||
function climbPastManyPropertyAccesses(node: Node): Node {
|
||||
return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node;
|
||||
}
|
||||
|
||||
function isCallExpressionTarget(node: Node): boolean {
|
||||
if (isRightSideOfPropertyAccess(node)) {
|
||||
node = node.parent;
|
||||
}
|
||||
node = climbPastPropertyAccess(node);
|
||||
return node && node.parent && node.parent.kind === SyntaxKind.CallExpression && (<CallExpression>node.parent).expression === node;
|
||||
}
|
||||
|
||||
function isNewExpressionTarget(node: Node): boolean {
|
||||
if (isRightSideOfPropertyAccess(node)) {
|
||||
node = node.parent;
|
||||
}
|
||||
node = climbPastPropertyAccess(node);
|
||||
return node && node.parent && node.parent.kind === SyntaxKind.NewExpression && (<CallExpression>node.parent).expression === node;
|
||||
}
|
||||
|
||||
/** Returns a CallLikeExpression where `node` is the target being invoked. */
|
||||
function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined {
|
||||
const target = climbPastManyPropertyAccesses(node);
|
||||
const callLike = target.parent;
|
||||
return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target && callLike;
|
||||
}
|
||||
|
||||
function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
|
||||
const callLike = getAncestorCallLikeExpression(node);
|
||||
return callLike && typeChecker.getResolvedSignature(callLike).declaration;
|
||||
}
|
||||
|
||||
function isNameOfModuleDeclaration(node: Node) {
|
||||
return node.parent.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>node.parent).name === node;
|
||||
}
|
||||
|
@ -5072,14 +5088,25 @@ namespace ts {
|
|||
};
|
||||
}
|
||||
|
||||
function getSymbolInfo(typeChecker: TypeChecker, symbol: Symbol, node: Node) {
|
||||
return {
|
||||
symbolName: typeChecker.symbolToString(symbol), // Do not get scoped name, just the name of the symbol
|
||||
symbolKind: getSymbolKind(symbol, node),
|
||||
containerName: symbol.parent ? typeChecker.symbolToString(symbol.parent, node) : ""
|
||||
};
|
||||
}
|
||||
|
||||
function createDefinitionFromSignatureDeclaration(decl: SignatureDeclaration): DefinitionInfo {
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, decl.symbol, decl);
|
||||
return createDefinitionInfo(decl, symbolKind, symbolName, containerName);
|
||||
}
|
||||
|
||||
function getDefinitionFromSymbol(symbol: Symbol, node: Node): DefinitionInfo[] {
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const result: DefinitionInfo[] = [];
|
||||
const declarations = symbol.getDeclarations();
|
||||
const symbolName = typeChecker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol
|
||||
const symbolKind = getSymbolKind(symbol, node);
|
||||
const containerSymbol = symbol.parent;
|
||||
const containerName = containerSymbol ? typeChecker.symbolToString(containerSymbol, node) : "";
|
||||
const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, symbol, node);
|
||||
|
||||
if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) &&
|
||||
!tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) {
|
||||
|
@ -5205,6 +5232,12 @@ namespace ts {
|
|||
}
|
||||
|
||||
const typeChecker = program.getTypeChecker();
|
||||
|
||||
const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node);
|
||||
if (calledDeclaration) {
|
||||
return [createDefinitionFromSignatureDeclaration(calledDeclaration)];
|
||||
}
|
||||
|
||||
let symbol = typeChecker.getSymbolAtLocation(node);
|
||||
|
||||
// Could not find a symbol e.g. node is string or number keyword,
|
||||
|
|
22
tests/cases/fourslash/goToDeclarationDecoratorOverloads.ts
Normal file
22
tests/cases/fourslash/goToDeclarationDecoratorOverloads.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
// @Target: ES6
|
||||
// @experimentaldecorators: true
|
||||
|
||||
////async function f() {}
|
||||
////
|
||||
/////*defDecString*/function dec(target: any, propertyKey: string): void;
|
||||
/////*defDecSymbol*/function dec(target: any, propertyKey: symbol): void;
|
||||
////function dec(target: any, propertyKey: string | symbol) {}
|
||||
////
|
||||
////declare const s: symbol;
|
||||
////class C {
|
||||
//// @/*useDecString*/dec f() {}
|
||||
//// @/*useDecSymbol*/dec [s]() {}
|
||||
////}
|
||||
|
||||
goTo.marker("useDecString");
|
||||
goTo.definition();
|
||||
verify.caretAtMarker("defDecString");
|
||||
|
||||
goTo.marker("useDecSymbol");
|
||||
goTo.definition();
|
||||
verify.caretAtMarker("defDecSymbol");
|
|
@ -11,11 +11,11 @@
|
|||
|
||||
goTo.marker('constructorOverloadReference1');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('constructorDefinition');
|
||||
verify.caretAtMarker('constructorOverload1');
|
||||
|
||||
goTo.marker('constructorOverloadReference2');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('constructorDefinition');
|
||||
verify.caretAtMarker('constructorOverload2');
|
||||
|
||||
goTo.marker('constructorOverload1');
|
||||
goTo.definition();
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
/////*functionOverload1*/function /*functionOverload*/functionOverload();
|
||||
/////*functionOverload1*/function /*functionOverload*/functionOverload(value: number);
|
||||
/////*functionOverload2*/function functionOverload(value: string);
|
||||
/////*functionOverloadDefinition*/function functionOverload() {}
|
||||
////
|
||||
/////*functionOverloadReference1*/functionOverload();
|
||||
/////*functionOverloadReference1*/functionOverload(123);
|
||||
/////*functionOverloadReference2*/functionOverload("123");
|
||||
/////*brokenOverload*/functionOverload({});
|
||||
|
||||
goTo.marker('functionOverloadReference1');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('functionOverloadDefinition');
|
||||
verify.caretAtMarker('functionOverload1');
|
||||
|
||||
goTo.marker('functionOverloadReference2');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('functionOverloadDefinition');
|
||||
verify.caretAtMarker('functionOverload2');
|
||||
|
||||
goTo.marker('brokenOverload');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('functionOverload1');
|
||||
|
||||
goTo.marker('functionOverload');
|
||||
goTo.definition();
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////class MethodOverload {
|
||||
//// static me/*staticMethodOverload1*/thod();
|
||||
//// static me/*staticMethodOverload2*/thod(foo: string);
|
||||
/////*staticMethodDefinition*/static method(foo?: any) { }
|
||||
//// public met/*instanceMethodOverload1*/hod(): any;
|
||||
//// public met/*instanceMethodOverload2*/hod(foo: string);
|
||||
//// /*staticMethodOverload1*/static /*staticMethodOverload1Name*/method();
|
||||
//// /*staticMethodOverload2*/static method(foo: string);
|
||||
//// /*staticMethodDefinition*/static method(foo?: any) { }
|
||||
//// /*instanceMethodOverload1*/public /*instanceMethodOverload1Name*/method(): any;
|
||||
//// /*instanceMethodOverload2*/public method(foo: string);
|
||||
/////*instanceMethodDefinition*/public method(foo?: any) { return "foo" }
|
||||
////}
|
||||
|
||||
|
@ -20,25 +20,25 @@
|
|||
|
||||
goTo.marker('staticMethodReference1');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('staticMethodDefinition');
|
||||
verify.caretAtMarker('staticMethodOverload1');
|
||||
|
||||
goTo.marker('staticMethodReference2');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('staticMethodDefinition');
|
||||
verify.caretAtMarker('staticMethodOverload2');
|
||||
|
||||
goTo.marker('instanceMethodReference1');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('instanceMethodDefinition');
|
||||
verify.caretAtMarker('instanceMethodOverload1');
|
||||
|
||||
goTo.marker('instanceMethodReference2');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('instanceMethodDefinition');
|
||||
verify.caretAtMarker('instanceMethodOverload2');
|
||||
|
||||
goTo.marker('staticMethodOverload1');
|
||||
goTo.marker('staticMethodOverload1Name');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('staticMethodDefinition');
|
||||
|
||||
goTo.marker('instanceMethodOverload1');
|
||||
goTo.marker('instanceMethodOverload1Name');
|
||||
goTo.definition();
|
||||
verify.caretAtMarker('instanceMethodDefinition');
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// Test that we can climb past more than one property access to reach a call expression.
|
||||
|
||||
////namespace A {
|
||||
//// export namespace B {
|
||||
//// export function f(value: number): void;
|
||||
//// /*1*/export function f(value: string): void;
|
||||
//// export function f(value: number | string) {}
|
||||
//// }
|
||||
////}
|
||||
////A.B./*2*/f("");
|
||||
|
||||
goTo.marker("2");
|
||||
goTo.definition();
|
||||
verify.caretAtMarker("1");
|
|
@ -0,0 +1,16 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
/////*defFNumber*/function f(strs: TemplateStringsArray, x: number): void;
|
||||
/////*defFBool*/function f(strs: TemplateStringsArray, x: boolean): void;
|
||||
////function f(strs: TemplateStringsArray, x: number | boolean) {}
|
||||
////
|
||||
/////*useFNumber*/f`${0}`;
|
||||
/////*useFBool*/f`${false}`;
|
||||
|
||||
goTo.marker("useFNumber");
|
||||
goTo.definition();
|
||||
verify.caretAtMarker("defFNumber");
|
||||
|
||||
goTo.marker("useFBool");
|
||||
goTo.definition();
|
||||
verify.caretAtMarker("defFBool");
|
33
tests/cases/fourslash/goToDefinition_super.ts
Normal file
33
tests/cases/fourslash/goToDefinition_super.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
///<reference path="fourslash.ts"/>
|
||||
|
||||
////class A {
|
||||
//// /*ctr*/constructor() {}
|
||||
//// x() {}
|
||||
////}
|
||||
/////*B*/class B extends A {}
|
||||
////class C extends B {
|
||||
//// constructor() {
|
||||
//// /*super*/super();
|
||||
//// }
|
||||
//// method() {
|
||||
//// /*superExpression*/super.x();
|
||||
//// }
|
||||
////}
|
||||
////class D {
|
||||
//// constructor() {
|
||||
//// /*superBroken*/super();
|
||||
//// }
|
||||
////}
|
||||
|
||||
// Super in call position goes to constructor.
|
||||
goTo.marker("super");
|
||||
goTo.definition();
|
||||
verify.caretAtMarker("ctr");
|
||||
|
||||
// Super in any other position goes to the superclass.
|
||||
goTo.marker("superExpression");
|
||||
goTo.definition();
|
||||
verify.caretAtMarker("B");
|
||||
|
||||
goTo.marker("superBroken");
|
||||
verify.definitionCountIs(0);
|
Loading…
Reference in a new issue