diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 39dde23b28a55..9d2566b7b0b05 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -3468,6 +3468,38 @@ namespace ts.projectSystem { it("works when project root is used with case-insensitive system", () => { verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ false); }); + + it("uses existing project even if project refresh is pending", () => { + const projectFolder = "/user/someuser/projects/myproject"; + const aFile: FileOrFolder = { + path: `${projectFolder}/src/a.ts`, + content: "export const x = 0;" + }; + const configFile: FileOrFolder = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const files = [aFile, configFile, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(aFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); + verifyProject(); + + const bFile: FileOrFolder = { + path: `${projectFolder}/src/b.ts`, + content: `export {}; declare module "./a" { export const y: number; }` + }; + files.push(bFile); + host.reloadFS(files); + service.openClientFile(bFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); + verifyProject(); + + function verifyProject() { + assert.isDefined(service.configuredProjects.get(configFile.path)); + const project = service.configuredProjects.get(configFile.path); + checkProjectActualFiles(project, files.map(f => f.path)); + } + }); }); describe("tsserverProjectSystem Language service", () => { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a5b09c6a63f5d..c4446c049b6ec 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -725,15 +725,6 @@ namespace ts.server { } } - private findContainingExternalProject(fileName: NormalizedPath): ExternalProject { - for (const proj of this.externalProjects) { - if (proj.containsFile(fileName)) { - return proj; - } - } - return undefined; - } - getFormatCodeOptions(file?: NormalizedPath) { let formatCodeSettings: FormatCodeSettings; if (file) { @@ -1991,13 +1982,24 @@ namespace ts.server { return this.openClientFileWithNormalizedPath(toNormalizedPath(fileName), fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath ? toNormalizedPath(projectRootPath) : undefined); } + private findExternalProjetContainingOpenScriptInfo(info: ScriptInfo): ExternalProject { + for (const proj of this.externalProjects) { + // Ensure project structure is uptodate to check if info is present in external project + proj.updateGraph(); + if (proj.containsScriptInfo(info)) { + return proj; + } + } + return undefined; + } + openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult { let configFileName: NormalizedPath; let sendConfigFileDiagEvent = false; let configFileErrors: ReadonlyArray; const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent); - let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName); + let project: ConfiguredProject | ExternalProject = this.findExternalProjetContainingOpenScriptInfo(info); if (!project) { configFileName = this.getConfigFileNameForFile(info, projectRootPath); if (configFileName) { @@ -2007,6 +2009,10 @@ namespace ts.server { // Send the event only if the project got created as part of this open request sendConfigFileDiagEvent = true; } + else { + // Ensure project is ready to check if it contains opened script info + project.updateGraph(); + } } } if (project && !project.languageServiceEnabled) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index abd03c26b0b2a..8ae40d84afd97 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7605,7 +7605,6 @@ declare namespace ts.server { * @param forceInferredProjectsRefresh when true updates the inferred projects even if there is no pending work to update the files/project structures */ private ensureProjectStructuresUptoDate(forceInferredProjectsRefresh?); - private findContainingExternalProject(fileName); getFormatCodeOptions(file?: NormalizedPath): FormatCodeSettings; private updateProjectGraphs(projects); private onSourceFileChanged(fileName, eventKind); @@ -7723,6 +7722,7 @@ declare namespace ts.server { * @param fileContent is a known version of the file content that is more up to date than the one on disk */ openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: string): OpenConfiguredProjectResult; + private findExternalProjetContainingOpenScriptInfo(info); openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult; /** * Close file whose contents is managed by the client