Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1546,7 +1546,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) {
Expand All @@ -1561,7 +1561,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) {
Expand All @@ -1582,7 +1582,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");
}
}

Expand Down
53 changes: 43 additions & 10 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2788,20 +2788,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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems you are checking callLike twice

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First one is checking whether it exists. Last one is returning it from the function.

}

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;
}
Expand Down Expand Up @@ -5068,14 +5084,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)) {
Expand Down Expand Up @@ -5201,6 +5228,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,
Expand Down
22 changes: 22 additions & 0 deletions tests/cases/fourslash/goToDeclarationDecoratorOverloads.ts
Original file line number Diff line number Diff line change
@@ -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");
4 changes: 2 additions & 2 deletions tests/cases/fourslash/goToDefinitionConstructorOverloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
13 changes: 9 additions & 4 deletions tests/cases/fourslash/goToDefinitionFunctionOverloads.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
22 changes: 11 additions & 11 deletions tests/cases/fourslash/goToDefinitionMethodOverloads.ts
Original file line number Diff line number Diff line change
@@ -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" }
////}

Expand All @@ -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');

Original file line number Diff line number Diff line change
@@ -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");
16 changes: 16 additions & 0 deletions tests/cases/fourslash/goToDefinitionTaggedTemplateOverloads.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 33 additions & 0 deletions tests/cases/fourslash/goToDefinition_super.ts
Original file line number Diff line number Diff line change
@@ -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);