Skip to content

Commit 6c57ebd

Browse files
committed
Update watches to wild card directories, input files, config files when project invalidates
1 parent 228858f commit 6c57ebd

File tree

3 files changed

+154
-78
lines changed

3 files changed

+154
-78
lines changed

src/compiler/tsbuild.ts

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,9 @@ namespace ts {
399399
let reportFileChangeDetected = false;
400400

401401
// Watches for the solution
402-
const existingWatchersForWildcards = createFileMap<Map<WildcardDirectoryWatcher>>(toPath);
402+
const allWatchedWildcardDirectories = createFileMap<Map<WildcardDirectoryWatcher>>(toPath);
403+
const allWatchedInputFiles = createFileMap<Map<FileWatcher>>(toPath);
404+
const allWatchedConfigFiles = createFileMap<FileWatcher>(toPath);
403405

404406
return {
405407
buildAllProjects,
@@ -439,7 +441,9 @@ namespace ts {
439441
timerToBuildInvalidatedProject = undefined;
440442
}
441443
reportFileChangeDetected = false;
442-
existingWatchersForWildcards.forEach(wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf));
444+
clearMap(allWatchedWildcardDirectories, wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf));
445+
clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher));
446+
clearMap(allWatchedConfigFiles, closeFileWatcher);
443447
}
444448

445449
function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine {
@@ -493,48 +497,73 @@ namespace ts {
493497
const cfg = parseConfigFile(resolved);
494498
if (cfg) {
495499
// Watch this file
496-
hostWithWatch.watchFile(resolved, () => {
497-
configFileCache.removeKey(resolved);
498-
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
499-
});
500+
watchConfigFile(resolved);
500501

501502
// Update watchers for wildcard directories
502-
if (cfg.configFileSpecs) {
503-
const existingWatches = existingWatchersForWildcards.getValue(resolved);
504-
let newWatches: Map<WildcardDirectoryWatcher> | undefined;
505-
if (!existingWatches) {
506-
newWatches = createMap();
507-
existingWatchersForWildcards.setValue(resolved, newWatches);
508-
}
509-
updateWatchingWildcardDirectories(existingWatches || newWatches!, createMapFromTemplate(cfg.configFileSpecs.wildcardDirectories), (dir, flags) => {
510-
return hostWithWatch.watchDirectory(dir, fileOrDirectory => {
511-
const fileOrDirectoryPath = toPath(fileOrDirectory);
512-
if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, cfg.options)) {
513-
// writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
514-
return;
515-
}
516-
517-
if (isOutputFile(fileOrDirectory, cfg)) {
518-
// writeLog(`${fileOrDirectory} is output file`);
519-
return;
520-
}
521-
522-
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Partial);
523-
}, !!(flags & WatchDirectoryFlags.Recursive));
524-
});
525-
}
503+
watchWildCardDirectories(resolved, cfg);
526504

527505
// Watch input files
528-
for (const input of cfg.fileNames) {
529-
hostWithWatch.watchFile(input, () => {
530-
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.None);
531-
});
532-
}
506+
watchInputFiles(resolved, cfg);
533507
}
534508
}
535509

536510
}
537511

