diff --git a/src/harness/client.ts b/src/harness/client.ts index 230517b599db9..b6385203969c3 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -362,6 +362,18 @@ namespace ts.server { })); } + getFileReferences(fileName: string): ReferenceEntry[] { + const request = this.processRequest(CommandNames.FileReferences, { file: fileName }); + const response = this.processResponse(request); + + return response.body!.refs.map(entry => ({ // TODO: GH#18217 + fileName: entry.file, + textSpan: this.decodeSpan(entry), + isWriteAccess: entry.isWriteAccess, + isDefinition: entry.isDefinition, + })); + } + getEmitOutput(file: string): EmitOutput { const request = this.processRequest(protocol.CommandTypes.EmitOutput, { file }); const response = this.processResponse(request); diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 4b285a749aa74..a14c75bf3cc73 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -1114,38 +1114,77 @@ namespace FourSlash { } } - public verifyBaselineFindAllReferences(markerName: string) { - const marker = this.getMarkerByName(markerName); - const references = this.languageService.findReferences(marker.fileName, marker.position); + public verifyBaselineFindAllReferences(...markerNames: string[]) { + const baseline = markerNames.map(markerName => { + const marker = this.getMarkerByName(markerName); + const references = this.languageService.findReferences(marker.fileName, marker.position); + const refsByFile = references + ? ts.group(ts.sort(ts.flatMap(references, r => r.references), (a, b) => a.textSpan.start - b.textSpan.start), ref => ref.fileName) + : ts.emptyArray; + + // Write input files + const baselineContent = this.getBaselineContentForGroupedReferences(refsByFile, markerName); + + // Write response JSON + return baselineContent + JSON.stringify(references, undefined, 2); + }).join("\n\n"); + Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baseline); + } + + public verifyBaselineGetFileReferences(fileName: string) { + const references = this.languageService.getFileReferences(fileName); const refsByFile = references - ? ts.group(ts.sort(ts.flatMap(references, r => r.references), (a, b) => a.textSpan.start - b.textSpan.start), ref => ref.fileName) + ? ts.group(ts.sort(references, (a, b) => a.textSpan.start - b.textSpan.start), ref => ref.fileName) : ts.emptyArray; // Write input files + let baselineContent = this.getBaselineContentForGroupedReferences(refsByFile); + + // Write response JSON + baselineContent += JSON.stringify(references, undefined, 2); + Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baselineContent); + } + + private getBaselineContentForGroupedReferences(refsByFile: readonly (readonly ts.ReferenceEntry[])[], markerName?: string) { + const marker = markerName !== undefined ? this.getMarkerByName(markerName) : undefined; let baselineContent = ""; for (const group of refsByFile) { baselineContent += getBaselineContentForFile(group[0].fileName, this.getFileContent(group[0].fileName)); baselineContent += "\n\n"; } - - // Write response JSON - baselineContent += JSON.stringify(references, undefined, 2); - Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baselineContent); + return baselineContent; function getBaselineContentForFile(fileName: string, content: string) { let newContent = `=== ${fileName} ===\n`; let pos = 0; for (const { textSpan } of refsByFile.find(refs => refs[0].fileName === fileName) ?? ts.emptyArray) { - if (fileName === marker.fileName && ts.textSpanContainsPosition(textSpan, marker.position)) { - newContent += "/*FIND ALL REFS*/"; - } const end = textSpan.start + textSpan.length; newContent += content.slice(pos, textSpan.start); - newContent += "[|"; - newContent += content.slice(textSpan.start, end); + pos = textSpan.start; + // It's easier to read if the /*FIND ALL REFS*/ comment is outside the range markers, which makes + // this code a bit more verbose than it would be if I were less picky about the baseline format. + if (fileName === marker?.fileName && marker.position === textSpan.start) { + newContent += "/*FIND ALL REFS*/"; + newContent += "[|"; + } + else if (fileName === marker?.fileName && ts.textSpanContainsPosition(textSpan, marker.position)) { + newContent += "[|"; + newContent += content.slice(pos, marker.position); + newContent += "/*FIND ALL REFS*/"; + pos = marker.position; + } + else { + newContent += "[|"; + } + newContent += content.slice(pos, end); newContent += "|]"; pos = end; } + if (marker?.fileName === fileName && marker.position >= pos) { + newContent += content.slice(pos, marker.position); + newContent += "/*FIND ALL REFS*/"; + pos = marker.position; + } newContent += content.slice(pos); return newContent.split(/\r?\n/).map(l => "// " + l).join("\n"); } diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index e96641c2dbb88..6fd2d834039d2 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -332,8 +332,12 @@ namespace FourSlashInterface { this.state.verifyTypeOfSymbolAtLocation(range, symbol, expected); } - public baselineFindAllReferences(markerName: string) { - this.state.verifyBaselineFindAllReferences(markerName); + public baselineFindAllReferences(...markerNames: string[]) { + this.state.verifyBaselineFindAllReferences(...markerNames); + } + + public baselineGetFileReferences(fileName: string) { + this.state.verifyBaselineGetFileReferences(fileName); } public referenceGroups(starts: ArrayOrSingle | ArrayOrSingle, parts: ReferenceGroup[]) { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index cc2f285fc5c8f..85164d58bb122 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -519,6 +519,9 @@ namespace Harness.LanguageService { findReferences(fileName: string, position: number): ts.ReferencedSymbol[] { return unwrapJSONCallResult(this.shim.findReferences(fileName, position)); } + getFileReferences(fileName: string): ts.ReferenceEntry[] { + return unwrapJSONCallResult(this.shim.getFileReferences(fileName)); + } getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { return unwrapJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position)); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 1f77bbfd7eb45..ad8926470a5b0 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -37,6 +37,9 @@ namespace ts.server.protocol { /* @internal */ EmitOutput = "emit-output", Exit = "exit", + FileReferences = "fileReferences", + /* @internal */ + FileReferencesFull = "fileReferences-full", Format = "format", Formatonkey = "formatonkey", /* @internal */ @@ -1152,6 +1155,25 @@ namespace ts.server.protocol { body?: ReferencesResponseBody; } + export interface FileReferencesRequest extends FileRequest { + command: CommandTypes.FileReferences; + } + + export interface FileReferencesResponseBody { + /** + * The file locations referencing the symbol. + */ + refs: readonly ReferencesResponseItem[]; + /** + * The name of the symbol. + */ + symbolName: string; + } + + export interface FileReferencesResponse extends Response { + body?: FileReferencesResponseBody; + } + /** * Argument for RenameRequest request. */ diff --git a/src/server/session.ts b/src/server/session.ts index c23656dc3b1b3..518c297c91bf5 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -409,6 +409,29 @@ namespace ts.server { return outputs.filter(o => o.references.length !== 0); } + function combineProjectOutputForFileReferences( + projects: Projects, + defaultProject: Project, + fileName: string + ): readonly ReferenceEntry[] { + const outputs: ReferenceEntry[] = []; + + combineProjectOutputWorker( + projects, + defaultProject, + /*initialLocation*/ undefined, + project => { + for (const referenceEntry of project.getLanguageService().getFileReferences(fileName) || emptyArray) { + if (!contains(outputs, referenceEntry, documentSpansEqual)) { + outputs.push(referenceEntry); + } + } + }, + ); + + return outputs; + } + interface ProjectAndLocation { readonly project: Project; readonly location: TLocation; @@ -1509,7 +1532,7 @@ namespace ts.server { return arrayFrom(map.values()); } - private getReferences(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.ReferencesResponseBody | undefined | readonly ReferencedSymbol[] { + private getReferences(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.ReferencesResponseBody | readonly ReferencedSymbol[] { const file = toNormalizedPath(args.file); const projects = this.getProjects(args); const position = this.getPositionInFile(args, file); @@ -1528,22 +1551,28 @@ namespace ts.server { const nameSpan = nameInfo && nameInfo.textSpan; const symbolStartOffset = nameSpan ? scriptInfo.positionToLineOffset(nameSpan.start).offset : 0; const symbolName = nameSpan ? scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan)) : ""; - const refs: readonly protocol.ReferencesResponseItem[] = flatMap(references, referencedSymbol => - referencedSymbol.references.map(({ fileName, textSpan, contextSpan, isWriteAccess, isDefinition }): protocol.ReferencesResponseItem => { - const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(fileName)); - const span = toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo); - const lineSpan = scriptInfo.lineToTextSpan(span.start.line - 1); - const lineText = scriptInfo.getSnapshot().getText(lineSpan.start, textSpanEnd(lineSpan)).replace(/\r|\n/g, ""); - return { - file: fileName, - ...span, - lineText, - isWriteAccess, - isDefinition - }; - })); + const refs: readonly protocol.ReferencesResponseItem[] = flatMap(references, referencedSymbol => { + return referencedSymbol.references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry)); + }); return { refs, symbolName, symbolStartOffset, symbolDisplayString }; } + + private getFileReferences(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.FileReferencesResponseBody | readonly ReferenceEntry[] { + const projects = this.getProjects(args); + const references = combineProjectOutputForFileReferences( + projects, + this.getDefaultProject(args), + args.file, + ); + + if (!simplifiedResult) return references; + const refs = references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry)); + return { + refs, + symbolName: `"${args.file}"` + }; + } + /** * @param fileName is the name of the file to be opened * @param fileContent is a version of the file content that is known to be more up to date than the one on disk @@ -2639,6 +2668,12 @@ namespace ts.server { [CommandNames.GetSpanOfEnclosingComment]: (request: protocol.SpanOfEnclosingCommentRequest) => { return this.requiredResponse(this.getSpanOfEnclosingComment(request.arguments)); }, + [CommandNames.FileReferences]: (request: protocol.FileReferencesRequest) => { + return this.requiredResponse(this.getFileReferences(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.FileReferencesFull]: (request: protocol.FileReferencesRequest) => { + return this.requiredResponse(this.getFileReferences(request.arguments, /*simplifiedResult*/ false)); + }, [CommandNames.Format]: (request: protocol.FormatRequest) => { return this.requiredResponse(this.getFormattingEditsForRange(request.arguments)); }, @@ -3068,4 +3103,18 @@ namespace ts.server { return text; } + + function referenceEntryToReferencesResponseItem(projectService: ProjectService, { fileName, textSpan, contextSpan, isWriteAccess, isDefinition }: ReferenceEntry): protocol.ReferencesResponseItem { + const scriptInfo = Debug.checkDefined(projectService.getScriptInfo(fileName)); + const span = toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo); + const lineSpan = scriptInfo.lineToTextSpan(span.start.line - 1); + const lineText = scriptInfo.getSnapshot().getText(lineSpan.start, textSpanEnd(lineSpan)).replace(/\r|\n/g, ""); + return { + file: fileName, + ...span, + lineText, + isWriteAccess, + isDefinition + }; + } } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index bc6e1f4cb8071..885595aed653b 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -622,7 +622,21 @@ namespace ts.FindAllReferences { // Could not find a symbol e.g. unknown identifier if (!symbol) { // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. - return !options.implementations && isStringLiteralLike(node) ? getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken) : undefined; + if (!options.implementations && isStringLiteralLike(node)) { + if (isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ true) || isExternalModuleReference(node.parent) || isImportDeclaration(node.parent) || isImportCall(node.parent)) { + const fileIncludeReasons = program.getFileIncludeReasons(); + const referencedFileName = node.getSourceFile().resolvedModules?.get(node.text)?.resolvedFileName; + const referencedFile = referencedFileName ? program.getSourceFile(referencedFileName) : undefined; + if (referencedFile) { + return [{ definition: { type: DefinitionKind.String, node }, references: getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray }]; + } + // Fall through to string literal references. This is not very likely to return + // anything useful, but I guess it's better than nothing, and there's an existing + // test that expects this to happen (fourslash/cases/untypedModuleImport.ts). + } + return getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken); + } + return undefined; } if (symbol.escapedName === InternalSymbolName.ExportEquals) { @@ -642,6 +656,35 @@ namespace ts.FindAllReferences { return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); } + export function getReferencesForFileName(fileName: string, program: Program, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly Entry[] { + const moduleSymbol = program.getSourceFile(fileName)?.symbol; + if (moduleSymbol) { + return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet)[0]?.references || emptyArray; + } + const fileIncludeReasons = program.getFileIncludeReasons(); + const referencedFile = program.getSourceFile(fileName); + return referencedFile && fileIncludeReasons && getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray; + } + + function getReferencesForNonModule(referencedFile: SourceFile, refFileMap: MultiMap, program: Program): readonly SpanEntry[] | undefined { + let entries: SpanEntry[] | undefined; + const references = refFileMap.get(referencedFile.path) || emptyArray; + for (const ref of references) { + if (isReferencedFile(ref)) { + const referencingFile = program.getSourceFileByPath(ref.file)!; + const location = getReferencedFileLocation(program.getSourceFileByPath, ref); + if (isReferenceFileLocation(location)) { + entries = append(entries, { + kind: EntryKind.Span, + fileName: referencingFile.fileName, + textSpan: createTextSpanFromRange(location) + }); + } + } + } + return entries; + } + function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) { if (node.parent && isNamespaceExportDeclaration(node.parent)) { const aliasedSymbol = checker.getAliasedSymbol(symbol); diff --git a/src/services/services.ts b/src/services/services.ts index a891c2297d6d7..fa0e3770d9049 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1728,6 +1728,11 @@ namespace ts { return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); } + function getFileReferences(fileName: string): ReferenceEntry[] { + synchronizeHostData(); + return FindAllReferences.Core.getReferencesForFileName(fileName, program, program.getSourceFiles()).map(FindAllReferences.toReferenceEntry); + } + function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles = false): NavigateToItem[] { synchronizeHostData(); const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles(); @@ -2526,6 +2531,7 @@ namespace ts { getTypeDefinitionAtPosition, getReferencesAtPosition, findReferences, + getFileReferences, getOccurrencesAtPosition, getDocumentHighlights, getNameOrDottedNameSpan, diff --git a/src/services/shims.ts b/src/services/shims.ts index 14f6a61e7478f..30144210271ea 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -208,6 +208,12 @@ namespace ts { */ findReferences(fileName: string, position: number): string; + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[] + */ + getFileReferences(fileName: string): string; + /** * @deprecated * Returns a JSON-encoded value of the type: @@ -916,6 +922,13 @@ namespace ts { ); } + public getFileReferences(fileName: string) { + return this.forwardJSONCall( + `getFileReferences('${fileName})`, + () => this.languageService.getFileReferences(fileName) + ); + } + public getOccurrencesAtPosition(fileName: string, position: number): string { return this.forwardJSONCall( `getOccurrencesAtPosition('${fileName}', ${position})`, diff --git a/src/services/types.ts b/src/services/types.ts index bc7d7328a70ae..35815ade6f53f 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -468,6 +468,7 @@ namespace ts { getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined; findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined; getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined; + getFileReferences(fileName: string): ReferenceEntry[]; /** @deprecated */ getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined; diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 523ea769fa738..1d389e21f0140 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -171,6 +171,7 @@ "unittests/tsserver/getApplicableRefactors.ts", "unittests/tsserver/getEditsForFileRename.ts", "unittests/tsserver/getExportReferences.ts", + "unittests/tsserver/getFileReferences.ts", "unittests/tsserver/importHelpers.ts", "unittests/tsserver/importSuggestionsCache.ts", "unittests/tsserver/inferredProjects.ts", diff --git a/src/testRunner/unittests/tsserver/getFileReferences.ts b/src/testRunner/unittests/tsserver/getFileReferences.ts new file mode 100644 index 0000000000000..f9d049f8ead3f --- /dev/null +++ b/src/testRunner/unittests/tsserver/getFileReferences.ts @@ -0,0 +1,58 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: getFileReferences", () => { + const importA = `import "./a";`; + const importCurlyFromA = `import {} from "./a";`; + const importAFromA = `import { a } from "/project/a";`; + const typeofImportA = `type T = typeof import("./a").a;`; + + const aTs: File = { + path: "/project/a.ts", + content: "export const a = {};", + }; + const bTs: File = { + path: "/project/b.ts", + content: importA, + }; + const cTs: File = { + path: "/project/c.ts", + content: importCurlyFromA + }; + const dTs: File = { + path: "/project/d.ts", + content: [importAFromA, typeofImportA].join("\n") + }; + const tsconfig: File = { + path: "/project/tsconfig.json", + content: "{}", + }; + + function makeSampleSession() { + const host = createServerHost([aTs, bTs, cTs, dTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs, bTs, cTs, dTs], session); + return session; + } + + it("should get file references", () => { + const session = makeSampleSession(); + + const response = executeSessionRequest( + session, + protocol.CommandTypes.FileReferences, + { file: aTs.path }, + ); + + const expectResponse: protocol.FileReferencesResponseBody = { + refs: [ + makeReferenceItem({ file: bTs, text: "./a", lineText: importA, contextText: importA, isDefinition: false, isWriteAccess: false }), + makeReferenceItem({ file: cTs, text: "./a", lineText: importCurlyFromA, contextText: importCurlyFromA, isDefinition: false, isWriteAccess: false }), + makeReferenceItem({ file: dTs, text: "/project/a", lineText: importAFromA, contextText: importAFromA, isDefinition: false, isWriteAccess: false }), + makeReferenceItem({ file: dTs, text: "./a", lineText: typeofImportA, contextText: typeofImportA, isDefinition: false, isWriteAccess: false }), + ], + symbolName: `"${aTs.path}"`, + }; + + assert.deepEqual(response, expectResponse); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index 8084b79056b30..96bae928817db 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -207,6 +207,8 @@ namespace ts.server { CommandNames.Implementation, CommandNames.ImplementationFull, CommandNames.Exit, + CommandNames.FileReferences, + CommandNames.FileReferencesFull, CommandNames.Format, CommandNames.Formatonkey, CommandNames.FormatFull, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 50b2adf403cfc..202725ad0e456 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5531,6 +5531,7 @@ declare namespace ts { getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined; findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined; getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined; + getFileReferences(fileName: string): ReferenceEntry[]; /** @deprecated */ getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined; getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; @@ -6536,6 +6537,7 @@ declare namespace ts.server.protocol { DefinitionAndBoundSpan = "definitionAndBoundSpan", Implementation = "implementation", Exit = "exit", + FileReferences = "fileReferences", Format = "format", Formatonkey = "formatonkey", Geterr = "geterr", @@ -7349,6 +7351,22 @@ declare namespace ts.server.protocol { interface ReferencesResponse extends Response { body?: ReferencesResponseBody; } + interface FileReferencesRequest extends FileRequest { + command: CommandTypes.FileReferences; + } + interface FileReferencesResponseBody { + /** + * The file locations referencing the symbol. + */ + refs: readonly ReferencesResponseItem[]; + /** + * The name of the symbol. + */ + symbolName: string; + } + interface FileReferencesResponse extends Response { + body?: FileReferencesResponseBody; + } /** * Argument for RenameRequest request. */ @@ -9980,6 +9998,7 @@ declare namespace ts.server { private mapRenameInfo; private toSpanGroups; private getReferences; + private getFileReferences; /** * @param fileName is the name of the file to be opened * @param fileContent is a version of the file content that is known to be more up to date than the one on disk diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 141feee127961..6c2ca21a3887e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5531,6 +5531,7 @@ declare namespace ts { getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined; findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined; getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined; + getFileReferences(fileName: string): ReferenceEntry[]; /** @deprecated */ getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined; getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; diff --git a/tests/baselines/reference/findAllRefsCommonJsRequire.baseline.jsonc b/tests/baselines/reference/findAllRefsCommonJsRequire.baseline.jsonc index 675238f9655e0..d6bed0659ba93 100644 --- a/tests/baselines/reference/findAllRefsCommonJsRequire.baseline.jsonc +++ b/tests/baselines/reference/findAllRefsCommonJsRequire.baseline.jsonc @@ -1,6 +1,6 @@ // === /b.js === -// const { [|f|]/*FIND ALL REFS*/ } = require('./a') -// [|f|] +// const { [|f|] } = require('./a') +// /*FIND ALL REFS*/[|f|] // === /a.js === // function [|f|]() { } diff --git a/tests/baselines/reference/findAllRefsCommonJsRequire2.baseline.jsonc b/tests/baselines/reference/findAllRefsCommonJsRequire2.baseline.jsonc index e647bf183db75..3ea2975673f4b 100644 --- a/tests/baselines/reference/findAllRefsCommonJsRequire2.baseline.jsonc +++ b/tests/baselines/reference/findAllRefsCommonJsRequire2.baseline.jsonc @@ -1,6 +1,6 @@ // === /b.js === -// const { [|f|]/*FIND ALL REFS*/ } = require('./a') -// [|f|] +// const { [|f|] } = require('./a') +// /*FIND ALL REFS*/[|f|] // === /a.js === // function [|f|]() { } diff --git a/tests/baselines/reference/findAllRefsCommonJsRequire3.baseline.jsonc b/tests/baselines/reference/findAllRefsCommonJsRequire3.baseline.jsonc index e1295c1a01228..87d82f1ae6ed2 100644 --- a/tests/baselines/reference/findAllRefsCommonJsRequire3.baseline.jsonc +++ b/tests/baselines/reference/findAllRefsCommonJsRequire3.baseline.jsonc @@ -1,6 +1,6 @@ // === /b.js === -// const { [|f|]/*FIND ALL REFS*/ } = require('./a') -// [|f|] +// const { [|f|] } = require('./a') +// /*FIND ALL REFS*/[|f|] // === /a.js === // function [|f|]() { } diff --git a/tests/baselines/reference/findAllRefsForImportCall.baseline.jsonc b/tests/baselines/reference/findAllRefsForImportCall.baseline.jsonc index cfff022786521..a64732cd61503 100644 --- a/tests/baselines/reference/findAllRefsForImportCall.baseline.jsonc +++ b/tests/baselines/reference/findAllRefsForImportCall.baseline.jsonc @@ -1,5 +1,5 @@ // === /app.ts === -// /*FIND ALL REFS*/export function [|hello|]() {}; +// export function [|he/*FIND ALL REFS*/llo|]() {}; // === /indirect-use.ts === // import("./re-export").then(mod => mod.services.app.[|hello|]()); diff --git a/tests/baselines/reference/findAllRefsForImportCallType.baseline.jsonc b/tests/baselines/reference/findAllRefsForImportCallType.baseline.jsonc index 1f512403f0296..0ffe79597f375 100644 --- a/tests/baselines/reference/findAllRefsForImportCallType.baseline.jsonc +++ b/tests/baselines/reference/findAllRefsForImportCallType.baseline.jsonc @@ -1,5 +1,5 @@ // === /app.ts === -// /*FIND ALL REFS*/export function [|hello|]() {}; +// export function [|he/*FIND ALL REFS*/llo|]() {}; // === /indirect-use.ts === // import type { app } from "./re-export"; diff --git a/tests/baselines/reference/findAllRefsNonModule.baseline.jsonc b/tests/baselines/reference/findAllRefsNonModule.baseline.jsonc new file mode 100644 index 0000000000000..47d7678bb21e2 --- /dev/null +++ b/tests/baselines/reference/findAllRefsNonModule.baseline.jsonc @@ -0,0 +1,186 @@ +// === /import.ts === +// import [|"./script/*FIND ALL REFS*/"|]; + +// === /require.js === +// require([|"./script"|]); +// console.log("./script"); + +// === /tripleSlash.ts === +// /// + +[ + { + "definition": { + "containerKind": "", + "containerName": "", + "fileName": "/import.ts", + "kind": "var", + "name": "./script", + "textSpan": { + "start": 8, + "length": 8 + }, + "displayParts": [ + { + "text": "\"./script\"", + "kind": "stringLiteral" + } + ] + }, + "references": [ + { + "textSpan": { + "start": 7, + "length": 10 + }, + "fileName": "/import.ts", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 8, + "length": 10 + }, + "fileName": "/require.js", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 21, + "length": 9 + }, + "fileName": "/tripleSlash.ts", + "isWriteAccess": false, + "isDefinition": false + } + ] + } +] + +// === /import.ts === +// import [|"./script"|]; + +// === /require.js === +// require([|"./script/*FIND ALL REFS*/"|]); +// console.log("./script"); + +// === /tripleSlash.ts === +// /// + +[ + { + "definition": { + "containerKind": "", + "containerName": "", + "fileName": "/require.js", + "kind": "var", + "name": "./script", + "textSpan": { + "start": 9, + "length": 8 + }, + "displayParts": [ + { + "text": "\"./script\"", + "kind": "stringLiteral" + } + ] + }, + "references": [ + { + "textSpan": { + "start": 7, + "length": 10 + }, + "fileName": "/import.ts", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 8, + "length": 10 + }, + "fileName": "/require.js", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 21, + "length": 9 + }, + "fileName": "/tripleSlash.ts", + "isWriteAccess": false, + "isDefinition": false + } + ] + } +] + +// === /require.js === +// require("[|./script|]"); +// console.log("[|./script|]/*FIND ALL REFS*/"); + +// === /stringLiteral.ts === +// console.log("[|./script|]"); + +[ + { + "definition": { + "containerKind": "", + "containerName": "", + "fileName": "/require.js", + "kind": "var", + "name": "./script", + "textSpan": { + "start": 34, + "length": 8 + }, + "displayParts": [ + { + "text": "\"./script\"", + "kind": "stringLiteral" + } + ] + }, + "references": [ + { + "textSpan": { + "start": 9, + "length": 8 + }, + "fileName": "/require.js", + "contextSpan": { + "start": 0, + "length": 20 + }, + "isWriteAccess": false, + "isDefinition": false, + "isInString": true + }, + { + "textSpan": { + "start": 34, + "length": 8 + }, + "fileName": "/require.js", + "isWriteAccess": false, + "isDefinition": false, + "isInString": true + }, + { + "textSpan": { + "start": 13, + "length": 8 + }, + "fileName": "/stringLiteral.ts", + "isWriteAccess": false, + "isDefinition": false, + "isInString": true + } + ] + } +] \ No newline at end of file diff --git a/tests/baselines/reference/findAllRefs_importType_js.1.baseline.jsonc b/tests/baselines/reference/findAllRefs_importType_js.1.baseline.jsonc index fa8e4c790bf3b..e8b02bb4811b7 100644 --- a/tests/baselines/reference/findAllRefs_importType_js.1.baseline.jsonc +++ b/tests/baselines/reference/findAllRefs_importType_js.1.baseline.jsonc @@ -5,7 +5,7 @@ // const y = 0; // === /a.js === -// /*FIND ALL REFS*/module.exports = class [|C|] {}; +// module.exports = class /*FIND ALL REFS*/[|C|] {}; // module.exports.D = class D {}; [ diff --git a/tests/baselines/reference/findAllRefs_importType_js.2.baseline.jsonc b/tests/baselines/reference/findAllRefs_importType_js.2.baseline.jsonc index 5e6981dd49a2d..55e21a8106311 100644 --- a/tests/baselines/reference/findAllRefs_importType_js.2.baseline.jsonc +++ b/tests/baselines/reference/findAllRefs_importType_js.2.baseline.jsonc @@ -1,6 +1,6 @@ // === /a.js === -// /*FIND ALL REFS*/module.exports = class C {}; -// module.exports.[|D|] = class D {}; +// module.exports = class C {}; +// module.exports./*FIND ALL REFS*/[|D|] = class D {}; // === /b.js === // /** @type {import("./a")} */ diff --git a/tests/baselines/reference/findAllRefs_importType_js.3.baseline.jsonc b/tests/baselines/reference/findAllRefs_importType_js.3.baseline.jsonc index b949b88ae8b1a..b7ca959132a05 100644 --- a/tests/baselines/reference/findAllRefs_importType_js.3.baseline.jsonc +++ b/tests/baselines/reference/findAllRefs_importType_js.3.baseline.jsonc @@ -1,6 +1,6 @@ // === /a.js === -// /*FIND ALL REFS*/module.exports = class C {}; -// module.exports.D = class [|D|] {}; +// module.exports = class C {}; +// module.exports.D = class /*FIND ALL REFS*/[|D|] {}; // === /b.js === // /** @type {import("./a")} */ diff --git a/tests/baselines/reference/getFileReferences1.baseline.jsonc b/tests/baselines/reference/getFileReferences1.baseline.jsonc new file mode 100644 index 0000000000000..6ec3f5ec54589 --- /dev/null +++ b/tests/baselines/reference/getFileReferences1.baseline.jsonc @@ -0,0 +1,64 @@ +// === /project/b.ts === +// import "[|./a|]"; + +// === /project/c.ts === +// import {} from "[|./a|]"; + +// === /project/d.ts === +// import { a } from "[|/project/a|]"; +// type T = typeof import("[|./a|]").a; + +[ + { + "textSpan": { + "start": 8, + "length": 3 + }, + "fileName": "/project/b.ts", + "contextSpan": { + "start": 0, + "length": 13 + }, + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 16, + "length": 3 + }, + "fileName": "/project/c.ts", + "contextSpan": { + "start": 0, + "length": 21 + }, + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 19, + "length": 10 + }, + "fileName": "/project/d.ts", + "contextSpan": { + "start": 0, + "length": 31 + }, + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 56, + "length": 3 + }, + "fileName": "/project/d.ts", + "contextSpan": { + "start": 32, + "length": 32 + }, + "isWriteAccess": false, + "isDefinition": false + } +] \ No newline at end of file diff --git a/tests/baselines/reference/getFileReferences2.baseline.jsonc b/tests/baselines/reference/getFileReferences2.baseline.jsonc new file mode 100644 index 0000000000000..aa7100e480da9 --- /dev/null +++ b/tests/baselines/reference/getFileReferences2.baseline.jsonc @@ -0,0 +1,14 @@ +// === /project/b.ts === +// import [|"./a"|]; + +[ + { + "textSpan": { + "start": 7, + "length": 5 + }, + "fileName": "/project/b.ts", + "isWriteAccess": false, + "isDefinition": false + } +] \ No newline at end of file diff --git a/tests/baselines/reference/getFileReferences_deduplicate.baseline.jsonc b/tests/baselines/reference/getFileReferences_deduplicate.baseline.jsonc new file mode 100644 index 0000000000000..bc83a05736875 --- /dev/null +++ b/tests/baselines/reference/getFileReferences_deduplicate.baseline.jsonc @@ -0,0 +1,26 @@ +// === /test.ts === +// import "[|./util|]"; + +// === /index.ts === +// export * from "[|./util|]"; + +[ + { + "fileName": "/index.ts", + "textSpan": { + "start": 15, + "length": 6 + }, + "isWriteAccess": false, + "isDefinition": false + }, + { + "fileName": "/test.ts", + "textSpan": { + "start": 8, + "length": 6 + }, + "isWriteAccess": false, + "isDefinition": false + } +] \ No newline at end of file diff --git a/tests/baselines/reference/getFileReferences_server1.baseline.jsonc b/tests/baselines/reference/getFileReferences_server1.baseline.jsonc new file mode 100644 index 0000000000000..f83fee577916d --- /dev/null +++ b/tests/baselines/reference/getFileReferences_server1.baseline.jsonc @@ -0,0 +1,48 @@ +// === /project/b.ts === +// import "[|./a|]"; + +// === /project/c.ts === +// import {} from "[|./a|]"; + +// === /project/d.ts === +// import { a } from "[|/project/a|]"; +// type T = typeof import("[|./a|]").a; + +[ + { + "fileName": "/project/b.ts", + "textSpan": { + "start": 8, + "length": 3 + }, + "isWriteAccess": false, + "isDefinition": false + }, + { + "fileName": "/project/c.ts", + "textSpan": { + "start": 16, + "length": 3 + }, + "isWriteAccess": false, + "isDefinition": false + }, + { + "fileName": "/project/d.ts", + "textSpan": { + "start": 19, + "length": 10 + }, + "isWriteAccess": false, + "isDefinition": false + }, + { + "fileName": "/project/d.ts", + "textSpan": { + "start": 56, + "length": 3 + }, + "isWriteAccess": false, + "isDefinition": false + } +] \ No newline at end of file diff --git a/tests/baselines/reference/getFileReferences_server2.baseline.jsonc b/tests/baselines/reference/getFileReferences_server2.baseline.jsonc new file mode 100644 index 0000000000000..f5d88f1f3d857 --- /dev/null +++ b/tests/baselines/reference/getFileReferences_server2.baseline.jsonc @@ -0,0 +1,38 @@ +// === /packages/client/index.ts === +// import "[|@shared/referenced|]"; + +// === /packages/server/index.js === +// const mod = require("[|../shared/src/referenced|]"); + +// === /packages/server/router.js === +// const blah = require("[|../shared/dist/referenced|]"); + +[ + { + "fileName": "/packages/server/index.js", + "textSpan": { + "start": 21, + "length": 24 + }, + "isWriteAccess": false, + "isDefinition": false + }, + { + "fileName": "/packages/server/router.js", + "textSpan": { + "start": 22, + "length": 25 + }, + "isWriteAccess": false, + "isDefinition": false + }, + { + "fileName": "/packages/client/index.ts", + "textSpan": { + "start": 8, + "length": 18 + }, + "isWriteAccess": false, + "isDefinition": false + } +] \ No newline at end of file diff --git a/tests/cases/fourslash/findAllRefsNonModule.ts b/tests/cases/fourslash/findAllRefsNonModule.ts new file mode 100644 index 0000000000000..371696132b628 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsNonModule.ts @@ -0,0 +1,21 @@ +/// + +// @checkJs: true + +// @Filename: /script.ts +//// console.log("I'm a script!"); + +// @Filename: /import.ts +//// import "./script/*1*/"; + +// @Filename: /require.js +//// require("./script/*2*/"); +//// console.log("./script/*3*/"); + +// @Filename: /tripleSlash.ts +//// /// + +// @Filename: /stringLiteral.ts +//// console.log("./script"); + +verify.baselineFindAllReferences("1", "2", "3"); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 98298bbfe5f21..d2ea70036a4b5 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -289,7 +289,8 @@ declare namespace FourSlashInterface { goToType(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; verifyGetEmitOutputForCurrentFile(expected: string): void; verifyGetEmitOutputContentsForCurrentFile(expected: ts.OutputFile[]): void; - baselineFindAllReferences(markerName: string): void; + baselineFindAllReferences(...markerNames: string[]): void; + baselineGetFileReferences(fileName: string): void; noReferences(markerNameOrRange?: string | Range): void; symbolAtLocation(startRange: Range, ...declarationRanges: Range[]): void; typeOfSymbolAtLocation(range: Range, symbol: any, expected: string): void; diff --git a/tests/cases/fourslash/getFileReferences1.ts b/tests/cases/fourslash/getFileReferences1.ts new file mode 100644 index 0000000000000..82ce2af09bbb3 --- /dev/null +++ b/tests/cases/fourslash/getFileReferences1.ts @@ -0,0 +1,16 @@ +/// + +// @Filename: /project/a.ts +//// export const a = 0; + +// @Filename: /project/b.ts +//// import "./a"; + +// @Filename: /project/c.ts +//// import {} from "./a"; + +// @Filename: /project/d.ts +//// import { a } from "/project/a"; +//// type T = typeof import("./a").a; + +verify.baselineGetFileReferences("/project/a.ts"); diff --git a/tests/cases/fourslash/getFileReferences2.ts b/tests/cases/fourslash/getFileReferences2.ts new file mode 100644 index 0000000000000..025a4ed915622 --- /dev/null +++ b/tests/cases/fourslash/getFileReferences2.ts @@ -0,0 +1,9 @@ +/// + +// @Filename: /project/a.ts +//// console.log("I'm a script!"); + +// @Filename: /project/b.ts +//// import "./a"; + +verify.baselineGetFileReferences("/project/a.ts"); diff --git a/tests/cases/fourslash/server/getFileReferences_deduplicate.ts b/tests/cases/fourslash/server/getFileReferences_deduplicate.ts new file mode 100644 index 0000000000000..9786f170537c0 --- /dev/null +++ b/tests/cases/fourslash/server/getFileReferences_deduplicate.ts @@ -0,0 +1,23 @@ +/// + +// @Filename: /tsconfig.json +//// { "files": [], "references": [{ "path": "tsconfig.build.json" }, { "path": "tsconfig.test.json" }] } + +// @Filename: /tsconfig.build.json +//// { "compilerOptions": { "rootDir": "src", "outDir": "dist/build", "composite": true }, "files": ["index.ts"] } + +// @Filename: /index.ts +//// export * from "./util"; + +// @Filename: /tsconfig.test.json +//// { "compilerOptions": { "rootDir": "src", "outDir": "dist/test", "composite": true }, "files": ["test.ts", "index.ts"] } + +// @Filename: /test.ts +//// import "./util"; + +// @Filename: /util.ts +//// export {} + +// util.ts is referenced by index.ts, which is included in tsconfig.build.json and tsconfig.test.json. +// That reference will be returned from both projects' language services. Test ensures it gets deduplicated. +verify.baselineGetFileReferences("/util.ts"); diff --git a/tests/cases/fourslash/server/getFileReferences_server1.ts b/tests/cases/fourslash/server/getFileReferences_server1.ts new file mode 100644 index 0000000000000..020b6ec586ca4 --- /dev/null +++ b/tests/cases/fourslash/server/getFileReferences_server1.ts @@ -0,0 +1,19 @@ +/// + +// @Filename: /project/tsconfig.json +//// {} + +// @Filename: /project/a.ts +//// export const a = 0; + +// @Filename: /project/b.ts +//// import "./a"; + +// @Filename: /project/c.ts +//// import {} from "./a"; + +// @Filename: /project/d.ts +//// import { a } from "/project/a"; +//// type T = typeof import("./a").a; + +verify.baselineGetFileReferences("/project/a.ts"); diff --git a/tests/cases/fourslash/server/getFileReferences_server2.ts b/tests/cases/fourslash/server/getFileReferences_server2.ts new file mode 100644 index 0000000000000..0217444eeee59 --- /dev/null +++ b/tests/cases/fourslash/server/getFileReferences_server2.ts @@ -0,0 +1,27 @@ +/// + +// @Filename: /tsconfig.json +//// { "files": [], "references": [{ "path": "packages/server" }, { "path": "packages/client" }] } + +// @Filename: /packages/shared/tsconfig.json +//// { "compilerOptions": { "rootDir": "src", "outDir": "dist", "composite": true } } + +// @Filename: /packages/shared/src/referenced.ts +//// export {}; + +// @Filename: /packages/server/tsconfig.json +//// { "compilerOptions": { "checkJs": true }, "references": [{ "path": "../shared" }] } + +// @Filename: /packages/server/index.js +//// const mod = require("../shared/src/referenced"); + +// @Filename: /packages/server/router.js +//// const blah = require("../shared/dist/referenced"); + +// @Filename: /packages/client/tsconfig.json +//// { "compilerOptions": { "paths": { "@shared/*": ["../shared/src/*"] } }, "references": [{ "path": "../shared" }] } + +// @Filename: /packages/client/index.ts +//// import "@shared/referenced"; + +verify.baselineGetFileReferences("/packages/shared/src/referenced.ts");