diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 7a3fcac973aac..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; } @@ -1245,8 +1253,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 78b454b565be5..63ad8b90da83c 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -743,6 +743,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 | CallHierarchyItem[] | undefined { + const args = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); + const response = this.processResponse(request); + return response.body && mapOneOrMany(response.body, item => this.convertCallHierarchyItem(item)); + } + + 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/fourslashImpl.ts b/src/harness/fourslashImpl.ts index f413341dcc721..e04f2037c6914 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; @@ -730,11 +736,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"); } } @@ -1407,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; @@ -1697,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 { @@ -3133,6 +3209,131 @@ 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}├ file: ${callHierarchyItem.file}\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]; + text += `${prefix}│ ╭ to:\n`; + 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}│ ╰ ` : + `${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 ? ts.mapOneOrMany(callHierarchyItem, item => this.formatCallHierarchy(item), result => result.join("")) : "none"; + Harness.Baseline.runBaseline(baselineFile, text); + } + + 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 }); @@ -3212,8 +3413,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}`; } @@ -3266,6 +3467,14 @@ namespace FourSlash { } } + function prefixMessage(message: string | undefined) { + return message ? `${message} - ` : ""; + } + + function textSpanEqualsRange(span: ts.TextSpan, range: Range) { + return span.start === range.pos && span.length === range.end - range.pos; + } + 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); diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 5e7ba4cd844a1..3ecd0005361db 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -537,9 +537,14 @@ namespace FourSlashInterface { this.state.getEditsForFileRename(options); } + public baselineCallHierarchy() { + this.state.baselineCallHierarchy(); + } + public moveToNewFile(options: MoveToNewFileOptions): void { this.state.moveToNewFile(options); } + public noMoveToNewFile(): void { this.state.noMoveToNewFile(); } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index c3ba14669bf83..759384aff3875 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 00fce8b14c054..cabe7dcd55dd1 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`. } /** @@ -2983,6 +2987,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 | 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 a1690aee1802b..0d81e6826b939 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1060,7 +1060,7 @@ namespace ts.server { if (simplifiedResult) { return { definitions: this.mapDefinitionInfo(definitions, project), - textSpan: toProcolTextSpan(textSpan, scriptInfo) + textSpan: toProtocolTextSpan(textSpan, scriptInfo) }; } @@ -1315,7 +1315,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; @@ -1415,8 +1415,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 @@ -1605,7 +1605,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 }; } @@ -1775,7 +1775,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 })); @@ -1796,8 +1796,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)) }; } @@ -2059,7 +2059,7 @@ namespace ts.server { return !spans ? undefined : simplifiedResult - ? spans.map(span => toProcolTextSpan(span, scriptInfo)) + ? spans.map(span => toProtocolTextSpan(span, scriptInfo)) : spans; } @@ -2131,7 +2131,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); @@ -2139,6 +2139,71 @@ 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 | protocol.CallHierarchyItem[] | undefined { + const { file, project } = this.getFileAndProject(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); + if (scriptInfo) { + const position = this.getPosition(args, scriptInfo); + 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, project } = this.getFileAndProject(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); + if (!scriptInfo) { + this.projectService.logErrorForScriptInfoNotFound(file); + return Errors.ThrowNoProject(); + } + 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, project } = this.getFileAndProject(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); + if (!scriptInfo) { + this.projectService.logErrorForScriptInfoNotFound(file); + return Errors.ThrowNoProject(); + } + const outgoingCalls = project.getLanguageService().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); @@ -2504,6 +2569,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) { @@ -2627,7 +2701,7 @@ namespace ts.server { readonly project: Project; } - function toProcolTextSpan(textSpan: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan { + function toProtocolTextSpan(textSpan: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan { return { start: scriptInfo.positionToLineOffset(textSpan.start), end: scriptInfo.positionToLineOffset(textSpanEnd(textSpan)) @@ -2635,8 +2709,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..838f9355d3dca --- /dev/null +++ b/src/services/callHierarchy.ts @@ -0,0 +1,485 @@ +/* @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 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; + } + if (isConstructorDeclaration(node)) { + return getFirstConstructorWithBody(node.parent); + } + if (isFunctionDeclaration(node) || isMethodDeclaration(node)) { + const symbol = getSymbolOfCallHierarchyDeclaration(typeChecker, node); + if (symbol && symbol.valueDeclaration && isFunctionLikeDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.body) { + return symbol.valueDeclaration; + } + return undefined; + } + return node; + } + + function findAllInitialDeclarations(typeChecker: TypeChecker, node: CallHierarchyDeclaration) { + const symbol = getSymbolOfCallHierarchyDeclaration(typeChecker, node); + let declarations: CallHierarchyDeclaration[] | undefined; + if (symbol && symbol.declarations) { + 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 findImplementationOrAllInitialDeclarations(typeChecker: TypeChecker, node: CallHierarchyDeclaration): CallHierarchyDeclaration | CallHierarchyDeclaration[] { + if (isFunctionLikeDeclaration(node)) { + return findImplementation(typeChecker, node) ?? + findAllInitialDeclarations(typeChecker, node) ?? + node; + } + return findAllInitialDeclarations(typeChecker, node) ?? node; + } + + /** Resolves the call hierarchy declaration for a node. */ + 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) + // - 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 findImplementationOrAllInitialDeclarations(typeChecker, location); + } + if (isPossibleCallHierarchyDeclaration(location)) { + const ancestor = findAncestor(location, isValidCallHierarchyDeclaration); + return ancestor && findImplementationOrAllInitialDeclarations(typeChecker, ancestor); + } + if (isDeclarationName(location)) { + if (isValidCallHierarchyDeclaration(location.parent)) { + return findImplementationOrAllInitialDeclarations(typeChecker, location.parent); + } + if (isPossibleCallHierarchyDeclaration(location.parent)) { + const ancestor = findAncestor(location.parent, isValidCallHierarchyDeclaration); + return ancestor && findImplementationOrAllInitialDeclarations(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: firstOrOnly(findImplementationOrAllInitialDeclarations(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) { + 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) { + 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 = findImplementation(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 72f1eed4ef88f..16507f1cf387c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2131,6 +2131,26 @@ namespace ts { return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName); } + function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { + synchronizeHostData(); + 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 = 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 = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); + return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : []; + } + return { dispose, cleanupSemanticCache, @@ -2186,6 +2206,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 66203e09996dd..58b655ef0a3ea 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 f3cf70f353acf..674b0076848d4 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 88ab07698bee0..bb953e5f3902d 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 | 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 5034fe0dc752e..425bdafdcd7f5 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -196,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) { @@ -236,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 { @@ -248,24 +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 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 { @@ -2297,4 +2331,23 @@ namespace ts { export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan { return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition); } -} \ No newline at end of file + + /** + * 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/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index 61ce6001c2d51..56ef9d77634d0 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -267,6 +267,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 00890b067f855..d4343d9e19fcb 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5071,6 +5071,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 | 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[]; @@ -5210,6 +5213,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; @@ -6071,6 +6089,9 @@ declare namespace ts.server.protocol { GetEditsForFileRename = "getEditsForFileRename", ConfigurePlugin = "configurePlugin", SelectionRange = "selectionRange", + PrepareCallHierarchy = "prepareCallHierarchy", + ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", + ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls" } /** * A TypeScript Server message @@ -8300,6 +8321,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 | 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", @@ -9391,6 +9445,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 89f8bb0543dea..084dbb25f1a2b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5071,6 +5071,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 | 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[]; @@ -5210,6 +5213,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/baselines/reference/callHierarchyAccessor.callHierarchy.txt b/tests/baselines/reference/callHierarchyAccessor.callHierarchy.txt new file mode 100644 index 0000000000000..0576190daf1f5 --- /dev/null +++ b/tests/baselines/reference/callHierarchyAccessor.callHierarchy.txt @@ -0,0 +1,65 @@ +╭ name: bar +├ kind: getter +├ file: /tests/cases/fourslash/callHierarchyAccessor.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyAccessor.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyAccessor.ts +│ │ ├ 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..e56084002c760 --- /dev/null +++ b/tests/baselines/reference/callHierarchyClass.callHierarchy.txt @@ -0,0 +1,65 @@ +╭ name: bar +├ kind: function +├ file: /tests/cases/fourslash/callHierarchyClass.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyClass.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyClass.ts +│ │ ├ 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..bcc7ab9ac8a78 --- /dev/null +++ b/tests/baselines/reference/callHierarchyConstNamedArrowFunction.callHierarchy.txt @@ -0,0 +1,65 @@ +╭ name: bar +├ kind: function +├ file: /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts +│ │ ├ 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..ed4a25e1641e8 --- /dev/null +++ b/tests/baselines/reference/callHierarchyConstNamedClassExpression.callHierarchy.txt @@ -0,0 +1,69 @@ +╭ name: Bar +├ kind: class +├ file: /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts +│ │ ├ 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..6c92745d50d5d --- /dev/null +++ b/tests/baselines/reference/callHierarchyConstNamedFunctionExpression.callHierarchy.txt @@ -0,0 +1,65 @@ +╭ name: bar +├ kind: function +├ file: /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts +│ │ ├ 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..698c9ab313b03 --- /dev/null +++ b/tests/baselines/reference/callHierarchyDecorator.callHierarchy.txt @@ -0,0 +1,65 @@ +╭ name: bar +├ kind: function +├ file: /tests/cases/fourslash/callHierarchyDecorator.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyDecorator.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyDecorator.ts +│ │ ├ 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..9b1e7b9604c20 --- /dev/null +++ b/tests/baselines/reference/callHierarchyExportDefaultClass.callHierarchy.txt @@ -0,0 +1,69 @@ +╭ name: default +├ kind: class +├ file: /tests/cases/fourslash/other.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/main.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/other.ts +│ │ ├ 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..919b219e6e88e --- /dev/null +++ b/tests/baselines/reference/callHierarchyExportDefaultFunction.callHierarchy.txt @@ -0,0 +1,65 @@ +╭ name: default +├ kind: function +├ file: /tests/cases/fourslash/other.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/main.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/other.ts +│ │ ├ 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..427fc13ab1ec4 --- /dev/null +++ b/tests/baselines/reference/callHierarchyExportEqualsFunction.callHierarchy.txt @@ -0,0 +1,47 @@ +╭ 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 () { +│ │ ^^^^^^^^^^^^^^^^^^^^^^ +│ │ 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 +│ │ ├ file: /tests/cases/fourslash/other.ts +│ │ ├ 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..acb1046489236 --- /dev/null +++ b/tests/baselines/reference/callHierarchyFile.callHierarchy.txt @@ -0,0 +1,41 @@ +╭ name: foo +├ kind: function +├ file: /tests/cases/fourslash/callHierarchyFile.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyFile.ts +│ │ ├ 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..475737801887e --- /dev/null +++ b/tests/baselines/reference/callHierarchyFunction.callHierarchy.txt @@ -0,0 +1,95 @@ +╭ name: bar +├ kind: function +├ file: /tests/cases/fourslash/callHierarchyFunction.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyFunction.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyFunction.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyFunction.ts +│ │ ├ 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/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 new file mode 100644 index 0000000000000..c0b5e0d1a66e5 --- /dev/null +++ b/tests/baselines/reference/callHierarchyInterfaceMethod.callHierarchy.txt @@ -0,0 +1,47 @@ +╭ name: foo +├ kind: method +├ file: /tests/cases/fourslash/callHierarchyInterfaceMethod.ts +├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyInterfaceMethod.ts +│ │ ├ 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..1ef619697f894 --- /dev/null +++ b/tests/baselines/reference/callHierarchyJsxElement.callHierarchy.txt @@ -0,0 +1,65 @@ +╭ name: Bar +├ kind: function +├ file: /tests/cases/fourslash/main.tsx +├ 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 +│ │ ├ file: /tests/cases/fourslash/main.tsx +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/main.tsx +│ │ ├ 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..47ef606473b2b --- /dev/null +++ b/tests/baselines/reference/callHierarchyTaggedTemplate.callHierarchy.txt @@ -0,0 +1,65 @@ +╭ 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[]) { +│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +│ │ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyTaggedTemplate.ts +│ │ ├ 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 +│ │ ├ file: /tests/cases/fourslash/callHierarchyTaggedTemplate.ts +│ │ ├ 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 new file mode 100644 index 0000000000000..3ab79569d1a33 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyAccessor.ts @@ -0,0 +1,17 @@ +/// + +//// function foo() { +//// new C().bar; +//// } +//// +//// class C { +//// get /**/bar() { +//// return baz(); +//// } +//// } +//// +//// function baz() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyClass.ts b/tests/cases/fourslash/callHierarchyClass.ts new file mode 100644 index 0000000000000..3b3fc7a7fae06 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyClass.ts @@ -0,0 +1,15 @@ +/// + +//// function foo() { +//// bar(); +//// } +//// +//// function /**/bar() { +//// new Baz(); +//// } +//// +//// class Baz { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts b/tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts new file mode 100644 index 0000000000000..cf4cd774c2539 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyConstNamedArrowFunction.ts @@ -0,0 +1,15 @@ +/// + +//// function foo() { +//// bar(); +//// } +//// +//// const /**/bar = () => { +//// baz(); +//// } +//// +//// function baz() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts b/tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts new file mode 100644 index 0000000000000..8f5dccc238b89 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyConstNamedClassExpression.ts @@ -0,0 +1,17 @@ +/// + +//// function foo() { +//// new Bar(); +//// } +//// +//// const /**/Bar = class { +//// constructor() { +//// baz(); +//// } +//// } +//// +//// function baz() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts b/tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts new file mode 100644 index 0000000000000..e8613dda8e17d --- /dev/null +++ b/tests/cases/fourslash/callHierarchyConstNamedFunctionExpression.ts @@ -0,0 +1,15 @@ +/// + +//// function foo() { +//// bar(); +//// } +//// +//// const /**/bar = function () { +//// baz(); +//// } +//// +//// function baz() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyDecorator.ts b/tests/cases/fourslash/callHierarchyDecorator.ts new file mode 100644 index 0000000000000..a14d4035a2243 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyDecorator.ts @@ -0,0 +1,16 @@ +/// + +// @experimentalDecorators: true +//// @bar +//// class Foo { +//// } +//// +//// function /**/bar() { +//// baz(); +//// } +//// +//// function baz() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyExportDefaultClass.ts b/tests/cases/fourslash/callHierarchyExportDefaultClass.ts new file mode 100644 index 0000000000000..19aef1dc462fd --- /dev/null +++ b/tests/cases/fourslash/callHierarchyExportDefaultClass.ts @@ -0,0 +1,21 @@ +/// + +// @filename: main.ts +//// import Bar from "./other"; +//// +//// function foo() { +//// new Bar(); +//// } + +// @filename: other.ts +//// export /**/default class { +//// constructor() { +//// baz(); +//// } +//// } +//// +//// function baz() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyExportDefaultFunction.ts b/tests/cases/fourslash/callHierarchyExportDefaultFunction.ts new file mode 100644 index 0000000000000..8a41f83683c2a --- /dev/null +++ b/tests/cases/fourslash/callHierarchyExportDefaultFunction.ts @@ -0,0 +1,19 @@ +/// + +// @filename: main.ts +//// import bar from "./other"; +//// +//// function foo() { +//// bar(); +//// } + +// @filename: other.ts +//// export /**/default function () { +//// baz(); +//// } +//// +//// function baz() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyExportEqualsFunction.ts b/tests/cases/fourslash/callHierarchyExportEqualsFunction.ts new file mode 100644 index 0000000000000..f2c6631909cf1 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyExportEqualsFunction.ts @@ -0,0 +1,20 @@ +/// + +// @filename: main.ts +//// import bar = require("./other"); +//// +//// function foo() { +//// bar(); +//// } + +// @filename: other.ts +//// export = /**/function () { +//// baz(); +//// } +//// +//// function baz() { +//// } + +// NOTE: exported function is unnamed, so we expand the item to the entire file... +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyFile.ts b/tests/cases/fourslash/callHierarchyFile.ts new file mode 100644 index 0000000000000..08d8fc0e92664 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyFile.ts @@ -0,0 +1,8 @@ +/// + +//// foo(); +//// function /**/foo() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyFunction.ts b/tests/cases/fourslash/callHierarchyFunction.ts new file mode 100644 index 0000000000000..72c0bed51e3c1 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyFunction.ts @@ -0,0 +1,20 @@ +/// + +//// function foo() { +//// bar(); +//// } +//// +//// function /**/bar() { +//// baz(); +//// quxx(); +//// baz(); +//// } +//// +//// function baz() { +//// } +//// +//// function quxx() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); 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/callHierarchyInterfaceMethod.ts b/tests/cases/fourslash/callHierarchyInterfaceMethod.ts new file mode 100644 index 0000000000000..7233c98c3e6a7 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyInterfaceMethod.ts @@ -0,0 +1,12 @@ +/// + +//// interface I { +//// /**/foo(): void; +//// } +//// +//// const obj: I = { foo() {} }; +//// +//// obj.foo(); + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyJsxElement.ts b/tests/cases/fourslash/callHierarchyJsxElement.ts new file mode 100644 index 0000000000000..67e63efefea52 --- /dev/null +++ b/tests/cases/fourslash/callHierarchyJsxElement.ts @@ -0,0 +1,17 @@ +/// + +// @jsx: preserve +// @filename: main.tsx +//// function foo() { +//// return ; +//// } +//// +//// function /**/Bar() { +//// baz(); +//// } +//// +//// function baz() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/callHierarchyTaggedTemplate.ts b/tests/cases/fourslash/callHierarchyTaggedTemplate.ts new file mode 100644 index 0000000000000..d6ad621ec7d0f --- /dev/null +++ b/tests/cases/fourslash/callHierarchyTaggedTemplate.ts @@ -0,0 +1,15 @@ +/// + +//// function foo() { +//// bar`a${1}b`; +//// } +//// +//// function /**/bar(array: TemplateStringsArray, ...args: any[]) { +//// baz(); +//// } +//// +//// function baz() { +//// } + +goTo.marker(); +verify.baselineCallHierarchy(); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 714883553cfcb..879b229c97309 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; moveToNewFile(options: { readonly newFileContents: { readonly [fileName: string]: string }; readonly preferences?: UserPreferences; @@ -769,4 +770,4 @@ declare namespace completion { export const statementKeywordsWithTypes: ReadonlyArray; export const statementKeywords: ReadonlyArray; export const statementInJsKeywords: ReadonlyArray; -} +} \ No newline at end of file