From 6ca65b71b448c58a74ee049dab5a482724fb08fe Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 9 Feb 2018 12:23:02 -0800 Subject: [PATCH 1/3] Refactoring project updates in openFile --- src/server/editorServices.ts | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5cdd3b05889..e1b18ed2b20 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2038,7 +2038,6 @@ namespace ts.server { 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); @@ -2049,8 +2048,15 @@ namespace ts.server { project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { project = this.createConfiguredProject(configFileName); - // Send the event only if the project got created as part of this open request - sendConfigFileDiagEvent = true; + // Send the event only if the project got created as part of this open request and info is part of the project + if (info.isOrphan()) { + // Since the file isnt part of configured project, do not send config file info + configFileName = undefined; + } + else { + configFileErrors = project.getAllProjectErrors(); + this.sendConfigFileDiagEvent(project as ConfiguredProject, fileName); + } } else { // Ensure project is ready to check if it contains opened script info @@ -2058,30 +2064,20 @@ namespace ts.server { } } } - if (project && !project.languageServiceEnabled) { - // if project language service is disabled then we create a program only for open files. - // this means that project should be marked as dirty to force rebuilding of the program - // on the next request - project.markAsDirty(); - } + + // Project we have at this point is going to be updated since its either found through + // - external project search, which updates the project before checking if info is present in it + // - configured project - either created or updated to ensure we know correct status of info // At this point if file is part of any any configured or external project, then it would be present in the containing projects // So if it still doesnt have any containing projects, it needs to be part of inferred project if (info.isOrphan()) { - // Since the file isnt part of configured project, do not send config file event - configFileName = undefined; - sendConfigFileDiagEvent = false; - this.assignOrphanScriptInfoToInferredProject(info, projectRootPath); } + Debug.assert(!info.isOrphan()); this.openFiles.set(info.path, projectRootPath); - if (sendConfigFileDiagEvent) { - configFileErrors = project.getAllProjectErrors(); - this.sendConfigFileDiagEvent(project as ConfiguredProject, fileName); - } - // Remove the configured projects that have zero references from open files. // This was postponed from closeOpenFile to after opening next file, // so that we can reuse the project if we need to right away From e702d90cfebc42f8acdf417baa261d2a89152eba Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 12 Feb 2018 13:11:13 -0800 Subject: [PATCH 2/3] Repro scenario for finding no project of #20629 --- .../unittests/tsserverProjectSystem.ts | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 08b04eda223..7094b7336e4 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2904,6 +2904,83 @@ namespace ts.projectSystem { tags: [] }); }); + + it("files opened, closed affecting multiple projects", () => { + const file: FileOrFolder = { + path: "/a/b/projects/config/file.ts", + content: `import {a} from "../files/file1"; export let b = a;` + }; + const config: FileOrFolder = { + path: "/a/b/projects/config/tsconfig.json", + content: "" + }; + const filesFile1: FileOrFolder = { + path: "/a/b/projects/files/file1.ts", + content: "export let a = 10;" + }; + const filesFile2: FileOrFolder = { + path: "/a/b/projects/files/file2.ts", + content: "export let aa = 10;" + }; + + const files = [config, file, filesFile1, filesFile2, libFile]; + const host = createServerHost(files); + const session = createSession(host); + // Create configured project + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: file.path + } + }); + + const projectService = session.getProjectService(); + const configuredProject = projectService.configuredProjects.get(config.path); + verifyConfiguredProject(); + + // open files/file1 = should not create another project + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: filesFile1.path + } + }); + verifyConfiguredProject(); + + // Close the file = should still have project + session.executeCommandSeq({ + command: protocol.CommandTypes.Close, + arguments: { + file: file.path + } + }); + verifyConfiguredProject(); + + // Open files/file2 - should create inferred project and close configured project + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: filesFile2.path + } + }); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [libFile.path, filesFile2.path]); + + // Actions on file1 would result in assert + session.executeCommandSeq({ + command: protocol.CommandTypes.Occurrences, + arguments: { + file: filesFile1.path, + line: 1, + offset: filesFile1.content.indexOf("a") + } + }); + + function verifyConfiguredProject() { + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProject, [file.path, filesFile1.path, libFile.path, config.path]); + } + }); }); describe("tsserverProjectSystem Proper errors", () => { From d9d98cf11a6c522f0b3e40f98aaf7df3fc0f0d85 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 12 Feb 2018 14:55:58 -0800 Subject: [PATCH 3/3] Handle the delayed updates due to user action correctly when ensuring the project structure is upto date Fixes #20629 --- .../unittests/tsserverProjectSystem.ts | 3 - src/server/editorServices.ts | 165 ++++++------------ src/server/project.ts | 32 ++-- src/server/session.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 25 +-- 5 files changed, 82 insertions(+), 145 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 7094b7336e4..8d84732f3aa 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2111,9 +2111,6 @@ namespace ts.projectSystem { /*closedFiles*/ undefined); checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const changedFiles = projectService.getChangedFiles_TestOnly(); - assert(changedFiles && changedFiles.length === 1, `expected 1 changed file, got ${JSON.stringify(changedFiles && changedFiles.length || 0)}`); - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkNumberOfProjects(projectService, { inferredProjects: 2 }); }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e1b18ed2b20..3d0d623c6d4 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -376,9 +376,9 @@ namespace ts.server { private safelist: SafeList = defaultTypeSafeList; private legacySafelist: { [key: string]: string } = {}; - private changedFiles: ScriptInfo[]; private pendingProjectUpdates = createMap(); - private pendingInferredProjectUpdate: boolean; + /* @internal */ + pendingEnsureProjectForOpenFiles: boolean; readonly currentDirectory: string; readonly toCanonicalFileName: (f: string) => string; @@ -483,11 +483,6 @@ namespace ts.server { return getNormalizedAbsolutePath(fileName, this.host.getCurrentDirectory()); } - /* @internal */ - getChangedFiles_TestOnly() { - return this.changedFiles; - } - /* @internal */ ensureInferredProjectsUpToDate_TestOnly() { this.ensureProjectStructuresUptoDate(); @@ -552,19 +547,18 @@ namespace ts.server { this.typingsCache.deleteTypingsForProject(response.projectName); break; } - this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project); } - private delayInferredProjectsRefresh() { - this.pendingInferredProjectUpdate = true; - this.throttledOperations.schedule("*refreshInferredProjects*", /*delay*/ 250, () => { + private delayEnsureProjectForOpenFiles() { + this.pendingEnsureProjectForOpenFiles = true; + this.throttledOperations.schedule("*ensureProjectForOpenFiles*", /*delay*/ 250, () => { if (this.pendingProjectUpdates.size !== 0) { - this.delayInferredProjectsRefresh(); + this.delayEnsureProjectForOpenFiles(); } else { - if (this.pendingInferredProjectUpdate) { - this.pendingInferredProjectUpdate = false; - this.refreshInferredProjects(); + if (this.pendingEnsureProjectForOpenFiles) { + this.ensureProjectForOpenFiles(); } // Send the event to notify that there were background project updates // send current list of open files @@ -574,6 +568,7 @@ namespace ts.server { } private delayUpdateProjectGraph(project: Project) { + project.markAsDirty(); const projectName = project.getProjectName(); this.pendingProjectUpdates.set(projectName, project); this.throttledOperations.schedule(projectName, /*delay*/ 250, () => { @@ -603,17 +598,16 @@ namespace ts.server { } /* @internal */ - delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) { - project.markAsDirty(); + delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project: Project) { this.delayUpdateProjectGraph(project); - this.delayInferredProjectsRefresh(); + this.delayEnsureProjectForOpenFiles(); } - private delayUpdateProjectGraphs(projects: Project[]) { + private delayUpdateProjectGraphs(projects: ReadonlyArray) { for (const project of projects) { this.delayUpdateProjectGraph(project); } - this.delayInferredProjectsRefresh(); + this.delayEnsureProjectForOpenFiles(); } setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions, projectRootPath?: string): void { @@ -632,7 +626,6 @@ namespace ts.server { this.compilerOptionsForInferredProjects = compilerOptions; } - const projectsToUpdate: Project[] = []; for (const project of this.inferredProjects) { // Only update compiler options in the following cases: // - Inferred projects without a projectRootPath, if the new options do not apply to @@ -648,11 +641,11 @@ namespace ts.server { project.setCompilerOptions(compilerOptions); project.compileOnSaveEnabled = compilerOptions.compileOnSave; project.markAsDirty(); - projectsToUpdate.push(project); + this.delayUpdateProjectGraph(project); } } - this.delayUpdateProjectGraphs(projectsToUpdate); + this.delayEnsureProjectForOpenFiles(); } findProject(projectName: string): Project | undefined { @@ -687,41 +680,27 @@ namespace ts.server { /** * Ensures the project structures are upto date * This means, - * - if there are changedFiles (the files were updated but their containing project graph was not upto date), - * their project graph is updated - * - If there are pendingProjectUpdates (scheduled to be updated with delay so they can batch update the graph if there are several changes in short time span) - * their project graph is updated - * - If there were project graph updates and/or there was pending inferred project update and/or called forced the inferred project structure refresh - * Inferred projects are created/updated/deleted based on open files states - * @param forceInferredProjectsRefresh when true updates the inferred projects even if there is no pending work to update the files/project structures + * - we go through all the projects and update them if they are dirty + * - if updates reflect some change in structure or there was pending request to ensure projects for open files + * ensure that each open script info has project */ - private ensureProjectStructuresUptoDate(forceInferredProjectsRefresh?: boolean) { - if (this.changedFiles) { - let projectsToUpdate: Project[]; - if (this.changedFiles.length === 1) { - // simpliest case - no allocations - projectsToUpdate = this.changedFiles[0].containingProjects; - } - else { - projectsToUpdate = []; - for (const f of this.changedFiles) { - addRange(projectsToUpdate, f.containingProjects); - } - } - this.changedFiles = undefined; - this.updateProjectGraphs(projectsToUpdate); - } + private ensureProjectStructuresUptoDate() { + let hasChanges = this.pendingEnsureProjectForOpenFiles; + this.pendingProjectUpdates.clear(); + const updateGraph = (project: Project) => { + hasChanges = this.updateProjectIfDirty(project) || hasChanges; + }; - if (this.pendingProjectUpdates.size !== 0) { - const projectsToUpdate = arrayFrom(this.pendingProjectUpdates.values()); - this.pendingProjectUpdates.clear(); - this.updateProjectGraphs(projectsToUpdate); + this.externalProjects.forEach(updateGraph); + this.configuredProjects.forEach(updateGraph); + this.inferredProjects.forEach(updateGraph); + if (hasChanges) { + this.ensureProjectForOpenFiles(); } + } - if (this.pendingInferredProjectUpdate || forceInferredProjectsRefresh) { - this.pendingInferredProjectUpdate = false; - this.refreshInferredProjects(); - } + private updateProjectIfDirty(project: Project) { + return project.dirty && project.updateGraph(); } getFormatCodeOptions(file?: NormalizedPath) { @@ -735,14 +714,6 @@ namespace ts.server { return formatCodeSettings || this.hostConfiguration.formatCodeOptions; } - private updateProjectGraphs(projects: Project[]) { - for (const p of projects) { - if (!p.updateGraph()) { - this.pendingInferredProjectUpdate = true; - } - } - } - private onSourceFileChanged(fileName: NormalizedPath, eventKind: FileWatcherEventKind) { const info = this.getScriptInfoForNormalizedPath(fileName); if (!info) { @@ -770,8 +741,6 @@ namespace ts.server { private handleDeletedFile(info: ScriptInfo) { this.stopWatchingScriptInfo(info); - // TODO: handle isOpen = true case - if (!info.isScriptOpen()) { this.deleteScriptInfo(info); @@ -808,7 +777,7 @@ namespace ts.server { // Reload is pending, do the reload if (project.pendingReload !== ConfigFileProgramReloadLevel.Full) { project.pendingReload = ConfigFileProgramReloadLevel.Partial; - this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project); } }, flags, @@ -1317,7 +1286,11 @@ namespace ts.server { this.logger.info("Open files: "); this.openFiles.forEach((projectRootPath, path) => { - this.logger.info(`\tFileName: ${this.getScriptInfoForPath(path as Path).fileName} ProjectRootPath: ${projectRootPath}`); + const info = this.getScriptInfoForPath(path as Path); + this.logger.info(`\tFileName: ${info.fileName} ProjectRootPath: ${projectRootPath}`); + if (writeProjectFileNames) { + this.logger.info(`\t\tProjects: ${info.containingProjects.map(p => p.getProjectName())}`); + } }); this.logger.endGroup(); @@ -1896,7 +1869,7 @@ namespace ts.server { // Reload Projects this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false, returnTrue); - this.refreshInferredProjects(); + this.ensureProjectForOpenFiles(); } private delayReloadConfiguredProjectForFiles(configFileExistenceInfo: ConfigFileExistenceInfo, ignoreIfNotRootOfInferredProject: boolean) { @@ -1908,7 +1881,7 @@ namespace ts.server { isRootOfInferredProject => isRootOfInferredProject : // Reload open files if they are root of inferred project returnTrue // Reload all the open files impacted by config file ); - this.delayInferredProjectsRefresh(); + this.delayEnsureProjectForOpenFiles(); } /** @@ -1992,8 +1965,8 @@ namespace ts.server { * This will go through open files and assign them to inferred project if open file is not part of any other project * After that all the inferred project graphs are updated */ - private refreshInferredProjects() { - this.logger.info("refreshInferredProjects: updating project structure from ..."); + private ensureProjectForOpenFiles() { + this.logger.info("Structure before ensureProjectForOpenFiles:"); this.printProjects(); this.openFiles.forEach((projectRootPath, path) => { @@ -2007,12 +1980,10 @@ namespace ts.server { this.removeRootOfInferredProjectIfNowPartOfOtherProject(info); } }); + this.pendingEnsureProjectForOpenFiles = false; + this.inferredProjects.forEach(p => this.updateProjectIfDirty(p)); - for (const p of this.inferredProjects) { - p.updateGraph(); - } - - this.logger.info("refreshInferredProjects: updated project structure ..."); + this.logger.info("Structure after ensureProjectForOpenFiles:"); this.printProjects(); } @@ -2149,11 +2120,6 @@ namespace ts.server { this.closeClientFile(file); } } - // 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) { - this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); - } } /* @internal */ @@ -2163,49 +2129,33 @@ namespace ts.server { const change = changes[i]; scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText); } - if (!this.changedFiles) { - this.changedFiles = [scriptInfo]; - } - else if (!contains(this.changedFiles, scriptInfo)) { - this.changedFiles.push(scriptInfo); - } } - private closeConfiguredProjectReferencedFromExternalProject(configFile: NormalizedPath): boolean { + private closeConfiguredProjectReferencedFromExternalProject(configFile: NormalizedPath) { const configuredProject = this.findConfiguredProjectByProjectName(configFile); if (configuredProject) { configuredProject.deleteExternalProjectReference(); if (!configuredProject.hasOpenRef()) { this.removeProject(configuredProject); - return true; + return; } } - return false; } - closeExternalProject(uncheckedFileName: string, suppressRefresh = false): void { + closeExternalProject(uncheckedFileName: string): void { const fileName = toNormalizedPath(uncheckedFileName); const configFiles = this.externalProjectToConfiguredProjectMap.get(fileName); if (configFiles) { - let shouldRefreshInferredProjects = false; for (const configFile of configFiles) { - if (this.closeConfiguredProjectReferencedFromExternalProject(configFile)) { - shouldRefreshInferredProjects = true; - } + this.closeConfiguredProjectReferencedFromExternalProject(configFile); } this.externalProjectToConfiguredProjectMap.delete(fileName); - if (shouldRefreshInferredProjects && !suppressRefresh) { - this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); - } } else { // close external project const externalProject = this.findExternalProjectByProjectName(uncheckedFileName); if (externalProject) { this.removeProject(externalProject); - if (!suppressRefresh) { - this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); - } } } } @@ -2218,17 +2168,15 @@ namespace ts.server { }); for (const externalProject of projects) { - this.openExternalProject(externalProject, /*suppressRefreshOfInferredProjects*/ true); + this.openExternalProject(externalProject); // delete project that is present in input list projectsToClose.delete(externalProject.projectFileName); } // close projects that were missing in the input list forEachKey(projectsToClose, externalProjectName => { - this.closeExternalProject(externalProjectName, /*suppressRefresh*/ true); + this.closeExternalProject(externalProjectName); }); - - this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); } /** Makes a filename safe to insert in a RegExp */ @@ -2349,7 +2297,7 @@ namespace ts.server { return excludedFiles; } - openExternalProject(proj: protocol.ExternalProject, suppressRefreshOfInferredProjects = false): void { + openExternalProject(proj: protocol.ExternalProject): void { // typingOptions has been deprecated and is only supported for backward compatibility // purposes. It should be removed in future releases - use typeAcquisition instead. if (proj.typingOptions && !proj.typeAcquisition) { @@ -2403,13 +2351,13 @@ namespace ts.server { } // some config files were added to external project (that previously were not there) // close existing project and later we'll open a set of configured projects for these files - this.closeExternalProject(proj.projectFileName, /*suppressRefresh*/ true); + this.closeExternalProject(proj.projectFileName); } else if (this.externalProjectToConfiguredProjectMap.get(proj.projectFileName)) { // this project used to include config files if (!tsConfigFiles) { // config files were removed from the project - close existing external project which in turn will close configured projects - this.closeExternalProject(proj.projectFileName, /*suppressRefresh*/ true); + this.closeExternalProject(proj.projectFileName); } else { // project previously had some config files - compare them with new set of files and close all configured projects that correspond to unused files @@ -2459,9 +2407,6 @@ namespace ts.server { this.externalProjectToConfiguredProjectMap.delete(proj.projectFileName); this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition, excludedFiles); } - if (!suppressRefreshOfInferredProjects) { - this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); - } } } } diff --git a/src/server/project.ts b/src/server/project.ts index 3094f1b0cb4..dc949b4b221 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -168,6 +168,9 @@ namespace ts.server { */ private projectStateVersion = 0; + /*@internal*/ + dirty = false; + /*@internal*/ hasChangedAutomaticTypeDirectiveNames = false; @@ -250,6 +253,7 @@ namespace ts.server { this.disableLanguageService(lastFileExceededProgramSize); } this.markAsDirty(); + this.projectService.pendingEnsureProjectForOpenFiles = true; } isKnownTypesPackageName(name: string): boolean { @@ -399,7 +403,7 @@ namespace ts.server { /*@internal*/ onInvalidatedResolution() { - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); + this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this); } /*@internal*/ @@ -417,7 +421,7 @@ namespace ts.server { /*@internal*/ onChangedAutomaticTypeDirectiveNames() { this.hasChangedAutomaticTypeDirectiveNames = true; - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); + this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this); } /*@internal*/ @@ -565,6 +569,7 @@ namespace ts.server { for (const root of this.rootFiles) { root.detachFromProject(this); } + this.projectService.pendingEnsureProjectForOpenFiles = true; this.rootFiles = undefined; this.rootFilesMap = undefined; @@ -748,7 +753,10 @@ namespace ts.server { } markAsDirty() { - this.projectStateVersion++; + if (!this.dirty) { + this.projectStateVersion++; + this.dirty = true; + } } /* @internal */ @@ -823,7 +831,9 @@ namespace ts.server { } const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasChanges); - if (this.setTypings(cachedTypings)) { + if (!arrayIsEqualTo(this.typingFiles, cachedTypings)) { + this.typingFiles = cachedTypings; + this.markAsDirty(); hasChanges = this.updateGraphWorker() || hasChanges; } } @@ -847,15 +857,6 @@ namespace ts.server { return include.filter(i => existing.indexOf(i) < 0); } - private setTypings(typings: SortedReadonlyArray): boolean { - if (arrayIsEqualTo(this.typingFiles, typings)) { - return false; - } - this.typingFiles = typings; - this.markAsDirty(); - return true; - } - private updateGraphWorker() { const oldProgram = this.program; Debug.assert(!this.isClosed(), "Called update graph worker of closed project"); @@ -864,6 +865,7 @@ namespace ts.server { this.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution(); this.resolutionCache.startCachingPerDirectoryResolution(); this.program = this.languageService.getProgram(); + this.dirty = false; this.resolutionCache.finishCachingPerDirectoryResolution(); // bump up the version if @@ -910,7 +912,7 @@ namespace ts.server { compareStringsCaseSensitive ); const elapsed = timestamp() - start; - this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`); + this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`); return hasChanges; } @@ -936,7 +938,7 @@ namespace ts.server { fileWatcher.close(); // When a missing file is created, we should update the graph. - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); + this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this); } }, WatchType.MissingFilePath, diff --git a/src/server/session.ts b/src/server/session.ts index 0704bde9e36..356ea0d254e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1769,7 +1769,7 @@ namespace ts.server { return this.requiredResponse(response); }, [CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => { - this.projectService.openExternalProject(request.arguments, /*suppressRefreshOfInferredProjects*/ false); + this.projectService.openExternalProject(request.arguments); // TODO: GH#20447 report errors return this.requiredResponse(/*response*/ true); }, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 90a73a823eb..81647a8d080 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7543,7 +7543,6 @@ declare namespace ts.server { */ updateGraph(): boolean; protected removeExistingTypings(include: string[]): string[]; - private setTypings(typings); private updateGraphWorker(); private detachScriptInfoFromProject(uncheckedFileName); private addMissingFileWatcher(missingFilePath); @@ -7779,9 +7778,7 @@ declare namespace ts.server { private readonly hostConfiguration; private safelist; private legacySafelist; - private changedFiles; private pendingProjectUpdates; - private pendingInferredProjectUpdate; readonly currentDirectory: string; readonly toCanonicalFileName: (f: string) => string; readonly host: ServerHost; @@ -7803,7 +7800,7 @@ declare namespace ts.server { toPath(fileName: string): Path; private loadTypesMap(); updateTypingsForProject(response: SetTypings | InvalidateCachedTypings | PackageInstalledResponse): void; - private delayInferredProjectsRefresh(); + private delayEnsureProjectForOpenFiles(); private delayUpdateProjectGraph(project); private sendProjectsUpdatedInBackgroundEvent(); private delayUpdateProjectGraphs(projects); @@ -7814,17 +7811,13 @@ declare namespace ts.server { /** * Ensures the project structures are upto date * This means, - * - if there are changedFiles (the files were updated but their containing project graph was not upto date), - * their project graph is updated - * - If there are pendingProjectUpdates (scheduled to be updated with delay so they can batch update the graph if there are several changes in short time span) - * their project graph is updated - * - If there were project graph updates and/or there was pending inferred project update and/or called forced the inferred project structure refresh - * Inferred projects are created/updated/deleted based on open files states - * @param forceInferredProjectsRefresh when true updates the inferred projects even if there is no pending work to update the files/project structures + * - we go through all the projects and update them if they are dirty + * - if updates reflect some change in structure or there was pending request to ensure projects for open files + * ensure that each open script info has project */ - private ensureProjectStructuresUptoDate(forceInferredProjectsRefresh?); + private ensureProjectStructuresUptoDate(); + private updateProjectIfDirty(project); getFormatCodeOptions(file?: NormalizedPath): FormatCodeSettings; - private updateProjectGraphs(projects); private onSourceFileChanged(fileName, eventKind); private handleDeletedFile(info); private onConfigChangedForConfiguredProject(project, eventKind); @@ -7937,7 +7930,7 @@ declare namespace ts.server { * This will go through open files and assign them to inferred project if open file is not part of any other project * After that all the inferred project graphs are updated */ - private refreshInferredProjects(); + private ensureProjectForOpenFiles(); /** * Open file whose contents is managed by the client * @param filename is absolute pathname @@ -7953,14 +7946,14 @@ declare namespace ts.server { closeClientFile(uncheckedFileName: string): void; private collectChanges(lastKnownProjectVersions, currentProjects, result); private closeConfiguredProjectReferencedFromExternalProject(configFile); - closeExternalProject(uncheckedFileName: string, suppressRefresh?: boolean): void; + closeExternalProject(uncheckedFileName: string): void; openExternalProjects(projects: protocol.ExternalProject[]): void; /** Makes a filename safe to insert in a RegExp */ private static readonly filenameEscapeRegexp; private static escapeFilenameForRegex(filename); resetSafeList(): void; applySafeList(proj: protocol.ExternalProject): NormalizedPath[]; - openExternalProject(proj: protocol.ExternalProject, suppressRefreshOfInferredProjects?: boolean): void; + openExternalProject(proj: protocol.ExternalProject): void; } }