From 4ca40ce4587b6de09e1c9070471b99a81f359965 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 18 Nov 2019 15:48:48 -0800 Subject: [PATCH 1/6] Add support for Call Hierarchies in language server --- src/compiler/core.ts | 6 +- src/harness/client.ts | 45 ++ src/harness/fourslash.ts | 237 ++++++++- src/harness/harnessLanguageService.ts | 9 + src/server/protocol.ts | 50 +- src/server/session.ts | 108 +++- src/services/callHierarchy.ts | 462 ++++++++++++++++++ src/services/services.ts | 23 + src/services/shims.ts | 27 + src/services/tsconfig.json | 1 + src/services/types.ts | 22 + src/services/utilities.ts | 56 ++- src/testRunner/unittests/tsserver/session.ts | 3 + .../reference/api/tsserverlibrary.d.ts | 60 +++ tests/baselines/reference/api/typescript.d.ts | 18 + .../cases/fourslash/callHierarchyAccessor.ts | 52 ++ tests/cases/fourslash/callHierarchyClass.ts | 50 ++ .../callHierarchyConstNamedArrowFunction.ts | 50 ++ .../callHierarchyConstNamedClassExpression.ts | 52 ++ ...llHierarchyConstNamedFunctionExpression.ts | 50 ++ .../cases/fourslash/callHierarchyDecorator.ts | 51 ++ .../callHierarchyExportDefaultClass.ts | 56 +++ .../callHierarchyExportDefaultFunction.ts | 54 ++ .../callHierarchyExportEqualsFunction.ts | 41 ++ tests/cases/fourslash/callHierarchyFile.ts | 29 ++ .../cases/fourslash/callHierarchyFunction.ts | 72 +++ .../fourslash/callHierarchyInterfaceMethod.ts | 33 ++ .../fourslash/callHierarchyJsxElement.ts | 52 ++ .../fourslash/callHierarchyTaggedTemplate.ts | 50 ++ tests/cases/fourslash/fourslash.ts | 35 ++ 30 files changed, 1820 insertions(+), 34 deletions(-) create mode 100644 src/services/callHierarchy.ts create mode 100644 tests/cases/fourslash/callHierarchyAccessor.ts create mode 100644 tests/cases/fourslash/callHierarchyClass.ts create mode 100644 tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts create mode 100644 tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts create mode 100644 tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts create mode 100644 tests/cases/fourslash/callHierarchyDecorator.ts create mode 100644 tests/cases/fourslash/callHierarchyExportDefaultClass.ts create mode 100644 tests/cases/fourslash/callHierarchyExportDefaultFunction.ts create mode 100644 tests/cases/fourslash/callHierarchyExportEqualsFunction.ts create mode 100644 tests/cases/fourslash/callHierarchyFile.ts create mode 100644 tests/cases/fourslash/callHierarchyFunction.ts create mode 100644 tests/cases/fourslash/callHierarchyInterfaceMethod.ts create mode 100644 tests/cases/fourslash/callHierarchyJsxElement.ts create mode 100644 tests/cases/fourslash/callHierarchyTaggedTemplate.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index d03b7e1c85c7b..0be570c44318d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1330,8 +1330,10 @@ namespace ts { return result; } - export function group(values: readonly T[], getGroupId: (value: T) => string): readonly (readonly T[])[] { - return arrayFrom(arrayToMultiMap(values, getGroupId).values()); + export function group(values: readonly T[], getGroupId: (value: T) => string): readonly (readonly T[])[]; + export function group(values: readonly T[], getGroupId: (value: T) => string, resultSelector: (values: readonly T[]) => R): R[]; + export function group(values: readonly T[], getGroupId: (value: T) => string, resultSelector: (values: readonly T[]) => readonly T[] = identity): readonly (readonly T[])[] { + return arrayFrom(arrayToMultiMap(values, getGroupId).values(), resultSelector); } export function clone(object: T): T { diff --git a/src/harness/client.ts b/src/harness/client.ts index 046a53030404a..b0a1bf0052d70 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -741,6 +741,51 @@ namespace ts.server { return notImplemented(); } + private convertCallHierarchyItem(item: protocol.CallHierarchyItem): CallHierarchyItem { + return { + file: item.file, + name: item.name, + kind: item.kind, + span: this.decodeSpan(item.span, item.file), + selectionSpan: this.decodeSpan(item.selectionSpan, item.file) + }; + } + + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | undefined { + const args = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); + const response = this.processResponse(request); + return response.body && this.convertCallHierarchyItem(response.body); + } + + private convertCallHierarchyIncomingCall(item: protocol.CallHierarchyIncomingCall): CallHierarchyIncomingCall { + return { + from: this.convertCallHierarchyItem(item.from), + fromSpans: item.fromSpans.map(span => this.decodeSpan(span, item.from.file)) + }; + } + + provideCallHierarchyIncomingCalls(fileName: string, position: number) { + const args = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); + const response = this.processResponse(request); + return response.body.map(item => this.convertCallHierarchyIncomingCall(item)); + } + + private convertCallHierarchyOutgoingCall(file: string, item: protocol.CallHierarchyOutgoingCall): CallHierarchyOutgoingCall { + return { + to: this.convertCallHierarchyItem(item.to), + fromSpans: item.fromSpans.map(span => this.decodeSpan(span, file)) + }; + } + + provideCallHierarchyOutgoingCalls(fileName: string, position: number) { + const args = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); + const response = this.processResponse(request); + return response.body.map(item => this.convertCallHierarchyOutgoingCall(fileName, item)); + } + getProgram(): Program { throw new Error("SourceFile objects are not serializable through the server protocol."); } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index f668037861798..d5da70c53b3c4 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -732,11 +732,8 @@ namespace FourSlash { if (!range) { this.raiseError(`goToDefinitionsAndBoundSpan failed - found a TextSpan ${JSON.stringify(defs.textSpan)} when it wasn't expected.`); } - else if (defs.textSpan.start !== range.pos || defs.textSpan.length !== range.end - range.pos) { - const expected: ts.TextSpan = { - start: range.pos, length: range.end - range.pos - }; - this.raiseError(`goToDefinitionsAndBoundSpan failed - expected to find TextSpan ${JSON.stringify(expected)} but got ${JSON.stringify(defs.textSpan)}`); + else { + this.assertTextSpanEqualsRange(defs.textSpan, range, "goToDefinitionsAndBoundSpan failed"); } } @@ -3080,6 +3077,142 @@ namespace FourSlash { Harness.IO.log(stringify(codeFixes)); } + public verifyCallHierarchy(options: false | FourSlashInterface.VerifyCallHierarchyOptions | Range) { + const callHierarchyItem = this.languageService.prepareCallHierarchy(this.activeFile.fileName, this.currentCaretPosition)!; + this.assertCallHierarchyItemMatches(callHierarchyItem, options); + } + + public verifyCallHierarchyIncomingCalls(options: FourSlashInterface.Sequence) { + const incomingCalls = this.languageService.provideCallHierarchyIncomingCalls(this.activeFile.fileName, this.currentCaretPosition)!; + this.assertCallHierarchyIncomingCallsMatch(incomingCalls, options); + } + + public verifyCallHierarchyOutgoingCalls(options: FourSlashInterface.Sequence) { + const outgoingCalls = this.languageService.provideCallHierarchyOutgoingCalls(this.activeFile.fileName, this.currentCaretPosition)!; + this.assertCallHierarchyOutgoingCallsMatch(outgoingCalls, options); + } + + private assertSequence(actual: readonly T[] | undefined, expected: FourSlashInterface.Sequence, equate: (actual: T, expected: U) => boolean | undefined, assertion: (actual: T, expected: U, message?: string) => void, message?: string, stringifyActual: (actual: T) => string = stringifyFallback, stringifyExpected: (expected: U) => string = stringifyFallback) { + // normalize expected input + if (!expected) { + expected = { exact: true, values: ts.emptyArray }; + } + else if (ts.isArray(expected)) { + expected = { exact: false, values: expected }; + } + + if (expected.exact) { + if (actual === undefined || actual.length === 0) { + if (expected.values.length !== 0) { + this.raiseError(`${prefixMessage(message)}Expected sequence to have exactly ${expected.values.length} item${expected.values.length > 1 ? "s" : ""} but got ${actual ? "an empty array" : "undefined"} instead.\nExpected:\n${stringifyArray(expected.values, stringifyExpected)}`); + } + return; + } + if (expected.values.length === 0) { + this.raiseError(`${prefixMessage(message)}Expected sequence to be empty but got ${actual.length} item${actual.length > 1 ? "s" : ""} instead.\nActual:\n${stringifyArray(actual, stringifyActual)}`); + return; + } + } + else if (actual === undefined || actual.length === 0) { + if (expected.values.length !== 0) { + this.raiseError(`${prefixMessage(message)}Expected sequence to have at least ${expected.values.length} item${expected.values.length > 1 ? "s" : ""} but got ${actual ? "an empty array" : "undefined"} instead.\nExpected:\n${stringifyArray(expected.values, stringifyExpected)}`); + } + return; + } + + let expectedIndex = 0; + let actualIndex = 0; + while (expectedIndex < expected.values.length && actualIndex < actual.length) { + const actualItem = actual[actualIndex]; + actualIndex++; + const expectedItem = expected.values[expectedIndex]; + const result = expected.exact || equate(actualItem, expectedItem); + if (result) { + assertion(actualItem, expectedItem, message); + expectedIndex++; + } + else if (expected.exact) { + this.raiseError(`${prefixMessage(message)}Expected item at index ${expectedIndex} to be ${stringifyExpected(expectedItem)} but got ${stringifyActual(actualItem)} instead.`); + } + else if (result === undefined) { + this.raiseError(`${prefixMessage(message)}Unable to compare items`); + } + } + + if (expectedIndex < expected.values.length) { + const expectedItem = expected.values[expectedIndex]; + this.raiseError(`${prefixMessage(message)}Expected array to contain ${stringifyExpected(expectedItem)} but it was not found.`); + } + } + + private assertCallHierarchyItemMatches(callHierarchyItem: ts.CallHierarchyItem | undefined, options: false | FourSlashInterface.VerifyCallHierarchyOptions | Range, message?: string) { + if (!options) { + assert.isUndefined(callHierarchyItem, this.messageAtLastKnownMarker(`${prefixMessage(message)}Expected location to not have a call hierarchy`)); + } + else { + assert.isDefined(callHierarchyItem, this.messageAtLastKnownMarker(`${prefixMessage(message)}Expected location to have a call hierarchy`)); + if (!callHierarchyItem) return; + if (isRange(options)) { + options = { selectionRange: options }; + } + if (options.kind !== undefined) { + assert.equal(callHierarchyItem.kind, options.kind, this.messageAtLastKnownMarker(`${prefixMessage(message)}Invalid kind`)); + } + if (options.range) { + assert.equal(callHierarchyItem.file, options.range.fileName, this.messageAtLastKnownMarker(`${prefixMessage(message)}Incorrect file for call hierarchy item`)); + this.assertTextSpanEqualsRange(callHierarchyItem.span, options.range, `${prefixMessage(message)}Incorrect range for declaration`); + } + if (options.selectionRange) { + assert.equal(callHierarchyItem.file, options.selectionRange.fileName, this.messageAtLastKnownMarker(`${prefixMessage(message)}Incorrect file for call hierarchy item`)); + this.assertTextSpanEqualsRange(callHierarchyItem.selectionSpan, options.selectionRange, `${prefixMessage(message)}Incorrect selectionRange for declaration`); + } + if (options.incoming !== undefined) { + const incomingCalls = this.languageService.provideCallHierarchyIncomingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start); + this.assertCallHierarchyIncomingCallsMatch(incomingCalls, options.incoming, message); + } + if (options.outgoing !== undefined) { + const outgoingCalls = this.languageService.provideCallHierarchyOutgoingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start); + this.assertCallHierarchyOutgoingCallsMatch(outgoingCalls, options.outgoing, message); + } + } + } + + private assertCallHierarchyIncomingCallsMatch(incomingCalls: ts.CallHierarchyIncomingCall[], options: FourSlashInterface.Sequence, message?: string) { + this.assertSequence(incomingCalls, options, + (actual, expected) => { + expected = normalizeCallHierarchyIncomingCallOptions(expected); + const from = normalizeCallHierarchyOptions(expected.from); + return from.selectionRange && textSpanEqualsRange(actual.from.selectionSpan, from.selectionRange); + }, + (actual, expected, message) => { + expected = normalizeCallHierarchyIncomingCallOptions(expected); + const from = normalizeCallHierarchyOptions(expected.from); + this.assertCallHierarchyItemMatches(actual.from, from, message); + if (expected.fromRanges !== undefined) this.assertSequence(actual.fromSpans, expected.fromRanges, textSpanEqualsRange, ts.noop, `${prefixMessage(message)}Invalid fromRange`); + }, `${prefixMessage(message)}Invalid incoming calls`); + } + + private assertCallHierarchyOutgoingCallsMatch(outgoingCalls: ts.CallHierarchyOutgoingCall[], options: FourSlashInterface.Sequence, message?: string) { + this.assertSequence(outgoingCalls, options, + (actual, expected) => { + expected = normalizeCallHierarchyOutgoingCallOptions(expected); + const to = normalizeCallHierarchyOptions(expected.to); + return to.selectionRange && textSpanEqualsRange(actual.to.selectionSpan, to.selectionRange); + }, + (actual, expected, message) => { + expected = normalizeCallHierarchyOutgoingCallOptions(expected); + const to = normalizeCallHierarchyOptions(expected.to); + this.assertCallHierarchyItemMatches(actual.to, to, message); + if (expected.fromRanges !== undefined) this.assertSequence(actual.fromSpans, expected.fromRanges, textSpanEqualsRange, ts.noop, `${prefixMessage(message)}Invalid fromRange`); + }, `${prefixMessage(message)}Invalid outgoing calls`); + } + + private assertTextSpanEqualsRange(span: ts.TextSpan, range: Range, message?: string) { + if (!textSpanEqualsRange(span, range)) { + this.raiseError(`${prefixMessage(message)}Expected to find TextSpan ${JSON.stringify({ start: range.pos, length: range.end - range.pos })} but got ${JSON.stringify(span)} instead.`); + } + } + private getLineContent(index: number) { const text = this.getFileContent(this.activeFile.fileName); const pos = this.languageServiceAdapterHost.lineAndCharacterToPosition(this.activeFile.fileName, { line: index, character: 0 }); @@ -3213,6 +3346,40 @@ namespace FourSlash { } } + function prefixMessage(message: string | undefined) { + return message ? `${message} - ` : ""; + } + + function stringifyArray(values: readonly T[], stringify: (value: T) => string, indent = " ") { + return values.length ? `${indent}[\n${indent} ${values.map(stringify).join(`,\n${indent} `)}\n${indent}]` : `${indent}[]`; + } + + function stringifyFallback(value: unknown) { + return JSON.stringify(value); + } + + function textSpanEqualsRange(span: ts.TextSpan, range: Range) { + return span.start === range.pos && span.length === range.end - range.pos; + } + + function isRange(value: object): value is Range { + return ts.hasProperty(value, "pos") + && ts.hasProperty(value, "end") + && ts.hasProperty(value, "fileName"); + } + + function normalizeCallHierarchyOptions(options: Range | FourSlashInterface.VerifyCallHierarchyOptions) { + return isRange(options) ? { selectionRange: options } : options; + } + + function normalizeCallHierarchyIncomingCallOptions(options: Range | FourSlashInterface.VerifyCallHierarchyIncomingCallOptions) { + return isRange(options) ? { from: options } : options; + } + + function normalizeCallHierarchyOutgoingCallOptions(options: Range | FourSlashInterface.VerifyCallHierarchyOutgoingCallOptions) { + return isRange(options) ? { to: options } : options; + } + function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: readonly ts.TextChange[]): ts.TextRange { forEachTextChange(textChanges, change => { const update = (p: number): number => updatePosition(p, change.span.start, ts.textSpanEnd(change.span), change.newText); @@ -3274,7 +3441,7 @@ namespace FourSlash { function runCode(code: string, state: TestState): void { // Compile and execute the test const wrappedCode = - `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) { + `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, sequence, verifyOperationIsCancelled) { ${code} })`; try { @@ -3288,7 +3455,7 @@ ${code} const cancellation = new FourSlashInterface.Cancellation(state); // eslint-disable-next-line no-eval const f = eval(wrappedCode); - f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, verifyOperationIsCancelled); + f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, FourSlashInterface.Sequence, verifyOperationIsCancelled); } catch (err) { throw err; @@ -4287,6 +4454,18 @@ namespace FourSlashInterface { this.state.getEditsForFileRename(options); } + public callHierarchy(options: false | FourSlash.Range | VerifyCallHierarchyOptions) { + this.state.verifyCallHierarchy(options); + } + + public callHierarchyIncomingCalls(options: Sequence) { + this.state.verifyCallHierarchyIncomingCalls(options); + } + + public callHierarchyOutgoingCalls(options: Sequence) { + this.state.verifyCallHierarchyOutgoingCalls(options); + } + public moveToNewFile(options: MoveToNewFileOptions): void { this.state.moveToNewFile(options); } @@ -5352,4 +5531,48 @@ namespace FourSlashInterface { readonly providePrefixAndSuffixTextForRename?: boolean; }; export type RenameLocationOptions = FourSlash.Range | { readonly range: FourSlash.Range, readonly prefixText?: string, readonly suffixText?: string }; + + export type Sequence = + | false // Indicates the actual result must be undefined or contain no elements + | readonly T[] // Indicates the actual result must at least contain the specified elements, in any order + | { + readonly exact?: boolean; // Indicates the actual result must contain all of the elements in `values` (no more and no fewer). + readonly values: readonly T[]; + }; + + export namespace Sequence { + export function atLeast(array: readonly T[]): Sequence { + return { exact: false, values: array }; + } + + export function exact(array: readonly T[]): Sequence { + return { exact: true, values: array }; + } + + export function one(value: T): Sequence { + return { exact: true, values: [value] }; + } + + export function none(): Sequence { + return false; + } + } + + export interface VerifyCallHierarchyOptions { + readonly range?: FourSlash.Range; + readonly kind?: ts.ScriptElementKind; + readonly selectionRange?: FourSlash.Range; + readonly incoming?: Sequence; + readonly outgoing?: Sequence; + } + + export interface VerifyCallHierarchyIncomingCallOptions { + readonly from: VerifyCallHierarchyOptions | FourSlash.Range; + readonly fromRanges?: Sequence; + } + + export interface VerifyCallHierarchyOutgoingCallOptions { + readonly to: VerifyCallHierarchyOptions | FourSlash.Range; + readonly fromRanges?: Sequence; + } } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index e355a7c664740..ac085e2d9cbb7 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -574,6 +574,15 @@ namespace Harness.LanguageService { getEditsForFileRename(): readonly ts.FileTextChanges[] { throw new Error("Not supported on the shim."); } + prepareCallHierarchy(fileName: string, position: number) { + return unwrapJSONCallResult(this.shim.prepareCallHierarchy(fileName, position)); + } + provideCallHierarchyIncomingCalls(fileName: string, position: number) { + return unwrapJSONCallResult(this.shim.provideCallHierarchyIncomingCalls(fileName, position)); + } + provideCallHierarchyOutgoingCalls(fileName: string, position: number) { + return unwrapJSONCallResult(this.shim.provideCallHierarchyOutgoingCalls(fileName, position)); + } getEmitOutput(fileName: string): ts.EmitOutput { return unwrapJSONCallResult(this.shim.getEmitOutput(fileName)); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 925e7638f9d4e..c7047959c85fc 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -4,7 +4,7 @@ * Declaration module describing the TypeScript Server protocol */ namespace ts.server.protocol { - // NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`. + // NOTE: If updating this, be sure to also update `allCommandNames` in `testRunner/unittests/tsserver/session.ts`. export const enum CommandTypes { JsxClosingTag = "jsxClosingTag", Brace = "brace", @@ -137,7 +137,11 @@ namespace ts.server.protocol { /* @internal */ SelectionRangeFull = "selectionRange-full", - // NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`. + PrepareCallHierarchy = "prepareCallHierarchy", + ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", + ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls", + + // NOTE: If updating this, be sure to also update `allCommandNames` in `testRunner/unittests/tsserver/session.ts`. } /** @@ -2953,6 +2957,48 @@ namespace ts.server.protocol { body?: NavigationTree; } + export interface CallHierarchyItem { + name: string; + kind: ScriptElementKind; + 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[]; + } + export const enum IndentStyle { None = "None", Block = "Block", diff --git a/src/server/session.ts b/src/server/session.ts index e2ca230e9424d..21a4eb7296be1 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1051,7 +1051,7 @@ namespace ts.server { if (simplifiedResult) { return { definitions: this.mapDefinitionInfo(definitions, project), - textSpan: toProcolTextSpan(textSpan, scriptInfo) + textSpan: toProtocolTextSpan(textSpan, scriptInfo) }; } @@ -1306,7 +1306,7 @@ namespace ts.server { if (info.canRename) { const { canRename, fileToRename, displayName, fullDisplayName, kind, kindModifiers, triggerSpan } = info; return identity( - { canRename, fileToRename, displayName, fullDisplayName, kind, kindModifiers, triggerSpan: toProcolTextSpan(triggerSpan, scriptInfo) }); + { canRename, fileToRename, displayName, fullDisplayName, kind, kindModifiers, triggerSpan: toProtocolTextSpan(triggerSpan, scriptInfo) }); } else { return info; @@ -1406,8 +1406,8 @@ namespace ts.server { if (simplifiedResult) { const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; return spans.map(s => ({ - textSpan: toProcolTextSpan(s.textSpan, scriptInfo), - hintSpan: toProcolTextSpan(s.hintSpan, scriptInfo), + textSpan: toProtocolTextSpan(s.textSpan, scriptInfo), + hintSpan: toProtocolTextSpan(s.hintSpan, scriptInfo), bannerText: s.bannerText, autoCollapse: s.autoCollapse, kind: s.kind @@ -1596,7 +1596,7 @@ namespace ts.server { const entries = mapDefined(completions.entries, entry => { if (completions.isMemberCompletion || startsWith(entry.name.toLowerCase(), prefix.toLowerCase())) { const { name, kind, kindModifiers, sortText, insertText, replacementSpan, hasAction, source, isRecommended } = entry; - const convertedSpan = replacementSpan ? toProcolTextSpan(replacementSpan, scriptInfo) : undefined; + const convertedSpan = replacementSpan ? toProtocolTextSpan(replacementSpan, scriptInfo) : undefined; // Use `hasAction || undefined` to avoid serializing `false`. return { name, kind, kindModifiers, sortText, insertText, replacementSpan: convertedSpan, hasAction: hasAction || undefined, source, isRecommended }; } @@ -1766,7 +1766,7 @@ namespace ts.server { text: item.text, kind: item.kind, kindModifiers: item.kindModifiers, - spans: item.spans.map(span => toProcolTextSpan(span, scriptInfo)), + spans: item.spans.map(span => toProtocolTextSpan(span, scriptInfo)), childItems: this.mapLocationNavigationBarItems(item.childItems, scriptInfo), indent: item.indent })); @@ -1787,8 +1787,8 @@ namespace ts.server { text: tree.text, kind: tree.kind, kindModifiers: tree.kindModifiers, - spans: tree.spans.map(span => toProcolTextSpan(span, scriptInfo)), - nameSpan: tree.nameSpan && toProcolTextSpan(tree.nameSpan, scriptInfo), + spans: tree.spans.map(span => toProtocolTextSpan(span, scriptInfo)), + nameSpan: tree.nameSpan && toProtocolTextSpan(tree.nameSpan, scriptInfo), childItems: map(tree.childItems, item => this.toLocationNavigationTree(item, scriptInfo)) }; } @@ -2050,7 +2050,7 @@ namespace ts.server { return !spans ? undefined : simplifiedResult - ? spans.map(span => toProcolTextSpan(span, scriptInfo)) + ? spans.map(span => toProtocolTextSpan(span, scriptInfo)) : spans; } @@ -2122,7 +2122,7 @@ namespace ts.server { private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange { const result: protocol.SelectionRange = { - textSpan: toProcolTextSpan(selectionRange.textSpan, scriptInfo), + textSpan: toProtocolTextSpan(selectionRange.textSpan, scriptInfo), }; if (selectionRange.parent) { result.parent = this.mapSelectionRange(selectionRange.parent, scriptInfo); @@ -2130,6 +2130,73 @@ namespace ts.server { return result; } + private toProtocolCallHierarchyItem(item: CallHierarchyItem, scriptInfo?: ScriptInfo): protocol.CallHierarchyItem { + if (!scriptInfo) { + const file = toNormalizedPath(item.file); + scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); + if (!scriptInfo) { + this.projectService.logErrorForScriptInfoNotFound(file); + return Errors.ThrowNoProject(); + } + } + return { + name: item.name, + kind: item.kind, + file: item.file, + span: toProtocolTextSpan(item.span, scriptInfo), + selectionSpan: toProtocolTextSpan(item.selectionSpan, scriptInfo) + }; + } + + private toProtocolCallHierarchyIncomingCall(incomingCall: CallHierarchyIncomingCall, scriptInfo: ScriptInfo): protocol.CallHierarchyIncomingCall { + return { + from: this.toProtocolCallHierarchyItem(incomingCall.from), + fromSpans: incomingCall.fromSpans.map(fromSpan => toProtocolTextSpan(fromSpan, scriptInfo)) + }; + } + + private toProtocolCallHierarchyOutgoingCall(outgoingCall: CallHierarchyOutgoingCall, scriptInfo: ScriptInfo): protocol.CallHierarchyOutgoingCall { + return { + to: this.toProtocolCallHierarchyItem(outgoingCall.to), + fromSpans: outgoingCall.fromSpans.map(fromSpan => toProtocolTextSpan(fromSpan, scriptInfo)) + }; + } + + private prepareCallHierarchy(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyItem | undefined { + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); + if (scriptInfo) { + const position = this.getPosition(args, scriptInfo); + const item = languageService.prepareCallHierarchy(file, position); + return !item + ? undefined + : this.toProtocolCallHierarchyItem(item, scriptInfo); + } + return undefined; + } + + private provideCallHierarchyIncomingCalls(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyIncomingCall[] { + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); + if (!scriptInfo) { + this.projectService.logErrorForScriptInfoNotFound(file); + return Errors.ThrowNoProject(); + } + const incomingCalls = languageService.provideCallHierarchyIncomingCalls(file, this.getPosition(args, scriptInfo)); + return incomingCalls.map(call => this.toProtocolCallHierarchyIncomingCall(call, scriptInfo)); + } + + private provideCallHierarchyOutgoingCalls(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyOutgoingCall[] { + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); + if (!scriptInfo) { + this.projectService.logErrorForScriptInfoNotFound(file); + return Errors.ThrowNoProject(); + } + const outgoingCalls = languageService.provideCallHierarchyOutgoingCalls(file, this.getPosition(args, scriptInfo)); + return outgoingCalls.map(call => this.toProtocolCallHierarchyOutgoingCall(call, scriptInfo)); + } + getCanonicalFileName(fileName: string) { const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); return normalizePath(name); @@ -2495,6 +2562,15 @@ namespace ts.server { [CommandNames.SelectionRangeFull]: (request: protocol.SelectionRangeRequest) => { return this.requiredResponse(this.getSmartSelectionRange(request.arguments, /*simplifiedResult*/ false)); }, + [CommandNames.PrepareCallHierarchy]: (request: protocol.PrepareCallHierarchyRequest) => { + return this.requiredResponse(this.prepareCallHierarchy(request.arguments)); + }, + [CommandNames.ProvideCallHierarchyIncomingCalls]: (request: protocol.ProvideCallHierarchyIncomingCallsRequest) => { + return this.requiredResponse(this.provideCallHierarchyIncomingCalls(request.arguments)); + }, + [CommandNames.ProvideCallHierarchyOutgoingCalls]: (request: protocol.ProvideCallHierarchyOutgoingCallsRequest) => { + return this.requiredResponse(this.provideCallHierarchyOutgoingCalls(request.arguments)); + }, }); public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) { @@ -2618,7 +2694,13 @@ namespace ts.server { readonly project: Project; } - function toProcolTextSpan(textSpan: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan { + // function toLanguageServiceTextSpan(textSpan: protocol.TextSpan, scriptInfo: ScriptInfo): TextSpan { + // const start = scriptInfo.lineOffsetToPosition(textSpan.start.line, textSpan.start.offset); + // const end = scriptInfo.lineOffsetToPosition(textSpan.end.line, textSpan.end.offset); + // return { start, length: end - start }; + // } + + function toProtocolTextSpan(textSpan: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan { return { start: scriptInfo.positionToLineOffset(textSpan.start), end: scriptInfo.positionToLineOffset(textSpanEnd(textSpan)) @@ -2626,8 +2708,8 @@ namespace ts.server { } function toProtocolTextSpanWithContext(span: TextSpan, contextSpan: TextSpan | undefined, scriptInfo: ScriptInfo): protocol.TextSpanWithContext { - const textSpan = toProcolTextSpan(span, scriptInfo); - const contextTextSpan = contextSpan && toProcolTextSpan(contextSpan, scriptInfo); + const textSpan = toProtocolTextSpan(span, scriptInfo); + const contextTextSpan = contextSpan && toProtocolTextSpan(contextSpan, scriptInfo); return contextTextSpan ? { ...textSpan, contextStart: contextTextSpan.start, contextEnd: contextTextSpan.end } : textSpan; diff --git a/src/services/callHierarchy.ts b/src/services/callHierarchy.ts new file mode 100644 index 0000000000000..53520b30c84e3 --- /dev/null +++ b/src/services/callHierarchy.ts @@ -0,0 +1,462 @@ +/* @internal */ +namespace ts.CallHierarchy { + export type NamedExpression = + | ClassExpression & { name: Identifier } + | FunctionExpression & { name: Identifier } + ; + + /** Indictates whether a node is named function or class expression. */ + function isNamedExpression(node: Node): node is NamedExpression { + return (isFunctionExpression(node) || isClassExpression(node)) && isNamedDeclaration(node); + } + + export type ConstNamedExpression = + | ClassExpression & { name: undefined, parent: VariableDeclaration & { name: Identifier } } + | FunctionExpression & { name: undefined, parent: VariableDeclaration & { name: Identifier } } + | ArrowFunction & { name: undefined, parent: VariableDeclaration & { name: Identifier } } + ; + + /** Indicates whether a node is a function, arrow, or class expression assigned to a constant variable. */ + function isConstNamedExpression(node: Node): node is ConstNamedExpression { + return (isFunctionExpression(node) || isArrowFunction(node) || isClassExpression(node)) + && isVariableDeclaration(node.parent) + && node === node.parent.initializer + && isIdentifier(node.parent.name) + && !!(getCombinedNodeFlags(node.parent) & NodeFlags.Const); + } + + export type CallHierarchyDeclaration = + | SourceFile + | ModuleDeclaration & { name: Identifier } + | FunctionDeclaration + | ClassDeclaration + | MethodDeclaration + | GetAccessorDeclaration + | SetAccessorDeclaration + | NamedExpression + | ConstNamedExpression + ; + + /** + * Indicates whether a node could possibly be a call hierarchy declaration. + * + * See `resolveCallHierarchyDeclaration` for the specific rules. + */ + function isPossibleCallHierarchyDeclaration(node: Node) { + return isSourceFile(node) + || isModuleDeclaration(node) + || isFunctionDeclaration(node) + || isFunctionExpression(node) + || isClassDeclaration(node) + || isClassExpression(node) + || isMethodDeclaration(node) + || isMethodSignature(node) + || isGetAccessorDeclaration(node) + || isSetAccessorDeclaration(node); + } + + /** + * Indicates whether a node is a valid a call hierarchy declaration. + * + * See `resolveCallHierarchyDeclaration` for the specific rules. + */ + function isValidCallHierarchyDeclaration(node: Node): node is CallHierarchyDeclaration { + return isSourceFile(node) + || isModuleDeclaration(node) && isIdentifier(node.name) + || isFunctionDeclaration(node) + || isClassDeclaration(node) + || isMethodDeclaration(node) + || isMethodSignature(node) + || isGetAccessorDeclaration(node) + || isSetAccessorDeclaration(node) + || isNamedExpression(node) + || isConstNamedExpression(node); + } + + /** Gets the node that can be used as a reference to a call hierarchy declaration. */ + function getCallHierarchyDeclarationReferenceNode(node: CallHierarchyDeclaration) { + if (isSourceFile(node)) return node; + if (isNamedDeclaration(node)) return node.name; + if (isConstNamedExpression(node)) return node.parent.name; + return Debug.assertDefined(node.modifiers && find(node.modifiers, isDefaultModifier)); + } + + function isDefaultModifier(node: Node) { + return node.kind === SyntaxKind.DefaultKeyword; + } + + /** Gets the symbol for a call hierarchy declaration. */ + function getSymbolOfCallHierarchyDeclaration(typeChecker: TypeChecker, node: CallHierarchyDeclaration) { + const location = getCallHierarchyDeclarationReferenceNode(node); + return location && typeChecker.getSymbolAtLocation(location); + } + + /** Gets the text and range for the name of a call hierarchy declaration. */ + function getCallHierarchyItemName(program: Program, node: CallHierarchyDeclaration): { text: string, pos: number, end: number } { + if (isSourceFile(node)) { + return { text: node.fileName, pos: 0, end: 0 }; + } + + if ((isFunctionDeclaration(node) || isClassDeclaration(node)) && !isNamedDeclaration(node)) { + const defaultModifier = node.modifiers && find(node.modifiers, isDefaultModifier); + if (defaultModifier) { + return { text: "default", pos: defaultModifier.getStart(), end: defaultModifier.getEnd() }; + } + } + + const declName = isConstNamedExpression(node) ? node.parent.name : + Debug.assertDefined(getNameOfDeclaration(node), "Expected call hierarchy item to have a name"); + + let text = + isIdentifier(declName) ? idText(declName) : + isStringOrNumericLiteralLike(declName) ? declName.text : + isComputedPropertyName(declName) ? + isStringOrNumericLiteralLike(declName.expression) ? declName.expression.text : + undefined : + undefined; + if (text === undefined) { + const typeChecker = program.getTypeChecker(); + const symbol = typeChecker.getSymbolAtLocation(declName); + if (symbol) { + text = typeChecker.symbolToString(symbol, node); + } + } + if (text === undefined) { + // get the text from printing the node on a single line without comments... + const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); + text = usingSingleLineStringWriter(writer => printer.writeNode(EmitHint.Unspecified, node, node.getSourceFile(), writer)); + } + return { text, pos: declName.getStart(), end: declName.getEnd() }; + } + + /** Finds the implementation of a function-like declaration, if one exists. */ + function findFunctionImplementation(typeChecker: TypeChecker, node: Extract): Extract | undefined; + function findFunctionImplementation(typeChecker: TypeChecker, node: FunctionLikeDeclaration): FunctionLikeDeclaration | undefined; + function findFunctionImplementation(typeChecker: TypeChecker, node: FunctionLikeDeclaration): FunctionLikeDeclaration | undefined { + if (node.body) { + return node; + } + if (isConstructorDeclaration(node)) { + return getFirstConstructorWithBody(node.parent); + } + if (isFunctionDeclaration(node) || isMethodDeclaration(node)) { + const symbol = getSymbolOfCallHierarchyDeclaration(typeChecker, node); + if (symbol && symbol.valueDeclaration && isFunctionLikeDeclaration(symbol.valueDeclaration)) { + return symbol.valueDeclaration; + } + } + return node; + } + + function findFirstDeclaration(typeChecker: TypeChecker, node: CallHierarchyDeclaration) { + const symbol = getSymbolOfCallHierarchyDeclaration(typeChecker, node); + if (symbol && symbol.declarations) { + for (const decl of symbol.declarations) { + if (isValidCallHierarchyDeclaration(decl)) return decl; + } + } + } + + /** Find the implementation or the first declaration for a call hierarchy declaration. */ + function findImplementationOrFirstDeclaration(typeChecker: TypeChecker, node: CallHierarchyDeclaration): CallHierarchyDeclaration { + if (isFunctionLikeDeclaration(node) && !node.body) { + return findFunctionImplementation(typeChecker, node) || + findFirstDeclaration(typeChecker, node) || + node; + } + return findFirstDeclaration(typeChecker, node) || node; + } + + /** Resolves the call hierarchy declaration for a node. */ + export function resolveCallHierarchyDeclaration(program: Program, location: Node): CallHierarchyDeclaration | undefined { + // A call hierarchy item must refer to either a SourceFile, Module Declaration, or something intrinsically callable that has a name: + // - Class Declarations + // - Class Expressions (with a name) + // - Function Declarations + // - Function Expressions (with a name or assigned to a const variable) + // - Arrow Functions (assigned to a const variable) + // - Constructors + // - Methods + // - Accessors + // + // If a call is contained in a non-named callable Node (function expression, arrow function, etc.), then + // its containing `CallHierarchyItem` is a containing function or SourceFile that matches the above list. + + const typeChecker = program.getTypeChecker(); + let followingSymbol = false; + while (true) { + if (isValidCallHierarchyDeclaration(location)) { + return findImplementationOrFirstDeclaration(typeChecker, location); + } + if (isPossibleCallHierarchyDeclaration(location)) { + const ancestor = findAncestor(location, isValidCallHierarchyDeclaration); + return ancestor && findImplementationOrFirstDeclaration(typeChecker, ancestor); + } + if (isDeclarationName(location)) { + if (isValidCallHierarchyDeclaration(location.parent)) { + return findImplementationOrFirstDeclaration(typeChecker, location.parent); + } + if (isPossibleCallHierarchyDeclaration(location.parent)) { + const ancestor = findAncestor(location.parent, isValidCallHierarchyDeclaration); + return ancestor && findImplementationOrFirstDeclaration(typeChecker, ancestor); + } + if (isVariableDeclaration(location.parent) && location.parent.initializer && isConstNamedExpression(location.parent.initializer)) { + return location.parent.initializer; + } + return undefined; + } + if (isConstructorDeclaration(location)) { + if (isValidCallHierarchyDeclaration(location.parent)) { + return location.parent; + } + return undefined; + } + if (!followingSymbol) { + let symbol = typeChecker.getSymbolAtLocation(location); + if (symbol) { + if (symbol.flags & SymbolFlags.Alias) { + symbol = typeChecker.getAliasedSymbol(symbol); + } + if (symbol.valueDeclaration) { + followingSymbol = true; + location = symbol.valueDeclaration; + continue; + } + } + } + return undefined; + } + } + + /** Creates a `CallHierarchyItem` for a call hierarchy declaration. */ + export function createCallHierarchyItem(program: Program, node: CallHierarchyDeclaration): CallHierarchyItem { + const sourceFile = node.getSourceFile(); + const name = getCallHierarchyItemName(program, node); + const kind = getNodeKind(node); + const span = createTextSpanFromBounds(skipTrivia(sourceFile.text, node.getFullStart(), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true), node.getEnd()); + const selectionSpan = createTextSpanFromBounds(name.pos, name.end); + return { file: sourceFile.fileName, kind, name: name.text, span, selectionSpan }; + } + + function isDefined(x: T): x is NonNullable { + return x !== undefined; + } + + interface CallSite { + declaration: CallHierarchyDeclaration; + range: TextRange; + } + + function convertEntryToCallSite(entry: FindAllReferences.Entry, _originalNode: Node, typeChecker: TypeChecker): CallSite | undefined { + if (entry.kind === FindAllReferences.EntryKind.Node) { + if (isCallOrNewExpressionTarget(entry.node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true) + || isTaggedTemplateTag(entry.node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true) + || isDecoratorTarget(entry.node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true) + || isJsxOpeningLikeElementTagName(entry.node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true) + || isRightSideOfPropertyAccess(entry.node) + || isArgumentExpressionOfElementAccess(entry.node)) { + const ancestor = findAncestor(entry.node, isValidCallHierarchyDeclaration) || entry.node.getSourceFile(); + return { declaration: findImplementationOrFirstDeclaration(typeChecker, ancestor), range: createTextRangeFromNode(entry.node, entry.node.getSourceFile()) }; + } + } + } + + function getCallSiteGroupKey(entry: CallSite) { + return "" + getNodeId(entry.declaration); + } + + function createCallHierarchyIncomingCall(from: CallHierarchyItem, fromSpans: TextSpan[]): CallHierarchyIncomingCall { + return { from, fromSpans }; + } + + function convertCallSiteGroupToIncomingCall(program: Program, entries: readonly CallSite[]) { + return createCallHierarchyIncomingCall(createCallHierarchyItem(program, entries[0].declaration), map(entries, entry => createTextSpanFromRange(entry.range))); + } + + /** Gets the call sites that call into the provided call hierarchy declaration. */ + export function getIncomingCalls(program: Program, declaration: CallHierarchyDeclaration, cancellationToken: CancellationToken): CallHierarchyIncomingCall[] { + // Source files and modules have no incoming calls. + if (isSourceFile(declaration) || isModuleDeclaration(declaration)) { + return []; + } + const location = getCallHierarchyDeclarationReferenceNode(declaration); + const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, /*options*/ undefined, convertEntryToCallSite), isDefined); + return calls ? group(calls, getCallSiteGroupKey, entries => convertCallSiteGroupToIncomingCall(program, entries)) : []; + } + + function createCallSiteCollector(program: Program, callSites: CallSite[]): (node: Node | undefined) => void { + function recordCallSite(node: CallExpression | NewExpression | TaggedTemplateExpression | PropertyAccessExpression | ElementAccessExpression | Decorator | JsxOpeningLikeElement) { + const target = + isTaggedTemplateExpression(node) ? node.tag : + isJsxOpeningLikeElement(node) ? node.tagName : + isAccessExpression(node) ? node : + node.expression; + const declaration = resolveCallHierarchyDeclaration(program, target); + if (declaration) callSites.push({ declaration, range: createTextRangeFromNode(target, node.getSourceFile()) }); + } + + function collect(node: Node | undefined) { + if (!node) return; + if (node.flags & NodeFlags.Ambient) { + // do not descend into ambient nodes. + return; + } + + if (isValidCallHierarchyDeclaration(node)) { + // do not descend into other call site declarations, other than class member names + if (isClassLike(node)) { + for (const member of node.members) { + if (member.name && isComputedPropertyName(member.name)) { + collect(member.name.expression); + } + } + } + return; + } + + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + // do not descend into nodes that cannot contain callable nodes + return; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + // do not descend into the type side of an assertion + collect((node as TypeAssertion | AsExpression).expression); + return; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + // do not descend into the type of a variable or parameter declaration + collect((node as VariableDeclaration | ParameterDeclaration).name); + collect((node as VariableDeclaration | ParameterDeclaration).initializer); + return; + case SyntaxKind.CallExpression: + // do not descend into the type arguments of a call expression + recordCallSite(node as CallExpression); + collect((node as CallExpression).expression); + forEach((node as CallExpression).arguments, collect); + return; + case SyntaxKind.NewExpression: + // do not descend into the type arguments of a new expression + recordCallSite(node as NewExpression); + collect((node as NewExpression).expression); + forEach((node as NewExpression).arguments, collect); + return; + case SyntaxKind.TaggedTemplateExpression: + // do not descend into the type arguments of a tagged template expression + recordCallSite(node as TaggedTemplateExpression); + collect((node as TaggedTemplateExpression).tag); + collect((node as TaggedTemplateExpression).template); + return; + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + // do not descend into the type arguments of a JsxOpeningLikeElement + recordCallSite(node as JsxOpeningLikeElement); + collect((node as JsxOpeningLikeElement).tagName); + collect((node as JsxOpeningLikeElement).attributes); + return; + case SyntaxKind.Decorator: + recordCallSite(node as Decorator); + collect((node as Decorator).expression); + return; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + recordCallSite(node as AccessExpression); + forEachChild(node, collect); + break; + } + + if (isPartOfTypeNode(node)) { + // do not descend into types + return; + } + + forEachChild(node, collect); + } + return collect; + } + + function collectCallSitesOfSourceFile(node: SourceFile, collect: (node: Node | undefined) => void) { + forEach(node.statements, collect); + } + + function collectCallSitesOfModuleDeclaration(node: ModuleDeclaration, collect: (node: Node | undefined) => void) { + if (!hasModifier(node, ModifierFlags.Ambient) && node.body && isModuleBlock(node.body)) { + forEach(node.body.statements, collect); + } + } + + function collectCallSitesOfFunctionLikeDeclaration(typeChecker: TypeChecker, node: FunctionLikeDeclaration, collect: (node: Node | undefined) => void) { + const implementation = findFunctionImplementation(typeChecker, node); + if (implementation) { + forEach(implementation.parameters, collect); + collect(implementation.body); + } + } + + function collectCallSitesOfClassLikeDeclaration(node: ClassLikeDeclaration, collect: (node: Node | undefined) => void) { + forEach(node.decorators, collect); + const heritage = getClassExtendsHeritageElement(node); + if (heritage) { + collect(heritage.expression); + } + for (const member of node.members) { + forEach(member.decorators, collect); + if (isPropertyDeclaration(member)) { + collect(member.initializer); + } + else if (isConstructorDeclaration(member) && member.body) { + forEach(member.parameters, collect); + collect(member.body); + } + } + } + + function collectCallSites(program: Program, node: CallHierarchyDeclaration) { + const callSites: CallSite[] = []; + const collect = createCallSiteCollector(program, callSites); + switch (node.kind) { + case SyntaxKind.SourceFile: + collectCallSitesOfSourceFile(node, collect); + break; + case SyntaxKind.ModuleDeclaration: + collectCallSitesOfModuleDeclaration(node, collect); + break; + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + collectCallSitesOfFunctionLikeDeclaration(program.getTypeChecker(), node, collect); + break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + collectCallSitesOfClassLikeDeclaration(node, collect); + break; + default: + Debug.assertNever(node); + } + return callSites; + } + + function createCallHierarchyOutgoingCall(to: CallHierarchyItem, fromSpans: TextSpan[]): CallHierarchyOutgoingCall { + return { to, fromSpans }; + } + + function convertCallSiteGroupToOutgoingCall(program: Program, entries: readonly CallSite[]) { + return createCallHierarchyOutgoingCall(createCallHierarchyItem(program, entries[0].declaration), map(entries, entry => createTextSpanFromRange(entry.range))); + } + + /** Gets the call sites that call out of the provided call hierarchy declaration. */ + export function getOutgoingCalls(program: Program, declaration: CallHierarchyDeclaration): CallHierarchyOutgoingCall[] { + if (declaration.flags & NodeFlags.Ambient || isMethodSignature(declaration)) { + return []; + } + return group(collectCallSites(program, declaration), getCallSiteGroupKey, entries => convertCallSiteGroupToOutgoingCall(program, entries)); + } +} \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index a9bdcef8c5f7b..256cc81270af6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2136,6 +2136,26 @@ namespace ts { return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName); } + function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | undefined { + synchronizeHostData(); + const declaration = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position)); + return declaration && CallHierarchy.createCallHierarchyItem(program, declaration); + } + + function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const declaration = CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position)); + return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : []; + } + + function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const declaration = CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position)); + return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : []; + } + return { dispose, cleanupSemanticCache, @@ -2191,6 +2211,9 @@ namespace ts { getEditsForRefactor, toLineColumnOffset: sourceMapper.toLineColumnOffset, getSourceMapper: () => sourceMapper, + prepareCallHierarchy, + provideCallHierarchyIncomingCalls, + provideCallHierarchyOutgoingCalls }; } diff --git a/src/services/shims.ts b/src/services/shims.ts index d500e93791b63..f122511b38df5 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -271,6 +271,10 @@ namespace ts { */ getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string; + prepareCallHierarchy(fileName: string, position: number): string; + provideCallHierarchyIncomingCalls(fileName: string, position: number): string; + provideCallHierarchyOutgoingCalls(fileName: string, position: number): string; + getEmitOutput(fileName: string): string; getEmitOutputObject(fileName: string): EmitOutput; } @@ -1020,6 +1024,29 @@ namespace ts { ); } + /// CALL HIERARCHY + + public prepareCallHierarchy(fileName: string, position: number): string { + return this.forwardJSONCall( + `prepareCallHierarchy('${fileName}', ${position})`, + () => this.languageService.prepareCallHierarchy(fileName, position) + ); + } + + public provideCallHierarchyIncomingCalls(fileName: string, position: number): string { + return this.forwardJSONCall( + `provideCallHierarchyIncomingCalls('${fileName}', ${position})`, + () => this.languageService.provideCallHierarchyIncomingCalls(fileName, position) + ); + } + + public provideCallHierarchyOutgoingCalls(fileName: string, position: number): string { + return this.forwardJSONCall( + `provideCallHierarchyOutgoingCalls('${fileName}', ${position})`, + () => this.languageService.provideCallHierarchyOutgoingCalls(fileName, position) + ); + } + /// Emit public getEmitOutput(fileName: string): string { return this.forwardJSONCall( diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index a75a96c961eae..f7913700f409a 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -18,6 +18,7 @@ "documentRegistry.ts", "importTracker.ts", "findAllReferences.ts", + "callHierarchy.ts", "getEditsForFileRename.ts", "goToDefinition.ts", "jsDoc.ts", diff --git a/src/services/types.ts b/src/services/types.ts index 21a92e169d861..30a549a57ad5b 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -351,6 +351,10 @@ namespace ts { getNavigationBarItems(fileName: string): NavigationBarItem[]; getNavigationTree(fileName: string): NavigationTree; + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | undefined; + provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; + provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; + getOutliningSpans(fileName: string): OutliningSpan[]; getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; @@ -522,6 +526,24 @@ namespace ts { childItems?: NavigationTree[]; } + export interface CallHierarchyItem { + name: string; + kind: ScriptElementKind; + file: string; + span: TextSpan; + selectionSpan: TextSpan; + } + + export interface CallHierarchyIncomingCall { + from: CallHierarchyItem; + fromSpans: TextSpan[]; + } + + export interface CallHierarchyOutgoingCall { + to: CallHierarchyItem; + fromSpans: TextSpan[]; + } + export interface TodoCommentDescriptor { text: string; priority: number; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index d0e75c5522fc9..f43f437beae94 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -6,7 +6,8 @@ interface PromiseConstructor { all(values: (T | PromiseLike)[]): Promise; } /* @internal */ -declare let Promise: PromiseConstructor; +// eslint-disable-next-line no-var +declare var Promise: PromiseConstructor; // These utilities are common to multiple language service features. /* @internal */ @@ -195,27 +196,58 @@ namespace ts { return false; } - export function isCallExpressionTarget(node: Node): boolean { - return isCallOrNewExpressionTargetWorker(node, isCallExpression); + export function isCallExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isCallExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); } - export function isNewExpressionTarget(node: Node): boolean { - return isCallOrNewExpressionTargetWorker(node, isNewExpression); + export function isNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); } - export function isCallOrNewExpressionTarget(node: Node): boolean { - return isCallOrNewExpressionTargetWorker(node, isCallOrNewExpression); + export function isCallOrNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isCallOrNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); } - function isCallOrNewExpressionTargetWorker(node: Node, pred: (node: Node) => node is T): boolean { - const target = climbPastPropertyAccess(node); - return !!target && !!target.parent && pred(target.parent) && target.parent.expression === target; + export function isTaggedTemplateTag(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isTaggedTemplateExpression, selectTagOfTaggedTemplateExpression, includeElementAccess, skipPastOuterExpressions); + } + + export function isDecoratorTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isDecorator, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); + } + + export function isJsxOpeningLikeElementTagName(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isJsxOpeningLikeElement, selectTagNameOfJsxOpeningLikeElement, includeElementAccess, skipPastOuterExpressions); + } + + function selectExpressionOfCallOrNewExpressionOrDecorator(node: CallExpression | NewExpression | Decorator) { + return node.expression; + } + + function selectTagOfTaggedTemplateExpression(node: TaggedTemplateExpression) { + return node.tag; + } + + function selectTagNameOfJsxOpeningLikeElement(node: JsxOpeningLikeElement) { + return node.tagName; + } + + function isCalleeWorker(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) { + let target = includeElementAccess ? climbPastPropertyOrElementAccess(node) : climbPastPropertyAccess(node); + if (skipPastOuterExpressions) { + target = skipOuterExpressions(target); + } + return !!target && !!target.parent && pred(target.parent) && calleeSelector(target.parent) === target; } export function climbPastPropertyAccess(node: Node) { return isRightSideOfPropertyAccess(node) ? node.parent : node; } + export function climbPastPropertyOrElementAccess(node: Node) { + return isRightSideOfPropertyAccess(node) || isArgumentExpressionOfElementAccess(node) ? node.parent : node; + } + export function getTargetLabel(referenceNode: Node, labelName: string): Identifier | undefined { while (referenceNode) { if (referenceNode.kind === SyntaxKind.LabeledStatement && (referenceNode).label.escapedText === labelName) { @@ -258,6 +290,10 @@ namespace ts { return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).name === node; } + export function isArgumentExpressionOfElementAccess(node: Node) { + return node && node.parent && node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent).argumentExpression === node; + } + export function isNameOfModuleDeclaration(node: Node) { return node.parent.kind === SyntaxKind.ModuleDeclaration && (node.parent).name === node; } diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index a16c4f57817ae..ca13987b92ece 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -265,6 +265,9 @@ namespace ts.server { CommandNames.GetEditsForFileRename, CommandNames.GetEditsForFileRenameFull, CommandNames.SelectionRange, + CommandNames.PrepareCallHierarchy, + CommandNames.ProvideCallHierarchyIncomingCalls, + CommandNames.ProvideCallHierarchyOutgoingCalls, ]; it("should not throw when commands are executed with invalid arguments", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e775580c1fb32..822cee0561036 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5044,6 +5044,9 @@ declare namespace ts { getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; getNavigationBarItems(fileName: string): NavigationBarItem[]; getNavigationTree(fileName: string): NavigationTree; + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | undefined; + provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; + provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; getOutliningSpans(fileName: string): OutliningSpan[]; getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; @@ -5183,6 +5186,21 @@ declare namespace ts { /** Present if non-empty */ childItems?: NavigationTree[]; } + interface CallHierarchyItem { + name: string; + kind: ScriptElementKind; + file: string; + span: TextSpan; + selectionSpan: TextSpan; + } + interface CallHierarchyIncomingCall { + from: CallHierarchyItem; + fromSpans: TextSpan[]; + } + interface CallHierarchyOutgoingCall { + to: CallHierarchyItem; + fromSpans: TextSpan[]; + } interface TodoCommentDescriptor { text: string; priority: number; @@ -6043,6 +6061,9 @@ declare namespace ts.server.protocol { GetEditsForFileRename = "getEditsForFileRename", ConfigurePlugin = "configurePlugin", SelectionRange = "selectionRange", + PrepareCallHierarchy = "prepareCallHierarchy", + ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", + ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls" } /** * A TypeScript Server message @@ -8247,6 +8268,39 @@ declare namespace ts.server.protocol { interface NavTreeResponse extends Response { body?: NavigationTree; } + interface CallHierarchyItem { + name: string; + kind: ScriptElementKind; + file: string; + span: TextSpan; + selectionSpan: TextSpan; + } + interface CallHierarchyIncomingCall { + from: CallHierarchyItem; + fromSpans: TextSpan[]; + } + interface CallHierarchyOutgoingCall { + to: CallHierarchyItem; + fromSpans: TextSpan[]; + } + interface PrepareCallHierarchyRequest extends FileLocationRequest { + command: CommandTypes.PrepareCallHierarchy; + } + interface PrepareCallHierarchyResponse extends Response { + readonly body: CallHierarchyItem; + } + interface ProvideCallHierarchyIncomingCallsRequest extends FileLocationRequest { + command: CommandTypes.ProvideCallHierarchyIncomingCalls; + } + interface ProvideCallHierarchyIncomingCallsResponse extends Response { + readonly body: CallHierarchyIncomingCall[]; + } + interface ProvideCallHierarchyOutgoingCallsRequest extends FileLocationRequest { + command: CommandTypes.ProvideCallHierarchyOutgoingCalls; + } + interface ProvideCallHierarchyOutgoingCallsResponse extends Response { + readonly body: CallHierarchyOutgoingCall[]; + } enum IndentStyle { None = "None", Block = "Block", @@ -9333,6 +9387,12 @@ declare namespace ts.server { private configurePlugin; private getSmartSelectionRange; private mapSelectionRange; + private toProtocolCallHierarchyItem; + private toProtocolCallHierarchyIncomingCall; + private toProtocolCallHierarchyOutgoingCall; + private prepareCallHierarchy; + private provideCallHierarchyIncomingCalls; + private provideCallHierarchyOutgoingCalls; getCanonicalFileName(fileName: string): string; exit(): void; private notRequired; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index fe23df054dac9..7c49e23e38cfe 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5044,6 +5044,9 @@ declare namespace ts { getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; getNavigationBarItems(fileName: string): NavigationBarItem[]; getNavigationTree(fileName: string): NavigationTree; + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | undefined; + provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; + provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; getOutliningSpans(fileName: string): OutliningSpan[]; getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; @@ -5183,6 +5186,21 @@ declare namespace ts { /** Present if non-empty */ childItems?: NavigationTree[]; } + interface CallHierarchyItem { + name: string; + kind: ScriptElementKind; + file: string; + span: TextSpan; + selectionSpan: TextSpan; + } + interface CallHierarchyIncomingCall { + from: CallHierarchyItem; + fromSpans: TextSpan[]; + } + interface CallHierarchyOutgoingCall { + to: CallHierarchyItem; + fromSpans: TextSpan[]; + } interface TodoCommentDescriptor { text: string; priority: number; diff --git a/tests/cases/fourslash/callHierarchyAccessor.ts b/tests/cases/fourslash/callHierarchyAccessor.ts new file mode 100644 index 0000000000000..5e0e7a9e1120c --- /dev/null +++ b/tests/cases/fourslash/callHierarchyAccessor.ts @@ -0,0 +1,52 @@ +/// + +//// [|function [|foo|]() { +//// new C().[|bar|]; +//// }|] +//// +//// class C { +//// [|get /**/[|bar|]() { +//// return [|baz|](); +//// }|] +//// } +//// +//// [|function [|baz|]() { +//// }|] + +const [ + fooFunctionRange, + fooFunctionSelectionRange, + barReferenceRange, + barAccessorRange, + barAccessorSelectionRange, + bazReferenceRange, + bazFunctionRange, + bazFunctionSelectionRange +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "getter", + range: barAccessorRange, + selectionRange: barAccessorSelectionRange, + incoming: sequence.one({ + from: { + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + }, + fromRanges: sequence.one( + barReferenceRange + ) + }), + outgoing: sequence.one({ + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: sequence.one( + bazReferenceRange + ) + }) +}); diff --git a/tests/cases/fourslash/callHierarchyClass.ts b/tests/cases/fourslash/callHierarchyClass.ts new file mode 100644 index 0000000000000..6095d0729e27d --- /dev/null +++ b/tests/cases/fourslash/callHierarchyClass.ts @@ -0,0 +1,50 @@ +/// + +//// [|function [|foo|]() { +//// [|bar|](); +//// }|] +//// +//// [|function /**/[|bar|]() { +//// new [|Baz|](); +//// }|] +//// +//// [|class [|Baz|] { +//// }|] + +const [ + fooFunctionRange, + fooFunctionSelectionRange, + barReferenceRange, + barFunctionRange, + barFunctionSelectionRange, + bazReferenceRange, + bazClassRange, + bazClassSelectionRange +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "function", + range: barFunctionRange, + selectionRange: barFunctionSelectionRange, + incoming: sequence.one({ + from: { + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + }, + fromRanges: sequence.one( + barReferenceRange + ) + }), + outgoing: sequence.one({ + to: { + kind: "class", + range: bazClassRange, + selectionRange: bazClassSelectionRange + }, + fromRanges: sequence.one( + bazReferenceRange + ) + }) +}); diff --git a/tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts b/tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts new file mode 100644 index 0000000000000..e1a9b3b6d14dc --- /dev/null +++ b/tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts @@ -0,0 +1,50 @@ +/// + +//// [|function [|foo|]() { +//// [|bar|](); +//// }|] +//// +//// const /**/[|bar|] = [|() => { +//// [|baz|](); +//// }|] +//// +//// [|function [|baz|]() { +//// }|] + +const [ + fooFunctionRange, + fooFunctionSelectionRange, + barReferenceRange, + barFunctionSelectionRange, + barFunctionRange, + bazReferenceRange, + bazFunctionRange, + bazFunctionSelectionRange +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "function", + range: barFunctionRange, + selectionRange: barFunctionSelectionRange, + incoming: sequence.one({ + from: { + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + }, + fromRanges: sequence.one( + barReferenceRange + ) + }), + outgoing: sequence.one({ + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: sequence.one( + bazReferenceRange + ) + }) +}); diff --git a/tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts b/tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts new file mode 100644 index 0000000000000..d336e6a8a636f --- /dev/null +++ b/tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts @@ -0,0 +1,52 @@ +/// + +//// [|function [|foo|]() { +//// new [|Bar|](); +//// }|] +//// +//// const /**/[|Bar|] = [|class { +//// constructor() { +//// [|baz|](); +//// } +//// }|] +//// +//// [|function [|baz|]() { +//// }|] + +const [ + fooFunctionRange, + fooFunctionSelectionRange, + barReferenceRange, + barClassSelectionRange, + barClassRange, + bazReferenceRange, + bazFunctionRange, + bazFunctionSelectionRange, +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "class", + range: barClassRange, + selectionRange: barClassSelectionRange, + incoming: sequence.one({ + from: { + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + }, + fromRanges: sequence.one( + barReferenceRange + ) + }), + outgoing: sequence.one({ + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: sequence.one( + bazReferenceRange + ) + }) +}); \ No newline at end of file diff --git a/tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts b/tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts new file mode 100644 index 0000000000000..10aa71055abd5 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts @@ -0,0 +1,50 @@ +/// + +//// [|function [|foo|]() { +//// [|bar|](); +//// }|] +//// +//// const /**/[|bar|] = [|function () { +//// [|baz|](); +//// }|] +//// +//// [|function [|baz|]() { +//// }|] + +const [ + fooFunctionRange, + fooFunctionSelectionRange, + barReferenceRange, + barFunctionSelectionRange, + barFunctionRange, + bazReferenceRange, + bazFunctionRange, + bazFunctionSelectionRange +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "function", + range: barFunctionRange, + selectionRange: barFunctionSelectionRange, + incoming: sequence.one({ + from: { + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + }, + fromRanges: sequence.one( + barReferenceRange + ) + }), + outgoing: sequence.one({ + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: sequence.one( + bazReferenceRange + ) + }) +}); diff --git a/tests/cases/fourslash/callHierarchyDecorator.ts b/tests/cases/fourslash/callHierarchyDecorator.ts new file mode 100644 index 0000000000000..bafd6743127f4 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyDecorator.ts @@ -0,0 +1,51 @@ +/// + +// @experimentalDecorators: true +//// [|@[|bar|] +//// class [|Foo|] { +//// }|] +//// +//// [|function /**/[|bar|]() { +//// [|baz|](); +//// }|] +//// +//// [|function [|baz|]() { +//// }|] + +const [ + fooClassRange, + barReferenceRange, + fooClassSelectionRange, + barFunctionRange, + barFunctionSelectionRange, + bazReferenceRange, + bazFunctionRange, + bazFunctionSelectionRange, +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "function", + range: barFunctionRange, + selectionRange: barFunctionSelectionRange, + incoming: sequence.one({ + from: { + kind: "class", + range: fooClassRange, + selectionRange: fooClassSelectionRange, + }, + fromRanges: sequence.one( + barReferenceRange + ) + }), + outgoing: sequence.one({ + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: sequence.one( + bazReferenceRange + ) + }) +}); diff --git a/tests/cases/fourslash/callHierarchyExportDefaultClass.ts b/tests/cases/fourslash/callHierarchyExportDefaultClass.ts new file mode 100644 index 0000000000000..dc92954b8e3f8 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyExportDefaultClass.ts @@ -0,0 +1,56 @@ +/// + +// @filename: main.ts +//// import Bar from "./other"; +//// +//// [|function [|foo|]() { +//// new [|Bar|](); +//// }|] + +// @filename: other.ts +//// [|export /**/[|default|] class { +//// constructor() { +//// [|baz|](); +//// } +//// }|] +//// +//// [|function [|baz|]() { +//// }|] + +const [ + fooFunctionRange, + fooFunctionSelectionRange, + barReferenceRange, + barFunctionRange, + barFunctionSelectionRange, + bazReferenceRange, + bazFunctionRange, + bazFunctionSelectionRange +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "class", + range: barFunctionRange, + selectionRange: barFunctionSelectionRange, + incoming: sequence.one({ + from: { + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + }, + fromRanges: sequence.one( + barReferenceRange + ) + }), + outgoing: sequence.one({ + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: sequence.one( + bazReferenceRange + ) + }) +}); diff --git a/tests/cases/fourslash/callHierarchyExportDefaultFunction.ts b/tests/cases/fourslash/callHierarchyExportDefaultFunction.ts new file mode 100644 index 0000000000000..e1aa5889289df --- /dev/null +++ b/tests/cases/fourslash/callHierarchyExportDefaultFunction.ts @@ -0,0 +1,54 @@ +/// + +// @filename: main.ts +//// import bar from "./other"; +//// +//// [|function [|foo|]() { +//// [|bar|](); +//// }|] + +// @filename: other.ts +//// [|export /**/[|default|] function () { +//// [|baz|](); +//// }|] +//// +//// [|function [|baz|]() { +//// }|] + +const [ + fooFunctionRange, + fooFunctionSelectionRange, + barReferenceRange, + barFunctionRange, + barFunctionSelectionRange, + bazReferenceRange, + bazFunctionRange, + bazFunctionSelectionRange +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "function", + range: barFunctionRange, + selectionRange: barFunctionSelectionRange, + incoming: sequence.one({ + from: { + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + }, + fromRanges: sequence.one( + barReferenceRange + ) + }), + outgoing: sequence.one({ + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: sequence.one( + bazReferenceRange + ) + }) +}); diff --git a/tests/cases/fourslash/callHierarchyExportEqualsFunction.ts b/tests/cases/fourslash/callHierarchyExportEqualsFunction.ts new file mode 100644 index 0000000000000..ec96e5a5043a8 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyExportEqualsFunction.ts @@ -0,0 +1,41 @@ +/// + +// @filename: main.ts +//// import bar = require("./other"); +//// +//// function foo() { +//// bar(); +//// } + +// @filename: other.ts +//// [|export = /**/function () { +//// [|baz|](); +//// } +//// +//// [|function [|baz|]() { +//// }|]|] + +const [ + otherFileRange, + bazReferenceRange, + bazFunctionRange, + bazFunctionSelectionRange +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + // NOTE: exported function is unnamed, so we expand the item to the entire file... + kind: "module", + range: otherFileRange, + incoming: sequence.none(), + outgoing: sequence.one({ + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: sequence.one( + bazReferenceRange + ) + }) +}); diff --git a/tests/cases/fourslash/callHierarchyFile.ts b/tests/cases/fourslash/callHierarchyFile.ts new file mode 100644 index 0000000000000..d4a5fa83483c6 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyFile.ts @@ -0,0 +1,29 @@ +/// + +//// [|[|foo|](); +//// [|function /**/[|foo|]() { +//// }|]|] + +const [ + fileRange, + fooReferenceRange, + fooFunctionRange, + fooFunctionSelectionRange, +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + incoming: sequence.one({ + from: { + kind: "script", + range: fileRange, + }, + fromRanges: sequence.one( + fooReferenceRange + ) + }), + outgoing: sequence.none() +}); diff --git a/tests/cases/fourslash/callHierarchyFunction.ts b/tests/cases/fourslash/callHierarchyFunction.ts new file mode 100644 index 0000000000000..ad22e08a8714d --- /dev/null +++ b/tests/cases/fourslash/callHierarchyFunction.ts @@ -0,0 +1,72 @@ +/// + +//// [|function [|foo|]() { +//// [|bar|](); +//// }|] +//// +//// [|function /**/[|bar|]() { +//// [|baz|](); +//// [|quxx|](); +//// [|baz|](); +//// }|] +//// +//// [|function [|baz|]() { +//// }|] +//// +//// [|function [|quxx|]() { +//// }|] + +const [ + fooFunctionRange, + fooFunctionSelectionRange, + barReferenceRange, + barFunctionRange, + barFunctionSelectionRange, + bazReferenceRange1, + quxxReferenceRange, + bazReferenceRange2, + bazFunctionRange, + bazFunctionSelectionRange, + quxxFunctionRange, + quxxFunctionSelectionRange, +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "function", + range: barFunctionRange, + selectionRange: barFunctionSelectionRange, + incoming: sequence.one({ + from: { + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + }, + fromRanges: sequence.one( + barReferenceRange + ) + }), + outgoing: sequence.exact([ + { + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: sequence.exact([ + bazReferenceRange1, + bazReferenceRange2 + ]) + }, + { + to: { + kind: "function", + range: quxxFunctionRange, + selectionRange: quxxFunctionSelectionRange + }, + fromRanges: sequence.one( + quxxReferenceRange + ) + }, + ]) +}); diff --git a/tests/cases/fourslash/callHierarchyInterfaceMethod.ts b/tests/cases/fourslash/callHierarchyInterfaceMethod.ts new file mode 100644 index 0000000000000..5fb330b831770 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyInterfaceMethod.ts @@ -0,0 +1,33 @@ +/// + +//// [|interface I { +//// /**/[|[|foo|](): void;|] +//// } +//// +//// const obj: I = { foo() {} }; +//// +//// obj.[|foo|]();|] + +const [ + fileRange, + fooMethodRange, + fooMethodSelectionRange, + fooReferenceRange +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "method", + range: fooMethodRange, + selectionRange: fooMethodSelectionRange, + incoming: sequence.one({ + from: { + kind: "script", + range: fileRange + }, + fromRanges: sequence.one( + fooReferenceRange + ) + }), + outgoing: sequence.none() +}); diff --git a/tests/cases/fourslash/callHierarchyJsxElement.ts b/tests/cases/fourslash/callHierarchyJsxElement.ts new file mode 100644 index 0000000000000..6cd967ac04be8 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyJsxElement.ts @@ -0,0 +1,52 @@ +/// + +// @jsx: preserve +// @filename: main.tsx +//// [|function [|foo|]() { +//// return <[|Bar|]/>; +//// }|] +//// +//// [|function /**/[|Bar|]() { +//// [|baz|](); +//// }|] +//// +//// [|function [|baz|]() { +//// }|] + +const [ + fooFunctionRange, + fooFunctionSelectionRange, + barReferenceRange, + barFunctionRange, + barFunctionSelectionRange, + bazReferenceRange, + bazFunctionRange, + bazFunctionSelectionRange +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "function", + range: barFunctionRange, + selectionRange: barFunctionSelectionRange, + incoming: sequence.one({ + from: { + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + }, + fromRanges: sequence.one( + barReferenceRange + ) + }), + outgoing: sequence.one({ + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: sequence.one( + bazReferenceRange + ) + }) +}); diff --git a/tests/cases/fourslash/callHierarchyTaggedTemplate.ts b/tests/cases/fourslash/callHierarchyTaggedTemplate.ts new file mode 100644 index 0000000000000..7e9991ba4906e --- /dev/null +++ b/tests/cases/fourslash/callHierarchyTaggedTemplate.ts @@ -0,0 +1,50 @@ +/// + +//// [|function [|foo|]() { +//// [|bar|]`a${1}b`; +//// }|] +//// +//// [|function /**/[|bar|](array: TemplateStringsArray, ...args: any[]) { +//// [|baz|](); +//// }|] +//// +//// [|function [|baz|]() { +//// }|] + +const [ + fooFunctionRange, + fooFunctionSelectionRange, + barReferenceRange, + barFunctionRange, + barFunctionSelectionRange, + bazReferenceRange, + bazFunctionRange, + bazFunctionSelectionRange +] = test.ranges(); + +goTo.marker(); +verify.callHierarchy({ + kind: "function", + range: barFunctionRange, + selectionRange: barFunctionSelectionRange, + incoming: sequence.one({ + from: { + kind: "function", + range: fooFunctionRange, + selectionRange: fooFunctionSelectionRange, + }, + fromRanges: { exact: true, values: [ + barReferenceRange + ] } + }), + outgoing: sequence.one({ + to: { + kind: "function", + range: bazFunctionRange, + selectionRange: bazFunctionSelectionRange + }, + fromRanges: { exact: true, values: [ + bazReferenceRange + ] } + }) +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index cd184df3bed97..a318a3f7f94ee 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -380,6 +380,9 @@ declare namespace FourSlashInterface { readonly newFileContents: { readonly [fileName: string]: string }; readonly preferences?: UserPreferences; }): void; + callHierarchy(options: false | Range | VerifyCallHierarchyOptions): void; + callHierarchyIncomingCalls(options: Sequence): void; + callHierarchyOutgoingCalls(options: Sequence): void; moveToNewFile(options: { readonly newFileContents: { readonly [fileName: string]: string }; readonly preferences?: UserPreferences; @@ -717,6 +720,32 @@ declare namespace FourSlashInterface { readonly providePrefixAndSuffixTextForRename?: boolean; }; type RenameLocationOptions = Range | { readonly range: Range, readonly prefixText?: string, readonly suffixText?: string }; + + type Sequence = + | false // Indicates the actual result must be undefined or contain no elements + | readonly T[] // Indicates the actual result must at least contain the specified elements, in any order + | { + readonly exact?: boolean; // Indicates the actual result must contain all of the elements in `values` (no more and no fewer). + readonly values: readonly T[]; + }; + + interface VerifyCallHierarchyOptions { + readonly range?: Range; + readonly kind?: string; + readonly selectionRange?: Range; + readonly incoming?: Sequence; + readonly outgoing?: Sequence; + } + + interface VerifyCallHierarchyIncomingCallOptions { + readonly from: VerifyCallHierarchyOptions | Range; + readonly fromRanges?: Sequence; + } + + interface VerifyCallHierarchyOutgoingCallOptions { + readonly to: VerifyCallHierarchyOptions | Range; + readonly fromRanges?: Sequence; + } } declare function verifyOperationIsCancelled(f: any): void; declare var test: FourSlashInterface.test_; @@ -768,3 +797,9 @@ declare namespace completion { export const statementKeywords: ReadonlyArray; export const statementInJsKeywords: ReadonlyArray; } +declare namespace sequence { + export function atLeast(array: readonly T[]): FourSlashInterface.Sequence; + export function exact(array: readonly T[]): FourSlashInterface.Sequence; + export function one(value: T): FourSlashInterface.Sequence; + export function none(): FourSlashInterface.Sequence; +} \ No newline at end of file From 64351ee100d5134059e07dd79eb999c021d795c0 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 18 Dec 2019 15:05:17 -0800 Subject: [PATCH 2/6] Use baselines for callHierarchy tests --- src/harness/fourslashImpl.ts | 222 +++++++++++++++++- src/harness/fourslashInterfaceImpl.ts | 4 + .../callHierarchyAccessor.callHierarchy.txt | 62 +++++ .../callHierarchyClass.callHierarchy.txt | 62 +++++ ...yConstNamedArrowFunction.callHierarchy.txt | 62 +++++ ...onstNamedClassExpression.callHierarchy.txt | 66 ++++++ ...tNamedFunctionExpression.callHierarchy.txt | 62 +++++ .../callHierarchyDecorator.callHierarchy.txt | 62 +++++ ...rarchyExportDefaultClass.callHierarchy.txt | 66 ++++++ ...chyExportDefaultFunction.callHierarchy.txt | 62 +++++ ...rchyExportEqualsFunction.callHierarchy.txt | 45 ++++ .../callHierarchyFile.callHierarchy.txt | 39 +++ .../callHierarchyFunction.callHierarchy.txt | 91 +++++++ ...HierarchyInterfaceMethod.callHierarchy.txt | 45 ++++ .../callHierarchyJsxElement.callHierarchy.txt | 62 +++++ ...lHierarchyTaggedTemplate.callHierarchy.txt | 62 +++++ .../cases/fourslash/callHierarchyAccessor.ts | 53 +---- tests/cases/fourslash/callHierarchyClass.ts | 53 +---- .../callHierarchyConstNamedArrowFunction.ts | 53 +---- .../callHierarchyConstNamedClassExpression.ts | 53 +---- ...llHierarchyConstNamedFunctionExpression.ts | 53 +---- .../cases/fourslash/callHierarchyDecorator.ts | 53 +---- .../callHierarchyExportDefaultClass.ts | 53 +---- .../callHierarchyExportDefaultFunction.ts | 53 +---- .../callHierarchyExportEqualsFunction.ts | 33 +-- tests/cases/fourslash/callHierarchyFile.ts | 29 +-- .../cases/fourslash/callHierarchyFunction.ts | 78 +----- .../fourslash/callHierarchyInterfaceMethod.ts | 29 +-- .../fourslash/callHierarchyJsxElement.ts | 53 +---- .../fourslash/callHierarchyTaggedTemplate.ts | 53 +---- tests/cases/fourslash/fourslash.ts | 1 + 31 files changed, 1180 insertions(+), 594 deletions(-) create mode 100644 tests/baselines/reference/callHierarchyAccessor.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyClass.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyConstNamedArrowFunction.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyConstNamedClassExpression.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyConstNamedFunctionExpression.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyDecorator.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyExportDefaultClass.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyExportDefaultFunction.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyExportEqualsFunction.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyFile.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyFunction.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyInterfaceMethod.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyJsxElement.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyTaggedTemplate.callHierarchy.txt diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 34ab3f0eb30eb..26815189e1532 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -143,6 +143,12 @@ namespace FourSlash { return ts.ScriptSnapshot.fromString(sourceText); } + const enum CallHierarchyItemDirection { + Root, + Incoming, + Outgoing + } + export class TestState { // Language service instance private languageServiceAdapterHost: Harness.LanguageService.LanguageServiceAdapterHost; @@ -1404,18 +1410,91 @@ namespace FourSlash { private alignmentForExtraInfo = 50; - private spanInfoToString(spanInfo: ts.TextSpan, prefixString: string) { + private spanLines(file: FourSlashFile, spanInfo: ts.TextSpan, { selection = false, fullLines = false, lineNumbers = false } = {}) { + if (selection) { + fullLines = true; + } + + let contextStartPos = spanInfo.start; + let contextEndPos = contextStartPos + spanInfo.length; + if (fullLines) { + if (contextStartPos > 0) { + while (contextStartPos > 1) { + const ch = file.content.charCodeAt(contextStartPos - 1); + if (ch === ts.CharacterCodes.lineFeed || ch === ts.CharacterCodes.carriageReturn) { + break; + } + contextStartPos--; + } + } + if (contextEndPos < file.content.length) { + while (contextEndPos < file.content.length - 1) { + const ch = file.content.charCodeAt(contextEndPos); + if (ch === ts.CharacterCodes.lineFeed || ch === ts.CharacterCodes.carriageReturn) { + break; + } + contextEndPos++; + } + } + } + + let contextString: string; + let contextLineMap: number[]; + let contextStart: ts.LineAndCharacter; + let contextEnd: ts.LineAndCharacter; + let selectionStart: ts.LineAndCharacter; + let selectionEnd: ts.LineAndCharacter; + let lineNumberPrefixLength: number; + if (lineNumbers) { + contextString = file.content; + contextLineMap = ts.computeLineStarts(contextString); + contextStart = ts.computeLineAndCharacterOfPosition(contextLineMap, contextStartPos); + contextEnd = ts.computeLineAndCharacterOfPosition(contextLineMap, contextEndPos); + selectionStart = ts.computeLineAndCharacterOfPosition(contextLineMap, spanInfo.start); + selectionEnd = ts.computeLineAndCharacterOfPosition(contextLineMap, ts.textSpanEnd(spanInfo)); + lineNumberPrefixLength = (contextEnd.line + 1).toString().length + 2; + } + else { + contextString = file.content.substring(contextStartPos, contextEndPos); + contextLineMap = ts.computeLineStarts(contextString); + contextStart = { line: 0, character: 0 }; + contextEnd = { line: contextLineMap.length - 1, character: 0 }; + selectionStart = selection ? ts.computeLineAndCharacterOfPosition(contextLineMap, spanInfo.start - contextStartPos) : contextStart; + selectionEnd = selection ? ts.computeLineAndCharacterOfPosition(contextLineMap, ts.textSpanEnd(spanInfo) - contextStartPos) : contextEnd; + lineNumberPrefixLength = 0; + } + + const output: string[] = []; + for (let lineNumber = contextStart.line; lineNumber <= contextEnd.line; lineNumber++) { + const spanLine = contextString.substring(contextLineMap[lineNumber], contextLineMap[lineNumber + 1]); + output.push(lineNumbers ? `${`${lineNumber + 1}: `.padStart(lineNumberPrefixLength, " ")}${spanLine}` : spanLine); + if (selection) { + if (lineNumber < selectionStart.line || lineNumber > selectionEnd.line) { + continue; + } + + const isEmpty = selectionStart.line === selectionEnd.line && selectionStart.character === selectionEnd.character; + const selectionPadLength = lineNumber === selectionStart.line ? selectionStart.character : 0; + const selectionPad = " ".repeat(selectionPadLength + lineNumberPrefixLength); + const selectionLength = isEmpty ? 0 : Math.max(lineNumber < selectionEnd.line ? spanLine.trimRight().length - selectionPadLength : selectionEnd.character - selectionPadLength, 1); + const selectionLine = isEmpty ? "<" : "^".repeat(selectionLength); + output.push(`${selectionPad}${selectionLine}`); + } + } + return output; + } + + private spanInfoToString(spanInfo: ts.TextSpan, prefixString: string, file: FourSlashFile = this.activeFile) { let resultString = "SpanInfo: " + JSON.stringify(spanInfo); if (spanInfo) { - const spanString = this.activeFile.content.substr(spanInfo.start, spanInfo.length); - const spanLineMap = ts.computeLineStarts(spanString); - for (let i = 0; i < spanLineMap.length; i++) { + const spanLines = this.spanLines(file, spanInfo); + for (let i = 0; i < spanLines.length; i++) { if (!i) { resultString += "\n"; } - resultString += prefixString + spanString.substring(spanLineMap[i], spanLineMap[i + 1]); + resultString += prefixString + spanLines[i]; } - resultString += "\n" + prefixString + ":=> (" + this.getLineColStringAtPosition(spanInfo.start) + ") to (" + this.getLineColStringAtPosition(ts.textSpanEnd(spanInfo)) + ")"; + resultString += "\n" + prefixString + ":=> (" + this.getLineColStringAtPosition(spanInfo.start, file) + ") to (" + this.getLineColStringAtPosition(ts.textSpanEnd(spanInfo), file) + ")"; } return resultString; @@ -1694,13 +1773,13 @@ namespace FourSlash { Harness.IO.log(stringify(help.items[help.selectedItemIndex])); } - private getBaselineFileNameForInternalFourslashFile() { + private getBaselineFileNameForInternalFourslashFile(ext = ".baseline") { return this.testData.globalOptions[MetadataOptionNames.baselineFile] || - ts.getBaseFileName(this.activeFile.fileName).replace(ts.Extension.Ts, ".baseline"); + ts.getBaseFileName(this.activeFile.fileName).replace(ts.Extension.Ts, ext); } - private getBaselineFileNameForContainingTestFile() { - return ts.getBaseFileName(this.originalInputFileName).replace(ts.Extension.Ts, ".baseline"); + private getBaselineFileNameForContainingTestFile(ext = ".baseline") { + return ts.getBaseFileName(this.originalInputFileName).replace(ts.Extension.Ts, ext); } private getSignatureHelp({ triggerReason }: FourSlashInterface.VerifySignatureHelpOptions): ts.SignatureHelpItems | undefined { @@ -3130,6 +3209,125 @@ namespace FourSlash { Harness.IO.log(stringify(codeFixes)); } + private formatCallHierarchyItemSpan(file: FourSlashFile, span: ts.TextSpan, prefix: string, trailingPrefix = prefix) { + const startLc = this.languageServiceAdapterHost.positionToLineAndCharacter(file.fileName, span.start); + const endLc = this.languageServiceAdapterHost.positionToLineAndCharacter(file.fileName, ts.textSpanEnd(span)); + const lines = this.spanLines(file, span, { fullLines: true, lineNumbers: true, selection: true }); + let text = ""; + text += `${prefix}╭ ${file.fileName}:${startLc.line + 1}:${startLc.character + 1}-${endLc.line + 1}:${endLc.character + 1}\n`; + for (const line of lines) { + text += `${prefix}│ ${line.trimRight()}\n`; + } + text += `${trailingPrefix}╰\n`; + return text; + } + + private formatCallHierarchyItemSpans(file: FourSlashFile, spans: ts.TextSpan[], prefix: string, trailingPrefix = prefix) { + let text = ""; + for (let i = 0; i < spans.length; i++) { + text += this.formatCallHierarchyItemSpan(file, spans[i], prefix, i < spans.length - 1 ? prefix : trailingPrefix); + } + return text; + } + + private formatCallHierarchyItem(file: FourSlashFile, callHierarchyItem: ts.CallHierarchyItem, direction: CallHierarchyItemDirection, seen: ts.Map, prefix: string, trailingPrefix: string = prefix) { + const key = `${callHierarchyItem.file}|${JSON.stringify(callHierarchyItem.span)}|${direction}`; + const alreadySeen = seen.has(key); + seen.set(key, true); + + const incomingCalls = + direction === CallHierarchyItemDirection.Outgoing ? { result: "skip" } as const : + alreadySeen ? { result: "seen" } as const : + { result: "show", values: this.languageService.provideCallHierarchyIncomingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start) } as const; + + const outgoingCalls = + direction === CallHierarchyItemDirection.Incoming ? { result: "skip" } as const : + alreadySeen ? { result: "seen" } as const : + { result: "show", values: this.languageService.provideCallHierarchyOutgoingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start) } as const; + + let text = ""; + text += `${prefix}╭ name: ${callHierarchyItem.name}\n`; + text += `${prefix}├ kind: ${callHierarchyItem.kind}\n`; + text += `${prefix}├ span:\n`; + text += this.formatCallHierarchyItemSpan(file, callHierarchyItem.span, `${prefix}│ `); + text += `${prefix}├ selectionSpan:\n`; + text += this.formatCallHierarchyItemSpan(file, callHierarchyItem.selectionSpan, `${prefix}│ `, + incomingCalls.result !== "skip" || outgoingCalls.result !== "skip" ? `${prefix}│ ` : + `${trailingPrefix}╰ `); + + if (incomingCalls.result === "seen") { + if (outgoingCalls.result === "skip") { + text += `${trailingPrefix}╰ incoming: ...\n`; + } + else { + text += `${prefix}├ incoming: ...\n`; + } + } + else if (incomingCalls.result === "show") { + if (!ts.some(incomingCalls.values)) { + if (outgoingCalls.result === "skip") { + text += `${trailingPrefix}╰ incoming: none\n`; + } + else { + text += `${prefix}├ incoming: none\n`; + } + } + else { + text += `${prefix}├ incoming:\n`; + for (let i = 0; i < incomingCalls.values.length; i++) { + const incomingCall = incomingCalls.values[i]; + const file = this.findFile(incomingCall.from.file); + text += `${prefix}│ ╭ from:\n`; + text += this.formatCallHierarchyItem(file, incomingCall.from, CallHierarchyItemDirection.Incoming, seen, `${prefix}│ │ `); + text += `${prefix}│ ├ fromSpans:\n`; + text += this.formatCallHierarchyItemSpans(file, incomingCall.fromSpans, `${prefix}│ │ `, + i < incomingCalls.values.length - 1 ? `${prefix}│ ╰ ` : + outgoingCalls.result !== "skip" ? `${prefix}│ ╰ ` : + `${trailingPrefix}╰ ╰ `); + } + } + } + + if (outgoingCalls.result === "seen") { + text += `${trailingPrefix}╰ outgoing: ...\n`; + } + else if (outgoingCalls.result === "show") { + if (!ts.some(outgoingCalls.values)) { + text += `${trailingPrefix}╰ outgoing: none\n`; + } + else { + text += `${prefix}├ outgoing:\n`; + for (let i = 0; i < outgoingCalls.values.length; i++) { + const outgoingCall = outgoingCalls.values[i]; + const file = this.findFile(outgoingCall.to.file); + text += `${prefix}│ ╭ to:\n`; + text += this.formatCallHierarchyItem(file, outgoingCall.to, CallHierarchyItemDirection.Outgoing, seen, `${prefix}│ │ `); + text += `${prefix}│ ├ fromSpans:\n`; + text += this.formatCallHierarchyItemSpans(file, outgoingCall.fromSpans, `${prefix}│ │ `, + i < outgoingCalls.values.length - 1 ? `${prefix}│ ╰ ` : + `${trailingPrefix}╰ ╰ `); + } + } + } + return text; + } + + private formatCallHierarchy(callHierarchyItem: ts.CallHierarchyItem | undefined) { + let text = ""; + if (callHierarchyItem) { + const file = this.findFile(callHierarchyItem.file); + text += this.formatCallHierarchyItem(file, callHierarchyItem, CallHierarchyItemDirection.Root, ts.createMap(), ""); + } + return text; + } + + public baselineCallHierarchy() { + const baselineFile = this.getBaselineFileNameForContainingTestFile(".callHierarchy.txt"); + const callHierarchyItem = this.languageService.prepareCallHierarchy(this.activeFile.fileName, this.currentCaretPosition)!; + const text = callHierarchyItem ? this.formatCallHierarchy(callHierarchyItem) : "none"; + Harness.Baseline.runBaseline(baselineFile, text); + } + public verifyCallHierarchy(options: false | FourSlashInterface.VerifyCallHierarchyOptions | Range) { const callHierarchyItem = this.languageService.prepareCallHierarchy(this.activeFile.fileName, this.currentCaretPosition)!; this.assertCallHierarchyItemMatches(callHierarchyItem, options); @@ -3345,8 +3543,8 @@ namespace FourSlash { return this.tryFindFileWorker(name).file !== undefined; } - private getLineColStringAtPosition(position: number) { - const pos = this.languageServiceAdapterHost.positionToLineAndCharacter(this.activeFile.fileName, position); + private getLineColStringAtPosition(position: number, file: FourSlashFile = this.activeFile) { + const pos = this.languageServiceAdapterHost.positionToLineAndCharacter(file.fileName, position); return `line ${(pos.line + 1)}, col ${pos.character}`; } diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index a25486ae25ccd..955b767ed711d 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -537,6 +537,10 @@ namespace FourSlashInterface { this.state.getEditsForFileRename(options); } + public baselineCallHierarchy() { + this.state.baselineCallHierarchy(); + } + public callHierarchy(options: false | FourSlash.Range | VerifyCallHierarchyOptions) { this.state.verifyCallHierarchy(options); } diff --git a/tests/baselines/reference/callHierarchyAccessor.callHierarchy.txt b/tests/baselines/reference/callHierarchyAccessor.callHierarchy.txt new file mode 100644 index 0000000000000..a749fd1aabc49 --- /dev/null +++ b/tests/baselines/reference/callHierarchyAccessor.callHierarchy.txt @@ -0,0 +1,62 @@ +╭ name: bar +├ kind: getter +├ span: +│ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:6:5-8:6 +│ │ 6: get bar() { +│ │ ^^^^^^^^^^^ +│ │ 7: return baz(); +│ │ ^^^^^^^^^^^^^^^^^^^^^ +│ │ 8: } +│ │ ^^^^^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:6:9-6:12 +│ │ 6: get bar() { +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:1:1-3:2 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: new C().bar; +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:1:10-1:13 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:2:13-2:16 +│ │ │ 2: new C().bar; +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:11:1-12:2 +│ │ │ │ 11: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 12: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:11:10-11:13 +│ │ │ │ 11: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:7:16-7:19 +│ │ │ 7: return baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyClass.callHierarchy.txt b/tests/baselines/reference/callHierarchyClass.callHierarchy.txt new file mode 100644 index 0000000000000..064a13547744c --- /dev/null +++ b/tests/baselines/reference/callHierarchyClass.callHierarchy.txt @@ -0,0 +1,62 @@ +╭ name: bar +├ kind: function +├ span: +│ ╭ /tests/cases/fourslash/callHierarchyClass.ts:5:1-7:2 +│ │ 5: function bar() { +│ │ ^^^^^^^^^^^^^^^^ +│ │ 6: new Baz(); +│ │ ^^^^^^^^^^^^^^ +│ │ 7: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/callHierarchyClass.ts:5:10-5:13 +│ │ 5: function bar() { +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyClass.ts:1:1-3:2 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: bar(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyClass.ts:1:10-1:13 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyClass.ts:2:5-2:8 +│ │ │ 2: bar(); +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: Baz +│ │ ├ kind: class +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyClass.ts:9:1-10:2 +│ │ │ │ 9: class Baz { +│ │ │ │ ^^^^^^^^^^^ +│ │ │ │ 10: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyClass.ts:9:7-9:10 +│ │ │ │ 9: class Baz { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyClass.ts:6:9-6:12 +│ │ │ 6: new Baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyConstNamedArrowFunction.callHierarchy.txt b/tests/baselines/reference/callHierarchyConstNamedArrowFunction.callHierarchy.txt new file mode 100644 index 0000000000000..3f4ee72d8fe68 --- /dev/null +++ b/tests/baselines/reference/callHierarchyConstNamedArrowFunction.callHierarchy.txt @@ -0,0 +1,62 @@ +╭ name: bar +├ kind: function +├ span: +│ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:5:13-7:2 +│ │ 5: const bar = () => { +│ │ ^^^^^^^ +│ │ 6: baz(); +│ │ ^^^^^^^^^^ +│ │ 7: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:5:7-5:10 +│ │ 5: const bar = () => { +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:1:1-3:2 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: bar(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:1:10-1:13 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:2:5-2:8 +│ │ │ 2: bar(); +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:9:1-10:2 +│ │ │ │ 9: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 10: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:9:10-9:13 +│ │ │ │ 9: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:6:5-6:8 +│ │ │ 6: baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyConstNamedClassExpression.callHierarchy.txt b/tests/baselines/reference/callHierarchyConstNamedClassExpression.callHierarchy.txt new file mode 100644 index 0000000000000..040c64524dd91 --- /dev/null +++ b/tests/baselines/reference/callHierarchyConstNamedClassExpression.callHierarchy.txt @@ -0,0 +1,66 @@ +╭ name: Bar +├ kind: class +├ span: +│ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:5:13-9:2 +│ │ 5: const Bar = class { +│ │ ^^^^^^^ +│ │ 6: constructor() { +│ │ ^^^^^^^^^^^^^^^^^^^ +│ │ 7: baz(); +│ │ ^^^^^^^^^^^^^^ +│ │ 8: } +│ │ ^^^^^ +│ │ 9: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:5:7-5:10 +│ │ 5: const Bar = class { +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:1:1-3:2 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: new Bar(); +│ │ │ │ ^^^^^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:1:10-1:13 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:2:9-2:12 +│ │ │ 2: new Bar(); +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:11:1-12:2 +│ │ │ │ 11: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 12: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:11:10-11:13 +│ │ │ │ 11: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:7:9-7:12 +│ │ │ 7: baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyConstNamedFunctionExpression.callHierarchy.txt b/tests/baselines/reference/callHierarchyConstNamedFunctionExpression.callHierarchy.txt new file mode 100644 index 0000000000000..e453112f12227 --- /dev/null +++ b/tests/baselines/reference/callHierarchyConstNamedFunctionExpression.callHierarchy.txt @@ -0,0 +1,62 @@ +╭ name: bar +├ kind: function +├ span: +│ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:5:13-7:2 +│ │ 5: const bar = function () { +│ │ ^^^^^^^^^^^^^ +│ │ 6: baz(); +│ │ ^^^^^^^^^^ +│ │ 7: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:5:7-5:10 +│ │ 5: const bar = function () { +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:1:1-3:2 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: bar(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:1:10-1:13 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:2:5-2:8 +│ │ │ 2: bar(); +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:9:1-10:2 +│ │ │ │ 9: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 10: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:9:10-9:13 +│ │ │ │ 9: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:6:5-6:8 +│ │ │ 6: baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyDecorator.callHierarchy.txt b/tests/baselines/reference/callHierarchyDecorator.callHierarchy.txt new file mode 100644 index 0000000000000..733dcf9784764 --- /dev/null +++ b/tests/baselines/reference/callHierarchyDecorator.callHierarchy.txt @@ -0,0 +1,62 @@ +╭ name: bar +├ kind: function +├ span: +│ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:5:1-7:2 +│ │ 5: function bar() { +│ │ ^^^^^^^^^^^^^^^^ +│ │ 6: baz(); +│ │ ^^^^^^^^^^ +│ │ 7: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:5:10-5:13 +│ │ 5: function bar() { +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: Foo +│ │ ├ kind: class +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:1:1-3:2 +│ │ │ │ 1: @bar +│ │ │ │ ^^^^ +│ │ │ │ 2: class Foo { +│ │ │ │ ^^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:2:7-2:10 +│ │ │ │ 2: class Foo { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:1:2-1:5 +│ │ │ 1: @bar +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:9:1-10:2 +│ │ │ │ 9: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 10: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:9:10-9:13 +│ │ │ │ 9: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:6:5-6:8 +│ │ │ 6: baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyExportDefaultClass.callHierarchy.txt b/tests/baselines/reference/callHierarchyExportDefaultClass.callHierarchy.txt new file mode 100644 index 0000000000000..f952a067f398f --- /dev/null +++ b/tests/baselines/reference/callHierarchyExportDefaultClass.callHierarchy.txt @@ -0,0 +1,66 @@ +╭ name: default +├ kind: class +├ span: +│ ╭ /tests/cases/fourslash/other.ts:1:1-5:2 +│ │ 1: export default class { +│ │ ^^^^^^^^^^^^^^^^^^^^^^ +│ │ 2: constructor() { +│ │ ^^^^^^^^^^^^^^^^^^^ +│ │ 3: baz(); +│ │ ^^^^^^^^^^^^^^ +│ │ 4: } +│ │ ^^^^^ +│ │ 5: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/other.ts:1:8-1:15 +│ │ 1: export default class { +│ │ ^^^^^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.ts:3:1-5:2 +│ │ │ │ 3: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 4: new Bar(); +│ │ │ │ ^^^^^^^^^^^^^^ +│ │ │ │ 5: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.ts:3:10-3:13 +│ │ │ │ 3: function foo() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:4:9-4:12 +│ │ │ 4: new Bar(); +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/other.ts:7:1-8:2 +│ │ │ │ 7: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 8: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/other.ts:7:10-7:13 +│ │ │ │ 7: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/other.ts:3:9-3:12 +│ │ │ 3: baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyExportDefaultFunction.callHierarchy.txt b/tests/baselines/reference/callHierarchyExportDefaultFunction.callHierarchy.txt new file mode 100644 index 0000000000000..b43df437d584e --- /dev/null +++ b/tests/baselines/reference/callHierarchyExportDefaultFunction.callHierarchy.txt @@ -0,0 +1,62 @@ +╭ name: default +├ kind: function +├ span: +│ ╭ /tests/cases/fourslash/other.ts:1:1-3:2 +│ │ 1: export default function () { +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ │ 2: baz(); +│ │ ^^^^^^^^^^ +│ │ 3: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/other.ts:1:8-1:15 +│ │ 1: export default function () { +│ │ ^^^^^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.ts:3:1-5:2 +│ │ │ │ 3: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 4: bar(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 5: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.ts:3:10-3:13 +│ │ │ │ 3: function foo() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:4:5-4:8 +│ │ │ 4: bar(); +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/other.ts:5:1-6:2 +│ │ │ │ 5: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 6: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/other.ts:5:10-5:13 +│ │ │ │ 5: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/other.ts:2:5-2:8 +│ │ │ 2: baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyExportEqualsFunction.callHierarchy.txt b/tests/baselines/reference/callHierarchyExportEqualsFunction.callHierarchy.txt new file mode 100644 index 0000000000000..b78312bcb9355 --- /dev/null +++ b/tests/baselines/reference/callHierarchyExportEqualsFunction.callHierarchy.txt @@ -0,0 +1,45 @@ +╭ name: /tests/cases/fourslash/other.ts +├ kind: module +├ span: +│ ╭ /tests/cases/fourslash/other.ts:1:1-6:2 +│ │ 1: export = function () { +│ │ ^^^^^^^^^^^^^^^^^^^^^^ +│ │ 2: baz(); +│ │ ^^^^^^^^^^ +│ │ 3: } +│ │ ^ +│ │ 4: +│ │ ^ +│ │ 5: function baz() { +│ │ ^^^^^^^^^^^^^^^^ +│ │ 6: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/other.ts:1:1-1:1 +│ │ 1: export = function () { +│ │ < +│ ╰ +├ incoming: none +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/other.ts:5:1-6:2 +│ │ │ │ 5: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 6: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/other.ts:5:10-5:13 +│ │ │ │ 5: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/other.ts:2:5-2:8 +│ │ │ 2: baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyFile.callHierarchy.txt b/tests/baselines/reference/callHierarchyFile.callHierarchy.txt new file mode 100644 index 0000000000000..1a993c810829d --- /dev/null +++ b/tests/baselines/reference/callHierarchyFile.callHierarchy.txt @@ -0,0 +1,39 @@ +╭ name: foo +├ kind: function +├ span: +│ ╭ /tests/cases/fourslash/callHierarchyFile.ts:2:1-3:2 +│ │ 2: function foo() { +│ │ ^^^^^^^^^^^^^^^^ +│ │ 3: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/callHierarchyFile.ts:2:10-2:13 +│ │ 2: function foo() { +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: /tests/cases/fourslash/callHierarchyFile.ts +│ │ ├ kind: script +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyFile.ts:1:1-3:2 +│ │ │ │ 1: foo(); +│ │ │ │ ^^^^^^ +│ │ │ │ 2: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyFile.ts:1:1-1:1 +│ │ │ │ 1: foo(); +│ │ │ │ < +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyFile.ts:1:1-1:4 +│ │ │ 1: foo(); +│ │ │ ^^^ +│ ╰ ╰ +╰ outgoing: none diff --git a/tests/baselines/reference/callHierarchyFunction.callHierarchy.txt b/tests/baselines/reference/callHierarchyFunction.callHierarchy.txt new file mode 100644 index 0000000000000..f659c886031f6 --- /dev/null +++ b/tests/baselines/reference/callHierarchyFunction.callHierarchy.txt @@ -0,0 +1,91 @@ +╭ name: bar +├ kind: function +├ span: +│ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:5:1-9:2 +│ │ 5: function bar() { +│ │ ^^^^^^^^^^^^^^^^ +│ │ 6: baz(); +│ │ ^^^^^^^^^^ +│ │ 7: quxx(); +│ │ ^^^^^^^^^^^ +│ │ 8: baz(); +│ │ ^^^^^^^^^^ +│ │ 9: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:5:10-5:13 +│ │ 5: function bar() { +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:1:1-3:2 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: bar(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:1:10-1:13 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:2:5-2:8 +│ │ │ 2: bar(); +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:11:1-12:2 +│ │ │ │ 11: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 12: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:11:10-11:13 +│ │ │ │ 11: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:6:5-6:8 +│ │ │ 6: baz(); +│ │ │ ^^^ +│ │ ╰ +│ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:8:5-8:8 +│ │ │ 8: baz(); +│ │ │ ^^^ +│ ╰ ╰ +│ ╭ to: +│ │ ╭ name: quxx +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:14:1-15:2 +│ │ │ │ 14: function quxx() { +│ │ │ │ ^^^^^^^^^^^^^^^^^ +│ │ │ │ 15: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:14:10-14:14 +│ │ │ │ 14: function quxx() { +│ │ │ │ ^^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:7:5-7:9 +│ │ │ 7: quxx(); +│ │ │ ^^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyInterfaceMethod.callHierarchy.txt b/tests/baselines/reference/callHierarchyInterfaceMethod.callHierarchy.txt new file mode 100644 index 0000000000000..e02d7b4f185a0 --- /dev/null +++ b/tests/baselines/reference/callHierarchyInterfaceMethod.callHierarchy.txt @@ -0,0 +1,45 @@ +╭ name: foo +├ kind: method +├ span: +│ ╭ /tests/cases/fourslash/callHierarchyInterfaceMethod.ts:2:5-2:17 +│ │ 2: foo(): void; +│ │ ^^^^^^^^^^^^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/callHierarchyInterfaceMethod.ts:2:5-2:8 +│ │ 2: foo(): void; +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: /tests/cases/fourslash/callHierarchyInterfaceMethod.ts +│ │ ├ kind: script +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyInterfaceMethod.ts:1:1-7:11 +│ │ │ │ 1: interface I { +│ │ │ │ ^^^^^^^^^^^^^ +│ │ │ │ 2: foo(): void; +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ │ 4: +│ │ │ │ ^ +│ │ │ │ 5: const obj: I = { foo() {} }; +│ │ │ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ │ │ │ 6: +│ │ │ │ ^ +│ │ │ │ 7: obj.foo(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyInterfaceMethod.ts:1:1-1:1 +│ │ │ │ 1: interface I { +│ │ │ │ < +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyInterfaceMethod.ts:7:5-7:8 +│ │ │ 7: obj.foo(); +│ │ │ ^^^ +│ ╰ ╰ +╰ outgoing: none diff --git a/tests/baselines/reference/callHierarchyJsxElement.callHierarchy.txt b/tests/baselines/reference/callHierarchyJsxElement.callHierarchy.txt new file mode 100644 index 0000000000000..65d0d2a68cf9d --- /dev/null +++ b/tests/baselines/reference/callHierarchyJsxElement.callHierarchy.txt @@ -0,0 +1,62 @@ +╭ name: Bar +├ kind: function +├ span: +│ ╭ /tests/cases/fourslash/main.tsx:5:1-7:2 +│ │ 5: function Bar() { +│ │ ^^^^^^^^^^^^^^^^ +│ │ 6: baz(); +│ │ ^^^^^^^^^^ +│ │ 7: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/main.tsx:5:10-5:13 +│ │ 5: function Bar() { +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.tsx:1:1-3:2 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: return ; +│ │ │ │ ^^^^^^^^^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.tsx:1:10-1:13 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.tsx:2:13-2:16 +│ │ │ 2: return ; +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.tsx:9:1-10:2 +│ │ │ │ 9: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 10: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.tsx:9:10-9:13 +│ │ │ │ 9: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.tsx:6:5-6:8 +│ │ │ 6: baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyTaggedTemplate.callHierarchy.txt b/tests/baselines/reference/callHierarchyTaggedTemplate.callHierarchy.txt new file mode 100644 index 0000000000000..d0b341d6df14d --- /dev/null +++ b/tests/baselines/reference/callHierarchyTaggedTemplate.callHierarchy.txt @@ -0,0 +1,62 @@ +╭ name: bar +├ kind: function +├ span: +│ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:5:1-7:2 +│ │ 5: function bar(array: TemplateStringsArray, ...args: any[]) { +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ │ 6: baz(); +│ │ ^^^^^^^^^^ +│ │ 7: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:5:10-5:13 +│ │ 5: function bar(array: TemplateStringsArray, ...args: any[]) { +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:1:1-3:2 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: bar`a${1}b`; +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:1:10-1:13 +│ │ │ │ 1: function foo() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:2:5-2:8 +│ │ │ 2: bar`a${1}b`; +│ │ │ ^^^ +│ ╰ ╰ +├ outgoing: +│ ╭ to: +│ │ ╭ name: baz +│ │ ├ kind: function +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:9:1-10:2 +│ │ │ │ 9: function baz() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 10: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:9:10-9:13 +│ │ │ │ 9: function baz() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:6:5-6:8 +│ │ │ 6: baz(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/cases/fourslash/callHierarchyAccessor.ts b/tests/cases/fourslash/callHierarchyAccessor.ts index 5e0e7a9e1120c..3ab79569d1a33 100644 --- a/tests/cases/fourslash/callHierarchyAccessor.ts +++ b/tests/cases/fourslash/callHierarchyAccessor.ts @@ -1,52 +1,17 @@ /// -//// [|function [|foo|]() { -//// new C().[|bar|]; -//// }|] +//// function foo() { +//// new C().bar; +//// } //// //// class C { -//// [|get /**/[|bar|]() { -//// return [|baz|](); -//// }|] +//// get /**/bar() { +//// return baz(); +//// } //// } //// -//// [|function [|baz|]() { -//// }|] - -const [ - fooFunctionRange, - fooFunctionSelectionRange, - barReferenceRange, - barAccessorRange, - barAccessorSelectionRange, - bazReferenceRange, - bazFunctionRange, - bazFunctionSelectionRange -] = test.ranges(); +//// function baz() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "getter", - range: barAccessorRange, - selectionRange: barAccessorSelectionRange, - incoming: sequence.one({ - from: { - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - }, - fromRanges: sequence.one( - barReferenceRange - ) - }), - outgoing: sequence.one({ - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: sequence.one( - bazReferenceRange - ) - }) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyClass.ts b/tests/cases/fourslash/callHierarchyClass.ts index 6095d0729e27d..3b3fc7a7fae06 100644 --- a/tests/cases/fourslash/callHierarchyClass.ts +++ b/tests/cases/fourslash/callHierarchyClass.ts @@ -1,50 +1,15 @@ /// -//// [|function [|foo|]() { -//// [|bar|](); -//// }|] +//// function foo() { +//// bar(); +//// } //// -//// [|function /**/[|bar|]() { -//// new [|Baz|](); -//// }|] +//// function /**/bar() { +//// new Baz(); +//// } //// -//// [|class [|Baz|] { -//// }|] - -const [ - fooFunctionRange, - fooFunctionSelectionRange, - barReferenceRange, - barFunctionRange, - barFunctionSelectionRange, - bazReferenceRange, - bazClassRange, - bazClassSelectionRange -] = test.ranges(); +//// class Baz { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "function", - range: barFunctionRange, - selectionRange: barFunctionSelectionRange, - incoming: sequence.one({ - from: { - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - }, - fromRanges: sequence.one( - barReferenceRange - ) - }), - outgoing: sequence.one({ - to: { - kind: "class", - range: bazClassRange, - selectionRange: bazClassSelectionRange - }, - fromRanges: sequence.one( - bazReferenceRange - ) - }) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts b/tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts index e1a9b3b6d14dc..cf4cd774c2539 100644 --- a/tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts +++ b/tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts @@ -1,50 +1,15 @@ /// -//// [|function [|foo|]() { -//// [|bar|](); -//// }|] +//// function foo() { +//// bar(); +//// } //// -//// const /**/[|bar|] = [|() => { -//// [|baz|](); -//// }|] +//// const /**/bar = () => { +//// baz(); +//// } //// -//// [|function [|baz|]() { -//// }|] - -const [ - fooFunctionRange, - fooFunctionSelectionRange, - barReferenceRange, - barFunctionSelectionRange, - barFunctionRange, - bazReferenceRange, - bazFunctionRange, - bazFunctionSelectionRange -] = test.ranges(); +//// function baz() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "function", - range: barFunctionRange, - selectionRange: barFunctionSelectionRange, - incoming: sequence.one({ - from: { - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - }, - fromRanges: sequence.one( - barReferenceRange - ) - }), - outgoing: sequence.one({ - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: sequence.one( - bazReferenceRange - ) - }) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts b/tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts index d336e6a8a636f..8f5dccc238b89 100644 --- a/tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts +++ b/tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts @@ -1,52 +1,17 @@ /// -//// [|function [|foo|]() { -//// new [|Bar|](); -//// }|] +//// function foo() { +//// new Bar(); +//// } //// -//// const /**/[|Bar|] = [|class { +//// const /**/Bar = class { //// constructor() { -//// [|baz|](); +//// baz(); //// } -//// }|] +//// } //// -//// [|function [|baz|]() { -//// }|] - -const [ - fooFunctionRange, - fooFunctionSelectionRange, - barReferenceRange, - barClassSelectionRange, - barClassRange, - bazReferenceRange, - bazFunctionRange, - bazFunctionSelectionRange, -] = test.ranges(); +//// function baz() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "class", - range: barClassRange, - selectionRange: barClassSelectionRange, - incoming: sequence.one({ - from: { - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - }, - fromRanges: sequence.one( - barReferenceRange - ) - }), - outgoing: sequence.one({ - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: sequence.one( - bazReferenceRange - ) - }) -}); \ No newline at end of file +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts b/tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts index 10aa71055abd5..e8613dda8e17d 100644 --- a/tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts +++ b/tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts @@ -1,50 +1,15 @@ /// -//// [|function [|foo|]() { -//// [|bar|](); -//// }|] +//// function foo() { +//// bar(); +//// } //// -//// const /**/[|bar|] = [|function () { -//// [|baz|](); -//// }|] +//// const /**/bar = function () { +//// baz(); +//// } //// -//// [|function [|baz|]() { -//// }|] - -const [ - fooFunctionRange, - fooFunctionSelectionRange, - barReferenceRange, - barFunctionSelectionRange, - barFunctionRange, - bazReferenceRange, - bazFunctionRange, - bazFunctionSelectionRange -] = test.ranges(); +//// function baz() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "function", - range: barFunctionRange, - selectionRange: barFunctionSelectionRange, - incoming: sequence.one({ - from: { - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - }, - fromRanges: sequence.one( - barReferenceRange - ) - }), - outgoing: sequence.one({ - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: sequence.one( - bazReferenceRange - ) - }) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyDecorator.ts b/tests/cases/fourslash/callHierarchyDecorator.ts index bafd6743127f4..a14d4035a2243 100644 --- a/tests/cases/fourslash/callHierarchyDecorator.ts +++ b/tests/cases/fourslash/callHierarchyDecorator.ts @@ -1,51 +1,16 @@ /// // @experimentalDecorators: true -//// [|@[|bar|] -//// class [|Foo|] { -//// }|] +//// @bar +//// class Foo { +//// } //// -//// [|function /**/[|bar|]() { -//// [|baz|](); -//// }|] +//// function /**/bar() { +//// baz(); +//// } //// -//// [|function [|baz|]() { -//// }|] - -const [ - fooClassRange, - barReferenceRange, - fooClassSelectionRange, - barFunctionRange, - barFunctionSelectionRange, - bazReferenceRange, - bazFunctionRange, - bazFunctionSelectionRange, -] = test.ranges(); +//// function baz() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "function", - range: barFunctionRange, - selectionRange: barFunctionSelectionRange, - incoming: sequence.one({ - from: { - kind: "class", - range: fooClassRange, - selectionRange: fooClassSelectionRange, - }, - fromRanges: sequence.one( - barReferenceRange - ) - }), - outgoing: sequence.one({ - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: sequence.one( - bazReferenceRange - ) - }) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyExportDefaultClass.ts b/tests/cases/fourslash/callHierarchyExportDefaultClass.ts index dc92954b8e3f8..19aef1dc462fd 100644 --- a/tests/cases/fourslash/callHierarchyExportDefaultClass.ts +++ b/tests/cases/fourslash/callHierarchyExportDefaultClass.ts @@ -3,54 +3,19 @@ // @filename: main.ts //// import Bar from "./other"; //// -//// [|function [|foo|]() { -//// new [|Bar|](); -//// }|] +//// function foo() { +//// new Bar(); +//// } // @filename: other.ts -//// [|export /**/[|default|] class { +//// export /**/default class { //// constructor() { -//// [|baz|](); +//// baz(); //// } -//// }|] +//// } //// -//// [|function [|baz|]() { -//// }|] - -const [ - fooFunctionRange, - fooFunctionSelectionRange, - barReferenceRange, - barFunctionRange, - barFunctionSelectionRange, - bazReferenceRange, - bazFunctionRange, - bazFunctionSelectionRange -] = test.ranges(); +//// function baz() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "class", - range: barFunctionRange, - selectionRange: barFunctionSelectionRange, - incoming: sequence.one({ - from: { - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - }, - fromRanges: sequence.one( - barReferenceRange - ) - }), - outgoing: sequence.one({ - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: sequence.one( - bazReferenceRange - ) - }) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyExportDefaultFunction.ts b/tests/cases/fourslash/callHierarchyExportDefaultFunction.ts index e1aa5889289df..8a41f83683c2a 100644 --- a/tests/cases/fourslash/callHierarchyExportDefaultFunction.ts +++ b/tests/cases/fourslash/callHierarchyExportDefaultFunction.ts @@ -3,52 +3,17 @@ // @filename: main.ts //// import bar from "./other"; //// -//// [|function [|foo|]() { -//// [|bar|](); -//// }|] +//// function foo() { +//// bar(); +//// } // @filename: other.ts -//// [|export /**/[|default|] function () { -//// [|baz|](); -//// }|] +//// export /**/default function () { +//// baz(); +//// } //// -//// [|function [|baz|]() { -//// }|] - -const [ - fooFunctionRange, - fooFunctionSelectionRange, - barReferenceRange, - barFunctionRange, - barFunctionSelectionRange, - bazReferenceRange, - bazFunctionRange, - bazFunctionSelectionRange -] = test.ranges(); +//// function baz() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "function", - range: barFunctionRange, - selectionRange: barFunctionSelectionRange, - incoming: sequence.one({ - from: { - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - }, - fromRanges: sequence.one( - barReferenceRange - ) - }), - outgoing: sequence.one({ - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: sequence.one( - bazReferenceRange - ) - }) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyExportEqualsFunction.ts b/tests/cases/fourslash/callHierarchyExportEqualsFunction.ts index ec96e5a5043a8..f2c6631909cf1 100644 --- a/tests/cases/fourslash/callHierarchyExportEqualsFunction.ts +++ b/tests/cases/fourslash/callHierarchyExportEqualsFunction.ts @@ -8,34 +8,13 @@ //// } // @filename: other.ts -//// [|export = /**/function () { -//// [|baz|](); +//// export = /**/function () { +//// baz(); //// } //// -//// [|function [|baz|]() { -//// }|]|] - -const [ - otherFileRange, - bazReferenceRange, - bazFunctionRange, - bazFunctionSelectionRange -] = test.ranges(); +//// function baz() { +//// } +// NOTE: exported function is unnamed, so we expand the item to the entire file... goTo.marker(); -verify.callHierarchy({ - // NOTE: exported function is unnamed, so we expand the item to the entire file... - kind: "module", - range: otherFileRange, - incoming: sequence.none(), - outgoing: sequence.one({ - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: sequence.one( - bazReferenceRange - ) - }) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyFile.ts b/tests/cases/fourslash/callHierarchyFile.ts index d4a5fa83483c6..08d8fc0e92664 100644 --- a/tests/cases/fourslash/callHierarchyFile.ts +++ b/tests/cases/fourslash/callHierarchyFile.ts @@ -1,29 +1,8 @@ /// -//// [|[|foo|](); -//// [|function /**/[|foo|]() { -//// }|]|] - -const [ - fileRange, - fooReferenceRange, - fooFunctionRange, - fooFunctionSelectionRange, -] = test.ranges(); +//// foo(); +//// function /**/foo() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - incoming: sequence.one({ - from: { - kind: "script", - range: fileRange, - }, - fromRanges: sequence.one( - fooReferenceRange - ) - }), - outgoing: sequence.none() -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyFunction.ts b/tests/cases/fourslash/callHierarchyFunction.ts index ad22e08a8714d..72c0bed51e3c1 100644 --- a/tests/cases/fourslash/callHierarchyFunction.ts +++ b/tests/cases/fourslash/callHierarchyFunction.ts @@ -1,72 +1,20 @@ /// -//// [|function [|foo|]() { -//// [|bar|](); -//// }|] +//// function foo() { +//// bar(); +//// } //// -//// [|function /**/[|bar|]() { -//// [|baz|](); -//// [|quxx|](); -//// [|baz|](); -//// }|] +//// function /**/bar() { +//// baz(); +//// quxx(); +//// baz(); +//// } //// -//// [|function [|baz|]() { -//// }|] +//// function baz() { +//// } //// -//// [|function [|quxx|]() { -//// }|] - -const [ - fooFunctionRange, - fooFunctionSelectionRange, - barReferenceRange, - barFunctionRange, - barFunctionSelectionRange, - bazReferenceRange1, - quxxReferenceRange, - bazReferenceRange2, - bazFunctionRange, - bazFunctionSelectionRange, - quxxFunctionRange, - quxxFunctionSelectionRange, -] = test.ranges(); +//// function quxx() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "function", - range: barFunctionRange, - selectionRange: barFunctionSelectionRange, - incoming: sequence.one({ - from: { - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - }, - fromRanges: sequence.one( - barReferenceRange - ) - }), - outgoing: sequence.exact([ - { - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: sequence.exact([ - bazReferenceRange1, - bazReferenceRange2 - ]) - }, - { - to: { - kind: "function", - range: quxxFunctionRange, - selectionRange: quxxFunctionSelectionRange - }, - fromRanges: sequence.one( - quxxReferenceRange - ) - }, - ]) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyInterfaceMethod.ts b/tests/cases/fourslash/callHierarchyInterfaceMethod.ts index 5fb330b831770..7233c98c3e6a7 100644 --- a/tests/cases/fourslash/callHierarchyInterfaceMethod.ts +++ b/tests/cases/fourslash/callHierarchyInterfaceMethod.ts @@ -1,33 +1,12 @@ /// -//// [|interface I { -//// /**/[|[|foo|](): void;|] +//// interface I { +//// /**/foo(): void; //// } //// //// const obj: I = { foo() {} }; //// -//// obj.[|foo|]();|] - -const [ - fileRange, - fooMethodRange, - fooMethodSelectionRange, - fooReferenceRange -] = test.ranges(); +//// obj.foo(); goTo.marker(); -verify.callHierarchy({ - kind: "method", - range: fooMethodRange, - selectionRange: fooMethodSelectionRange, - incoming: sequence.one({ - from: { - kind: "script", - range: fileRange - }, - fromRanges: sequence.one( - fooReferenceRange - ) - }), - outgoing: sequence.none() -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyJsxElement.ts b/tests/cases/fourslash/callHierarchyJsxElement.ts index 6cd967ac04be8..67e63efefea52 100644 --- a/tests/cases/fourslash/callHierarchyJsxElement.ts +++ b/tests/cases/fourslash/callHierarchyJsxElement.ts @@ -2,51 +2,16 @@ // @jsx: preserve // @filename: main.tsx -//// [|function [|foo|]() { -//// return <[|Bar|]/>; -//// }|] +//// function foo() { +//// return ; +//// } //// -//// [|function /**/[|Bar|]() { -//// [|baz|](); -//// }|] +//// function /**/Bar() { +//// baz(); +//// } //// -//// [|function [|baz|]() { -//// }|] - -const [ - fooFunctionRange, - fooFunctionSelectionRange, - barReferenceRange, - barFunctionRange, - barFunctionSelectionRange, - bazReferenceRange, - bazFunctionRange, - bazFunctionSelectionRange -] = test.ranges(); +//// function baz() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "function", - range: barFunctionRange, - selectionRange: barFunctionSelectionRange, - incoming: sequence.one({ - from: { - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - }, - fromRanges: sequence.one( - barReferenceRange - ) - }), - outgoing: sequence.one({ - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: sequence.one( - bazReferenceRange - ) - }) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyTaggedTemplate.ts b/tests/cases/fourslash/callHierarchyTaggedTemplate.ts index 7e9991ba4906e..d6ad621ec7d0f 100644 --- a/tests/cases/fourslash/callHierarchyTaggedTemplate.ts +++ b/tests/cases/fourslash/callHierarchyTaggedTemplate.ts @@ -1,50 +1,15 @@ /// -//// [|function [|foo|]() { -//// [|bar|]`a${1}b`; -//// }|] +//// function foo() { +//// bar`a${1}b`; +//// } //// -//// [|function /**/[|bar|](array: TemplateStringsArray, ...args: any[]) { -//// [|baz|](); -//// }|] +//// function /**/bar(array: TemplateStringsArray, ...args: any[]) { +//// baz(); +//// } //// -//// [|function [|baz|]() { -//// }|] - -const [ - fooFunctionRange, - fooFunctionSelectionRange, - barReferenceRange, - barFunctionRange, - barFunctionSelectionRange, - bazReferenceRange, - bazFunctionRange, - bazFunctionSelectionRange -] = test.ranges(); +//// function baz() { +//// } goTo.marker(); -verify.callHierarchy({ - kind: "function", - range: barFunctionRange, - selectionRange: barFunctionSelectionRange, - incoming: sequence.one({ - from: { - kind: "function", - range: fooFunctionRange, - selectionRange: fooFunctionSelectionRange, - }, - fromRanges: { exact: true, values: [ - barReferenceRange - ] } - }), - outgoing: sequence.one({ - to: { - kind: "function", - range: bazFunctionRange, - selectionRange: bazFunctionSelectionRange - }, - fromRanges: { exact: true, values: [ - bazReferenceRange - ] } - }) -}); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 73df5223d3025..98a1919ad0bdf 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -382,6 +382,7 @@ declare namespace FourSlashInterface { readonly newFileContents: { readonly [fileName: string]: string }; readonly preferences?: UserPreferences; }): void; + baselineCallHierarchy(): void; callHierarchy(options: false | Range | VerifyCallHierarchyOptions): void; callHierarchyIncomingCalls(options: Sequence): void; callHierarchyOutgoingCalls(options: Sequence): void; From 93e93f7dd94b0019faa329aae5a8f9318ec91632 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 18 Dec 2019 15:07:34 -0800 Subject: [PATCH 3/6] Clean up commented code --- src/server/session.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index 067c4379bb256..cf1c1f46668ae 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2703,12 +2703,6 @@ namespace ts.server { readonly project: Project; } - // function toLanguageServiceTextSpan(textSpan: protocol.TextSpan, scriptInfo: ScriptInfo): TextSpan { - // const start = scriptInfo.lineOffsetToPosition(textSpan.start.line, textSpan.start.offset); - // const end = scriptInfo.lineOffsetToPosition(textSpan.end.line, textSpan.end.offset); - // return { start, length: end - start }; - // } - function toProtocolTextSpan(textSpan: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan { return { start: scriptInfo.positionToLineOffset(textSpan.start), From e0cedbe9f02c5ddde0c5188d3b6ec49e9d598018 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 18 Dec 2019 16:35:05 -0800 Subject: [PATCH 4/6] Support multiple hierarchy items when an implementation can't be found --- src/compiler/core.ts | 12 +- src/harness/client.ts | 4 +- src/harness/fourslashImpl.ts | 168 +----------------- src/harness/fourslashInterfaceImpl.ts | 56 ------ src/server/protocol.ts | 2 +- src/server/session.ts | 8 +- src/services/callHierarchy.ts | 63 ++++--- src/services/services.ts | 10 +- src/services/types.ts | 2 +- src/services/utilities.ts | 19 ++ .../reference/api/tsserverlibrary.d.ts | 4 +- tests/baselines/reference/api/typescript.d.ts | 2 +- .../callHierarchyAccessor.callHierarchy.txt | 3 + .../callHierarchyClass.callHierarchy.txt | 3 + ...yConstNamedArrowFunction.callHierarchy.txt | 3 + ...onstNamedClassExpression.callHierarchy.txt | 3 + ...tNamedFunctionExpression.callHierarchy.txt | 3 + .../callHierarchyDecorator.callHierarchy.txt | 3 + ...rarchyExportDefaultClass.callHierarchy.txt | 3 + ...chyExportDefaultFunction.callHierarchy.txt | 3 + ...rchyExportEqualsFunction.callHierarchy.txt | 2 + .../callHierarchyFile.callHierarchy.txt | 2 + .../callHierarchyFunction.callHierarchy.txt | 4 + ...archyFunctionAmbiguity.1.callHierarchy.txt | 78 ++++++++ ...archyFunctionAmbiguity.2.callHierarchy.txt | 78 ++++++++ ...archyFunctionAmbiguity.3.callHierarchy.txt | 78 ++++++++ ...archyFunctionAmbiguity.4.callHierarchy.txt | 78 ++++++++ ...archyFunctionAmbiguity.5.callHierarchy.txt | 59 ++++++ ...HierarchyInterfaceMethod.callHierarchy.txt | 2 + .../callHierarchyJsxElement.callHierarchy.txt | 3 + ...lHierarchyTaggedTemplate.callHierarchy.txt | 3 + .../callHierarchyFunctionAmbiguity.1.ts | 16 ++ .../callHierarchyFunctionAmbiguity.2.ts | 16 ++ .../callHierarchyFunctionAmbiguity.3.ts | 16 ++ .../callHierarchyFunctionAmbiguity.4.ts | 16 ++ .../callHierarchyFunctionAmbiguity.5.ts | 16 ++ tests/cases/fourslash/fourslash.ts | 35 ---- 37 files changed, 584 insertions(+), 292 deletions(-) create mode 100644 tests/baselines/reference/callHierarchyFunctionAmbiguity.1.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyFunctionAmbiguity.2.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyFunctionAmbiguity.3.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyFunctionAmbiguity.4.callHierarchy.txt create mode 100644 tests/baselines/reference/callHierarchyFunctionAmbiguity.5.callHierarchy.txt create mode 100644 tests/cases/fourslash/callHierarchyFunctionAmbiguity.1.ts create mode 100644 tests/cases/fourslash/callHierarchyFunctionAmbiguity.2.ts create mode 100644 tests/cases/fourslash/callHierarchyFunctionAmbiguity.3.ts create mode 100644 tests/cases/fourslash/callHierarchyFunctionAmbiguity.4.ts create mode 100644 tests/cases/fourslash/callHierarchyFunctionAmbiguity.5.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 0163049978727..203835b49f6a9 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -630,10 +630,18 @@ namespace ts { return [...array1, ...array2]; } + function selectIndex(_: unknown, i: number) { + return i; + } + + export function indicesOf(array: readonly unknown[]): number[] { + return array.map(selectIndex); + } + function deduplicateRelational(array: readonly T[], equalityComparer: EqualityComparer, comparer: Comparer) { // Perform a stable sort of the array. This ensures the first entry in a list of // duplicates remains the first entry in the result. - const indices = array.map((_, i) => i); + const indices = indicesOf(array); stableSortIndices(array, indices, comparer); let last = array[indices[0]]; @@ -939,7 +947,7 @@ namespace ts { * Stable sort of an array. Elements equal to each other maintain their relative position in the array. */ export function stableSort(array: readonly T[], comparer: Comparer): SortedReadonlyArray { - const indices = array.map((_, i) => i); + const indices = indicesOf(array); stableSortIndices(array, indices, comparer); return indices.map(i => array[i]) as SortedArray as SortedReadonlyArray; } diff --git a/src/harness/client.ts b/src/harness/client.ts index 2c31fca88fd2e..63ad8b90da83c 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -753,11 +753,11 @@ namespace ts.server { }; } - prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | undefined { + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { const args = this.createFileLocationRequestArgs(fileName, position); const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); const response = this.processResponse(request); - return response.body && this.convertCallHierarchyItem(response.body); + return response.body && mapOneOrMany(response.body, item => this.convertCallHierarchyItem(item)); } private convertCallHierarchyIncomingCall(item: protocol.CallHierarchyIncomingCall): CallHierarchyIncomingCall { diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 26815189e1532..e04f2037c6914 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -3248,6 +3248,7 @@ namespace FourSlash { let text = ""; text += `${prefix}╭ name: ${callHierarchyItem.name}\n`; text += `${prefix}├ kind: ${callHierarchyItem.kind}\n`; + text += `${prefix}├ file: ${callHierarchyItem.file}\n`; text += `${prefix}├ span:\n`; text += this.formatCallHierarchyItemSpan(file, callHierarchyItem.span, `${prefix}│ `); text += `${prefix}├ selectionSpan:\n`; @@ -3299,9 +3300,8 @@ namespace FourSlash { text += `${prefix}├ outgoing:\n`; for (let i = 0; i < outgoingCalls.values.length; i++) { const outgoingCall = outgoingCalls.values[i]; - const file = this.findFile(outgoingCall.to.file); text += `${prefix}│ ╭ to:\n`; - text += this.formatCallHierarchyItem(file, outgoingCall.to, CallHierarchyItemDirection.Outgoing, seen, `${prefix}│ │ `); + text += this.formatCallHierarchyItem(this.findFile(outgoingCall.to.file), outgoingCall.to, CallHierarchyItemDirection.Outgoing, seen, `${prefix}│ │ `); text += `${prefix}│ ├ fromSpans:\n`; text += this.formatCallHierarchyItemSpans(file, outgoingCall.fromSpans, `${prefix}│ │ `, i < outgoingCalls.values.length - 1 ? `${prefix}│ ╰ ` : @@ -3323,141 +3323,11 @@ namespace FourSlash { public baselineCallHierarchy() { const baselineFile = this.getBaselineFileNameForContainingTestFile(".callHierarchy.txt"); - const callHierarchyItem = this.languageService.prepareCallHierarchy(this.activeFile.fileName, this.currentCaretPosition)!; - const text = callHierarchyItem ? this.formatCallHierarchy(callHierarchyItem) : "none"; + const callHierarchyItem = this.languageService.prepareCallHierarchy(this.activeFile.fileName, this.currentCaretPosition); + const text = callHierarchyItem ? ts.mapOneOrMany(callHierarchyItem, item => this.formatCallHierarchy(item), result => result.join("")) : "none"; Harness.Baseline.runBaseline(baselineFile, text); } - public verifyCallHierarchy(options: false | FourSlashInterface.VerifyCallHierarchyOptions | Range) { - const callHierarchyItem = this.languageService.prepareCallHierarchy(this.activeFile.fileName, this.currentCaretPosition)!; - this.assertCallHierarchyItemMatches(callHierarchyItem, options); - } - - public verifyCallHierarchyIncomingCalls(options: FourSlashInterface.Sequence) { - const incomingCalls = this.languageService.provideCallHierarchyIncomingCalls(this.activeFile.fileName, this.currentCaretPosition)!; - this.assertCallHierarchyIncomingCallsMatch(incomingCalls, options); - } - - public verifyCallHierarchyOutgoingCalls(options: FourSlashInterface.Sequence) { - const outgoingCalls = this.languageService.provideCallHierarchyOutgoingCalls(this.activeFile.fileName, this.currentCaretPosition)!; - this.assertCallHierarchyOutgoingCallsMatch(outgoingCalls, options); - } - - private assertSequence(actual: readonly T[] | undefined, expected: FourSlashInterface.Sequence, equate: (actual: T, expected: U) => boolean | undefined, assertion: (actual: T, expected: U, message?: string) => void, message?: string, stringifyActual: (actual: T) => string = stringifyFallback, stringifyExpected: (expected: U) => string = stringifyFallback) { - // normalize expected input - if (!expected) { - expected = { exact: true, values: ts.emptyArray }; - } - else if (ts.isArray(expected)) { - expected = { exact: false, values: expected }; - } - - if (expected.exact) { - if (actual === undefined || actual.length === 0) { - if (expected.values.length !== 0) { - this.raiseError(`${prefixMessage(message)}Expected sequence to have exactly ${expected.values.length} item${expected.values.length > 1 ? "s" : ""} but got ${actual ? "an empty array" : "undefined"} instead.\nExpected:\n${stringifyArray(expected.values, stringifyExpected)}`); - } - return; - } - if (expected.values.length === 0) { - this.raiseError(`${prefixMessage(message)}Expected sequence to be empty but got ${actual.length} item${actual.length > 1 ? "s" : ""} instead.\nActual:\n${stringifyArray(actual, stringifyActual)}`); - return; - } - } - else if (actual === undefined || actual.length === 0) { - if (expected.values.length !== 0) { - this.raiseError(`${prefixMessage(message)}Expected sequence to have at least ${expected.values.length} item${expected.values.length > 1 ? "s" : ""} but got ${actual ? "an empty array" : "undefined"} instead.\nExpected:\n${stringifyArray(expected.values, stringifyExpected)}`); - } - return; - } - - let expectedIndex = 0; - let actualIndex = 0; - while (expectedIndex < expected.values.length && actualIndex < actual.length) { - const actualItem = actual[actualIndex]; - actualIndex++; - const expectedItem = expected.values[expectedIndex]; - const result = expected.exact || equate(actualItem, expectedItem); - if (result) { - assertion(actualItem, expectedItem, message); - expectedIndex++; - } - else if (expected.exact) { - this.raiseError(`${prefixMessage(message)}Expected item at index ${expectedIndex} to be ${stringifyExpected(expectedItem)} but got ${stringifyActual(actualItem)} instead.`); - } - else if (result === undefined) { - this.raiseError(`${prefixMessage(message)}Unable to compare items`); - } - } - - if (expectedIndex < expected.values.length) { - const expectedItem = expected.values[expectedIndex]; - this.raiseError(`${prefixMessage(message)}Expected array to contain ${stringifyExpected(expectedItem)} but it was not found.`); - } - } - - private assertCallHierarchyItemMatches(callHierarchyItem: ts.CallHierarchyItem | undefined, options: false | FourSlashInterface.VerifyCallHierarchyOptions | Range, message?: string) { - if (!options) { - assert.isUndefined(callHierarchyItem, this.messageAtLastKnownMarker(`${prefixMessage(message)}Expected location to not have a call hierarchy`)); - } - else { - assert.isDefined(callHierarchyItem, this.messageAtLastKnownMarker(`${prefixMessage(message)}Expected location to have a call hierarchy`)); - if (!callHierarchyItem) return; - if (isRange(options)) { - options = { selectionRange: options }; - } - if (options.kind !== undefined) { - assert.equal(callHierarchyItem.kind, options.kind, this.messageAtLastKnownMarker(`${prefixMessage(message)}Invalid kind`)); - } - if (options.range) { - assert.equal(callHierarchyItem.file, options.range.fileName, this.messageAtLastKnownMarker(`${prefixMessage(message)}Incorrect file for call hierarchy item`)); - this.assertTextSpanEqualsRange(callHierarchyItem.span, options.range, `${prefixMessage(message)}Incorrect range for declaration`); - } - if (options.selectionRange) { - assert.equal(callHierarchyItem.file, options.selectionRange.fileName, this.messageAtLastKnownMarker(`${prefixMessage(message)}Incorrect file for call hierarchy item`)); - this.assertTextSpanEqualsRange(callHierarchyItem.selectionSpan, options.selectionRange, `${prefixMessage(message)}Incorrect selectionRange for declaration`); - } - if (options.incoming !== undefined) { - const incomingCalls = this.languageService.provideCallHierarchyIncomingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start); - this.assertCallHierarchyIncomingCallsMatch(incomingCalls, options.incoming, message); - } - if (options.outgoing !== undefined) { - const outgoingCalls = this.languageService.provideCallHierarchyOutgoingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start); - this.assertCallHierarchyOutgoingCallsMatch(outgoingCalls, options.outgoing, message); - } - } - } - - private assertCallHierarchyIncomingCallsMatch(incomingCalls: ts.CallHierarchyIncomingCall[], options: FourSlashInterface.Sequence, message?: string) { - this.assertSequence(incomingCalls, options, - (actual, expected) => { - expected = normalizeCallHierarchyIncomingCallOptions(expected); - const from = normalizeCallHierarchyOptions(expected.from); - return from.selectionRange && textSpanEqualsRange(actual.from.selectionSpan, from.selectionRange); - }, - (actual, expected, message) => { - expected = normalizeCallHierarchyIncomingCallOptions(expected); - const from = normalizeCallHierarchyOptions(expected.from); - this.assertCallHierarchyItemMatches(actual.from, from, message); - if (expected.fromRanges !== undefined) this.assertSequence(actual.fromSpans, expected.fromRanges, textSpanEqualsRange, ts.noop, `${prefixMessage(message)}Invalid fromRange`); - }, `${prefixMessage(message)}Invalid incoming calls`); - } - - private assertCallHierarchyOutgoingCallsMatch(outgoingCalls: ts.CallHierarchyOutgoingCall[], options: FourSlashInterface.Sequence, message?: string) { - this.assertSequence(outgoingCalls, options, - (actual, expected) => { - expected = normalizeCallHierarchyOutgoingCallOptions(expected); - const to = normalizeCallHierarchyOptions(expected.to); - return to.selectionRange && textSpanEqualsRange(actual.to.selectionSpan, to.selectionRange); - }, - (actual, expected, message) => { - expected = normalizeCallHierarchyOutgoingCallOptions(expected); - const to = normalizeCallHierarchyOptions(expected.to); - this.assertCallHierarchyItemMatches(actual.to, to, message); - if (expected.fromRanges !== undefined) this.assertSequence(actual.fromSpans, expected.fromRanges, textSpanEqualsRange, ts.noop, `${prefixMessage(message)}Invalid fromRange`); - }, `${prefixMessage(message)}Invalid outgoing calls`); - } - private assertTextSpanEqualsRange(span: ts.TextSpan, range: Range, message?: string) { if (!textSpanEqualsRange(span, range)) { this.raiseError(`${prefixMessage(message)}Expected to find TextSpan ${JSON.stringify({ start: range.pos, length: range.end - range.pos })} but got ${JSON.stringify(span)} instead.`); @@ -3601,36 +3471,10 @@ namespace FourSlash { return message ? `${message} - ` : ""; } - function stringifyArray(values: readonly T[], stringify: (value: T) => string, indent = " ") { - return values.length ? `${indent}[\n${indent} ${values.map(stringify).join(`,\n${indent} `)}\n${indent}]` : `${indent}[]`; - } - - function stringifyFallback(value: unknown) { - return JSON.stringify(value); - } - function textSpanEqualsRange(span: ts.TextSpan, range: Range) { return span.start === range.pos && span.length === range.end - range.pos; } - function isRange(value: object): value is Range { - return ts.hasProperty(value, "pos") - && ts.hasProperty(value, "end") - && ts.hasProperty(value, "fileName"); - } - - function normalizeCallHierarchyOptions(options: Range | FourSlashInterface.VerifyCallHierarchyOptions) { - return isRange(options) ? { selectionRange: options } : options; - } - - function normalizeCallHierarchyIncomingCallOptions(options: Range | FourSlashInterface.VerifyCallHierarchyIncomingCallOptions) { - return isRange(options) ? { from: options } : options; - } - - function normalizeCallHierarchyOutgoingCallOptions(options: Range | FourSlashInterface.VerifyCallHierarchyOutgoingCallOptions) { - return isRange(options) ? { to: options } : options; - } - function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: readonly ts.TextChange[]): ts.TextRange { forEachTextChange(textChanges, change => { const update = (p: number): number => updatePosition(p, change.span.start, ts.textSpanEnd(change.span), change.newText); @@ -3692,7 +3536,7 @@ namespace FourSlash { function runCode(code: string, state: TestState): void { // Compile and execute the test const wrappedCode = - `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, sequence, verifyOperationIsCancelled) { + `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) { ${code} })`; try { @@ -3706,7 +3550,7 @@ ${code} const cancellation = new FourSlashInterface.Cancellation(state); // eslint-disable-next-line no-eval const f = eval(wrappedCode); - f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, FourSlashInterface.Sequence, verifyOperationIsCancelled); + f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, verifyOperationIsCancelled); } catch (err) { throw err; diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 955b767ed711d..3ecd0005361db 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -541,18 +541,6 @@ namespace FourSlashInterface { this.state.baselineCallHierarchy(); } - public callHierarchy(options: false | FourSlash.Range | VerifyCallHierarchyOptions) { - this.state.verifyCallHierarchy(options); - } - - public callHierarchyIncomingCalls(options: Sequence) { - this.state.verifyCallHierarchyIncomingCalls(options); - } - - public callHierarchyOutgoingCalls(options: Sequence) { - this.state.verifyCallHierarchyOutgoingCalls(options); - } - public moveToNewFile(options: MoveToNewFileOptions): void { this.state.moveToNewFile(options); } @@ -1619,48 +1607,4 @@ namespace FourSlashInterface { readonly providePrefixAndSuffixTextForRename?: boolean; }; export type RenameLocationOptions = FourSlash.Range | { readonly range: FourSlash.Range, readonly prefixText?: string, readonly suffixText?: string }; - - export type Sequence = - | false // Indicates the actual result must be undefined or contain no elements - | readonly T[] // Indicates the actual result must at least contain the specified elements, in any order - | { - readonly exact?: boolean; // Indicates the actual result must contain all of the elements in `values` (no more and no fewer). - readonly values: readonly T[]; - }; - - export namespace Sequence { - export function atLeast(array: readonly T[]): Sequence { - return { exact: false, values: array }; - } - - export function exact(array: readonly T[]): Sequence { - return { exact: true, values: array }; - } - - export function one(value: T): Sequence { - return { exact: true, values: [value] }; - } - - export function none(): Sequence { - return false; - } - } - - export interface VerifyCallHierarchyOptions { - readonly range?: FourSlash.Range; - readonly kind?: ts.ScriptElementKind; - readonly selectionRange?: FourSlash.Range; - readonly incoming?: Sequence; - readonly outgoing?: Sequence; - } - - export interface VerifyCallHierarchyIncomingCallOptions { - readonly from: VerifyCallHierarchyOptions | FourSlash.Range; - readonly fromRanges?: Sequence; - } - - export interface VerifyCallHierarchyOutgoingCallOptions { - readonly to: VerifyCallHierarchyOptions | FourSlash.Range; - readonly fromRanges?: Sequence; - } } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 89601b44f6177..cabe7dcd55dd1 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -3010,7 +3010,7 @@ namespace ts.server.protocol { } export interface PrepareCallHierarchyResponse extends Response { - readonly body: CallHierarchyItem; + readonly body: CallHierarchyItem | CallHierarchyItem[]; } export interface ProvideCallHierarchyIncomingCallsRequest extends FileLocationRequest { diff --git a/src/server/session.ts b/src/server/session.ts index cf1c1f46668ae..b7a924e1d59da 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2171,15 +2171,13 @@ namespace ts.server { }; } - private prepareCallHierarchy(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyItem | undefined { + private prepareCallHierarchy(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyItem | protocol.CallHierarchyItem[] | undefined { const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); if (scriptInfo) { const position = this.getPosition(args, scriptInfo); - const item = languageService.prepareCallHierarchy(file, position); - return !item - ? undefined - : this.toProtocolCallHierarchyItem(item, scriptInfo); + const result = languageService.prepareCallHierarchy(file, position); + return result && mapOneOrMany(result, item => this.toProtocolCallHierarchyItem(item, scriptInfo)); } return undefined; } diff --git a/src/services/callHierarchy.ts b/src/services/callHierarchy.ts index 53520b30c84e3..838f9355d3dca 100644 --- a/src/services/callHierarchy.ts +++ b/src/services/callHierarchy.ts @@ -130,9 +130,9 @@ namespace ts.CallHierarchy { } /** Finds the implementation of a function-like declaration, if one exists. */ - function findFunctionImplementation(typeChecker: TypeChecker, node: Extract): Extract | undefined; - function findFunctionImplementation(typeChecker: TypeChecker, node: FunctionLikeDeclaration): FunctionLikeDeclaration | undefined; - function findFunctionImplementation(typeChecker: TypeChecker, node: FunctionLikeDeclaration): FunctionLikeDeclaration | undefined { + function findImplementation(typeChecker: TypeChecker, node: Extract): Extract | undefined; + function findImplementation(typeChecker: TypeChecker, node: FunctionLikeDeclaration): FunctionLikeDeclaration | undefined; + function findImplementation(typeChecker: TypeChecker, node: FunctionLikeDeclaration): FunctionLikeDeclaration | undefined { if (node.body) { return node; } @@ -141,34 +141,47 @@ namespace ts.CallHierarchy { } if (isFunctionDeclaration(node) || isMethodDeclaration(node)) { const symbol = getSymbolOfCallHierarchyDeclaration(typeChecker, node); - if (symbol && symbol.valueDeclaration && isFunctionLikeDeclaration(symbol.valueDeclaration)) { + if (symbol && symbol.valueDeclaration && isFunctionLikeDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.body) { return symbol.valueDeclaration; } + return undefined; } return node; } - function findFirstDeclaration(typeChecker: TypeChecker, node: CallHierarchyDeclaration) { + function findAllInitialDeclarations(typeChecker: TypeChecker, node: CallHierarchyDeclaration) { const symbol = getSymbolOfCallHierarchyDeclaration(typeChecker, node); + let declarations: CallHierarchyDeclaration[] | undefined; if (symbol && symbol.declarations) { - for (const decl of symbol.declarations) { - if (isValidCallHierarchyDeclaration(decl)) return decl; + const indices = indicesOf(symbol.declarations); + const keys = map(symbol.declarations, decl => ({ file: decl.getSourceFile().fileName, pos: decl.pos })); + indices.sort((a, b) => compareStringsCaseSensitive(keys[a].file, keys[b].file) || keys[a].pos - keys[b].pos); + const sortedDeclarations = map(indices, i => symbol.declarations[i]); + let lastDecl: CallHierarchyDeclaration | undefined; + for (const decl of sortedDeclarations) { + if (isValidCallHierarchyDeclaration(decl)) { + if (!lastDecl || lastDecl.parent !== decl.parent || lastDecl.end !== decl.pos) { + declarations = append(declarations, decl); + } + lastDecl = decl; + } } } + return declarations; } /** Find the implementation or the first declaration for a call hierarchy declaration. */ - function findImplementationOrFirstDeclaration(typeChecker: TypeChecker, node: CallHierarchyDeclaration): CallHierarchyDeclaration { - if (isFunctionLikeDeclaration(node) && !node.body) { - return findFunctionImplementation(typeChecker, node) || - findFirstDeclaration(typeChecker, node) || + function findImplementationOrAllInitialDeclarations(typeChecker: TypeChecker, node: CallHierarchyDeclaration): CallHierarchyDeclaration | CallHierarchyDeclaration[] { + if (isFunctionLikeDeclaration(node)) { + return findImplementation(typeChecker, node) ?? + findAllInitialDeclarations(typeChecker, node) ?? node; } - return findFirstDeclaration(typeChecker, node) || node; + return findAllInitialDeclarations(typeChecker, node) ?? node; } /** Resolves the call hierarchy declaration for a node. */ - export function resolveCallHierarchyDeclaration(program: Program, location: Node): CallHierarchyDeclaration | undefined { + export function resolveCallHierarchyDeclaration(program: Program, location: Node): CallHierarchyDeclaration | CallHierarchyDeclaration[] | undefined { // A call hierarchy item must refer to either a SourceFile, Module Declaration, or something intrinsically callable that has a name: // - Class Declarations // - Class Expressions (with a name) @@ -186,19 +199,19 @@ namespace ts.CallHierarchy { let followingSymbol = false; while (true) { if (isValidCallHierarchyDeclaration(location)) { - return findImplementationOrFirstDeclaration(typeChecker, location); + return findImplementationOrAllInitialDeclarations(typeChecker, location); } if (isPossibleCallHierarchyDeclaration(location)) { const ancestor = findAncestor(location, isValidCallHierarchyDeclaration); - return ancestor && findImplementationOrFirstDeclaration(typeChecker, ancestor); + return ancestor && findImplementationOrAllInitialDeclarations(typeChecker, ancestor); } if (isDeclarationName(location)) { if (isValidCallHierarchyDeclaration(location.parent)) { - return findImplementationOrFirstDeclaration(typeChecker, location.parent); + return findImplementationOrAllInitialDeclarations(typeChecker, location.parent); } if (isPossibleCallHierarchyDeclaration(location.parent)) { const ancestor = findAncestor(location.parent, isValidCallHierarchyDeclaration); - return ancestor && findImplementationOrFirstDeclaration(typeChecker, ancestor); + return ancestor && findImplementationOrAllInitialDeclarations(typeChecker, ancestor); } if (isVariableDeclaration(location.parent) && location.parent.initializer && isConstNamedExpression(location.parent.initializer)) { return location.parent.initializer; @@ -256,7 +269,7 @@ namespace ts.CallHierarchy { || isRightSideOfPropertyAccess(entry.node) || isArgumentExpressionOfElementAccess(entry.node)) { const ancestor = findAncestor(entry.node, isValidCallHierarchyDeclaration) || entry.node.getSourceFile(); - return { declaration: findImplementationOrFirstDeclaration(typeChecker, ancestor), range: createTextRangeFromNode(entry.node, entry.node.getSourceFile()) }; + return { declaration: firstOrOnly(findImplementationOrAllInitialDeclarations(typeChecker, ancestor)), range: createTextRangeFromNode(entry.node, entry.node.getSourceFile()) }; } } } @@ -292,7 +305,17 @@ namespace ts.CallHierarchy { isAccessExpression(node) ? node : node.expression; const declaration = resolveCallHierarchyDeclaration(program, target); - if (declaration) callSites.push({ declaration, range: createTextRangeFromNode(target, node.getSourceFile()) }); + if (declaration) { + const range = createTextRangeFromNode(target, node.getSourceFile()); + if (isArray(declaration)) { + for (const decl of declaration) { + callSites.push({ declaration: decl, range }); + } + } + else { + callSites.push({ declaration, range }); + } + } } function collect(node: Node | undefined) { @@ -391,7 +414,7 @@ namespace ts.CallHierarchy { } function collectCallSitesOfFunctionLikeDeclaration(typeChecker: TypeChecker, node: FunctionLikeDeclaration, collect: (node: Node | undefined) => void) { - const implementation = findFunctionImplementation(typeChecker, node); + const implementation = findImplementation(typeChecker, node); if (implementation) { forEach(implementation.parameters, collect); collect(implementation.body); diff --git a/src/services/services.ts b/src/services/services.ts index d39862aea342e..16507f1cf387c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2131,23 +2131,23 @@ namespace ts { return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName); } - function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | undefined { + function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { synchronizeHostData(); - const declaration = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position)); - return declaration && CallHierarchy.createCallHierarchyItem(program, declaration); + const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position)); + return declarations && mapOneOrMany(declarations, declaration => CallHierarchy.createCallHierarchyItem(program, declaration)); } function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); - const declaration = CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position)); + const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : []; } function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); - const declaration = CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position)); + const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : []; } diff --git a/src/services/types.ts b/src/services/types.ts index b9736e2dc0bc0..bb953e5f3902d 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -351,7 +351,7 @@ namespace ts { getNavigationBarItems(fileName: string): NavigationBarItem[]; getNavigationTree(fileName: string): NavigationTree; - prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | undefined; + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined; provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index a60cb93d3c4fe..c7420608c719c 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2332,4 +2332,23 @@ namespace ts { export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan { return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition); } + + /** + * If the provided value is an array, the mapping function is applied to each element; otherwise, the mapping function is applied + * to the provided value itself. + */ + export function mapOneOrMany(valueOrArray: T | readonly T[], f: (x: T, i: number) => U): U | U[]; + export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U): U | U[] | undefined; + export function mapOneOrMany(valueOrArray: T | readonly T[], f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U; + export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U | undefined; + export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U | U[] = identity): U | U[] | undefined { + return valueOrArray ? isArray(valueOrArray) ? resultSelector(map(valueOrArray, f)) : f(valueOrArray, 0) : undefined; + } + + /** + * If the provided value is an array, the first element of the array is returned; otherwise, the provided value is returned instead. + */ + export function firstOrOnly(valueOrArray: T | readonly T[]): T { + return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray; + } } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 4d45556a61cca..d4343d9e19fcb 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5071,7 +5071,7 @@ declare namespace ts { getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; getNavigationBarItems(fileName: string): NavigationBarItem[]; getNavigationTree(fileName: string): NavigationTree; - prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | undefined; + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined; provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; getOutliningSpans(fileName: string): OutliningSpan[]; @@ -8340,7 +8340,7 @@ declare namespace ts.server.protocol { command: CommandTypes.PrepareCallHierarchy; } interface PrepareCallHierarchyResponse extends Response { - readonly body: CallHierarchyItem; + readonly body: CallHierarchyItem | CallHierarchyItem[]; } interface ProvideCallHierarchyIncomingCallsRequest extends FileLocationRequest { command: CommandTypes.ProvideCallHierarchyIncomingCalls; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index d037cd5e53b91..084dbb25f1a2b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5071,7 +5071,7 @@ declare namespace ts { getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; getNavigationBarItems(fileName: string): NavigationBarItem[]; getNavigationTree(fileName: string): NavigationTree; - prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | undefined; + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined; provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; getOutliningSpans(fileName: string): OutliningSpan[]; diff --git a/tests/baselines/reference/callHierarchyAccessor.callHierarchy.txt b/tests/baselines/reference/callHierarchyAccessor.callHierarchy.txt index a749fd1aabc49..0576190daf1f5 100644 --- a/tests/baselines/reference/callHierarchyAccessor.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyAccessor.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: bar ├ kind: getter +├ file: /tests/cases/fourslash/callHierarchyAccessor.ts ├ span: │ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:6:5-8:6 │ │ 6: get bar() { @@ -18,6 +19,7 @@ │ ╭ from: │ │ ╭ name: foo │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyAccessor.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:1:1-3:2 │ │ │ │ 1: function foo() { @@ -42,6 +44,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyAccessor.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyAccessor.ts:11:1-12:2 │ │ │ │ 11: function baz() { diff --git a/tests/baselines/reference/callHierarchyClass.callHierarchy.txt b/tests/baselines/reference/callHierarchyClass.callHierarchy.txt index 064a13547744c..e56084002c760 100644 --- a/tests/baselines/reference/callHierarchyClass.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyClass.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: bar ├ kind: function +├ file: /tests/cases/fourslash/callHierarchyClass.ts ├ span: │ ╭ /tests/cases/fourslash/callHierarchyClass.ts:5:1-7:2 │ │ 5: function bar() { @@ -18,6 +19,7 @@ │ ╭ from: │ │ ╭ name: foo │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyClass.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyClass.ts:1:1-3:2 │ │ │ │ 1: function foo() { @@ -42,6 +44,7 @@ │ ╭ to: │ │ ╭ name: Baz │ │ ├ kind: class +│ │ ├ file: /tests/cases/fourslash/callHierarchyClass.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyClass.ts:9:1-10:2 │ │ │ │ 9: class Baz { diff --git a/tests/baselines/reference/callHierarchyConstNamedArrowFunction.callHierarchy.txt b/tests/baselines/reference/callHierarchyConstNamedArrowFunction.callHierarchy.txt index 3f4ee72d8fe68..bcc7ab9ac8a78 100644 --- a/tests/baselines/reference/callHierarchyConstNamedArrowFunction.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyConstNamedArrowFunction.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: bar ├ kind: function +├ file: /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts ├ span: │ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:5:13-7:2 │ │ 5: const bar = () => { @@ -18,6 +19,7 @@ │ ╭ from: │ │ ╭ name: foo │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:1:1-3:2 │ │ │ │ 1: function foo() { @@ -42,6 +44,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts:9:1-10:2 │ │ │ │ 9: function baz() { diff --git a/tests/baselines/reference/callHierarchyConstNamedClassExpression.callHierarchy.txt b/tests/baselines/reference/callHierarchyConstNamedClassExpression.callHierarchy.txt index 040c64524dd91..ed4a25e1641e8 100644 --- a/tests/baselines/reference/callHierarchyConstNamedClassExpression.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyConstNamedClassExpression.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: Bar ├ kind: class +├ file: /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts ├ span: │ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:5:13-9:2 │ │ 5: const Bar = class { @@ -22,6 +23,7 @@ │ ╭ from: │ │ ╭ name: foo │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:1:1-3:2 │ │ │ │ 1: function foo() { @@ -46,6 +48,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts:11:1-12:2 │ │ │ │ 11: function baz() { diff --git a/tests/baselines/reference/callHierarchyConstNamedFunctionExpression.callHierarchy.txt b/tests/baselines/reference/callHierarchyConstNamedFunctionExpression.callHierarchy.txt index e453112f12227..6c92745d50d5d 100644 --- a/tests/baselines/reference/callHierarchyConstNamedFunctionExpression.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyConstNamedFunctionExpression.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: bar ├ kind: function +├ file: /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts ├ span: │ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:5:13-7:2 │ │ 5: const bar = function () { @@ -18,6 +19,7 @@ │ ╭ from: │ │ ╭ name: foo │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:1:1-3:2 │ │ │ │ 1: function foo() { @@ -42,6 +44,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts:9:1-10:2 │ │ │ │ 9: function baz() { diff --git a/tests/baselines/reference/callHierarchyDecorator.callHierarchy.txt b/tests/baselines/reference/callHierarchyDecorator.callHierarchy.txt index 733dcf9784764..698c9ab313b03 100644 --- a/tests/baselines/reference/callHierarchyDecorator.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyDecorator.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: bar ├ kind: function +├ file: /tests/cases/fourslash/callHierarchyDecorator.ts ├ span: │ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:5:1-7:2 │ │ 5: function bar() { @@ -18,6 +19,7 @@ │ ╭ from: │ │ ╭ name: Foo │ │ ├ kind: class +│ │ ├ file: /tests/cases/fourslash/callHierarchyDecorator.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:1:1-3:2 │ │ │ │ 1: @bar @@ -42,6 +44,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyDecorator.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyDecorator.ts:9:1-10:2 │ │ │ │ 9: function baz() { diff --git a/tests/baselines/reference/callHierarchyExportDefaultClass.callHierarchy.txt b/tests/baselines/reference/callHierarchyExportDefaultClass.callHierarchy.txt index f952a067f398f..9b1e7b9604c20 100644 --- a/tests/baselines/reference/callHierarchyExportDefaultClass.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyExportDefaultClass.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: default ├ kind: class +├ file: /tests/cases/fourslash/other.ts ├ span: │ ╭ /tests/cases/fourslash/other.ts:1:1-5:2 │ │ 1: export default class { @@ -22,6 +23,7 @@ │ ╭ from: │ │ ╭ name: foo │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/main.ts:3:1-5:2 │ │ │ │ 3: function foo() { @@ -46,6 +48,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/other.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/other.ts:7:1-8:2 │ │ │ │ 7: function baz() { diff --git a/tests/baselines/reference/callHierarchyExportDefaultFunction.callHierarchy.txt b/tests/baselines/reference/callHierarchyExportDefaultFunction.callHierarchy.txt index b43df437d584e..919b219e6e88e 100644 --- a/tests/baselines/reference/callHierarchyExportDefaultFunction.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyExportDefaultFunction.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: default ├ kind: function +├ file: /tests/cases/fourslash/other.ts ├ span: │ ╭ /tests/cases/fourslash/other.ts:1:1-3:2 │ │ 1: export default function () { @@ -18,6 +19,7 @@ │ ╭ from: │ │ ╭ name: foo │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/main.ts:3:1-5:2 │ │ │ │ 3: function foo() { @@ -42,6 +44,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/other.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/other.ts:5:1-6:2 │ │ │ │ 5: function baz() { diff --git a/tests/baselines/reference/callHierarchyExportEqualsFunction.callHierarchy.txt b/tests/baselines/reference/callHierarchyExportEqualsFunction.callHierarchy.txt index b78312bcb9355..427fc13ab1ec4 100644 --- a/tests/baselines/reference/callHierarchyExportEqualsFunction.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyExportEqualsFunction.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: /tests/cases/fourslash/other.ts ├ kind: module +├ file: /tests/cases/fourslash/other.ts ├ span: │ ╭ /tests/cases/fourslash/other.ts:1:1-6:2 │ │ 1: export = function () { @@ -25,6 +26,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/other.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/other.ts:5:1-6:2 │ │ │ │ 5: function baz() { diff --git a/tests/baselines/reference/callHierarchyFile.callHierarchy.txt b/tests/baselines/reference/callHierarchyFile.callHierarchy.txt index 1a993c810829d..acb1046489236 100644 --- a/tests/baselines/reference/callHierarchyFile.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyFile.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: foo ├ kind: function +├ file: /tests/cases/fourslash/callHierarchyFile.ts ├ span: │ ╭ /tests/cases/fourslash/callHierarchyFile.ts:2:1-3:2 │ │ 2: function foo() { @@ -16,6 +17,7 @@ │ ╭ from: │ │ ╭ name: /tests/cases/fourslash/callHierarchyFile.ts │ │ ├ kind: script +│ │ ├ file: /tests/cases/fourslash/callHierarchyFile.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyFile.ts:1:1-3:2 │ │ │ │ 1: foo(); diff --git a/tests/baselines/reference/callHierarchyFunction.callHierarchy.txt b/tests/baselines/reference/callHierarchyFunction.callHierarchy.txt index f659c886031f6..475737801887e 100644 --- a/tests/baselines/reference/callHierarchyFunction.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyFunction.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: bar ├ kind: function +├ file: /tests/cases/fourslash/callHierarchyFunction.ts ├ span: │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:5:1-9:2 │ │ 5: function bar() { @@ -22,6 +23,7 @@ │ ╭ from: │ │ ╭ name: foo │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyFunction.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:1:1-3:2 │ │ │ │ 1: function foo() { @@ -46,6 +48,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyFunction.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:11:1-12:2 │ │ │ │ 11: function baz() { @@ -71,6 +74,7 @@ │ ╭ to: │ │ ╭ name: quxx │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyFunction.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyFunction.ts:14:1-15:2 │ │ │ │ 14: function quxx() { diff --git a/tests/baselines/reference/callHierarchyFunctionAmbiguity.1.callHierarchy.txt b/tests/baselines/reference/callHierarchyFunctionAmbiguity.1.callHierarchy.txt new file mode 100644 index 0000000000000..438218d45bf19 --- /dev/null +++ b/tests/baselines/reference/callHierarchyFunctionAmbiguity.1.callHierarchy.txt @@ -0,0 +1,78 @@ +╭ name: foo +├ kind: function +├ file: /tests/cases/fourslash/a.d.ts +├ span: +│ ╭ /tests/cases/fourslash/a.d.ts:1:1-1:40 +│ │ 1: declare function foo(x?: number): void; +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/a.d.ts:1:18-1:21 +│ │ 1: declare function foo(x?: number): void; +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: bar +│ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.ts +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:1-3:2 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: foo(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:10-1:13 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:2:5-2:8 +│ │ │ 2: foo(); +│ │ │ ^^^ +│ ╰ ╰ +╰ outgoing: none +╭ name: foo +├ kind: function +├ file: /tests/cases/fourslash/b.d.ts +├ span: +│ ╭ /tests/cases/fourslash/b.d.ts:1:1-1:40 +│ │ 1: declare function foo(x?: string): void; +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/b.d.ts:1:18-1:21 +│ │ 1: declare function foo(x?: string): void; +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: bar +│ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.ts +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:1-3:2 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: foo(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:10-1:13 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:2:5-2:8 +│ │ │ 2: foo(); +│ │ │ ^^^ +│ ╰ ╰ +╰ outgoing: none diff --git a/tests/baselines/reference/callHierarchyFunctionAmbiguity.2.callHierarchy.txt b/tests/baselines/reference/callHierarchyFunctionAmbiguity.2.callHierarchy.txt new file mode 100644 index 0000000000000..438218d45bf19 --- /dev/null +++ b/tests/baselines/reference/callHierarchyFunctionAmbiguity.2.callHierarchy.txt @@ -0,0 +1,78 @@ +╭ name: foo +├ kind: function +├ file: /tests/cases/fourslash/a.d.ts +├ span: +│ ╭ /tests/cases/fourslash/a.d.ts:1:1-1:40 +│ │ 1: declare function foo(x?: number): void; +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/a.d.ts:1:18-1:21 +│ │ 1: declare function foo(x?: number): void; +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: bar +│ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.ts +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:1-3:2 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: foo(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:10-1:13 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:2:5-2:8 +│ │ │ 2: foo(); +│ │ │ ^^^ +│ ╰ ╰ +╰ outgoing: none +╭ name: foo +├ kind: function +├ file: /tests/cases/fourslash/b.d.ts +├ span: +│ ╭ /tests/cases/fourslash/b.d.ts:1:1-1:40 +│ │ 1: declare function foo(x?: string): void; +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/b.d.ts:1:18-1:21 +│ │ 1: declare function foo(x?: string): void; +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: bar +│ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.ts +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:1-3:2 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: foo(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:10-1:13 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:2:5-2:8 +│ │ │ 2: foo(); +│ │ │ ^^^ +│ ╰ ╰ +╰ outgoing: none diff --git a/tests/baselines/reference/callHierarchyFunctionAmbiguity.3.callHierarchy.txt b/tests/baselines/reference/callHierarchyFunctionAmbiguity.3.callHierarchy.txt new file mode 100644 index 0000000000000..438218d45bf19 --- /dev/null +++ b/tests/baselines/reference/callHierarchyFunctionAmbiguity.3.callHierarchy.txt @@ -0,0 +1,78 @@ +╭ name: foo +├ kind: function +├ file: /tests/cases/fourslash/a.d.ts +├ span: +│ ╭ /tests/cases/fourslash/a.d.ts:1:1-1:40 +│ │ 1: declare function foo(x?: number): void; +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/a.d.ts:1:18-1:21 +│ │ 1: declare function foo(x?: number): void; +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: bar +│ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.ts +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:1-3:2 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: foo(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:10-1:13 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:2:5-2:8 +│ │ │ 2: foo(); +│ │ │ ^^^ +│ ╰ ╰ +╰ outgoing: none +╭ name: foo +├ kind: function +├ file: /tests/cases/fourslash/b.d.ts +├ span: +│ ╭ /tests/cases/fourslash/b.d.ts:1:1-1:40 +│ │ 1: declare function foo(x?: string): void; +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/b.d.ts:1:18-1:21 +│ │ 1: declare function foo(x?: string): void; +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: bar +│ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.ts +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:1-3:2 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: foo(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:10-1:13 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:2:5-2:8 +│ │ │ 2: foo(); +│ │ │ ^^^ +│ ╰ ╰ +╰ outgoing: none diff --git a/tests/baselines/reference/callHierarchyFunctionAmbiguity.4.callHierarchy.txt b/tests/baselines/reference/callHierarchyFunctionAmbiguity.4.callHierarchy.txt new file mode 100644 index 0000000000000..438218d45bf19 --- /dev/null +++ b/tests/baselines/reference/callHierarchyFunctionAmbiguity.4.callHierarchy.txt @@ -0,0 +1,78 @@ +╭ name: foo +├ kind: function +├ file: /tests/cases/fourslash/a.d.ts +├ span: +│ ╭ /tests/cases/fourslash/a.d.ts:1:1-1:40 +│ │ 1: declare function foo(x?: number): void; +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/a.d.ts:1:18-1:21 +│ │ 1: declare function foo(x?: number): void; +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: bar +│ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.ts +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:1-3:2 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: foo(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:10-1:13 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:2:5-2:8 +│ │ │ 2: foo(); +│ │ │ ^^^ +│ ╰ ╰ +╰ outgoing: none +╭ name: foo +├ kind: function +├ file: /tests/cases/fourslash/b.d.ts +├ span: +│ ╭ /tests/cases/fourslash/b.d.ts:1:1-1:40 +│ │ 1: declare function foo(x?: string): void; +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/b.d.ts:1:18-1:21 +│ │ 1: declare function foo(x?: string): void; +│ │ ^^^ +│ ╰ +├ incoming: +│ ╭ from: +│ │ ╭ name: bar +│ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.ts +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:1-3:2 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^^^^^^^^^^^^^^ +│ │ │ │ 2: foo(); +│ │ │ │ ^^^^^^^^^^ +│ │ │ │ 3: } +│ │ │ │ ^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/main.ts:1:10-1:13 +│ │ │ │ 1: function bar() { +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ incoming: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:2:5-2:8 +│ │ │ 2: foo(); +│ │ │ ^^^ +│ ╰ ╰ +╰ outgoing: none diff --git a/tests/baselines/reference/callHierarchyFunctionAmbiguity.5.callHierarchy.txt b/tests/baselines/reference/callHierarchyFunctionAmbiguity.5.callHierarchy.txt new file mode 100644 index 0000000000000..7acaa23e04404 --- /dev/null +++ b/tests/baselines/reference/callHierarchyFunctionAmbiguity.5.callHierarchy.txt @@ -0,0 +1,59 @@ +╭ name: bar +├ kind: function +├ file: /tests/cases/fourslash/main.ts +├ span: +│ ╭ /tests/cases/fourslash/main.ts:1:1-3:2 +│ │ 1: function bar() { +│ │ ^^^^^^^^^^^^^^^^ +│ │ 2: foo(); +│ │ ^^^^^^^^^^ +│ │ 3: } +│ │ ^ +│ ╰ +├ selectionSpan: +│ ╭ /tests/cases/fourslash/main.ts:1:10-1:13 +│ │ 1: function bar() { +│ │ ^^^ +│ ╰ +├ incoming: none +├ outgoing: +│ ╭ to: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/a.d.ts +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/a.d.ts:1:1-1:40 +│ │ │ │ 1: declare function foo(x?: number): void; +│ │ │ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/a.d.ts:1:18-1:21 +│ │ │ │ 1: declare function foo(x?: number): void; +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:2:5-2:8 +│ │ │ 2: foo(); +│ │ │ ^^^ +│ ╰ ╰ +│ ╭ to: +│ │ ╭ name: foo +│ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/b.d.ts +│ │ ├ span: +│ │ │ ╭ /tests/cases/fourslash/b.d.ts:1:1-1:40 +│ │ │ │ 1: declare function foo(x?: string): void; +│ │ │ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ │ │ ╰ +│ │ ├ selectionSpan: +│ │ │ ╭ /tests/cases/fourslash/b.d.ts:1:18-1:21 +│ │ │ │ 1: declare function foo(x?: string): void; +│ │ │ │ ^^^ +│ │ │ ╰ +│ │ ╰ outgoing: none +│ ├ fromSpans: +│ │ ╭ /tests/cases/fourslash/main.ts:2:5-2:8 +│ │ │ 2: foo(); +│ │ │ ^^^ +╰ ╰ ╰ diff --git a/tests/baselines/reference/callHierarchyInterfaceMethod.callHierarchy.txt b/tests/baselines/reference/callHierarchyInterfaceMethod.callHierarchy.txt index e02d7b4f185a0..c0b5e0d1a66e5 100644 --- a/tests/baselines/reference/callHierarchyInterfaceMethod.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyInterfaceMethod.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: foo ├ kind: method +├ file: /tests/cases/fourslash/callHierarchyInterfaceMethod.ts ├ span: │ ╭ /tests/cases/fourslash/callHierarchyInterfaceMethod.ts:2:5-2:17 │ │ 2: foo(): void; @@ -14,6 +15,7 @@ │ ╭ from: │ │ ╭ name: /tests/cases/fourslash/callHierarchyInterfaceMethod.ts │ │ ├ kind: script +│ │ ├ file: /tests/cases/fourslash/callHierarchyInterfaceMethod.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyInterfaceMethod.ts:1:1-7:11 │ │ │ │ 1: interface I { diff --git a/tests/baselines/reference/callHierarchyJsxElement.callHierarchy.txt b/tests/baselines/reference/callHierarchyJsxElement.callHierarchy.txt index 65d0d2a68cf9d..1ef619697f894 100644 --- a/tests/baselines/reference/callHierarchyJsxElement.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyJsxElement.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: Bar ├ kind: function +├ file: /tests/cases/fourslash/main.tsx ├ span: │ ╭ /tests/cases/fourslash/main.tsx:5:1-7:2 │ │ 5: function Bar() { @@ -18,6 +19,7 @@ │ ╭ from: │ │ ╭ name: foo │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.tsx │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/main.tsx:1:1-3:2 │ │ │ │ 1: function foo() { @@ -42,6 +44,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/main.tsx │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/main.tsx:9:1-10:2 │ │ │ │ 9: function baz() { diff --git a/tests/baselines/reference/callHierarchyTaggedTemplate.callHierarchy.txt b/tests/baselines/reference/callHierarchyTaggedTemplate.callHierarchy.txt index d0b341d6df14d..47ef606473b2b 100644 --- a/tests/baselines/reference/callHierarchyTaggedTemplate.callHierarchy.txt +++ b/tests/baselines/reference/callHierarchyTaggedTemplate.callHierarchy.txt @@ -1,5 +1,6 @@ ╭ name: bar ├ kind: function +├ file: /tests/cases/fourslash/callHierarchyTaggedTemplate.ts ├ span: │ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:5:1-7:2 │ │ 5: function bar(array: TemplateStringsArray, ...args: any[]) { @@ -18,6 +19,7 @@ │ ╭ from: │ │ ╭ name: foo │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyTaggedTemplate.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:1:1-3:2 │ │ │ │ 1: function foo() { @@ -42,6 +44,7 @@ │ ╭ to: │ │ ╭ name: baz │ │ ├ kind: function +│ │ ├ file: /tests/cases/fourslash/callHierarchyTaggedTemplate.ts │ │ ├ span: │ │ │ ╭ /tests/cases/fourslash/callHierarchyTaggedTemplate.ts:9:1-10:2 │ │ │ │ 9: function baz() { diff --git a/tests/cases/fourslash/callHierarchyFunctionAmbiguity.1.ts b/tests/cases/fourslash/callHierarchyFunctionAmbiguity.1.ts new file mode 100644 index 0000000000000..ae567b3115242 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyFunctionAmbiguity.1.ts @@ -0,0 +1,16 @@ +/// + +// @filename: a.d.ts +//// declare function foo(x?: number): void; + +// @filename: b.d.ts +//// declare function foo(x?: string): void; +//// declare function foo(x?: boolean): void; + +// @filename: main.ts +//// function bar() { +//// /**/foo(); +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyFunctionAmbiguity.2.ts b/tests/cases/fourslash/callHierarchyFunctionAmbiguity.2.ts new file mode 100644 index 0000000000000..1d1476c0c8a63 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyFunctionAmbiguity.2.ts @@ -0,0 +1,16 @@ +/// + +// @filename: a.d.ts +//// declare function /**/foo(x?: number): void; + +// @filename: b.d.ts +//// declare function foo(x?: string): void; +//// declare function foo(x?: boolean): void; + +// @filename: main.ts +//// function bar() { +//// foo(); +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyFunctionAmbiguity.3.ts b/tests/cases/fourslash/callHierarchyFunctionAmbiguity.3.ts new file mode 100644 index 0000000000000..425e2a2ac00b8 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyFunctionAmbiguity.3.ts @@ -0,0 +1,16 @@ +/// + +// @filename: a.d.ts +//// declare function foo(x?: number): void; + +// @filename: b.d.ts +//// declare function /**/foo(x?: string): void; +//// declare function foo(x?: boolean): void; + +// @filename: main.ts +//// function bar() { +//// foo(); +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyFunctionAmbiguity.4.ts b/tests/cases/fourslash/callHierarchyFunctionAmbiguity.4.ts new file mode 100644 index 0000000000000..a960a146519d9 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyFunctionAmbiguity.4.ts @@ -0,0 +1,16 @@ +/// + +// @filename: a.d.ts +//// declare function foo(x?: number): void; + +// @filename: b.d.ts +//// declare function foo(x?: string): void; +//// declare function /**/foo(x?: boolean): void; + +// @filename: main.ts +//// function bar() { +//// foo(); +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyFunctionAmbiguity.5.ts b/tests/cases/fourslash/callHierarchyFunctionAmbiguity.5.ts new file mode 100644 index 0000000000000..e0c3b9a12130e --- /dev/null +++ b/tests/cases/fourslash/callHierarchyFunctionAmbiguity.5.ts @@ -0,0 +1,16 @@ +/// + +// @filename: a.d.ts +//// declare function foo(x?: number): void; + +// @filename: b.d.ts +//// declare function foo(x?: string): void; +//// declare function foo(x?: boolean): void; + +// @filename: main.ts +//// function /**/bar() { +//// foo(); +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 98a1919ad0bdf..879b229c97309 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -383,9 +383,6 @@ declare namespace FourSlashInterface { readonly preferences?: UserPreferences; }): void; baselineCallHierarchy(): void; - callHierarchy(options: false | Range | VerifyCallHierarchyOptions): void; - callHierarchyIncomingCalls(options: Sequence): void; - callHierarchyOutgoingCalls(options: Sequence): void; moveToNewFile(options: { readonly newFileContents: { readonly [fileName: string]: string }; readonly preferences?: UserPreferences; @@ -723,32 +720,6 @@ declare namespace FourSlashInterface { readonly providePrefixAndSuffixTextForRename?: boolean; }; type RenameLocationOptions = Range | { readonly range: Range, readonly prefixText?: string, readonly suffixText?: string }; - - type Sequence = - | false // Indicates the actual result must be undefined or contain no elements - | readonly T[] // Indicates the actual result must at least contain the specified elements, in any order - | { - readonly exact?: boolean; // Indicates the actual result must contain all of the elements in `values` (no more and no fewer). - readonly values: readonly T[]; - }; - - interface VerifyCallHierarchyOptions { - readonly range?: Range; - readonly kind?: string; - readonly selectionRange?: Range; - readonly incoming?: Sequence; - readonly outgoing?: Sequence; - } - - interface VerifyCallHierarchyIncomingCallOptions { - readonly from: VerifyCallHierarchyOptions | Range; - readonly fromRanges?: Sequence; - } - - interface VerifyCallHierarchyOutgoingCallOptions { - readonly to: VerifyCallHierarchyOptions | Range; - readonly fromRanges?: Sequence; - } } declare function verifyOperationIsCancelled(f: any): void; declare var test: FourSlashInterface.test_; @@ -799,10 +770,4 @@ declare namespace completion { export const statementKeywordsWithTypes: ReadonlyArray; export const statementKeywords: ReadonlyArray; export const statementInJsKeywords: ReadonlyArray; -} -declare namespace sequence { - export function atLeast(array: readonly T[]): FourSlashInterface.Sequence; - export function exact(array: readonly T[]): FourSlashInterface.Sequence; - export function one(value: T): FourSlashInterface.Sequence; - export function none(): FourSlashInterface.Sequence; } \ No newline at end of file From b51fb318891563364d8b24189eab623c7bf15569 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 18 Dec 2019 16:43:42 -0800 Subject: [PATCH 5/6] Use optional chaining in a few places --- src/services/utilities.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index c7420608c719c..425bdafdcd7f5 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -267,11 +267,11 @@ namespace ts { } export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } { - return node.kind === SyntaxKind.Identifier && isBreakOrContinueStatement(node.parent) && node.parent.label === node; + return isIdentifier(node) && tryCast(node.parent, isBreakOrContinueStatement)?.label === node; } export function isLabelOfLabeledStatement(node: Node): node is Identifier { - return node.kind === SyntaxKind.Identifier && isLabeledStatement(node.parent) && node.parent.label === node; + return isIdentifier(node) && tryCast(node.parent, isLabeledStatement)?.label === node; } export function isLabelName(node: Node): boolean { @@ -279,28 +279,27 @@ namespace ts { } export function isTagName(node: Node): boolean { - return isJSDocTag(node.parent) && node.parent.tagName === node; + return tryCast(node.parent, isJSDocTag)?.tagName === node; } export function isRightSideOfQualifiedName(node: Node) { - return node.parent.kind === SyntaxKind.QualifiedName && (node.parent).right === node; + return tryCast(node.parent, isQualifiedName)?.right === node; } export function isRightSideOfPropertyAccess(node: Node) { - return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).name === node; + return tryCast(node.parent, isPropertyAccessExpression)?.name === node; } export function isArgumentExpressionOfElementAccess(node: Node) { - return node && node.parent && node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent).argumentExpression === node; + return tryCast(node.parent, isElementAccessExpression)?.argumentExpression === node; } export function isNameOfModuleDeclaration(node: Node) { - return node.parent.kind === SyntaxKind.ModuleDeclaration && (node.parent).name === node; + return tryCast(node.parent, isModuleDeclaration)?.name === node; } export function isNameOfFunctionDeclaration(node: Node): boolean { - return node.kind === SyntaxKind.Identifier && - isFunctionLike(node.parent) && (node.parent).name === node; + return isIdentifier(node) && tryCast(node.parent, isFunctionLike)?.name === node; } export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral | NoSubstitutionTemplateLiteral): boolean { From c01d40af985c0acda3cb40ae636893112a08b541 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 20 Dec 2019 18:37:02 -0800 Subject: [PATCH 6/6] Use getFileAndProject --- src/server/session.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index b7a924e1d59da..0d81e6826b939 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2172,35 +2172,35 @@ namespace ts.server { } private prepareCallHierarchy(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyItem | protocol.CallHierarchyItem[] | undefined { - const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const { file, project } = this.getFileAndProject(args); const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); if (scriptInfo) { const position = this.getPosition(args, scriptInfo); - const result = languageService.prepareCallHierarchy(file, position); + const result = project.getLanguageService().prepareCallHierarchy(file, position); return result && mapOneOrMany(result, item => this.toProtocolCallHierarchyItem(item, scriptInfo)); } return undefined; } private provideCallHierarchyIncomingCalls(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyIncomingCall[] { - const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const { file, project } = this.getFileAndProject(args); const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); if (!scriptInfo) { this.projectService.logErrorForScriptInfoNotFound(file); return Errors.ThrowNoProject(); } - const incomingCalls = languageService.provideCallHierarchyIncomingCalls(file, this.getPosition(args, scriptInfo)); + const incomingCalls = project.getLanguageService().provideCallHierarchyIncomingCalls(file, this.getPosition(args, scriptInfo)); return incomingCalls.map(call => this.toProtocolCallHierarchyIncomingCall(call, scriptInfo)); } private provideCallHierarchyOutgoingCalls(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyOutgoingCall[] { - const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const { file, project } = this.getFileAndProject(args); const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); if (!scriptInfo) { this.projectService.logErrorForScriptInfoNotFound(file); return Errors.ThrowNoProject(); } - const outgoingCalls = languageService.provideCallHierarchyOutgoingCalls(file, this.getPosition(args, scriptInfo)); + const outgoingCalls = project.getLanguageService().provideCallHierarchyOutgoingCalls(file, this.getPosition(args, scriptInfo)); return outgoingCalls.map(call => this.toProtocolCallHierarchyOutgoingCall(call, scriptInfo)); }