From 42e6860ba78417b636de076987bb507e5c85becf Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Sat, 14 Nov 2020 01:08:36 -0500 Subject: [PATCH] Share extended config watchers across projects in server New shared watcher map in ProjectService that stores callbacks per project to be invoked when the file watcher is triggered. The FileWatcher is created with the watch options of the first Project to watch the extended config. --- src/compiler/watchPublic.ts | 2 +- src/compiler/watchUtilities.ts | 67 ++++++++++++++++++++++++++++++++++ src/server/editorServices.ts | 17 ++++++++- src/server/project.ts | 35 ------------------ 4 files changed, 83 insertions(+), 38 deletions(-) diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 190ce83ab8bb5..6df785235250d 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -792,7 +792,7 @@ namespace ts { } function watchExtendedConfigFiles() { - const configFile = builderProgram.getCompilerOptions().configFile; + const { configFile } = builderProgram.getCompilerOptions(); if (configFile) { updateExtendedConfigFilesWatch( configFile, diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 3cfe35e62c644..53484a91c9d10 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -282,6 +282,73 @@ namespace ts { ); } + export interface SharedExtendedConfigFileWatcher

{ + watcher: FileWatcher; + callbacks: ESMap; + } + + export function updateSharedExtendedConfigFilesWatch

( + project: P, + projectCallback: FileWatcherCallback, + configFile: TsConfigSourceFile, + sharedExtendedConfigFilesMap: ESMap>, + watchFactory: WatchFactory, + watchOptions: WatchOptions | undefined + ) { + const extendedSourceFiles = configFile.extendedSourceFiles || emptyArray; + const newSharedExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, identity, returnTrue); + + sharedExtendedConfigFilesMap.forEach((existingWatcher, key) => { + if (newSharedExtendedConfigFilesMap.has(key)) { + existingWatcher.callbacks.set(project, projectCallback); + } + else { + existingWatcher.callbacks.delete(project); + if (existingWatcher.callbacks.size === 0) { + sharedExtendedConfigFilesMap.delete(key); + closeFileWatcherOf(existingWatcher); + } + } + }); + + newSharedExtendedConfigFilesMap.forEach((_true, extendedConfigPath) => { + if (!sharedExtendedConfigFilesMap.has(extendedConfigPath)) { + const newWatcher = createSharedExtendedConfigFileWatcher(extendedConfigPath); + sharedExtendedConfigFilesMap.set(extendedConfigPath, newWatcher); + } + }); + + function createSharedExtendedConfigFileWatcher(extendedConfigPath: string) { + const callbacks = new Map(); + callbacks.set(project, projectCallback); + const watcher = watchFactory.watchFile( + extendedConfigPath, + invokeProjectCallbacks, + PollingInterval.High, + watchOptions, + WatchType.ExtendedConfigFile + ); + return { watcher, callbacks }; + + function invokeProjectCallbacks(fileName: string, eventKind: FileWatcherEventKind) { + return callbacks.forEach((callback) => callback(fileName, eventKind)); + } + } + } + + export function removeProjectFromSharedExtendedConfigFilesWatch

( + project: P, + sharedExtendedConfigFilesMap: ESMap> + ) { + sharedExtendedConfigFilesMap.forEach((existingWatcher, key) => { + existingWatcher.callbacks.delete(project); + if (existingWatcher.callbacks.size === 0) { + sharedExtendedConfigFilesMap.delete(key); + closeFileWatcherOf(existingWatcher); + } + }); + } + /** * Updates the existing missing file watches with the new set of missing files after new program is created */ diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 547a1884919a3..3dc7c9c9426bb 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -756,6 +756,9 @@ namespace ts.server { /*@internal*/ readonly watchFactory: WatchFactory; + /*@internal*/ + private sharedExtendedConfigFilesMap = new Map>(); + /*@internal*/ readonly packageJsonCache: PackageJsonCache; /*@internal*/ @@ -1385,6 +1388,7 @@ namespace ts.server { project.print(/*writeProjectFileNames*/ true); project.close(); + removeProjectFromSharedExtendedConfigFilesWatch(project, this.sharedExtendedConfigFilesMap); if (Debug.shouldAssert(AssertionLevel.Normal)) { this.filenameToScriptInfo.forEach(info => Debug.assert( !info.isAttached(project), @@ -2157,15 +2161,24 @@ namespace ts.server { const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, compilerOptions, parsedCommandLine.fileNames, fileNamePropertyReader); if (lastFileExceededProgramSize) { project.disableLanguageService(lastFileExceededProgramSize); - project.stopWatchingExtendedConfigFiles(); project.stopWatchingWildCards(); + removeProjectFromSharedExtendedConfigFilesWatch(project, this.sharedExtendedConfigFilesMap); } else { project.setCompilerOptions(compilerOptions); project.setWatchOptions(parsedCommandLine.watchOptions); project.enableLanguageService(); - project.watchExtendedConfigFiles(); project.watchWildcards(new Map(getEntries(parsedCommandLine.wildcardDirectories!))); // TODO: GH#18217 + if (compilerOptions.configFile) { + updateSharedExtendedConfigFilesWatch( + project, + (fileName) => this.onExtendedConfigChangedForConfiguredProject(project, fileName), + compilerOptions.configFile, + this.sharedExtendedConfigFilesMap, + this.watchFactory, + this.hostConfiguration.watchOptions, + ); + } } project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides); const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles()); diff --git a/src/server/project.ts b/src/server/project.ts index 1d214b34b5afa..db704b7dfa865 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -2055,8 +2055,6 @@ namespace ts.server { export class ConfiguredProject extends Project { /* @internal */ configFileWatcher: FileWatcher | undefined; - /* @internal */ - private extendedConfigFileWatchers: ESMap | undefined; private directoriesWatchedForWildcards: ESMap | undefined; readonly canonicalConfigFilePath: NormalizedPath; @@ -2270,38 +2268,6 @@ namespace ts.server { this.projectErrors = projectErrors; } - /* @internal */ - createExtendedConfigFileWatcher(extendedConfigFile: string): FileWatcher { - return this.projectService.watchFactory.watchFile( - extendedConfigFile, - (fileName) => this.projectService.onExtendedConfigChangedForConfiguredProject(this, fileName), - PollingInterval.High, - this.projectService.getWatchOptions(this), - WatchType.ExtendedConfigFile, - this - ); - } - - /* @internal */ - watchExtendedConfigFiles() { - const configFile = this.getCompilerOptions().configFile; - if (configFile) { - updateExtendedConfigFilesWatch( - configFile, - this.extendedConfigFileWatchers || (this.extendedConfigFileWatchers = new Map()), - (extendedConfigFile) => this.createExtendedConfigFileWatcher(extendedConfigFile), - ); - } - } - - /* @internal */ - stopWatchingExtendedConfigFiles() { - if (this.extendedConfigFileWatchers) { - clearMap(this.extendedConfigFileWatchers, closeFileWatcher); - this.extendedConfigFileWatchers = undefined; - } - } - /*@internal*/ watchWildcards(wildcardDirectories: ESMap) { updateWatchingWildcardDirectories( @@ -2326,7 +2292,6 @@ namespace ts.server { this.configFileWatcher = undefined; } - this.stopWatchingExtendedConfigFiles(); this.stopWatchingWildCards(); this.configFileSpecs = undefined; this.openFileWatchTriggered.clear();