From 46d223dc1b6b094ae4f4da82c1ab0aada853e627 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 2 Aug 2018 15:33:22 -0700 Subject: [PATCH] Fixes to ensure getDefinitionAndBoundSpan works correctly when using composite projects Project references need to be detached from the project when closing project In SourceMapDecoder handle when the redirected file to project reference is set as the output of the project Keep configured project alive if project it references has open ref Fixes #26164 --- src/compiler/program.ts | 2 +- src/compiler/sourcemapDecoder.ts | 5 +- src/server/editorServices.ts | 14 ++++++ src/server/project.ts | 14 ++++++ .../unittests/tsserverProjectSystem.ts | 46 +++++++++++++++++-- 5 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index c70477b4a17..703add00025 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2358,7 +2358,7 @@ namespace ts { if (sourceFile === undefined) { return undefined; } - + sourceFile.path = toPath(refPath); const commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath); return { commandLine, sourceFile }; } diff --git a/src/compiler/sourcemapDecoder.ts b/src/compiler/sourcemapDecoder.ts index 76f5943be66..28cbbe39cf5 100644 --- a/src/compiler/sourcemapDecoder.ts +++ b/src/compiler/sourcemapDecoder.ts @@ -100,7 +100,8 @@ namespace ts.sourcemaps { // Lookup file in program, if provided const path = toPath(fileName, location, host.getCanonicalFileName); const file = program && program.getSourceFile(path); - if (!file) { + // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file + if (!file || file.resolvedPath !== path) { // Otherwise check the cache (which may hit disk) return fallbackCache.get(path); } @@ -373,4 +374,4 @@ namespace ts.sourcemaps { encodedText.charCodeAt(pos) === CharacterCodes.comma || encodedText.charCodeAt(pos) === CharacterCodes.semicolon); } -} \ No newline at end of file +} diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d841669b07b..a0ea670dc7c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2243,6 +2243,20 @@ namespace ts.server { toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath); markOriginalProjectsAsUsed(project); } + else { + // If the configured project for project reference has more than zero references, keep it alive + const resolvedProjectReferences = project.getResolvedProjectReferences(); + if (resolvedProjectReferences) { + for (const ref of resolvedProjectReferences) { + if (ref) { + const refProject = this.configuredProjects.get(ref.sourceFile.path); + if (refProject && refProject.hasOpenRef()) { + toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath); + } + } + } + } + } }); // Remove all the non marked projects diff --git a/src/server/project.ts b/src/server/project.ts index a8db5d80511..6cbe97db7be 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -572,6 +572,14 @@ namespace ts.server { for (const f of this.program.getSourceFiles()) { this.detachScriptInfoIfNotRoot(f.fileName); } + const projectReferences = this.program.getProjectReferences(); + if (projectReferences) { + for (const ref of projectReferences) { + if (ref) { + this.detachScriptInfoFromProject(ref.sourceFile.fileName); + } + } + } } // Release external files forEach(this.externalFiles, externalFile => this.detachScriptInfoIfNotRoot(externalFile)); @@ -1367,6 +1375,12 @@ namespace ts.server { this.projectReferences = refs; } + /*@internal*/ + getResolvedProjectReferences() { + const program = this.getCurrentProgram(); + return program && program.getProjectReferences(); + } + enablePlugins() { const host = this.projectService.host; const options = this.getCompilationSettings(); diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 7ae8fea9d59..eb6b5cdfe90 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -9183,8 +9183,21 @@ export function Test2() { content: 'import { fnA, instanceA } from "../a/bin/a";\nimport { fnB } from "../b/bin/b";\nexport function fnUser() { fnA(); fnB(); instanceA; }', }; - function makeSampleProjects() { - const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, userTs, dummyFile]); + const userTsForConfigProject: File = { + path: "/user/user.ts", + content: 'import { fnA, instanceA } from "../a/a";\nimport { fnB } from "../b/b";\nexport function fnUser() { fnA(); fnB(); instanceA; }', + }; + + const userTsconfig: File = { + path: "/user/tsconfig.json", + content: JSON.stringify({ + file: ["user.ts"], + references: [{ path: "../a" }, { path: "../b" }] + }) + }; + + function makeSampleProjects(addUserTsConfig?: boolean) { + const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]); const session = createSession(host); checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); @@ -9195,7 +9208,7 @@ export function Test2() { openFilesForSession([userTs], session); const service = session.getProjectService(); - checkNumberOfProjects(service, { inferredProjects: 1 }); + checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 1 } : { inferredProjects: 1 }); return session; } @@ -9247,6 +9260,10 @@ export function Test2() { verifyATsConfigProject(session); // ATsConfig should still be alive } + function verifyUserTsConfigProject(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aDts.path, userTsconfig.path]); + } + it("goToDefinition", () => { const session = makeSampleProjects(); const response = executeSessionRequest(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()")); @@ -9264,6 +9281,29 @@ export function Test2() { verifySingleInferredProject(session); }); + it("getDefinitionAndBoundSpan with file navigation", () => { + const session = makeSampleProjects(/*addUserTsConfig*/ true); + const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA", { index: 1 }), + definitions: [protocolFileSpanFromSubstring(aTs, "fnA")], + }); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); debugger; + verifyUserTsConfigProject(session); + + // Navigate to the definition + closeFilesForSession([userTs], session); + openFilesForSession([aTs], session); + + // UserTs configured project should be alive + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + verifyUserTsConfigProject(session); + verifyATsConfigProject(session); + + closeFilesForSession([aTs], session); + verifyOnlyOrphanInferredProject(session); + }); + it("goToType", () => { const session = makeSampleProjects(); const response = executeSessionRequest(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA"));