Skip to content

Commit 7d25e43

Browse files
sheetalkamatmolisani
authored andcommitted
Move shared watcher into utilities and add more tests
1 parent 45f19cc commit 7d25e43

14 files changed

+1074
-698
lines changed

src/compiler/tsbuildPublic.ts

Lines changed: 21 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,6 @@ namespace ts {
210210
originalGetSourceFile: CompilerHost["getSourceFile"];
211211
}
212212

213-
interface SharedExtendedConfigFileWatcher extends FileWatcher {
214-
projects: Set<ResolvedConfigFilePath>;
215-
}
216-
217213
interface SolutionBuilderState<T extends BuilderProgram = BuilderProgram> extends WatchFactory<WatchType, ResolvedConfigFileName> {
218214
readonly host: SolutionBuilderHost<T>;
219215
readonly hostWithWatch: SolutionBuilderWithWatchHost<T>;
@@ -258,7 +254,7 @@ namespace ts {
258254
readonly allWatchedWildcardDirectories: ESMap<ResolvedConfigFilePath, ESMap<string, WildcardDirectoryWatcher>>;
259255
readonly allWatchedInputFiles: ESMap<ResolvedConfigFilePath, ESMap<Path, FileWatcher>>;
260256
readonly allWatchedConfigFiles: ESMap<ResolvedConfigFilePath, FileWatcher>;
261-
readonly allWatchedExtendedConfigFiles: ESMap<ResolvedConfigFilePath, SharedExtendedConfigFileWatcher>;
257+
readonly allWatchedExtendedConfigFiles: ESMap<Path, SharedExtendedConfigFileWatcher<ResolvedConfigFilePath>>;
262258

263259
timerToBuildInvalidatedProject: any;
264260
reportFileChangeDetected: boolean;
@@ -468,8 +464,8 @@ namespace ts {
468464
{ onDeleteValue: closeFileWatcher }
469465
);
470466

471-
state.allWatchedExtendedConfigFiles.forEach((watcher) => {
472-
watcher.projects.forEach((project) => {
467+
state.allWatchedExtendedConfigFiles.forEach(watcher => {
468+
watcher.projects.forEach(project => {
473469
if (!currentProjects.has(project)) {
474470
watcher.projects.delete(project);
475471
}
@@ -1806,50 +1802,21 @@ namespace ts {
18061802
}
18071803

18081804
function watchExtendedConfigFiles(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) {
1809-
const extendedConfigs = arrayToMap(
1810-
(parsed?.options.configFile?.extendedSourceFiles || emptyArray) as ResolvedConfigFileName[],
1811-
extendedSourceFile => toResolvedConfigFilePath(state, extendedSourceFile)
1805+
updateSharedExtendedConfigFileWatcher(
1806+
resolvedPath,
1807+
parsed,
1808+
state.allWatchedExtendedConfigFiles,
1809+
(extendedConfigFileName, extendedConfigFilePath) => state.watchFile(
1810+
extendedConfigFileName,
1811+
() => state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath)?.projects.forEach(projectConfigFilePath =>
1812+
invalidateProjectAndScheduleBuilds(state, projectConfigFilePath, ConfigFileProgramReloadLevel.Full)
1813+
),
1814+
PollingInterval.High,
1815+
parsed?.watchOptions,
1816+
WatchType.ExtendedConfigFile,
1817+
),
1818+
fileName => toPath(state, fileName),
18121819
);
1813-
extendedConfigs.forEach((extendedConfigFileName, extendedConfigFilePath) => {
1814-
const watcher = state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath);
1815-
if (watcher) {
1816-
watcher.projects.add(resolvedPath);
1817-
}
1818-
else {
1819-
// start watching previously unseen extended config
1820-
const projects = new Set<ResolvedConfigFilePath>([resolvedPath]);
1821-
let fileWatcher = state.watchFile(
1822-
extendedConfigFileName,
1823-
() => {
1824-
projects.forEach((projectConfigFilePath) => {
1825-
invalidateProjectAndScheduleBuilds(state, projectConfigFilePath, ConfigFileProgramReloadLevel.Full);
1826-
});
1827-
},
1828-
PollingInterval.High,
1829-
parsed?.watchOptions,
1830-
WatchType.ExtendedConfigFile,
1831-
extendedConfigFileName
1832-
);
1833-
state.allWatchedExtendedConfigFiles.set(extendedConfigFilePath, {
1834-
close: () => {
1835-
if (projects.size === 0) {
1836-
Debug.assert(fileWatcher !== undefined);
1837-
fileWatcher.close();
1838-
fileWatcher = undefined!;
1839-
state.allWatchedExtendedConfigFiles.delete(extendedConfigFilePath);
1840-
}
1841-
},
1842-
projects,
1843-
});
1844-
}
1845-
});
1846-
// remove project from all unrelated watchers
1847-
state.allWatchedExtendedConfigFiles.forEach((watcher, extendedConfigFilePath) => {
1848-
if (!extendedConfigs.has(extendedConfigFilePath)) {
1849-
watcher.projects.delete(resolvedPath);
1850-
watcher.close();
1851-
}
1852-
});
18531820
}
18541821

18551822
function watchWildCardDirectories(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) {
@@ -1922,7 +1889,10 @@ namespace ts {
19221889

19231890
function stopWatching(state: SolutionBuilderState) {
19241891
clearMap(state.allWatchedConfigFiles, closeFileWatcher);
1925-
clearMap(state.allWatchedExtendedConfigFiles, closeFileWatcher);
1892+
clearMap(state.allWatchedExtendedConfigFiles, watcher => {
1893+
watcher.projects.clear();
1894+
watcher.close();
1895+
});
19261896
clearMap(state.allWatchedWildcardDirectories, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcherOf));
19271897
clearMap(state.allWatchedInputFiles, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcher));
19281898
}

src/compiler/watchPublic.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -790,11 +790,16 @@ namespace ts {
790790
}
791791

792792
function watchExtendedConfigFiles() {
793-
updateExtendedConfigFilesMap(
794-
compilerOptions.configFile,
795-
extendedConfigFilesMap || (extendedConfigFilesMap = new Map()),
796-
toPath,
797-
watchExtendedConfigFile
793+
// Update the extended config files watcher
794+
mutateMap(
795+
extendedConfigFilesMap ||= new Map(),
796+
arrayToMap(compilerOptions.configFile?.extendedSourceFiles || emptyArray, toPath),
797+
{
798+
// Watch the extended config files
799+
createNewValue: watchExtendedConfigFile,
800+
// Config files that are no longer extended should no longer be watched.
801+
onDeleteValue: closeFileWatcher
802+
}
798803
);
799804
}
800805

src/compiler/watchUtilities.ts

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -257,29 +257,49 @@ namespace ts {
257257
Full
258258
}
259259

260+
export interface SharedExtendedConfigFileWatcher<T> extends FileWatcher {
261+
fileWatcher: FileWatcher;
262+
projects: Set<T>;
263+
}
264+
260265
/**
261-
* Updates the map of extended config file watches with a new set of extended config files from a base config file
266+
* Updates the map of shared extended config file watches with a new set of extended config files from a base config file of the project
262267
*/
263-
export function updateExtendedConfigFilesMap(
264-
configFile: TsConfigSourceFile | undefined,
265-
extendedConfigFilesMap: ESMap<Path, FileWatcher>,
266-
toPath: (file: string) => Path,
267-
createExtendedConfigFileWatch: (extendedConfigPath: Path) => FileWatcher,
268+
export function updateSharedExtendedConfigFileWatcher<T>(
269+
projectPath: T,
270+
parsed: ParsedCommandLine | undefined,
271+
extendedConfigFilesMap: ESMap<Path, SharedExtendedConfigFileWatcher<T>>,
272+
createExtendedConfigFileWatch: (extendedConfigPath: string, extendedConfigFilePath: Path) => FileWatcher,
273+
toPath: (fileName: string) => Path,
268274
) {
269-
const extendedSourceFiles = configFile?.extendedSourceFiles ?? emptyArray;
270-
// TODO(rbuckton): Should be a `Set` but that requires changing the below code that uses `mutateMap`
271-
const newExtendedConfigFilesMap = arrayToMap(extendedSourceFiles, toPath, returnTrue);
275+
const extendedConfigs = arrayToMap(parsed?.options.configFile?.extendedSourceFiles || emptyArray, toPath);
276+
// remove project from all unrelated watchers
277+
extendedConfigFilesMap.forEach((watcher, extendedConfigFilePath) => {
278+
if (!extendedConfigs.has(extendedConfigFilePath)) {
279+
watcher.projects.delete(projectPath);
280+
watcher.close();
281+
}
282+
});
272283
// Update the extended config files watcher
273-
mutateMap(
274-
extendedConfigFilesMap,
275-
newExtendedConfigFilesMap,
276-
{
277-
// Watch the extended config files
278-
createNewValue: createExtendedConfigFileWatch,
279-
// Config files that are no longer extended should no longer be watched.
280-
onDeleteValue: closeFileWatcher
284+
extendedConfigs.forEach((extendedConfigFileName, extendedConfigFilePath) => {
285+
const existing = extendedConfigFilesMap.get(extendedConfigFilePath);
286+
if (existing) {
287+
existing.projects.add(projectPath);
281288
}
282-
);
289+
else {
290+
// start watching previously unseen extended config
291+
extendedConfigFilesMap.set(extendedConfigFilePath, {
292+
projects: new Set([projectPath]),
293+
fileWatcher: createExtendedConfigFileWatch(extendedConfigFileName, extendedConfigFilePath),
294+
close: () => {
295+
const existing = extendedConfigFilesMap.get(extendedConfigFilePath);
296+
if (!existing || existing.projects.size !== 0) return;
297+
existing.fileWatcher.close();
298+
extendedConfigFilesMap.delete(extendedConfigFilePath);
299+
},
300+
});
301+
}
302+
});
283303
}
284304

285305
/**

src/server/editorServices.ts

Lines changed: 29 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -641,10 +641,6 @@ namespace ts.server {
641641
errors: Diagnostic[] | undefined;
642642
}
643643

644-
interface SharedExtendedConfigFileWatcher extends FileWatcher {
645-
projects: Set<ConfiguredProject>;
646-
}
647-
648644
export class ProjectService {
649645

650646
/*@internal*/
@@ -761,7 +757,7 @@ namespace ts.server {
761757
readonly watchFactory: WatchFactory<WatchType, Project>;
762758

763759
/*@internal*/
764-
private readonly sharedExtendedConfigFileWatchers = new Map<Path, SharedExtendedConfigFileWatcher>();
760+
private readonly sharedExtendedConfigFileWatchers = new Map<Path, SharedExtendedConfigFileWatcher<NormalizedPath>>();
765761

766762
/*@internal*/
767763
readonly packageJsonCache: PackageJsonCache;
@@ -1358,61 +1354,40 @@ namespace ts.server {
13581354
}
13591355

13601356
/*@internal*/
1361-
private updateSharedExtendedConfigFileMap(project: ConfiguredProject) {
1362-
const extendedConfigPaths: readonly Path[] = project.getCompilerOptions().configFile?.extendedSourceFiles
1363-
?.map((file) => this.toPath(file)) ?? emptyArray;
1364-
for (const extendedConfigPath of extendedConfigPaths) {
1365-
const watcher = this.sharedExtendedConfigFileWatchers.get(extendedConfigPath);
1366-
if (watcher) {
1367-
watcher.projects.add(project);
1368-
}
1369-
else {
1370-
// start watching previously unseen extended config
1371-
const projects = new Set<ConfiguredProject>([project]);
1372-
const fileWatcherCallback = () => {
1373-
const reason = `Change in extended config file ${extendedConfigPath} detected`;
1374-
projects.forEach((project: ConfiguredProject) => {
1357+
updateSharedExtendedConfigFileMap({ canonicalConfigFilePath }: ConfiguredProject, parsedCommandLine: ParsedCommandLine) {
1358+
updateSharedExtendedConfigFileWatcher(
1359+
canonicalConfigFilePath,
1360+
parsedCommandLine,
1361+
this.sharedExtendedConfigFileWatchers,
1362+
(extendedConfigFileName, extendedConfigFilePath) => this.watchFactory.watchFile(
1363+
extendedConfigFileName,
1364+
() => {
1365+
let ensureProjectsForOpenFiles = false;
1366+
this.sharedExtendedConfigFileWatchers.get(extendedConfigFilePath)?.projects.forEach(canonicalPath => {
1367+
const project = this.configuredProjects.get(canonicalPath);
13751368
// Skip refresh if project is not yet loaded
1376-
if (project.isInitialLoadPending()) return;
1369+
if (!project || project.isInitialLoadPending()) return;
13771370
project.pendingReload = ConfigFileProgramReloadLevel.Full;
1378-
project.pendingReloadReason = reason;
1371+
project.pendingReloadReason = `Change in extended config file ${extendedConfigFileName} detected`;
13791372
this.delayUpdateProjectGraph(project);
1373+
ensureProjectsForOpenFiles = true;
13801374
});
1381-
};
1382-
const fileWatcher = this.watchFactory.watchFile(
1383-
extendedConfigPath,
1384-
fileWatcherCallback,
1385-
PollingInterval.High,
1386-
this.hostConfiguration.watchOptions,
1387-
WatchType.ExtendedConfigFile
1388-
);
1389-
this.sharedExtendedConfigFileWatchers.set(extendedConfigPath, {
1390-
close: () => fileWatcher.close(),
1391-
projects,
1392-
});
1393-
}
1394-
}
1395-
// remove project from all unrelated watchers
1396-
this.sharedExtendedConfigFileWatchers.forEach((watcher, extendedConfigPath) => {
1397-
if (!extendedConfigPaths.includes(extendedConfigPath)) {
1398-
watcher.projects.delete(project);
1399-
if (watcher.projects.size === 0) {
1400-
watcher.close();
1401-
this.sharedExtendedConfigFileWatchers.delete(extendedConfigPath);
1402-
}
1403-
}
1404-
});
1375+
if (ensureProjectsForOpenFiles) this.delayEnsureProjectForOpenFiles();
1376+
},
1377+
PollingInterval.High,
1378+
this.hostConfiguration.watchOptions,
1379+
WatchType.ExtendedConfigFile
1380+
),
1381+
fileName => this.toPath(fileName),
1382+
);
14051383
}
14061384

14071385
/*@internal*/
1408-
private removeProjectFromSharedExtendedConfigFileMap(project: ConfiguredProject) {
1409-
for (const [sharedConfigPath, watcher] of arrayFrom(this.sharedExtendedConfigFileWatchers.entries())) {
1410-
watcher.projects.delete(project);
1411-
if (watcher.projects.size === 0) {
1412-
watcher.close();
1413-
this.sharedExtendedConfigFileWatchers.delete(sharedConfigPath);
1414-
}
1415-
}
1386+
removeProjectFromSharedExtendedConfigFileMap(project: ConfiguredProject) {
1387+
this.sharedExtendedConfigFileWatchers.forEach(watcher => {
1388+
watcher.projects.delete(project.canonicalConfigFilePath);
1389+
watcher.close();
1390+
});
14161391
}
14171392

14181393
/**
@@ -1470,7 +1445,6 @@ namespace ts.server {
14701445
this.configuredProjects.delete((<ConfiguredProject>project).canonicalConfigFilePath);
14711446
this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath);
14721447
this.setConfigFileExistenceInfoByClosedConfiguredProject(<ConfiguredProject>project);
1473-
this.removeProjectFromSharedExtendedConfigFileMap(project as ConfiguredProject);
14741448
break;
14751449
case ProjectKind.Inferred:
14761450
unorderedRemoveItem(this.inferredProjects, <InferredProject>project);
@@ -2206,7 +2180,7 @@ namespace ts.server {
22062180
project.setWatchOptions(parsedCommandLine.watchOptions);
22072181
project.enableLanguageService();
22082182
project.watchWildcards(new Map(getEntries(parsedCommandLine.wildcardDirectories!))); // TODO: GH#18217
2209-
this.updateSharedExtendedConfigFileMap(project);
2183+
this.updateSharedExtendedConfigFileMap(project, parsedCommandLine);
22102184
}
22112185
project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides);
22122186
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());

src/server/project.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2292,6 +2292,7 @@ namespace ts.server {
22922292
}
22932293

22942294
this.stopWatchingWildCards();
2295+
this.projectService.removeProjectFromSharedExtendedConfigFileMap(this);
22952296
this.projectErrors = undefined;
22962297
this.openFileWatchTriggered.clear();
22972298
this.compilerHost = undefined;

0 commit comments

Comments
 (0)