512+
function watchConfigFile(resolved: ResolvedConfigFileName) {
513+
if (!allWatchedConfigFiles.hasKey(resolved)) {
514+
allWatchedConfigFiles.setValue(resolved, hostWithWatch.watchFile(resolved, () => {
515+
configFileCache.removeKey(resolved);
516+
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
517+
}));
518+
}
519+
}
520+
521+
function getOrCreateExistingWatches<T>(resolved: ResolvedConfigFileName, allWatches: ConfigFileMap<Map<T>>) {
522+
const existingWatches = allWatches.getValue(resolved);
523+
let newWatches: Map<T> | undefined;
524+
if (!existingWatches) {
525+
newWatches = createMap();
526+
allWatches.setValue(resolved, newWatches);
527+
}
528+
return existingWatches || newWatches!;
529+
}
530+
531+
function watchWildCardDirectories(resolved: ResolvedConfigFileName, parsed: ParsedCommandLine) {
532+
updateWatchingWildcardDirectories(
533+
getOrCreateExistingWatches(resolved, allWatchedWildcardDirectories),
534+
createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories),
535+
(dir, flags) => {
536+
return hostWithWatch.watchDirectory(dir, fileOrDirectory => {
537+
const fileOrDirectoryPath = toPath(fileOrDirectory);
538+
if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) {
539+
// writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
540+
return;
541+
}
542+
543+
if (isOutputFile(fileOrDirectory, parsed)) {
544+
// writeLog(`${fileOrDirectory} is output file`);
545+
return;
546+
}
547+
548+
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Partial);
549+
}, !!(flags & WatchDirectoryFlags.Recursive));
550+
}
551+
);
552+
}
553+
554+
function watchInputFiles(resolved: ResolvedConfigFileName, parsed: ParsedCommandLine) {
555+
mutateMap(
556+
getOrCreateExistingWatches(resolved, allWatchedInputFiles),
557+
arrayToMap(parsed.fileNames, toPath),
558+
{
559+
createNewValue: (_key, input) => hostWithWatch.watchFile(input, () => {
560+
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.None);
561+
}),
562+
onDeleteValue: closeFileWatcher,
563+
}
564+
);
565+
}
566+
538567
function isOutputFile(fileName: string, configFile: ParsedCommandLine) {
539568
if (configFile.options.noEmit) return false;
540569

@@ -879,10 +908,12 @@ namespace ts {
879908
if (!resolved) return; // ??
880909
const proj = parseConfigFile(resolved);
881910
if (!proj) return; // ?
882-
// TODO:: If full reload , update watch for wild cards
883-
// TODO:: If full or partial reload, update watch for input files
884-
885-
if (reloadLevel === ConfigFileProgramReloadLevel.Partial) {
911+
if (reloadLevel === ConfigFileProgramReloadLevel.Full) {
912+
watchConfigFile(resolved);
913+
watchWildCardDirectories(resolved, proj);
914+
watchInputFiles(resolved, proj);
915+
}
916+
else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) {
886917
// Update file names
887918
const result = getFileNamesFromConfigSpecs(proj.configFileSpecs!, getDirectoryPath(project), proj.options, parseConfigFileHost);
888919
if (result.fileNames.length !== 0) {
@@ -892,6 +923,7 @@ namespace ts {
892923
proj.errors.push(getErrorForNoInputFiles(proj.configFileSpecs!, resolved));
893924
}
894925
proj.fileNames = result.fileNames;
926+
watchInputFiles(resolved, proj);
895927
}
896928

897929
const status = getUpToDateStatus(proj);

src/compiler/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4329,7 +4329,7 @@ namespace ts {
43294329
/**
43304330
* clears already present map by calling onDeleteExistingValue callback before deleting that key/value
43314331
*/
4332-
export function clearMap<T>(map: Map<T>, onDeleteValue: (valueInMap: T, key: string) => void) {
4332+
export function clearMap<T>(map: { forEach: Map<T>["forEach"]; clear: Map<T>["clear"]; }, onDeleteValue: (valueInMap: T, key: string) => void) {
43334333
// Remove all
43344334
map.forEach(onDeleteValue);
43354335
map.clear();

src/testRunner/unittests/tsbuildWatchMode.ts

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,17 @@ namespace ts.tscWatch {
6262
return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => [f, host.getModifiedTime(f)] as OutputFileStamp);
6363
}
6464

65-
function getOutputFileStamps(host: WatchedSystem): OutputFileStamp[] {
66-
return [
65+
function getOutputFileStamps(host: WatchedSystem, additionalFiles?: ReadonlyArray<[SubProject, string]>): OutputFileStamp[] {
66+
const result = [
6767
...getOutputStamps(host, SubProject.core, "anotherModule"),
6868
...getOutputStamps(host, SubProject.core, "index"),
6969
...getOutputStamps(host, SubProject.logic, "index"),
7070
...getOutputStamps(host, SubProject.tests, "index"),
7171
];
72+
if (additionalFiles) {
73+
additionalFiles.forEach(([subProject, baseFileNameWithoutExtension]) => result.push(...getOutputStamps(host, subProject, baseFileNameWithoutExtension)));
74+
}
75+
return result;
7276
}
7377

7478
function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: string[]) {
@@ -108,49 +112,89 @@ namespace ts.tscWatch {
108112
createSolutionInWatchMode();
109113
});
110114

111-
it("change builds changes and reports found errors message", () => {
112-
const host = createSolutionInWatchMode();
113-
verifyChange(`${core[1].content}
115+
describe("validates the changes and watched files", () => {
116+
const newFileWithoutExtension = "newFile";
117+
const newFile: File = {
118+
path: projectFilePath(SubProject.core, `${newFileWithoutExtension}.ts`),
119+
content: `export const newFileConst = 30;`
120+
};
121+
122+
function createSolutionInWatchModeToVerifyChanges(additionalFiles?: ReadonlyArray<[SubProject, string]>) {
123+
const host = createSolutionInWatchMode();
124+
return { host, verifyChangeWithFile, verifyChangeAfterTimeout, verifyWatches };
125+
126+
function verifyChangeWithFile(fileName: string, content: string) {
127+
const outputFileStamps = getOutputFileStamps(host, additionalFiles);
128+
host.writeFile(fileName, content);
129+
verifyChangeAfterTimeout(outputFileStamps);
130+
}
131+
132+
function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[]) {
133+
host.checkTimeoutQueueLengthAndRun(1); // Builds core
134+
const changedCore = getOutputFileStamps(host, additionalFiles);
135+
verifyChangedFiles(changedCore, outputFileStamps, [
136+
...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really
137+
...getOutputFileNames(SubProject.core, "index"),
138+
...(additionalFiles ? getOutputFileNames(SubProject.core, newFileWithoutExtension) : emptyArray)
139+
]);
140+
host.checkTimeoutQueueLengthAndRun(1); // Builds tests
141+
const changedTests = getOutputFileStamps(host, additionalFiles);
142+
verifyChangedFiles(changedTests, changedCore, [
143+
...getOutputFileNames(SubProject.tests, "index") // Again these need not be written
144+
]);
145+
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
146+
const changedLogic = getOutputFileStamps(host, additionalFiles);
147+
verifyChangedFiles(changedLogic, changedTests, [
148+
...getOutputFileNames(SubProject.logic, "index") // Again these need not be written
149+
]);
150+
host.checkTimeoutQueueLength(0);
151+
checkOutputErrorsIncremental(host, emptyArray);
152+
verifyWatches();
153+
}
154+
155+
function verifyWatches() {
156+
checkWatchedFiles(host, additionalFiles ? testProjectExpectedWatchedFiles.concat(newFile.path) : testProjectExpectedWatchedFiles);
157+
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
158+
checkWatchedDirectories(host, [projectPath(SubProject.core), projectPath(SubProject.logic)], /*recursive*/ true);
159+
}
160+
}
161+
162+
it("change builds changes and reports found errors message", () => {
163+
const { host, verifyChangeWithFile, verifyChangeAfterTimeout } = createSolutionInWatchModeToVerifyChanges();
164+
verifyChange(`${core[1].content}
114165
export class someClass { }`);
115166

116-
// Another change requeues and builds it
117-
verifyChange(core[1].content);
167+
// Another change requeues and builds it
168+
verifyChange(core[1].content);
118169

119-
// Two changes together report only single time message: File change detected. Starting incremental compilation...
120-
const outputFileStamps = getOutputFileStamps(host);
121-
const change1 = `${core[1].content}
170+
// Two changes together report only single time message: File change detected. Starting incremental compilation...
171+
const outputFileStamps = getOutputFileStamps(host);
172+
const change1 = `${core[1].content}
122173
export class someClass { }`;
123-
host.writeFile(core[1].path, change1);
124-
host.writeFile(core[1].path, `${change1}
174+
host.writeFile(core[1].path, change1);
175+
host.writeFile(core[1].path, `${change1}
125176
export class someClass2 { }`);
126-
verifyChangeAfterTimeout(outputFileStamps);
127-
128-
function verifyChange(coreContent: string) {
129-
const outputFileStamps = getOutputFileStamps(host);
130-
host.writeFile(core[1].path, coreContent);
131177
verifyChangeAfterTimeout(outputFileStamps);
132-
}
133178

134-
function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[]) {
135-
host.checkTimeoutQueueLengthAndRun(1); // Builds core
136-
const changedCore = getOutputFileStamps(host);
137-
verifyChangedFiles(changedCore, outputFileStamps, [
138-
...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really
139-
...getOutputFileNames(SubProject.core, "index")
140-
]);
141-
host.checkTimeoutQueueLengthAndRun(1); // Builds tests
142-
const changedTests = getOutputFileStamps(host);
143-
verifyChangedFiles(changedTests, changedCore, [
144-
...getOutputFileNames(SubProject.tests, "index") // Again these need not be written
145-
]);
146-
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
147-
const changedLogic = getOutputFileStamps(host);
148-
verifyChangedFiles(changedLogic, changedTests, [
149-
...getOutputFileNames(SubProject.logic, "index") // Again these need not be written
150-
]);
151-
host.checkTimeoutQueueLength(0);
152-
checkOutputErrorsIncremental(host, emptyArray);
153-
}
179+
function verifyChange(coreContent: string) {
180+
verifyChangeWithFile(core[1].path, coreContent);
181+
}
182+
});
183+
184+
it("builds when new file is added, and its subsequent updates", () => {
185+
const additinalFiles: ReadonlyArray<[SubProject, string]> = [[SubProject.core, newFileWithoutExtension]];
186+
const { verifyChangeWithFile } = createSolutionInWatchModeToVerifyChanges(additinalFiles);
187+
verifyChange(newFile.content);
188+
189+
// Another change requeues and builds it
190+
verifyChange(`${newFile.content}
191+
export class someClass2 { }`);
192+
193+
function verifyChange(newFileContent: string) {
194+
verifyChangeWithFile(newFile.path, newFileContent);
195+
}
196+
});
197+
154198
});
155199

156200
// TODO: write tests reporting errors but that will have more involved work since file

0 commit comments

Comments
 (0)