Skip to content

Commit

Permalink
Refactor shared extended config map and watchers
Browse files Browse the repository at this point in the history
Remove all server-related utility functions/types from
watchUtilities. Store config-project mapping and config file watchers
inside ProjectService with new private methods to add or remove
projects.
  • Loading branch information
molisani committed Nov 19, 2020
1 parent 42e6860 commit 4224dd7
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 99 deletions.
67 changes: 0 additions & 67 deletions src/compiler/watchUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,73 +282,6 @@ namespace ts {
);
}

export interface SharedExtendedConfigFileWatcher<P> {
watcher: FileWatcher;
callbacks: ESMap<P, FileWatcherCallback>;
}

export function updateSharedExtendedConfigFilesWatch<P>(
project: P,
projectCallback: FileWatcherCallback,
configFile: TsConfigSourceFile,
sharedExtendedConfigFilesMap: ESMap<string, SharedExtendedConfigFileWatcher<P>>,
watchFactory: WatchFactory<WatchType, any>,
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<P, FileWatcherCallback>();
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<P>(
project: P,
sharedExtendedConfigFilesMap: ESMap<string, SharedExtendedConfigFileWatcher<P>>
) {
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
*/
Expand Down
83 changes: 51 additions & 32 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,9 @@ namespace ts.server {
readonly watchFactory: WatchFactory<WatchType, Project>;

/*@internal*/
private sharedExtendedConfigFilesMap = new Map<string, SharedExtendedConfigFileWatcher<Project>>();
private sharedExtendedConfigFileMap = createMultiMap<Path, ConfiguredProject>();
/*@internal*/
private sharedExtendedConfigFileWatchers = new Map<Path, FileWatcher>();

/*@internal*/
readonly packageJsonCache: PackageJsonCache;
Expand Down Expand Up @@ -1355,14 +1357,52 @@ namespace ts.server {
}

/*@internal*/
onExtendedConfigChangedForConfiguredProject(project: ConfiguredProject, extendedConfigFile: string) {
this.logExtendedConfigFileWatchUpdate(asNormalizedPath(extendedConfigFile), project.canonicalConfigFilePath, ConfigFileWatcherStatus.ReloadingFiles);
private updateSharedExtendedConfigFileMap(project: ConfiguredProject) {
const extendedSourceFiles = project.getCompilerOptions().configFile?.extendedSourceFiles || emptyArray;
extendedSourceFiles.forEach((extendedSourceFile: string) => {
const extendedConfigPath = this.toPath(extendedSourceFile);
if (!this.sharedExtendedConfigFileMap.has(extendedConfigPath)) {
const watcher = this.watchFactory.watchFile(
extendedConfigPath,
() => this.onSharedExtendedConfigChanged(extendedConfigPath),
PollingInterval.High,
this.hostConfiguration.watchOptions,
WatchType.ExtendedConfigFile
);
this.sharedExtendedConfigFileWatchers.set(extendedConfigPath, watcher);
}
const otherProjects = this.sharedExtendedConfigFileMap.get(extendedConfigPath);
if (!otherProjects || !otherProjects.includes(project)) {
this.sharedExtendedConfigFileMap.add(extendedConfigPath, project);
}
});
}

// Skip refresh if project is not yet loaded
if (project.isInitialLoadPending()) return;
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = `Change in extended config file ${extendedConfigFile} detected`;
this.delayUpdateProjectGraph(project);
/*@internal*/
private removeProjectFromSharedExtendedConfigFileMap(project: ConfiguredProject) {
for (const key of arrayFrom(this.sharedExtendedConfigFileMap.keys())) {
this.sharedExtendedConfigFileMap.remove(key, project);
const otherProjects = this.sharedExtendedConfigFileMap.get(key) || emptyArray;
if (otherProjects.length === 0) {
const watcher = this.sharedExtendedConfigFileWatchers.get(key);
if (watcher) {
watcher.close();
this.sharedExtendedConfigFileWatchers.delete(key);
}
}
}
}

/*@internal*/
private onSharedExtendedConfigChanged(extendedConfigPath: Path) {
const projects = this.sharedExtendedConfigFileMap.get(extendedConfigPath) || emptyArray;
projects.forEach((project: ConfiguredProject) => {
// Skip refresh if project is not yet loaded
if (project.isInitialLoadPending()) return;
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = `Change in extended config file ${extendedConfigPath} detected`;
this.delayUpdateProjectGraph(project);
});
}

/**
Expand All @@ -1388,7 +1428,6 @@ 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),
Expand Down Expand Up @@ -1421,6 +1460,7 @@ namespace ts.server {
this.configuredProjects.delete((<ConfiguredProject>project).canonicalConfigFilePath);
this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath);
this.setConfigFileExistenceInfoByClosedConfiguredProject(<ConfiguredProject>project);
this.removeProjectFromSharedExtendedConfigFileMap(project as ConfiguredProject);
break;
case ProjectKind.Inferred:
unorderedRemoveItem(this.inferredProjects, <InferredProject>project);
Expand Down Expand Up @@ -1689,18 +1729,6 @@ namespace ts.server {
this.logger.info(`ConfigFilePresence:: Current Watches: ${watches}:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`);
}

/*@internal*/
private logExtendedConfigFileWatchUpdate(extendedConfigFile: NormalizedPath, canonicalConfigFilePath: string, status: ConfigFileWatcherStatus) {
if (!this.logger.hasLevel(LogLevel.verbose)) {
return;
}
const watches: WatchType[] = [];
if (this.configuredProjects.has(canonicalConfigFilePath)) {
watches.push(WatchType.ExtendedConfigFile);
}
this.logger.info(`ExtendedConfigFilePresence:: Current Watches: ${watches}:: File: ${extendedConfigFile} Status: ${status}`);
}

/**
* Create the watcher for the configFileExistenceInfo
*/
Expand Down Expand Up @@ -2162,23 +2190,14 @@ namespace ts.server {
if (lastFileExceededProgramSize) {
project.disableLanguageService(lastFileExceededProgramSize);
project.stopWatchingWildCards();
removeProjectFromSharedExtendedConfigFilesWatch(project, this.sharedExtendedConfigFilesMap);
this.removeProjectFromSharedExtendedConfigFileMap(project);
}
else {
project.setCompilerOptions(compilerOptions);
project.setWatchOptions(parsedCommandLine.watchOptions);
project.enableLanguageService();
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,
);
}
this.updateSharedExtendedConfigFileMap(project);
}
project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides);
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());
Expand Down

0 comments on commit 4224dd7

Please sign in to comment.