From f885cd971eb31b67d95dfb2eca91888353263afc Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 28 Mar 2018 18:37:07 -0700 Subject: [PATCH 1/2] Do not watch child directories of the sym link folders Fixes #22668 --- src/compiler/sys.ts | 31 +++++++++------- src/harness/unittests/tscWatchMode.ts | 44 +++++++++++++++++++++++ src/harness/virtualFileSystemWithWatch.ts | 11 +++--- 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index f459515301cac..b2818918af8ce 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -337,6 +337,7 @@ namespace ts { getAccessileSortedChildDirectories(path: string): ReadonlyArray; directoryExists(dir: string): boolean; filePathComparer: Comparer; + realpath(s: string): string; } /** @@ -392,9 +393,12 @@ namespace ts { function watchChildDirectories(parentDir: string, existingChildWatches: ChildWatches, callback: DirectoryWatcherCallback): ChildWatches { let newChildWatches: DirectoryWatcher[] | undefined; enumerateInsertsAndDeletes( - host.directoryExists(parentDir) ? host.getAccessileSortedChildDirectories(parentDir) : emptyArray, + host.directoryExists(parentDir) ? mapDefined(host.getAccessileSortedChildDirectories(parentDir), child => { + const childFullName = getNormalizedAbsolutePath(child, parentDir); + return host.filePathComparer(childFullName, host.realpath(childFullName)) === Comparison.EqualTo ? childFullName : undefined; + }) : emptyArray, existingChildWatches, - (child, childWatcher) => host.filePathComparer(getNormalizedAbsolutePath(child, parentDir), childWatcher.dirName), + (child, childWatcher) => host.filePathComparer(child, childWatcher.dirName), createAndAddChildDirectoryWatcher, closeFileWatcher, addChildDirectoryWatcher @@ -406,7 +410,7 @@ namespace ts { * Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list */ function createAndAddChildDirectoryWatcher(childName: string) { - const result = createDirectoryWatcher(getNormalizedAbsolutePath(childName, parentDir), callback); + const result = createDirectoryWatcher(childName, callback); addChildDirectoryWatcher(result); } @@ -601,14 +605,7 @@ namespace ts { exit(exitCode?: number): void { process.exit(exitCode); }, - realpath(path: string): string { - try { - return _fs.realpathSync(path); - } - catch { - return path; - } - }, + realpath, debugMode: some(process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), tryEnableSourceMapsForHost() { try { @@ -700,7 +697,8 @@ namespace ts { filePathComparer: useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive, directoryExists, getAccessileSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, - watchDirectory + watchDirectory, + realpath }); return (directoryName, callback, recursive) => { @@ -1043,6 +1041,15 @@ namespace ts { return filter(_fs.readdirSync(path), dir => fileSystemEntryExists(combinePaths(path, dir), FileSystemEntryKind.Directory)); } + function realpath(path: string): string { + try { + return _fs.realpathSync(path); + } + catch { + return path; + } + } + function getModifiedTime(path: string) { try { return _fs.statSync(path).mtime; diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index e056153518aa9..999201029ded3 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -2358,6 +2358,50 @@ declare module "fs" { it("uses non recursive dynamic polling when renaming file in subfolder", () => { verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling); }); + + it("when there are symlinks to folders in recursive folders", () => { + const cwd = "/home/user/projects/myproject"; + const file1: FileOrFolder = { + path: `${cwd}/src/file.ts`, + content: `import * as a from "a"` + }; + const tsconfig: FileOrFolder = { + path: `${cwd}/tsconfig.json`, + content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` + }; + const realA: FileOrFolder = { + path: `${cwd}/node_modules/reala/index.d.ts`, + content: `export {}` + }; + const realB: FileOrFolder = { + path: `${cwd}/node_modules/realb/index.d.ts`, + content: `export {}` + }; + const symLinkA: FileOrFolder = { + path: `${cwd}/node_modules/a`, + symLink: `${cwd}/node_modules/reala` + }; + const symLinkB: FileOrFolder = { + path: `${cwd}/node_modules/b`, + symLink: `${cwd}/node_modules/realb` + }; + const symLinkBInA: FileOrFolder = { + path: `${cwd}/node_modules/reala/node_modules/b`, + symLink: `${cwd}/node_modules/b` + }; + const symLinkAInB: FileOrFolder = { + path: `${cwd}/node_modules/realb/node_modules/a`, + symLink: `${cwd}/node_modules/a` + }; + const files = [file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory); + const host = createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); + createWatchOfConfigFile("tsconfig.json", host); + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + checkWatchedDirectories(host, [cwd, `${cwd}/node_modules`, `${cwd}/node_modules/@types`, `${cwd}/node_modules/reala`, `${cwd}/node_modules/realb`, + `${cwd}/node_modules/reala/node_modules`, `${cwd}/node_modules/realb/node_modules`, `${cwd}/src`], /*recursive*/ false); + }); }); }); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index cd5f5cf1b186b..21f16ab579d46 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -314,7 +314,8 @@ interface Array {}` directoryExists: path => this.directoryExists(path), getAccessileSortedChildDirectories: path => this.getDirectories(path), filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive, - watchDirectory + watchDirectory, + realpath: s => this.realpath(s) }); } else if (tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory) { @@ -323,7 +324,8 @@ interface Array {}` directoryExists: path => this.directoryExists(path), getAccessileSortedChildDirectories: path => this.getDirectories(path), filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive, - watchDirectory + watchDirectory, + realpath: s => this.realpath(s) }); } else if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { @@ -333,7 +335,8 @@ interface Array {}` directoryExists: path => this.directoryExists(path), getAccessileSortedChildDirectories: path => this.getDirectories(path), filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive, - watchDirectory + watchDirectory, + realpath: s => this.realpath(s) }); } } @@ -649,7 +652,7 @@ interface Array {}` const realpath = this.realpath(path); if (path !== realpath) { - return this.getRealFsEntry(isFsEntry, realpath as Path); + return this.getRealFsEntry(isFsEntry, this.toPath(realpath)); } return undefined; From d8bf95ff108314fc343931abf7e593c6dd705c7c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 29 Mar 2018 09:13:03 -0700 Subject: [PATCH 2/2] PR feedback --- src/compiler/sys.ts | 8 +++++--- src/harness/virtualFileSystemWithWatch.ts | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index b2818918af8ce..8c2612155e443 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -334,7 +334,7 @@ namespace ts { /*@internal*/ export interface RecursiveDirectoryWatcherHost { watchDirectory: HostWatchDirectory; - getAccessileSortedChildDirectories(path: string): ReadonlyArray; + getAccessibleSortedChildDirectories(path: string): ReadonlyArray; directoryExists(dir: string): boolean; filePathComparer: Comparer; realpath(s: string): string; @@ -393,8 +393,10 @@ namespace ts { function watchChildDirectories(parentDir: string, existingChildWatches: ChildWatches, callback: DirectoryWatcherCallback): ChildWatches { let newChildWatches: DirectoryWatcher[] | undefined; enumerateInsertsAndDeletes( - host.directoryExists(parentDir) ? mapDefined(host.getAccessileSortedChildDirectories(parentDir), child => { + host.directoryExists(parentDir) ? mapDefined(host.getAccessibleSortedChildDirectories(parentDir), child => { const childFullName = getNormalizedAbsolutePath(child, parentDir); + // Filter our the symbolic link directories since those arent included in recursive watch + // which is same behaviour when recursive: true is passed to fs.watch return host.filePathComparer(childFullName, host.realpath(childFullName)) === Comparison.EqualTo ? childFullName : undefined; }) : emptyArray, existingChildWatches, @@ -696,7 +698,7 @@ namespace ts { const watchDirectoryRecursively = createRecursiveDirectoryWatcher({ filePathComparer: useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive, directoryExists, - getAccessileSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, + getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, watchDirectory, realpath }); diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 21f16ab579d46..81232fb63f4ef 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -312,7 +312,7 @@ interface Array {}` const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchFile(directory, () => cb(directory), PollingInterval.Medium); this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({ directoryExists: path => this.directoryExists(path), - getAccessileSortedChildDirectories: path => this.getDirectories(path), + getAccessibleSortedChildDirectories: path => this.getDirectories(path), filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive, watchDirectory, realpath: s => this.realpath(s) @@ -322,7 +322,7 @@ interface Array {}` const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchDirectory(directory, fileName => cb(fileName), /*recursive*/ false); this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({ directoryExists: path => this.directoryExists(path), - getAccessileSortedChildDirectories: path => this.getDirectories(path), + getAccessibleSortedChildDirectories: path => this.getDirectories(path), filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive, watchDirectory, realpath: s => this.realpath(s) @@ -333,7 +333,7 @@ interface Array {}` const watchDirectory: HostWatchDirectory = (directory, cb) => watchFile(directory, () => cb(directory), PollingInterval.Medium); this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({ directoryExists: path => this.directoryExists(path), - getAccessileSortedChildDirectories: path => this.getDirectories(path), + getAccessibleSortedChildDirectories: path => this.getDirectories(path), filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive, watchDirectory, realpath: s => this.realpath(s)