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"));