Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/compiler/resolutionCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,9 @@ namespace ts {
: FileWatcherEventKind.Changed;
}

/*@internal*/
export const ignoredPaths = ["/node_modules/.", "/.git"];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we're special casing .git and not just ignoring all dotfolders?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if anything starting with "." is too aggressive but we definitely have reports about ".git" and hence added that one. We can relax this further later if needed.


/*@internal*/
export interface RecursiveDirectoryWatcherHost {
watchDirectory: HostWatchDirectory;
Expand Down Expand Up @@ -371,6 +374,8 @@ namespace ts {
else {
directoryWatcher = {
watcher: host.watchDirectory(dirName, fileName => {
if (isIgnoredPath(fileName)) return;

// Call the actual callback
callbackCache.forEach((callbacks, rootDirName) => {
if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) {
Expand Down Expand Up @@ -426,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 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),
Expand All @@ -452,6 +457,16 @@ namespace ts {
(newChildWatches || (newChildWatches = [])).push(childWatcher);
}
}

function isIgnoredPath(path: string) {
return some(ignoredPaths, searchPath => isInPath(path, searchPath));
}

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
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
75 changes: 75 additions & 0 deletions src/testRunner/unittests/tsserver/watchEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,79 @@ 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<string>();
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();

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);
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false);
checkProjectActualFiles(project, fileNames);
}
});

}