Repository navigation
Call Hierarchy support for TypeScript #31863
Description
Activity
- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptCommittedThe team has roadmapped this issueThe team has roadmapped this issue
on Jun 11, 2019 what's changes in this issue? not
Find All References?Ron Buckton (@rbuckton) When I talked this over this Johannes Rieken (@jrieken) from the VS Code team, it sounds like we will need a specialized TypeScript server api to implement this correctly and efficiently. Determining outgoing calls in particular is very difficult using current TSServer apis. Can we please revisit this for 3.8
- addedIn DiscussionNot yet reached consensusNot yet reached consensusand removedCommittedThe team has roadmapped this issueThe team has roadmapped this issue
on Oct 31, 2019 DanielRosenwasser commented
on Oct 31, 2019 MemberMore actionsHey Johannes Rieken (@jrieken), what sort of things were insufficient with the current approach? It'd help to get an idea so we can make sure those are tested.
A few thing that I know of:
- Determining outgoing calls of a function is difficult
- Using references, we can't distinguish function calls from other references to that function
DanielRosenwasser commented
on Oct 31, 2019 MemberMore actionsI suppose that it would be hard to understand every call-like behavior (tagged templates, decorators, function calls, new invocations, etc.
Matt Bierner (@mjbvz), Johannes Rieken (@jrieken): I've been working on the TSServer API for call hierarchies, and here's what I've come up with:
export const enum CommandTypes { ... PrepareCallHierarchy = "prepareCallHierarchy", ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls", } export interface CallHierarchyItem { name: string; kind: ScriptElementKind; detail: string; file: string; span: TextSpan; selectionSpan: TextSpan; } export interface CallHierarchyIncomingCall { from: CallHierarchyItem; fromSpans: TextSpan[]; } export interface CallHierarchyOutgoingCall { to: CallHierarchyItem; fromSpans: TextSpan[]; } export interface PrepareCallHierarchyRequest extends FileLocationRequest { command: CommandTypes.PrepareCallHierarchy; } export interface PrepareCallHierarchyResponse extends Response { readonly body: CallHierarchyItem; } export interface ProvideCallHierarchyIncomingCallsRequest extends FileLocationRequest { command: CommandTypes.ProvideCallHierarchyIncomingCalls; } export interface ProvideCallHierarchyIncomingCallsResponse extends Response { readonly body: CallHierarchyIncomingCall[]; } export interface ProvideCallHierarchyOutgoingCallsRequest extends FileLocationRequest { command: CommandTypes.ProvideCallHierarchyOutgoingCalls; } export interface ProvideCallHierarchyOutgoingCallsResponse extends Response { readonly body: CallHierarchyOutgoingCall[]; }
Rather than round-tripping a
CallHierarchyItemas would be done in the VS Code API, VS Code would send TSServer the file and location of the declaration related to theCallHierarchyItem. We can then use that to determine the declaration from which to find incoming and outgoing calls.I've built a prototype of both the VS Code support for the TSServer API, as well as the TSServer implementation itself, though I do have a few additional questions:
Files in the Call Hierarchy
How should a
SourceFilebe represented as aCallHierarchyItem? In TS/JS, its frequently the case that the caller of a function is the source file itself:function foo() { } foo();
I am including source files in the prototype I have built, however I'm not certain of the best way to represent the "name" of the call hierarchy item:
Call Hierarchy Item Details
The comment for the
detailsproperty of aCallHierarchyItemindicates it should be used for something like a signature. Given that this is merely astring, what is the intended format? Since TS/JS functions/methods differ only in signature and not in implementation, does providing the signature even make sense here?Items to show in Call Hierarchy
This has been brought up in microsoft/vscode#84100, but I'll mention it here as well. TS/JS have several different calling conventions we should be aware of:
CallExpression(i.e.foo())NewExpression(i.e.new C())TaggedTemplateExpression(i.e.foo`text`)PropertyAccessExpression/ElementAccessExpression(when the reference is an accessor).Decorator(i.e.@dec class C { })JsxElement(i.e.<Foo />)

Method Signatures
Should we support Call Hierarchy on Method Signatures in interfaces? They obviously wouldn't have outgoing calls, but they could have incoming calls.
The VS version of Call Hierarchy does seem to support this (though they also include things such as places that a member is overridden or implemented as well):
Also, a
CallHierarchyItemcan only represent a single declaration, however TypeScript supports merging interfaces and function signatures across disparate files (either through global symbol merging or module augmentation). How should we represent theCallHierarchyItemfor the following cases:// file1.d.ts interface Shared { foo(): void; // merges with `Shared` in file2 } declare function bar(): void; // merges with `bar` in file2 // file2.d.ts interface Shared { foo(): void; // merges with `Shared` in file1 } declare function bar(): void; // merges with `bar` in file1 // file3.ts declare const x: Shared; x.foo(); // Which `foo` should we pick for the call hierarchy item's range/selection range? bar(); // Which `bar` should we pick for the call hierarchy item's range/selection range?
Rather than round-tripping a CallHierarchyItem as would be done in the VS Code API, VS Code would send TSServer the file and location of the declaration related to the CallHierarchyItem
We actually do the same - internally each item is associated with an ID and only that goes over the wire. For the API we wanted to be more expressive and give implementor a chance to store data subsequent requests in an item.
I'm not certain of the best way to represent the "name" of the call hierarchy item:
That's a good question. I think the base-filename, e.g
main.tsandSymbolKind.Filewould be a good start. Maybe put the rest of the path as detail?Call Hierarchy Item Details
Not sure tbh - the details field is a bit of a free form field for extensions to add "something" useful. We have the same for outline and there TS isn't using it, some extension are. Generally, it will be rendered as is, no MD formatting or so.
Should we support Call Hierarchy on Method Signatures in interfaces?
👍 I think that's useful in order to see how an interface is being used.
How should we represent the CallHierarchyItem for the following cases:
🤷♂ though question... for calls each item could just be a call but when starting/preparing it's a little more trick... We might need to evolve our API so that prepare can return N items.
That's a good question. I think the base-filename, e.g
main.tsandSymbolKind.Filewould be a good start. Maybe put the rest of the path as detail?Is there an API I can leverage to format the path in an OS-specific way? As you can see from the screenshot above, it's currently using TypeScript's internal path representation (
c:/dev/tests) as opposed to a Windows-specific representation (C:\dev\tests). It would also be nice if this were easy to be made relative to the workspace. I know this is something you already do for resource Urls in the tree view and to deduplicate editor tabs.Yeah, we have asRelativePath which should be able to handle that


See microsoft/vscode#16110