diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 4426e52ab66..83e92c2018d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -639,6 +639,11 @@ namespace ts.projectSystem { this.timeoutCallbacks.unregister(timeoutId); } + checkTimeoutQueueLengthAndRun(expected: number) { + this.checkTimeoutQueueLength(expected); + this.runQueuedTimeoutCallbacks(); + } + checkTimeoutQueueLength(expected: number) { const callbacksCount = this.timeoutCallbacks.count(); assert.equal(callbacksCount, expected, `expected ${expected} timeout callbacks queued but found ${callbacksCount}.`); @@ -899,6 +904,9 @@ namespace ts.projectSystem { // remove the tsconfig file host.reloadFS(filesWithoutConfig); + checkNumberOfInferredProjects(projectService, 1); + host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects + checkNumberOfInferredProjects(projectService, 2); checkNumberOfConfiguredProjects(projectService, 0); checkWatchedDirectories(host, ["/a/b", "/a"]); @@ -920,7 +928,7 @@ namespace ts.projectSystem { // add a new ts file host.reloadFS([commonFile1, commonFile2, libFile, configFile]); - host.runQueuedTimeoutCallbacks(); + host.checkTimeoutQueueLengthAndRun(2); // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer. checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); }); @@ -1022,12 +1030,12 @@ namespace ts.projectSystem { // delete commonFile2 host.reloadFS([commonFile1, configFile]); - host.runQueuedTimeoutCallbacks(); + host.checkTimeoutQueueLengthAndRun(2); checkProjectRootFiles(project, [commonFile1.path]); // re-add commonFile2 host.reloadFS([commonFile1, commonFile2, configFile]); - host.runQueuedTimeoutCallbacks(); + host.checkTimeoutQueueLengthAndRun(2); checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); }); @@ -1086,6 +1094,9 @@ namespace ts.projectSystem { }`; host.reloadFS(files); + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects checkNumberOfConfiguredProjects(projectService, 1); checkProjectRootFiles(project, [commonFile1.path]); @@ -1158,6 +1169,7 @@ namespace ts.projectSystem { "files": ["${file1.path}"] }`; host.reloadFS(files); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); checkNumberOfInferredProjects(projectService, 1); }); @@ -1587,7 +1599,7 @@ namespace ts.projectSystem { }; host.reloadFS([file1, modifiedFile2, file3]); - + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfInferredProjects(projectService, 1); checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, modifiedFile2.path, file3.path]); }); @@ -1618,6 +1630,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { inferredProjects: 1 }); host.reloadFS([file1, file3]); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { inferredProjects: 2 }); @@ -1779,7 +1792,7 @@ namespace ts.projectSystem { host.reloadFS([file1, file2, configFile]); - host.checkTimeoutQueueLength(0); // TODO: update graph scheduling (instead of instant update graph) + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]); @@ -1814,6 +1827,7 @@ namespace ts.projectSystem { host.reloadFS([file1, file2, modifiedConfigFile]); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]); }); @@ -1923,7 +1937,7 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); host.reloadFS([file1, file2]); - + host.checkTimeoutQueueLengthAndRun(1); checkNumberOfProjects(projectService, { inferredProjects: 2 }); checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); @@ -2217,6 +2231,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { inferredProjects: 2 }); projectService.setCompilerOptionsForInferredProjects({ moduleResolution: ModuleResolutionKind.Classic }); + host.checkTimeoutQueueLengthAndRun(3); checkNumberOfProjects(projectService, { inferredProjects: 1 }); }); @@ -2406,7 +2421,7 @@ namespace ts.projectSystem { assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state"); host.reloadFS([f1, f2, configWithExclude]); - + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { configuredProjects: 1 }); assert.isTrue(project.languageServiceEnabled, "Language service enabled"); assert.equal(lastEvent.data.project, project, "project"); @@ -2783,6 +2798,7 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, app.path, config1.path]); host.reloadFS([libES5, libES2015Promise, app, config2]); + host.checkTimeoutQueueLengthAndRun(2); projectService.checkNumberOfProjects({ configuredProjects: 1 }); checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, libES2015Promise.path, app.path, config2.path]); diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index ceae7582ace..b29a619b1b4 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -138,6 +138,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [file1.path, jquery.path, tsconfig.path]); }); @@ -337,6 +338,8 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path, file3.path, lodash.path, react.path]); }); @@ -460,6 +463,8 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path, file3.path, commander.path, express.path, jquery.path, moment.path]); }); @@ -538,7 +543,7 @@ namespace ts.projectSystem { for (const f of typingFiles) { assert.isTrue(host.fileExists(f.path), `expected file ${f.path} to exist`); } - + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [lodashJs.path, commanderJs.path, file3.path, commander.path, express.path, jquery.path, moment.path, lodash.path]); }); @@ -641,7 +646,7 @@ namespace ts.projectSystem { assert.equal(installer.pendingRunRequests.length, 0, "expected no throttled requests"); installer.executePendingCommands(); - + host.checkTimeoutQueueLengthAndRun(3); // for 2 projects and 1 refreshing inferred project checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path, commander.path, jquery.path, lodash.path, cordova.path]); checkProjectActualFiles(p2, [file3.path, grunt.path, gulp.path]); }); @@ -695,6 +700,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); }); @@ -742,6 +748,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); }); @@ -788,6 +795,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); }); @@ -826,9 +834,10 @@ namespace ts.projectSystem { installer.checkPendingCommands(/*expectedCount*/ 0); host.reloadFS([f, fixedPackageJson]); + host.checkTimeoutQueueLengthAndRun(2); // To refresh the project and refresh inferred projects // expected install request installer.installAll(/*expectedCount*/ 1); - + host.checkTimeoutQueueLengthAndRun(2); service.checkNumberOfProjects({ inferredProjects: 1 }); checkProjectActualFiles(service.inferredProjects[0], [f.path, commander.path]); }); @@ -950,8 +959,7 @@ namespace ts.projectSystem { endOffset: 0 } }); - host.checkTimeoutQueueLength(1); - host.runQueuedTimeoutCallbacks(); + host.checkTimeoutQueueLengthAndRun(2); // This enqueues the updategraph and refresh inferred projects const version2 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion(); assert.equal(version1, version2, "set of unresolved imports should not change"); }); @@ -1132,6 +1140,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); assert.isTrue(seenTelemetryEvent); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { inferredProjects: 1 }); checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]); }); @@ -1185,6 +1194,7 @@ namespace ts.projectSystem { assert.isTrue(!!endEvent); assert.isTrue(beginEvent.eventId === endEvent.eventId); assert.isTrue(endEvent.installSuccess); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { inferredProjects: 1 }); checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]); }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9aa627e75a1..6d9be7a6520 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -358,6 +358,8 @@ namespace ts.server { private static safelist: SafeList = defaultTypeSafeList; private changedFiles: ScriptInfo[]; + private pendingProjectUpdates = createMap(); + private pendingInferredProjectUpdate: boolean; readonly toCanonicalFileName: (f: string) => string; @@ -444,8 +446,44 @@ namespace ts.server { break; } project.markAsDirty(); - // TODO: (sheetalkamat) Schedule Update graph instead of immediate - project.updateGraph(); + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + } + + private delayInferredProjectsRefresh() { + this.pendingInferredProjectUpdate = true; + this.throttledOperations.schedule("*refreshInferredProjects*", /*delay*/ 250, () => { + if (this.pendingProjectUpdates.size !== 0) { + this.delayInferredProjectsRefresh(); + } + else if (this.pendingInferredProjectUpdate) { + this.pendingInferredProjectUpdate = false; + this.refreshInferredProjects(); + } + }); + } + + private delayUpdateProjectGraph(project: Project) { + const projectName = project.getProjectName(); + this.pendingProjectUpdates.set(projectName, project); + this.throttledOperations.schedule(projectName, /*delay*/ 250, () => { + const project = this.pendingProjectUpdates.get(projectName); + if (project) { + this.pendingProjectUpdates.delete(projectName); + project.updateGraph(); + } + }); + } + + delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) { + this.delayUpdateProjectGraph(project); + this.delayInferredProjectsRefresh(); + } + + private delayUpdateProjectGraphs(projects: Project[]) { + for (const project of projects) { + this.delayUpdateProjectGraph(project); + } + this.delayInferredProjectsRefresh(); } setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions): void { @@ -457,9 +495,9 @@ namespace ts.server { for (const proj of this.inferredProjects) { proj.setCompilerOptions(this.compilerOptionsForInferredProjects); proj.compileOnSaveEnabled = projectCompilerOptions.compileOnSave; + proj.markAsDirty(); } - // TODO: (sheetalkamat) (ensure updated projects) - this.updateProjectGraphs(this.inferredProjects); + this.delayUpdateProjectGraphs(this.inferredProjects); } stopWatchingDirectory(directory: string) { @@ -479,14 +517,17 @@ namespace ts.server { getDefaultProjectForFile(fileName: NormalizedPath, refreshInferredProjects: boolean) { if (refreshInferredProjects) { - // TODO: (sheetalkamat) needs to update inferred projects too ? this.ensureInferredProjectsUpToDate(); } const scriptInfo = this.getScriptInfoForNormalizedPath(fileName); return scriptInfo && scriptInfo.getDefaultProject(); } - private ensureInferredProjectsUpToDate() { + /** + * Ensures the project structures are upto date + * @param refreshInferredProjects when true updates the inferred projects even if there is no pending work + */ + private ensureInferredProjectsUpToDate(refreshInferredProjects?: boolean) { if (this.changedFiles) { let projectsToUpdate: Project[]; if (this.changedFiles.length === 1) { @@ -499,10 +540,20 @@ namespace ts.server { projectsToUpdate = projectsToUpdate.concat(f.containingProjects); } } - this.updateProjectGraphs(projectsToUpdate); this.changedFiles = undefined; + this.updateProjectGraphs(projectsToUpdate); + } + + if (this.pendingProjectUpdates.size !== 0) { + const projectsToUpdate = arrayFrom(this.pendingProjectUpdates.values()); + this.pendingProjectUpdates.clear(); + this.updateProjectGraphs(projectsToUpdate); + } + + if (this.pendingInferredProjectUpdate || refreshInferredProjects) { + this.pendingInferredProjectUpdate = false; + this.refreshInferredProjects(); } - // TODO: (sheetalkamat) should also refresh inferred projects here since we are postponing the changes in closed files } private findContainingExternalProject(fileName: NormalizedPath): ExternalProject { @@ -526,15 +577,11 @@ namespace ts.server { } private updateProjectGraphs(projects: Project[]) { - let shouldRefreshInferredProjects = false; for (const p of projects) { if (!p.updateGraph()) { - shouldRefreshInferredProjects = true; + this.pendingInferredProjectUpdate = true; } } - if (shouldRefreshInferredProjects) { - this.refreshInferredProjects(); - } } private onSourceFileChanged(fileName: NormalizedPath, eventKind: FileWatcherEventKind) { @@ -559,7 +606,7 @@ namespace ts.server { // file has been changed which might affect the set of referenced files in projects that include // this file and set of inferred projects info.reloadFromFile(); - this.recordChangedFile(info); + this.delayUpdateProjectGraphs(info.containingProjects); } } } @@ -580,36 +627,28 @@ namespace ts.server { info.detachAllProjects(); - // TODO: (sheetalkamat) Schedule this too ? // update projects to make sure that set of referenced files is correct - this.updateProjectGraphs(containingProjects); + this.delayUpdateProjectGraphs(containingProjects); - if (!this.eventHandler) { - return; - } + // TODO: (sheetalkamat) Someway to send this event so that error checks are updated? + // if (!this.eventHandler) { + // return; + // } - for (const openFile of this.openFiles) { - this.eventHandler({ - eventName: ContextEvent, - data: { project: openFile.getDefaultProject(), fileName: openFile.fileName } - }); - } + // for (const openFile of this.openFiles) { + // this.eventHandler({ + // eventName: ContextEvent, + // data: { project: openFile.getDefaultProject(), fileName: openFile.fileName } + // }); + // } } - - this.printProjects(); } private onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) { this.logger.info(`Type root file ${fileName} changed`); project.getCachedServerHost().addOrDeleteFileOrFolder(fileName); project.updateTypes(); - // TODO: (sheetalkamat) schedule project update and referesh inferred projects instead - - //this.throttledOperations.schedule(project.getConfigFilePath() + " * type root", /*delay*/ 250, () => { - // this.reloadConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?) - project.updateGraph(); - this.refreshInferredProjects(); - //}); + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } /** @@ -650,10 +689,7 @@ namespace ts.server { error.code !== Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files.code); } this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader); - // TODO: (sheetalkamat) schedule the update graph - if (!project.updateGraph()) { - this.refreshInferredProjects(); - } + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) { @@ -664,16 +700,14 @@ namespace ts.server { // Reload the configured projects for these open files in the project as // they could be held up by another config file somewhere in the parent directory const openFilesInProject = this.getOrphanFiles(); - // TODO: (sheetalkamat) can this be scheduled too - this.reloadConfiguredProjectForFiles(openFilesInProject); + this.reloadConfiguredProjectForFiles(openFilesInProject, project => { project.pendingReload = true; this.delayUpdateProjectGraph(project); }); + this.delayInferredProjectsRefresh(); } else { this.logger.info(`Config file changed: ${configFileName}`); - // TODO: (sheetalkamat) This should be scheduled and marked for reload of configured project since we dont want to updating this on every change - this.reloadConfiguredProject(project); - this.reportConfigFileDiagnostics(configFileName, project.getGlobalProjectErrors(), /*triggerFile*/ configFileName); + project.pendingReload = true; + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } - this.refreshInferredProjects(); } /** @@ -698,6 +732,8 @@ namespace ts.server { this.logger.info(`remove project: ${project.getRootFiles().toString()}`); project.close(); + // Remove the project from pending project updates + this.pendingProjectUpdates.delete(project.getProjectName()); switch (project.projectKind) { case ProjectKind.External: @@ -1249,7 +1285,8 @@ namespace ts.server { * Read the config file of the project again and update the project * @param project */ - private reloadConfiguredProject(project: ConfiguredProject) { + /* @internal */ + reloadConfiguredProject(project: ConfiguredProject) { // At this point, there is no reason to not have configFile in the host // note: the returned "success" is true does not mean the "configFileErrors" is empty. @@ -1257,7 +1294,8 @@ namespace ts.server { // regardless the "success" here is true or not. const host = project.getCachedServerHost(); host.clearCache(); - const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath(), host); + const configFileName = project.getConfigFilePath(); + const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, host); project.configFileSpecs = configFileSpecs; if (!success) { // reset project settings to default @@ -1266,6 +1304,7 @@ namespace ts.server { else { this.updateConfiguredProject(project, projectOptions, configFileErrors); } + this.reportConfigFileDiagnostics(configFileName, project.getGlobalProjectErrors(), /*triggerFile*/ configFileName); } /** @@ -1405,11 +1444,11 @@ namespace ts.server { */ reloadProjects() { this.logger.info("reload projects."); - this.reloadConfiguredProjectForFiles(this.openFiles); + this.reloadConfiguredProjectForFiles(this.openFiles, project => this.reloadConfiguredProject(project)); this.refreshInferredProjects(); } - reloadConfiguredProjectForFiles(openFiles: ScriptInfo[]) { + reloadConfiguredProjectForFiles(openFiles: ScriptInfo[], reload: (project: ConfiguredProject) => void) { const mapUpdatedProjects = createMap(); // try to reload config file for all open files for (const info of openFiles) { @@ -1425,7 +1464,7 @@ namespace ts.server { mapUpdatedProjects.set(configFileName, true); } else if (!mapUpdatedProjects.has(configFileName)) { - this.reloadConfiguredProject(project); + reload(project); mapUpdatedProjects.set(configFileName, true); } } @@ -1437,7 +1476,7 @@ namespace ts.server { * It is called on the premise that all the configured projects are * up to date. */ - refreshInferredProjects() { + private refreshInferredProjects() { this.logger.info("updating project structure from ..."); this.printProjects(); @@ -1547,17 +1586,6 @@ namespace ts.server { return files; } - private recordChangedFile(scriptInfo: ScriptInfo) { - if (!this.changedFiles) { - this.changedFiles = [scriptInfo]; - } - else if (this.changedFiles.indexOf(scriptInfo) < 0) { - this.changedFiles.push(scriptInfo); - } - // TODO: (sheetalkamat) Schedule the project update graphs - this.updateProjectGraphs(scriptInfo.containingProjects); - } - /* @internal */ applyChangesInOpenFiles(openFiles: protocol.ExternalFile[], changedFiles: protocol.ChangedOpenFile[], closedFiles: string[]): void { if (openFiles) { @@ -1578,7 +1606,12 @@ namespace ts.server { const change = file.changes[i]; scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText); } - this.recordChangedFile(scriptInfo); + if (!this.changedFiles) { + this.changedFiles = [scriptInfo]; + } + else if (this.changedFiles.indexOf(scriptInfo) < 0) { + this.changedFiles.push(scriptInfo); + } } } @@ -1590,8 +1623,7 @@ namespace ts.server { // if files were open or closed then explicitly refresh list of inferred projects // otherwise if there were only changes in files - record changed files in `changedFiles` and defer the update if (openFiles || closedFiles) { - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.refreshInferredProjects(); + this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); } } @@ -1614,8 +1646,7 @@ namespace ts.server { } this.externalProjectToConfiguredProjectMap.delete(fileName); if (shouldRefreshInferredProjects && !suppressRefresh) { - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.refreshInferredProjects(); + this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); } } else { @@ -1624,8 +1655,7 @@ namespace ts.server { if (externalProject) { this.removeProject(externalProject); if (!suppressRefresh) { - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.refreshInferredProjects(); + this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); } } } @@ -1649,8 +1679,7 @@ namespace ts.server { this.closeExternalProject(externalProjectName, /*suppressRefresh*/ true); }); - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.refreshInferredProjects(); + this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); } /** Makes a filename safe to insert in a RegExp */ @@ -1844,8 +1873,7 @@ namespace ts.server { this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition); } if (!suppressRefreshOfInferredProjects) { - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.refreshInferredProjects(); + this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); } } } diff --git a/src/server/project.ts b/src/server/project.ts index e3e7de78eef..5b78f1f6630 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -303,7 +303,11 @@ namespace ts.server { // if we have a program - release all files that are enlisted in program for (const f of this.program.getSourceFiles()) { const info = this.projectService.getScriptInfo(f.fileName); - info.detachFromProject(this); + // We might not find the script info in case its not associated with the project any more + // and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk) + if (info) { + info.detachFromProject(this); + } } } if (!this.program || !this.languageServiceEnabled) { @@ -555,7 +559,6 @@ namespace ts.server { * @returns: true if set of files in the project stays the same and false - otherwise. */ updateGraph(): boolean { - // TODO: (sheetalkamat) If reload scheduled do that before updating the graph this.lsHost.startRecordingFilesWithChangedResolutions(); let hasChanges = this.updateGraphWorker(); @@ -662,8 +665,7 @@ namespace ts.server { // When a missing file is created, we should update the graph. this.markAsDirty(); - // TODO: (sheetalkamat) schedule the update graph instead of doing it right away - this.updateGraph(); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } }); this.missingFilesMap.set(missingFilePath, fileWatcher); @@ -980,6 +982,9 @@ namespace ts.server { private typeRootsWatchers: FileWatcher[]; readonly canonicalConfigFilePath: NormalizedPath; + /* @internal */ + pendingReload: boolean; + /*@internal*/ configFileSpecs: ConfigFileSpecs; @@ -1001,6 +1006,19 @@ namespace ts.server { this.enablePlugins(); } + /** + * Checks if the project has reload from disk pending, if thats pending, it reloads (and then updates graph as part of that) instead of just updating the graph + * @returns: true if set of files in the project stays the same and false - otherwise. + */ + updateGraph(): boolean { + if (this.pendingReload) { + this.pendingReload = false; + this.projectService.reloadConfiguredProject(this); + return true; + } + return super.updateGraph(); + } + getCachedServerHost() { return this.lsHost.host as CachedServerHost; } diff --git a/src/server/session.ts b/src/server/session.ts index 2656b6a20ba..a51e17cde65 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -459,15 +459,6 @@ namespace ts.server { } } - private updateProjectStructure(seq: number, matchSeq: (seq: number) => boolean, ms = 1500) { - this.host.setTimeout(() => { - if (matchSeq(seq)) { - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.projectService.refreshInferredProjects(); - } - }, ms); - } - private updateErrorCheck(next: NextStep, checkList: PendingErrorCheck[], seq: number, matchSeq: (seq: number) => boolean, ms = 1500, followMs = 200, requireOpen = true) { if (followMs > ms) { followMs = ms; @@ -1302,7 +1293,7 @@ namespace ts.server { scriptInfo.editContent(start, end, args.insertString); this.changeSeq++; } - this.updateProjectStructure(this.changeSeq, n => n === this.changeSeq); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } }