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); + } + }); + }