Skip to content

Commit 56d754c

Browse files
committed
Add watchDirectory to be using dynamic polling
1 parent f4954d0 commit 56d754c

File tree

4 files changed

+69
-31
lines changed

4 files changed

+69
-31
lines changed

src/compiler/sys.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ namespace ts {
494494
const useNonPollingWatchers = process.env.TSC_NONPOLLING_WATCHER;
495495
const tscWatchFile = process.env.TSC_WATCHFILE;
496496
const tscWatchDirectory = process.env.TSC_WATCHDIRECTORY;
497-
497+
let dynamicPollingWatchFile: HostWatchFile | undefined;
498498
const nodeSystem: System = {
499499
args: process.argv.slice(2),
500500
newLine: _os.EOL,
@@ -607,7 +607,8 @@ namespace ts {
607607
return watchFileUsingFsWatch;
608608
case "UseFsEventsWithFallbackDynamicPolling":
609609
// Use notifications from FS to watch with falling back to dynamic watch file
610-
return watchFileUsingDynamicWatchFile;
610+
dynamicPollingWatchFile = createDynamicPriorityPollingWatchFile(nodeSystem);
611+
return createWatchFileUsingDynamicWatchFile(dynamicPollingWatchFile);
611612
}
612613
return useNonPollingWatchers ?
613614
createNonPollingWatchFile() :
@@ -623,7 +624,11 @@ namespace ts {
623624
return watchDirectoryUsingFsWatch;
624625
}
625626

626-
const watchDirectory = tscWatchDirectory === "RecursiveDirectoryUsingFsWatchFile" ? watchDirectoryUsingFsWatchFile : watchDirectoryUsingFsWatch;
627+
const watchDirectory = tscWatchDirectory === "RecursiveDirectoryUsingFsWatchFile" ?
628+
createWatchDirectoryUsing(fsWatchFile) :
629+
tscWatchDirectory === "RecursiveDirectoryUsingDynamicPriorityPolling" ?
630+
createWatchDirectoryUsing(dynamicPollingWatchFile || createDynamicPriorityPollingWatchFile(nodeSystem)) :
631+
watchDirectoryUsingFsWatch;
627632
const watchDirectoryRecursively = createRecursiveDirectoryWatcher({
628633
filePathComparer: useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
629634
directoryExists,
@@ -838,9 +843,8 @@ namespace ts {
838843
return fsWatch(fileName, FileSystemEntryKind.File, createFsWatchCallbackForFileWatcherCallback(fileName, callback), /*recursive*/ false, fsWatchFile, pollingInterval);
839844
}
840845

841-
function watchFileUsingDynamicWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval?: number) {
842-
const watchFile = createDynamicPriorityPollingWatchFile(nodeSystem);
843-
return fsWatch(fileName, FileSystemEntryKind.File, createFsWatchCallbackForFileWatcherCallback(fileName, callback), /*recursive*/ false, watchFile, pollingInterval);
846+
function createWatchFileUsingDynamicWatchFile(watchFile: HostWatchFile): HostWatchFile {
847+
return (fileName, callback, pollingInterval) => fsWatch(fileName, FileSystemEntryKind.File, createFsWatchCallbackForFileWatcherCallback(fileName, callback), /*recursive*/ false, watchFile, pollingInterval);
844848
}
845849

846850
function fsWatchDirectory(directoryName: string, callback: FsWatchCallback, recursive?: boolean): FileWatcher {
@@ -851,8 +855,8 @@ namespace ts {
851855
return fsWatchDirectory(directoryName, createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback), recursive);
852856
}
853857

854-
function watchDirectoryUsingFsWatchFile(directoryName: string, callback: DirectoryWatcherCallback) {
855-
return fsWatchFile(directoryName, () => callback(directoryName), PollingInterval.Medium);
858+
function createWatchDirectoryUsing(fsWatchFile: HostWatchFile): HostWatchDirectory {
859+
return (directoryName, callback) => fsWatchFile(directoryName, () => callback(directoryName), PollingInterval.Medium);
856860
}
857861

858862
function readFile(fileName: string, _encoding?: string): string | undefined {

src/harness/unittests/tscWatchMode.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2177,7 +2177,7 @@ declare module "fs" {
21772177
});
21782178

21792179
describe("tsc-watch when watchDirectories implementation", () => {
2180-
function verifyRenamingFileInSubFolder(usesWatchFile: boolean) {
2180+
function verifyRenamingFileInSubFolder(tscWatchDirectory: TestFSWithWatch.Tsc_WatchDirectory) {
21812181
const projectFolder = "/a/username/project";
21822182
const projectSrcFolder = `${projectFolder}/src`;
21832183
const configFile: FileOrFolder = {
@@ -2191,14 +2191,14 @@ declare module "fs" {
21912191
const programFiles = [file, libFile];
21922192
const files = [file, configFile, libFile];
21932193
const environmentVariables = createMap<string>();
2194-
environmentVariables.set("TSC_WATCHDIRECTORY", usesWatchFile ? "RecursiveDirectoryUsingFsWatchFile" : "RecursiveDirectoryUsingNonRecursiveWatchDirectory");
2194+
environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory);
21952195
const host = createWatchedSystem(files, { environmentVariables });
21962196
const watch = createWatchModeWithConfigFile(configFile.path, host);
21972197
const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`];
21982198
// Watching files config file, file, lib file
21992199
const expectedWatchedFiles = files.map(f => f.path);
2200-
const expectedWatchedDirectories = usesWatchFile ? [] : projectFolders;
2201-
if (usesWatchFile) {
2200+
const expectedWatchedDirectories = tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray;
2201+
if (tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.WatchFile) {
22022202
expectedWatchedFiles.push(...projectFolders);
22032203
}
22042204

@@ -2208,31 +2208,40 @@ declare module "fs" {
22082208
file.path = file.path.replace("file1.ts", "file2.ts");
22092209
expectedWatchedFiles[0] = file.path;
22102210
host.reloadFS(files);
2211+
if (tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling) {
2212+
// With dynamic polling the fs change would be detected only by running timeouts
2213+
host.runQueuedTimeoutCallbacks();
2214+
}
2215+
// Delayed update program
22112216
host.runQueuedTimeoutCallbacks();
22122217
verifyProgram(checkOutputErrorsIncremental);
22132218

22142219
function verifyProgram(checkOutputErrors: (host: WatchedSystem, errors: ReadonlyArray<Diagnostic>) => void) {
2215-
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
2216-
2217-
// Watching config file, file, lib file and directories
2218-
ts.TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedFiles", host.watchedFiles, expectedWatchedFiles, 1);
2219-
ts.TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories, 1);
2220-
22212220
checkProgramActualFiles(watch(), programFiles.map(f => f.path));
22222221
checkOutputErrors(host, emptyArray);
22232222

22242223
const outputFile = changeExtension(file.path, ".js");
22252224
assert(host.fileExists(outputFile));
22262225
assert.equal(host.readFile(outputFile), file.content);
2226+
2227+
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
2228+
2229+
// Watching config file, file, lib file and directories
2230+
ts.TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedFiles", host.watchedFiles, expectedWatchedFiles, 1);
2231+
ts.TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories, 1);
22272232
}
22282233
}
22292234

22302235
it("uses watchFile when renaming file in subfolder", () => {
2231-
verifyRenamingFileInSubFolder(/*usesWatchFile*/ true);
2236+
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.WatchFile);
22322237
});
22332238

22342239
it("uses non recursive watchDirectory when renaming file in subfolder", () => {
2235-
verifyRenamingFileInSubFolder(/*usesWatchFile*/ false);
2240+
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory);
2241+
});
2242+
2243+
it("uses non recursive dynamic polling when renaming file in subfolder", () => {
2244+
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling);
22362245
});
22372246
});
22382247
});

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6581,7 +6581,7 @@ namespace ts.projectSystem {
65816581
});
65826582

65836583
describe("watchDirectories implementation", () => {
6584-
function verifyCompletionListWithNewFileInSubFolder(usesWatchFile: boolean) {
6584+
function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: TestFSWithWatch.Tsc_WatchDirectory) {
65856585
const projectFolder = "/a/username/project";
65866586
const projectSrcFolder = `${projectFolder}/src`;
65876587
const configFile: FileOrFolder = {
@@ -6602,7 +6602,11 @@ namespace ts.projectSystem {
66026602
// All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder
66036603
const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1);
66046604
const expectedWatchedDirectories = createMap<number>();
6605-
const mapOfDirectories = usesWatchFile ? expectedWatchedFiles : expectedWatchedDirectories;
6605+
const mapOfDirectories = tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory ?
6606+
expectedWatchedDirectories :
6607+
tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.WatchFile ?
6608+
expectedWatchedFiles :
6609+
createMap();
66066610
// For failed resolution lookup and tsconfig files
66076611
mapOfDirectories.set(projectFolder, 2);
66086612
// Through above recursive watches
@@ -6612,7 +6616,7 @@ namespace ts.projectSystem {
66126616
const expectedCompletions = ["file1"];
66136617
const completionPosition = index.content.lastIndexOf('"');
66146618
const environmentVariables = createMap<string>();
6615-
environmentVariables.set("TSC_WATCHDIRECTORY", usesWatchFile ? "RecursiveDirectoryUsingFsWatchFile" : "RecursiveDirectoryUsingNonRecursiveWatchDirectory");
6619+
environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory);
66166620
const host = createServerHost(files, { environmentVariables });
66176621
const projectService = createProjectService(host);
66186622
projectService.openClientFile(index.path);
@@ -6636,23 +6640,27 @@ namespace ts.projectSystem {
66366640
verifyProjectAndCompletions();
66376641

66386642
function verifyProjectAndCompletions() {
6643+
const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
6644+
checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions);
6645+
66396646
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
66406647

66416648
ts.TestFSWithWatch.checkMultiMapKeyCount("watchedFiles", host.watchedFiles, expectedWatchedFiles);
66426649
ts.TestFSWithWatch.checkMultiMapKeyCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories);
66436650
checkProjectActualFiles(project, fileNames);
6644-
6645-
const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
6646-
checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions);
66476651
}
66486652
}
66496653

66506654
it("uses watchFile when file is added to subfolder, completion list has new file", () => {
6651-
verifyCompletionListWithNewFileInSubFolder(/*usesWatchFile*/ true);
6655+
verifyCompletionListWithNewFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.WatchFile);
66526656
});
66536657

66546658
it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => {
6655-
verifyCompletionListWithNewFileInSubFolder(/*usesWatchFile*/ false);
6659+
verifyCompletionListWithNewFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory);
6660+
});
6661+
6662+
it("uses dynamic polling when file is added to subfolder, completion list has new file", () => {
6663+
verifyCompletionListWithNewFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling);
66566664
});
66576665
});
66586666
}

src/harness/virtualFileSystemWithWatch.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,12 @@ interface Array<T> {}`
257257
ignoreWatchInvokedWithTriggerAsFileCreate: boolean;
258258
}
259259

260+
export enum Tsc_WatchDirectory {
261+
WatchFile = "RecursiveDirectoryUsingFsWatchFile",
262+
NonRecursiveWatchDirectory = "RecursiveDirectoryUsingNonRecursiveWatchDirectory",
263+
DynamicPolling = "RecursiveDirectoryUsingDynamicPriorityPolling"
264+
}
265+
260266
export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, ModuleResolutionHost {
261267
args: string[] = [];
262268

@@ -286,8 +292,8 @@ interface Array<T> {}`
286292
this.dynamicPriorityWatchFile = this.environmentVariables && this.environmentVariables.get("TSC_WATCHFILE") === "DynamicPriorityPolling" ?
287293
createDynamicPriorityPollingWatchFile(this) :
288294
undefined;
289-
const tscWatchDirectory = this.environmentVariables && this.environmentVariables.get("TSC_WATCHDIRECTORY");
290-
if (tscWatchDirectory === "RecursiveDirectoryUsingFsWatchFile") {
295+
const tscWatchDirectory = this.environmentVariables && this.environmentVariables.get("TSC_WATCHDIRECTORY") as Tsc_WatchDirectory;
296+
if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) {
291297
const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchFile(directory, () => cb(directory), PollingInterval.Medium);
292298
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
293299
directoryExists: path => this.directoryExists(path),
@@ -296,7 +302,7 @@ interface Array<T> {}`
296302
watchDirectory
297303
});
298304
}
299-
else if (tscWatchDirectory === "RecursiveDirectoryUsingNonRecursiveWatchDirectory") {
305+
else if (tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory) {
300306
const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchDirectory(directory, fileName => cb(fileName), /*recursive*/ false);
301307
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
302308
directoryExists: path => this.directoryExists(path),
@@ -305,6 +311,16 @@ interface Array<T> {}`
305311
watchDirectory
306312
});
307313
}
314+
else if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) {
315+
const watchFile = createDynamicPriorityPollingWatchFile(this);
316+
const watchDirectory: HostWatchDirectory = (directory, cb) => watchFile(directory, () => cb(directory), PollingInterval.Medium);
317+
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
318+
directoryExists: path => this.directoryExists(path),
319+
getAccessileSortedChildDirectories: path => this.getDirectories(path),
320+
filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
321+
watchDirectory
322+
});
323+
}
308324
}
309325

310326
getNewLine() {
@@ -348,6 +364,7 @@ interface Array<T> {}`
348364
if (currentEntry.content !== fileOrDirectory.content) {
349365
currentEntry.content = fileOrDirectory.content;
350366
currentEntry.modifiedTime = new Date();
367+
this.fs.get(getDirectoryPath(currentEntry.path)).modifiedTime = new Date();
351368
if (options && options.invokeDirectoryWatcherInsteadOfFileChanged) {
352369
this.invokeDirectoryWatcher(getDirectoryPath(currentEntry.fullPath), currentEntry.fullPath);
353370
}

0 commit comments

Comments
 (0)