From cd18da8f1712458a49b94f727a0f5316ee7713fc Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 4 Sep 2020 16:04:01 -0700 Subject: [PATCH] Ensure that file updates are reflected --- src/server/editorServices.ts | 18 ++++++++- .../unittests/tsserver/reloadProjects.ts | 40 ++++++++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 7882d268efe2a..ccd3eea2048e5 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -11,6 +11,7 @@ namespace ts.server { export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState"; export const ProjectInfoTelemetryEvent = "projectInfo"; export const OpenFileInfoTelemetryEvent = "openFileInfo"; + const ensureProjectForOpenFileSchedule = "*ensureProjectForOpenFiles*"; export interface ProjectsUpdatedInBackgroundEvent { eventName: typeof ProjectsUpdatedInBackgroundEvent; @@ -878,7 +879,7 @@ namespace ts.server { /*@internal*/ delayEnsureProjectForOpenFiles() { this.pendingEnsureProjectForOpenFiles = true; - this.throttledOperations.schedule("*ensureProjectForOpenFiles*", /*delay*/ 2500, () => { + this.throttledOperations.schedule(ensureProjectForOpenFileSchedule, /*delay*/ 2500, () => { if (this.pendingProjectUpdates.size !== 0) { this.delayEnsureProjectForOpenFiles(); } @@ -2796,6 +2797,21 @@ namespace ts.server { // (and would separate out below reloading of projects to be called when immediate reload is needed) // as there is no need to load contents of the files from the disk + // Reload script infos + this.filenameToScriptInfo.forEach(info => { + if (this.openFiles.has(info.path)) return; // Skip open files + if (!info.fileWatcher) return; // not watched file + // Handle as if file is changed or deleted + this.onSourceFileChanged(info, this.host.fileExists(info.fileName) ? FileWatcherEventKind.Changed : FileWatcherEventKind.Deleted); + }); + // Cancel all project updates since we will be updating them now + this.pendingProjectUpdates.forEach((_project, projectName) => { + this.throttledOperations.cancel(projectName); + this.pendingProjectUpdates.delete(projectName); + }); + this.throttledOperations.cancel(ensureProjectForOpenFileSchedule); + this.pendingEnsureProjectForOpenFiles = false; + // Reload Projects this.reloadConfiguredProjectForFiles(this.openFiles as ESMap, /*clearSemanticCache*/ true, /*delayReload*/ false, returnTrue, "User requested reload projects"); this.externalProjects.forEach(project => { diff --git a/src/testRunner/unittests/tsserver/reloadProjects.ts b/src/testRunner/unittests/tsserver/reloadProjects.ts index d0654c1ec8663..970a9b2995eba 100644 --- a/src/testRunner/unittests/tsserver/reloadProjects.ts +++ b/src/testRunner/unittests/tsserver/reloadProjects.ts @@ -9,21 +9,40 @@ namespace ts.projectSystem { const file1: File = { path: `${tscWatch.projectRoot}/file1.ts`, content: `import { foo } from "module1"; - foo();` + foo(); + import { bar } from "./file2"; + bar();` }; const file2: File = { path: `${tscWatch.projectRoot}/file2.ts`, - content: `${file1.content} - export function bar(){}` + content: `export function bar(){}` }; const moduleFile: File = { path: `${tscWatch.projectRoot}/node_modules/module1/index.d.ts`, content: `export function foo(): string;` }; + function verifyFileUpdates(host: TestServerHost, service: TestProjectService, project: server.Project) { + // update file + const updatedText = `${file2.content} + bar();`; + host.writeFile(file2.path, updatedText); + host.checkTimeoutQueueLength(0); + service.reloadProjects(); + assert.equal(project.getCurrentProgram()?.getSourceFile(file2.path)?.text, updatedText); + + // delete file + host.deleteFile(file2.path); + host.checkTimeoutQueueLength(0); + service.reloadProjects(); + assert.isUndefined(project.getCurrentProgram()?.getSourceFile(file2.path)?.text); + assert.isUndefined(service.getScriptInfo(file2.path)); + } + it("configured project", () => { const host = createServerHost([configFile, libFile, file1, file2]); const service = createProjectService(host); + service.setHostConfiguration({ watchOptions: { excludeFiles: [file2.path] } }); service.openClientFile(file1.path); checkNumberOfProjects(service, { configuredProjects: 1 }); const project = service.configuredProjects.get(configFile.path)!; @@ -37,18 +56,21 @@ namespace ts.projectSystem { checkNumberOfProjects(service, { configuredProjects: 1 }); assert.strictEqual(service.configuredProjects.get(configFile.path), project); checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, configFile.path, moduleFile.path]); + + verifyFileUpdates(host, service, project); }); it("inferred project", () => { const host = createServerHost([libFile, file1, file2]); const service = createProjectService(host, /*parameters*/ undefined, { useInferredProjectPerProjectRoot: true, }); + service.setHostConfiguration({ watchOptions: { excludeFiles: [file2.path] } }); const timeoutId = host.getNextTimeoutId(); service.setCompilerOptionsForInferredProjects({ excludeDirectories: ["node_modules"] }, tscWatch.projectRoot); host.clearTimeout(timeoutId); service.openClientFile(file1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, tscWatch.projectRoot); checkNumberOfProjects(service, { inferredProjects: 1 }); const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); // Install module1 host.ensureFileOrFolder(moduleFile); @@ -57,12 +79,15 @@ namespace ts.projectSystem { service.reloadProjects(); checkNumberOfProjects(service, { inferredProjects: 1 }); assert.strictEqual(service.inferredProjects[0], project); - checkProjectActualFiles(project, [libFile.path, file1.path, moduleFile.path]); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, moduleFile.path]); + + verifyFileUpdates(host, service, project); }); it("external project", () => { const host = createServerHost([libFile, file1, file2]); const service = createProjectService(host); + service.setHostConfiguration({ watchOptions: { excludeFiles: [file2.path] } }); service.openExternalProject({ projectFileName: `${tscWatch.projectRoot}/project.sln`, options: { excludeDirectories: ["node_modules"] }, @@ -81,11 +106,14 @@ namespace ts.projectSystem { checkNumberOfProjects(service, { externalProjects: 1 }); assert.strictEqual(service.externalProjects[0], project); checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, moduleFile.path]); + + verifyFileUpdates(host, service, project); }); it("external project with config file", () => { const host = createServerHost([libFile, file1, file2, configFile]); const service = createProjectService(host); + service.setHostConfiguration({ watchOptions: { excludeFiles: [file2.path] } }); service.openExternalProject({ projectFileName: `${tscWatch.projectRoot}/project.sln`, options: { excludeDirectories: ["node_modules"] }, @@ -104,6 +132,8 @@ namespace ts.projectSystem { checkNumberOfProjects(service, { configuredProjects: 1 }); assert.strictEqual(service.configuredProjects.get(configFile.path), project); checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, configFile.path, moduleFile.path]); + + verifyFileUpdates(host, service, project); }); }); }