From 56d754cf0faa657dc9cf943d0769efea317597f6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 17 Jan 2018 12:56:33 -0800 Subject: [PATCH] Add watchDirectory to be using dynamic polling --- src/compiler/sys.ts | 20 ++++++----- src/harness/unittests/tscWatchMode.ts | 33 ++++++++++++------- .../unittests/tsserverProjectSystem.ts | 24 +++++++++----- src/harness/virtualFileSystemWithWatch.ts | 23 +++++++++++-- 4 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 91faaa620fe..a12fee09cdc 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -494,7 +494,7 @@ namespace ts { const useNonPollingWatchers = process.env.TSC_NONPOLLING_WATCHER; const tscWatchFile = process.env.TSC_WATCHFILE; const tscWatchDirectory = process.env.TSC_WATCHDIRECTORY; - + let dynamicPollingWatchFile: HostWatchFile | undefined; const nodeSystem: System = { args: process.argv.slice(2), newLine: _os.EOL, @@ -607,7 +607,8 @@ namespace ts { return watchFileUsingFsWatch; case "UseFsEventsWithFallbackDynamicPolling": // Use notifications from FS to watch with falling back to dynamic watch file - return watchFileUsingDynamicWatchFile; + dynamicPollingWatchFile = createDynamicPriorityPollingWatchFile(nodeSystem); + return createWatchFileUsingDynamicWatchFile(dynamicPollingWatchFile); } return useNonPollingWatchers ? createNonPollingWatchFile() : @@ -623,7 +624,11 @@ namespace ts { return watchDirectoryUsingFsWatch; } - const watchDirectory = tscWatchDirectory === "RecursiveDirectoryUsingFsWatchFile" ? watchDirectoryUsingFsWatchFile : watchDirectoryUsingFsWatch; + const watchDirectory = tscWatchDirectory === "RecursiveDirectoryUsingFsWatchFile" ? + createWatchDirectoryUsing(fsWatchFile) : + tscWatchDirectory === "RecursiveDirectoryUsingDynamicPriorityPolling" ? + createWatchDirectoryUsing(dynamicPollingWatchFile || createDynamicPriorityPollingWatchFile(nodeSystem)) : + watchDirectoryUsingFsWatch; const watchDirectoryRecursively = createRecursiveDirectoryWatcher({ filePathComparer: useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive, directoryExists, @@ -838,9 +843,8 @@ namespace ts { return fsWatch(fileName, FileSystemEntryKind.File, createFsWatchCallbackForFileWatcherCallback(fileName, callback), /*recursive*/ false, fsWatchFile, pollingInterval); } - function watchFileUsingDynamicWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval?: number) { - const watchFile = createDynamicPriorityPollingWatchFile(nodeSystem); - return fsWatch(fileName, FileSystemEntryKind.File, createFsWatchCallbackForFileWatcherCallback(fileName, callback), /*recursive*/ false, watchFile, pollingInterval); + function createWatchFileUsingDynamicWatchFile(watchFile: HostWatchFile): HostWatchFile { + return (fileName, callback, pollingInterval) => fsWatch(fileName, FileSystemEntryKind.File, createFsWatchCallbackForFileWatcherCallback(fileName, callback), /*recursive*/ false, watchFile, pollingInterval); } function fsWatchDirectory(directoryName: string, callback: FsWatchCallback, recursive?: boolean): FileWatcher { @@ -851,8 +855,8 @@ namespace ts { return fsWatchDirectory(directoryName, createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback), recursive); } - function watchDirectoryUsingFsWatchFile(directoryName: string, callback: DirectoryWatcherCallback) { - return fsWatchFile(directoryName, () => callback(directoryName), PollingInterval.Medium); + function createWatchDirectoryUsing(fsWatchFile: HostWatchFile): HostWatchDirectory { + return (directoryName, callback) => fsWatchFile(directoryName, () => callback(directoryName), PollingInterval.Medium); } function readFile(fileName: string, _encoding?: string): string | undefined { diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 82bfb7f4b89..75db67bee71 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -2177,7 +2177,7 @@ declare module "fs" { }); describe("tsc-watch when watchDirectories implementation", () => { - function verifyRenamingFileInSubFolder(usesWatchFile: boolean) { + function verifyRenamingFileInSubFolder(tscWatchDirectory: TestFSWithWatch.Tsc_WatchDirectory) { const projectFolder = "/a/username/project"; const projectSrcFolder = `${projectFolder}/src`; const configFile: FileOrFolder = { @@ -2191,14 +2191,14 @@ declare module "fs" { const programFiles = [file, libFile]; const files = [file, configFile, libFile]; const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", usesWatchFile ? "RecursiveDirectoryUsingFsWatchFile" : "RecursiveDirectoryUsingNonRecursiveWatchDirectory"); + environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); const host = createWatchedSystem(files, { environmentVariables }); const watch = createWatchModeWithConfigFile(configFile.path, host); const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`]; // Watching files config file, file, lib file const expectedWatchedFiles = files.map(f => f.path); - const expectedWatchedDirectories = usesWatchFile ? [] : projectFolders; - if (usesWatchFile) { + const expectedWatchedDirectories = tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray; + if (tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.WatchFile) { expectedWatchedFiles.push(...projectFolders); } @@ -2208,31 +2208,40 @@ declare module "fs" { file.path = file.path.replace("file1.ts", "file2.ts"); expectedWatchedFiles[0] = file.path; host.reloadFS(files); + if (tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling) { + // With dynamic polling the fs change would be detected only by running timeouts + host.runQueuedTimeoutCallbacks(); + } + // Delayed update program host.runQueuedTimeoutCallbacks(); verifyProgram(checkOutputErrorsIncremental); function verifyProgram(checkOutputErrors: (host: WatchedSystem, errors: ReadonlyArray) => void) { - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - - // Watching config file, file, lib file and directories - ts.TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedFiles", host.watchedFiles, expectedWatchedFiles, 1); - ts.TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories, 1); - checkProgramActualFiles(watch(), programFiles.map(f => f.path)); checkOutputErrors(host, emptyArray); const outputFile = changeExtension(file.path, ".js"); assert(host.fileExists(outputFile)); assert.equal(host.readFile(outputFile), file.content); + + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + + // Watching config file, file, lib file and directories + ts.TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedFiles", host.watchedFiles, expectedWatchedFiles, 1); + ts.TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories, 1); } } it("uses watchFile when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(/*usesWatchFile*/ true); + verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.WatchFile); }); it("uses non recursive watchDirectory when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(/*usesWatchFile*/ false); + verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory); + }); + + it("uses non recursive dynamic polling when renaming file in subfolder", () => { + verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling); }); }); }); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index b343a269c20..9b4705c9e34 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -6581,7 +6581,7 @@ namespace ts.projectSystem { }); describe("watchDirectories implementation", () => { - function verifyCompletionListWithNewFileInSubFolder(usesWatchFile: boolean) { + function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: TestFSWithWatch.Tsc_WatchDirectory) { const projectFolder = "/a/username/project"; const projectSrcFolder = `${projectFolder}/src`; const configFile: FileOrFolder = { @@ -6602,7 +6602,11 @@ namespace ts.projectSystem { // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1); const expectedWatchedDirectories = createMap(); - const mapOfDirectories = usesWatchFile ? expectedWatchedFiles : expectedWatchedDirectories; + const mapOfDirectories = tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory ? + expectedWatchedDirectories : + tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.WatchFile ? + expectedWatchedFiles : + createMap(); // For failed resolution lookup and tsconfig files mapOfDirectories.set(projectFolder, 2); // Through above recursive watches @@ -6612,7 +6616,7 @@ namespace ts.projectSystem { const expectedCompletions = ["file1"]; const completionPosition = index.content.lastIndexOf('"'); const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", usesWatchFile ? "RecursiveDirectoryUsingFsWatchFile" : "RecursiveDirectoryUsingNonRecursiveWatchDirectory"); + environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); const host = createServerHost(files, { environmentVariables }); const projectService = createProjectService(host); projectService.openClientFile(index.path); @@ -6636,23 +6640,27 @@ namespace ts.projectSystem { verifyProjectAndCompletions(); function verifyProjectAndCompletions() { + const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); + checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions); + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); ts.TestFSWithWatch.checkMultiMapKeyCount("watchedFiles", host.watchedFiles, expectedWatchedFiles); ts.TestFSWithWatch.checkMultiMapKeyCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories); checkProjectActualFiles(project, fileNames); - - const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); - checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions); } } it("uses watchFile when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(/*usesWatchFile*/ true); + verifyCompletionListWithNewFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.WatchFile); }); it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(/*usesWatchFile*/ false); + verifyCompletionListWithNewFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory); + }); + + it("uses dynamic polling when file is added to subfolder, completion list has new file", () => { + verifyCompletionListWithNewFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling); }); }); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index bb7fa128c79..9bb106fe6be 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -257,6 +257,12 @@ interface Array {}` ignoreWatchInvokedWithTriggerAsFileCreate: boolean; } + export enum Tsc_WatchDirectory { + WatchFile = "RecursiveDirectoryUsingFsWatchFile", + NonRecursiveWatchDirectory = "RecursiveDirectoryUsingNonRecursiveWatchDirectory", + DynamicPolling = "RecursiveDirectoryUsingDynamicPriorityPolling" + } + export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, ModuleResolutionHost { args: string[] = []; @@ -286,8 +292,8 @@ interface Array {}` this.dynamicPriorityWatchFile = this.environmentVariables && this.environmentVariables.get("TSC_WATCHFILE") === "DynamicPriorityPolling" ? createDynamicPriorityPollingWatchFile(this) : undefined; - const tscWatchDirectory = this.environmentVariables && this.environmentVariables.get("TSC_WATCHDIRECTORY"); - if (tscWatchDirectory === "RecursiveDirectoryUsingFsWatchFile") { + const tscWatchDirectory = this.environmentVariables && this.environmentVariables.get("TSC_WATCHDIRECTORY") as Tsc_WatchDirectory; + if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) { const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchFile(directory, () => cb(directory), PollingInterval.Medium); this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({ directoryExists: path => this.directoryExists(path), @@ -296,7 +302,7 @@ interface Array {}` watchDirectory }); } - else if (tscWatchDirectory === "RecursiveDirectoryUsingNonRecursiveWatchDirectory") { + else if (tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory) { const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchDirectory(directory, fileName => cb(fileName), /*recursive*/ false); this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({ directoryExists: path => this.directoryExists(path), @@ -305,6 +311,16 @@ interface Array {}` watchDirectory }); } + else if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { + const watchFile = createDynamicPriorityPollingWatchFile(this); + const watchDirectory: HostWatchDirectory = (directory, cb) => watchFile(directory, () => cb(directory), PollingInterval.Medium); + this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({ + directoryExists: path => this.directoryExists(path), + getAccessileSortedChildDirectories: path => this.getDirectories(path), + filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive, + watchDirectory + }); + } } getNewLine() { @@ -348,6 +364,7 @@ interface Array {}` if (currentEntry.content !== fileOrDirectory.content) { currentEntry.content = fileOrDirectory.content; currentEntry.modifiedTime = new Date(); + this.fs.get(getDirectoryPath(currentEntry.path)).modifiedTime = new Date(); if (options && options.invokeDirectoryWatcherInsteadOfFileChanged) { this.invokeDirectoryWatcher(getDirectoryPath(currentEntry.fullPath), currentEntry.fullPath); }