From 79a1f297278cd35705c7640e11a0e9307504a221 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 6 Mar 2019 14:46:14 -0800 Subject: [PATCH 1/2] In recursive directory watching ignore folders and files in node_modules starting with "." Fixes #30004 --- src/compiler/sys.ts | 14 ++++- .../unittests/tsserver/watchEnvironment.ts | 59 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 6d8c4628092..487ea103840 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -371,6 +371,8 @@ namespace ts { else { directoryWatcher = { watcher: host.watchDirectory(dirName, fileName => { + if (isInNodeModulesStartingWithDot(fileName)) return; + // Call the actual callback callbackCache.forEach((callbacks, rootDirName) => { if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) { @@ -426,7 +428,7 @@ namespace ts { 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 filePathComparer(childFullName, normalizePath(host.realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; + return !isInNodeModulesStartingWithDot(childFullName) && filePathComparer(childFullName, normalizePath(host.realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; }) : emptyArray, existingChildWatches, (child, childWatcher) => filePathComparer(child, childWatcher.dirName), @@ -452,6 +454,16 @@ namespace ts { (newChildWatches || (newChildWatches = [])).push(childWatcher); } } + + function isInNodeModulesStartingWithDot(path: string) { + return isInPath(path, "/node_modules/."); + } + + function isInPath(path: string, searchPath: string) { + if (stringContains(path, searchPath)) return true; + if (host.useCaseSensitiveFileNames) return false; + return stringContains(toCanonicalFilePath(path), searchPath); + } } // TODO: GH#18217 Methods on System are often used as if they are certainly defined diff --git a/src/testRunner/unittests/tsserver/watchEnvironment.ts b/src/testRunner/unittests/tsserver/watchEnvironment.ts index 2c195e0c7d2..43090f0970a 100644 --- a/src/testRunner/unittests/tsserver/watchEnvironment.ts +++ b/src/testRunner/unittests/tsserver/watchEnvironment.ts @@ -132,4 +132,63 @@ namespace ts.projectSystem { verifyRootedDirectoryWatch("c:/users/username/"); }); }); + + it(`unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem recursive watch directory implementation does not watch files/directories in node_modules starting with "."`, () => { + const projectFolder = "/a/username/project"; + const projectSrcFolder = `${projectFolder}/src`; + const configFile: File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const index: File = { + path: `${projectSrcFolder}/index.ts`, + content: `import {} from "file"` + }; + const file1: File = { + path: `${projectSrcFolder}/file1.ts`, + content: "" + }; + const nodeModulesExistingUnusedFile: File = { + path: `${projectFolder}/node_modules/someFile.d.ts`, + content: "" + }; + + const fileNames = [index, file1, configFile, libFile].map(file => file.path); + // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder + const expectedWatchedFiles = arrayToMap(fileNames.slice(1), identity, () => 1); + const expectedWatchedDirectories = arrayToMap([projectFolder, projectSrcFolder, `${projectFolder}/${nodeModules}`, `${projectFolder}/${nodeModulesAtTypes}`], identity, () => 1); + + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); + const host = createServerHost([index, file1, configFile, libFile, nodeModulesExistingUnusedFile], { environmentVariables }); + const projectService = createProjectService(host); + projectService.openClientFile(index.path); + + const project = Debug.assertDefined(projectService.configuredProjects.get(configFile.path)); + verifyProject(); + + const nodeModulesIgnoredFileFromIgnoreDirectory: File = { + path: `${projectFolder}/node_modules/.cache/someFile.d.ts`, + content: "" + }; + host.ensureFileOrFolder(nodeModulesIgnoredFileFromIgnoreDirectory); + host.checkTimeoutQueueLength(0); + verifyProject(); + + const nodeModulesIgnoredFile: File = { + path: `${projectFolder}/node_modules/.cacheFile.ts`, + content: "" + }; + host.ensureFileOrFolder(nodeModulesIgnoredFile); + host.checkTimeoutQueueLength(0); + verifyProject(); + + function verifyProject() { + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + checkWatchedFilesDetailed(host, expectedWatchedFiles); + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false); + checkProjectActualFiles(project, fileNames); + } + }); + } From ec2ee9ff3afdf4089b71e06dfc64465d1ee210fd Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 6 Mar 2019 15:00:26 -0800 Subject: [PATCH 2/2] Ignore anything starting with ".git" Fixes #29782 --- src/compiler/resolutionCache.ts | 6 +++--- src/compiler/sys.ts | 11 +++++++---- src/compiler/watch.ts | 2 +- src/server/editorServices.ts | 4 ++-- .../unittests/tsserver/watchEnvironment.ts | 16 ++++++++++++++++ 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 237c1637d31..b82a5a78d71 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -71,8 +71,8 @@ namespace ts { nonRecursive?: boolean; } - export function isPathInNodeModulesStartingWithDot(path: Path) { - return stringContains(path, "/node_modules/."); + export function isPathIgnored(path: Path) { + return some(ignoredPaths, searchPath => stringContains(path, searchPath)); } export const maxNumberOfFilesToIterateForInvalidation = 256; @@ -696,7 +696,7 @@ namespace ts { } else { // If something to do with folder/file starting with "." in node_modules folder, skip it - if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return false; + if (isPathIgnored(fileOrDirectoryPath)) return false; // Some file or directory in the watching directory is created // Return early if it does not have any of the watching extension or not the custom failed lookup path diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 487ea103840..f38d0ae506c 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -326,6 +326,9 @@ namespace ts { : FileWatcherEventKind.Changed; } + /*@internal*/ + export const ignoredPaths = ["/node_modules/.", "/.git"]; + /*@internal*/ export interface RecursiveDirectoryWatcherHost { watchDirectory: HostWatchDirectory; @@ -371,7 +374,7 @@ namespace ts { else { directoryWatcher = { watcher: host.watchDirectory(dirName, fileName => { - if (isInNodeModulesStartingWithDot(fileName)) return; + if (isIgnoredPath(fileName)) return; // Call the actual callback callbackCache.forEach((callbacks, rootDirName) => { @@ -428,7 +431,7 @@ namespace ts { 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 !isInNodeModulesStartingWithDot(childFullName) && filePathComparer(childFullName, normalizePath(host.realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; + return !isIgnoredPath(childFullName) && filePathComparer(childFullName, normalizePath(host.realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; }) : emptyArray, existingChildWatches, (child, childWatcher) => filePathComparer(child, childWatcher.dirName), @@ -455,8 +458,8 @@ namespace ts { } } - function isInNodeModulesStartingWithDot(path: string) { - return isInPath(path, "/node_modules/."); + function isIgnoredPath(path: string) { + return some(ignoredPaths, searchPath => isInPath(path, searchPath)); } function isInPath(path: string, searchPath: string) { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 827a3279b3b..fa2a06e4ed0 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -987,7 +987,7 @@ namespace ts { } nextSourceFileVersion(fileOrDirectoryPath); - if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return; + if (isPathIgnored(fileOrDirectoryPath)) return; // If the the added or created file or directory is not supported file name, ignore the file // But when watched directory is added/removed, we need to reload the file list diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 10939b68114..56ae6dfdc22 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1002,7 +1002,7 @@ namespace ts.server { fileOrDirectory => { const fileOrDirectoryPath = this.toPath(fileOrDirectory); project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); - if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return; + if (isPathIgnored(fileOrDirectoryPath)) return; const configFilename = project.getConfigFilePath(); // If the the added or created file or directory is not supported file name, ignore the file @@ -2071,7 +2071,7 @@ namespace ts.server { watchDir, (fileOrDirectory) => { const fileOrDirectoryPath = this.toPath(fileOrDirectory); - if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return; + if (isPathIgnored(fileOrDirectoryPath)) return; // Has extension Debug.assert(result.refCount > 0); diff --git a/src/testRunner/unittests/tsserver/watchEnvironment.ts b/src/testRunner/unittests/tsserver/watchEnvironment.ts index 43090f0970a..bc8e84a8025 100644 --- a/src/testRunner/unittests/tsserver/watchEnvironment.ts +++ b/src/testRunner/unittests/tsserver/watchEnvironment.ts @@ -183,6 +183,22 @@ namespace ts.projectSystem { host.checkTimeoutQueueLength(0); verifyProject(); + const gitIgnoredFileFromIgnoreDirectory: File = { + path: `${projectFolder}/.git/someFile.d.ts`, + content: "" + }; + host.ensureFileOrFolder(gitIgnoredFileFromIgnoreDirectory); + host.checkTimeoutQueueLength(0); + verifyProject(); + + const gitIgnoredFile: File = { + path: `${projectFolder}/.gitCache.d.ts`, + content: "" + }; + host.ensureFileOrFolder(gitIgnoredFile); + host.checkTimeoutQueueLength(0); + verifyProject(); + function verifyProject() { checkWatchedDirectories(host, emptyArray, /*recursive*/ true); checkWatchedFilesDetailed(host, expectedWatchedFiles);