diff --git a/src/server/session.ts b/src/server/session.ts index e6c4c33a53d0d..14ad20d7aaeac 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -336,16 +336,23 @@ namespace ts.server { function combineProjectOutputForReferences(projects: Projects, defaultProject: Project, initialLocation: sourcemaps.SourceMappableLocation, projectService: ProjectService): ReadonlyArray { const outputs: ReferencedSymbol[] = []; - combineProjectOutputWorker(projects, defaultProject, initialLocation, projectService, ({ project, location }, tryAddToTodo) => { + combineProjectOutputWorker(projects, defaultProject, initialLocation, projectService, ({ project, location }, getMappedLocation) => { for (const outputReferencedSymbol of project.getLanguageService().findReferences(location.fileName, location.position) || emptyArray) { - let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, outputReferencedSymbol.definition)); + const mappedDefinitionFile = getMappedLocation(project, documentSpanLocation(outputReferencedSymbol.definition)); + const definition: ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ? outputReferencedSymbol.definition : { + ...outputReferencedSymbol.definition, + textSpan: createTextSpan(mappedDefinitionFile.position, outputReferencedSymbol.definition.textSpan.length), + fileName: mappedDefinitionFile.fileName, + }; + let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, definition)); if (!symbolToAddTo) { - symbolToAddTo = { definition: outputReferencedSymbol.definition, references: [] }; + symbolToAddTo = { definition, references: [] }; outputs.push(symbolToAddTo); } for (const ref of outputReferencedSymbol.references) { - if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !tryAddToTodo(project, documentSpanLocation(ref))) { + // If it's in a mapped file, that is added to the todo list by `getMappedLocation`. + if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !getMappedLocation(project, documentSpanLocation(ref))) { symbolToAddTo.references.push(ref); } } @@ -373,12 +380,17 @@ namespace ts.server { } } + type CombineProjectOutputCallback = ( + where: ProjectAndLocation, + getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => sourcemaps.SourceMappableLocation | undefined, + ) => void; + function combineProjectOutputWorker( projects: Projects, defaultProject: Project, initialLocation: TLocation, projectService: ProjectService, - cb: (where: ProjectAndLocation, getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => boolean) => void, + cb: CombineProjectOutputCallback, getDefinition: (() => sourcemaps.SourceMappableLocation | undefined) | undefined, ): void { let toDo: ProjectAndLocation[] | undefined; @@ -417,13 +429,13 @@ namespace ts.server { projectService: ProjectService, toDo: ProjectAndLocation[] | undefined, seenProjects: Map, - cb: (where: ProjectAndLocation, getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => boolean) => void, + cb: CombineProjectOutputCallback, ): ProjectAndLocation[] | undefined { if (projectAndLocation.project.getCancellationToken().isCancellationRequested()) return undefined; // Skip rest of toDo if cancelled cb(projectAndLocation, (project, location) => { seenProjects.set(projectAndLocation.project.projectName, true); const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(project, location); - if (!originalLocation) return false; + if (!originalLocation) return undefined; const originalScriptInfo = projectService.getScriptInfo(originalLocation.fileName)!; toDo = toDo || []; @@ -437,7 +449,7 @@ namespace ts.server { for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects); }); } - return true; + return originalLocation; }); return toDo; } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index ed130f6772b4f..4d9b74a38dec3 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -3491,7 +3491,7 @@ namespace ts.projectSystem { host.checkTimeoutQueueLength(2); // Update configured project and projects for open file checkProjectActualFiles(services.configuredProjects.get(config.path)!, filesWithFileA.map(f => f.path)); - // host.fileExists = originalFileExists; + // host.fileExists = originalFileExists; openFile(fileSubA); // This should create inferred project since fileSubA not on the disk checkProjectActualFiles(services.configuredProjects.get(config.path)!, mapDefined(filesWithFileA, f => f === fileA ? undefined : f.path)); @@ -3672,7 +3672,7 @@ namespace ts.projectSystem { path: `${projectRoot}/app1/tsconfig.json`, content: JSON.stringify({ files: ["app.ts", "../core/core.ts"], - compilerOptions: { outFile : "build/output.js" }, + compilerOptions: { outFile: "build/output.js" }, compileOnSave: true }) }; @@ -6778,9 +6778,9 @@ namespace ts.projectSystem { fileName: "/a.1.ts", textChanges: [ { - start: { line: 0, offset: 0 }, - end: { line: 0, offset: 0 }, - newText: "export const a = 0;", + start: { line: 0, offset: 0 }, + end: { line: 0, offset: 0 }, + newText: "export const a = 0;", }, ], } @@ -8672,7 +8672,7 @@ new C();` })); checkWatchedDirectories(host, [], /*recursive*/ false); checkWatchedDirectories(host, arrayFrom(expectedRecursiveDirectories.keys()), /*recursive*/ true); - } + } describe("from files in same folder", () => { function getFiles(fileContent: string) { @@ -9750,7 +9750,7 @@ declare class TestLib { function verifyATsConfigOriginalProject(session: TestSession) { checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); verifyInferredProjectUnchanged(session); - verifyATsConfigProject(session); + verifyATsConfigProject(session); // Close user file should close all the projects closeFilesForSession([userTs], session); verifyOnlyOrphanInferredProject(session); @@ -9900,11 +9900,12 @@ declare class TestLib { verifyATsConfigWhenOpened(session); }); + interface ReferencesFullRequest extends protocol.FileLocationRequest { readonly command: protocol.CommandTypes.ReferencesFull; } + interface ReferencesFullResponse extends protocol.Response { readonly body: ReadonlyArray; } + it("findAllReferencesFull", () => { const session = makeSampleProjects(); - interface ReferencesFullRequest extends protocol.FileLocationRequest { command: protocol.CommandTypes.ReferencesFull; } - interface ReferencesFullResponse extends protocol.Response { body: ReadonlyArray; } const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()")); function fnAVoid(kind: SymbolDisplayPartKind): SymbolDisplayPart[] { @@ -9961,6 +9962,67 @@ declare class TestLib { verifyATsConfigOriginalProject(session); }); + it("findAllReferencesFull definition is in mapped file", () => { + const aTs: File = { path: "/a/a.ts", content: `function f() {}` }; + const aTsconfig: File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }), + }; + const bTs: File = { path: "/b/b.ts", content: `f();` }; + const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) }; + const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` }; + const aDtsMap: File = { + path: "/bin/a.d.ts.map", + content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }), + }; + + const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap])); + checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); + openFilesForSession([bTs], session); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); + + const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()")); + + assert.deepEqual>(responseFull, [ + { + definition: { + containerKind: ScriptElementKind.unknown, + containerName: "", + displayParts: [ + keywordPart(SyntaxKind.FunctionKeyword), + spacePart(), + displayPart("f", SymbolDisplayPartKind.functionName), + punctuationPart(SyntaxKind.OpenParenToken), + punctuationPart(SyntaxKind.CloseParenToken), + punctuationPart(SyntaxKind.ColonToken), + spacePart(), + keywordPart(SyntaxKind.VoidKeyword), + ], + fileName: aTs.path, + kind: ScriptElementKind.functionElement, + name: "function f(): void", + textSpan: { start: 9, length: 1 }, + }, + references: [ + { + fileName: bTs.path, + isDefinition: false, + isInString: undefined, + isWriteAccess: false, + textSpan: { start: 0, length: 1 }, + }, + { + fileName: aTs.path, + isDefinition: true, + isInString: undefined, + isWriteAccess: true, + textSpan: { start: 9, length: 1 }, + }, + ], + } + ]); + }); + it("findAllReferences -- target does not exist", () => { const session = makeSampleProjects();