Skip to content

Commit a7fa187

Browse files
authored
Merge pull request #19058 from Microsoft/whenWatchesFail
Swallow the directory watcher exceptions and ignore them
2 parents d0168af + 52d7c72 commit a7fa187

14 files changed

+79
-25
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14012,7 +14012,7 @@ namespace ts {
1401214012
*/
1401314013
function isUnhyphenatedJsxName(name: string | __String) {
1401414014
// - is the only character supported in JSX attribute names that isn't valid in JavaScript identifiers
14015-
return (name as string).indexOf("-") < 0;
14015+
return !stringContains(name as string, "-");
1401614016
}
1401714017

1401814018
/**

src/compiler/core.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,7 +1648,7 @@ namespace ts {
16481648
}
16491649

16501650
export function isUrl(path: string) {
1651-
return path && !isRootedDiskPath(path) && path.indexOf("://") !== -1;
1651+
return path && !isRootedDiskPath(path) && stringContains(path, "://");
16521652
}
16531653

16541654
export function pathIsRelative(path: string): boolean {
@@ -1917,8 +1917,12 @@ namespace ts {
19171917
return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos;
19181918
}
19191919

1920+
export function stringContains(str: string, substring: string): boolean {
1921+
return str.indexOf(substring) !== -1;
1922+
}
1923+
19201924
export function hasExtension(fileName: string): boolean {
1921-
return getBaseFileName(fileName).indexOf(".") >= 0;
1925+
return stringContains(getBaseFileName(fileName), ".");
19221926
}
19231927

19241928
export function fileExtensionIs(path: string, extension: string): boolean {

src/compiler/declarationEmitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ namespace ts {
172172

173173
function hasInternalAnnotation(range: CommentRange) {
174174
const comment = currentText.substring(range.pos, range.end);
175-
return comment.indexOf("@internal") >= 0;
175+
return stringContains(comment, "@internal");
176176
}
177177

178178
function stripInternal(node: Node) {

src/compiler/emitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1226,7 +1226,7 @@ namespace ts {
12261226
// check if numeric literal is a decimal literal that was originally written with a dot
12271227
const text = getLiteralTextOfNode(<LiteralExpression>expression);
12281228
return !expression.numericLiteralFlags
1229-
&& text.indexOf(tokenToString(SyntaxKind.DotToken)) < 0;
1229+
&& !stringContains(text, tokenToString(SyntaxKind.DotToken));
12301230
}
12311231
else if (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) {
12321232
// check if constant enum value is integer

src/compiler/moduleNameResolver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,7 @@ namespace ts {
10611061
export function getPackageNameFromAtTypesDirectory(mangledName: string): string {
10621062
const withoutAtTypePrefix = removePrefix(mangledName, "@types/");
10631063
if (withoutAtTypePrefix !== mangledName) {
1064-
return withoutAtTypePrefix.indexOf(mangledScopedPackageSeparator) !== -1 ?
1064+
return stringContains(withoutAtTypePrefix, mangledScopedPackageSeparator) ?
10651065
"@" + withoutAtTypePrefix.replace(mangledScopedPackageSeparator, ts.directorySeparator) :
10661066
withoutAtTypePrefix;
10671067
}

src/compiler/resolutionCache.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -329,17 +329,14 @@ namespace ts {
329329
let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()));
330330
let dirPath = getDirectoryPath(failedLookupLocationPath);
331331

332-
// If the directory is node_modules use it to watch
333-
if (isNodeModulesDirectory(dirPath)) {
334-
return { dir, dirPath };
332+
// If directory path contains node module, get the most parent node_modules directory for watching
333+
while (stringContains(dirPath, "/node_modules/")) {
334+
dir = getDirectoryPath(dir);
335+
dirPath = getDirectoryPath(dirPath);
335336
}
336337

337-
// If directory path contains node module, get the node_modules directory for watching
338-
if (dirPath.indexOf("/node_modules/") !== -1) {
339-
while (!isNodeModulesDirectory(dirPath)) {
340-
dir = getDirectoryPath(dir);
341-
dirPath = getDirectoryPath(dirPath);
342-
}
338+
// If the directory is node_modules use it to watch
339+
if (isNodeModulesDirectory(dirPath)) {
343340
return { dir, dirPath };
344341
}
345342

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2397,6 +2397,43 @@ namespace ts.projectSystem {
23972397
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
23982398

23992399
});
2400+
2401+
it("Failed lookup locations are uses parent most node_modules directory", () => {
2402+
const file1: FileOrFolder = {
2403+
path: "/a/b/src/file1.ts",
2404+
content: 'import { classc } from "module1"'
2405+
};
2406+
const module1: FileOrFolder = {
2407+
path: "/a/b/node_modules/module1/index.d.ts",
2408+
content: `import { class2 } from "module2";
2409+
export classc { method2a(): class2; }`
2410+
};
2411+
const module2: FileOrFolder = {
2412+
path: "/a/b/node_modules/module2/index.d.ts",
2413+
content: "export class2 { method2() { return 10; } }"
2414+
};
2415+
const module3: FileOrFolder = {
2416+
path: "/a/b/node_modules/module/node_modules/module3/index.d.ts",
2417+
content: "export class3 { method2() { return 10; } }"
2418+
};
2419+
const configFile: FileOrFolder = {
2420+
path: "/a/b/src/tsconfig.json",
2421+
content: JSON.stringify({ files: [file1.path] })
2422+
};
2423+
const files = [file1, module1, module2, module3, configFile, libFile];
2424+
const host = createServerHost(files);
2425+
const projectService = createProjectService(host);
2426+
projectService.openClientFile(file1.path);
2427+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
2428+
const project = projectService.configuredProjects.get(configFile.path);
2429+
assert.isDefined(project);
2430+
checkProjectActualFiles(project, [file1.path, libFile.path, module1.path, module2.path, configFile.path]);
2431+
checkWatchedFiles(host, [libFile.path, module1.path, module2.path, configFile.path]);
2432+
checkWatchedDirectories(host, [], /*recursive*/ false);
2433+
const watchedRecursiveDirectories = getTypeRootsFromLocation("/a/b/src");
2434+
watchedRecursiveDirectories.push("/a/b/src", "/a/b/node_modules");
2435+
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
2436+
});
24002437
});
24012438

24022439
describe("Proper errors", () => {

src/server/editorServices.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1218,7 +1218,7 @@ namespace ts.server {
12181218
projectRootPath?: NormalizedPath) {
12191219
let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
12201220

1221-
while (!projectRootPath || searchPath.indexOf(projectRootPath) >= 0) {
1221+
while (!projectRootPath || stringContains(searchPath, projectRootPath)) {
12221222
const canonicalSearchPath = normalizedPathToPath(searchPath, this.currentDirectory, this.toCanonicalFileName);
12231223
const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json"));
12241224
let result = action(tsconfigFileName, combinePaths(canonicalSearchPath, "tsconfig.json"));

src/server/server.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -753,10 +753,23 @@ namespace ts.server {
753753
const sys = <ServerHost>ts.sys;
754754
// use watchGuard process on Windows when node version is 4 or later
755755
const useWatchGuard = process.platform === "win32" && getNodeMajorVersion() >= 4;
756+
const originalWatchDirectory: ServerHost["watchDirectory"] = sys.watchDirectory.bind(sys);
757+
const noopWatcher: FileWatcher = { close: noop };
758+
// This is the function that catches the exceptions when watching directory, and yet lets project service continue to function
759+
// Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point
760+
function watchDirectorySwallowingException(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher {
761+
try {
762+
return originalWatchDirectory(path, callback, recursive);
763+
}
764+
catch (e) {
765+
logger.info(`Exception when creating directory watcher: ${e.message}`);
766+
return noopWatcher;
767+
}
768+
}
769+
756770
if (useWatchGuard) {
757771
const currentDrive = extractWatchDirectoryCacheKey(sys.resolvePath(sys.getCurrentDirectory()), /*currentDriveKey*/ undefined);
758772
const statusCache = createMap<boolean>();
759-
const originalWatchDirectory = sys.watchDirectory;
760773
sys.watchDirectory = function (path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher {
761774
const cacheKey = extractWatchDirectoryCacheKey(path, currentDrive);
762775
let status = cacheKey && statusCache.get(cacheKey);
@@ -790,14 +803,17 @@ namespace ts.server {
790803
}
791804
if (status) {
792805
// this drive is safe to use - call real 'watchDirectory'
793-
return originalWatchDirectory.call(sys, path, callback, recursive);
806+
return watchDirectorySwallowingException(path, callback, recursive);
794807
}
795808
else {
796809
// this drive is unsafe - return no-op watcher
797-
return { close() { } };
810+
return noopWatcher;
798811
}
799812
};
800813
}
814+
else {
815+
sys.watchDirectory = watchDirectorySwallowingException;
816+
}
801817

802818
// Override sys.write because fs.writeSync is not reliable on Node 4
803819
sys.write = (s: string) => writeMessage(new Buffer(s, "utf8"));

src/server/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1608,7 +1608,7 @@ namespace ts.server {
16081608
}
16091609

16101610
// No need to analyze lib.d.ts
1611-
const fileNamesInProject = fileNames.filter(value => value.indexOf("lib.d.ts") < 0);
1611+
const fileNamesInProject = fileNames.filter(value => !stringContains(value, "lib.d.ts"));
16121612
if (fileNamesInProject.length === 0) {
16131613
return;
16141614
}

src/server/typingsInstaller/nodeTypingsInstaller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ namespace ts.server.typingsInstaller {
8888
this.npmPath = npmLocation !== undefined ? npmLocation : getDefaultNPMLocation(process.argv[0]);
8989

9090
// If the NPM path contains spaces and isn't wrapped in quotes, do so.
91-
if (this.npmPath.indexOf(" ") !== -1 && this.npmPath[0] !== `"`) {
91+
if (stringContains(this.npmPath, " ") && this.npmPath[0] !== `"`) {
9292
this.npmPath = `"${this.npmPath}"`;
9393
}
9494
if (this.log.isEnabled()) {

src/services/pathCompletions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ namespace ts.Completions.PathCompletions {
195195
const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix);
196196
const normalizedPrefixBase = getBaseFileName(normalizedPrefix);
197197

198-
const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1;
198+
const fragmentHasPath = stringContains(fragment, directorySeparator);
199199

200200
// Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
201201
const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory;
@@ -235,7 +235,7 @@ namespace ts.Completions.PathCompletions {
235235

236236
function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions, typeChecker: TypeChecker, host: LanguageServiceHost): string[] {
237237
// Check If this is a nested module
238-
const isNestedModule = fragment.indexOf(directorySeparator) !== -1;
238+
const isNestedModule = stringContains(fragment, directorySeparator);
239239
const moduleNameFragment = isNestedModule ? fragment.substr(0, fragment.lastIndexOf(directorySeparator)) : undefined;
240240

241241
// Get modules that the type checker picked up

src/services/refactors/extractSymbol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ namespace ts.refactor.extractSymbol {
660660

661661
function getUniqueName(baseName: string, fileText: string): string {
662662
let nameText = baseName;
663-
for (let i = 1; fileText.indexOf(nameText) !== -1; i++) {
663+
for (let i = 1; stringContains(fileText, nameText); i++) {
664664
nameText = `${baseName}_${i}`;
665665
}
666666
return nameText;

src/services/services.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1952,7 +1952,7 @@ namespace ts {
19521952
function isNodeModulesFile(path: string): boolean {
19531953
const node_modulesFolderName = "/node_modules/";
19541954

1955-
return path.indexOf(node_modulesFolderName) !== -1;
1955+
return stringContains(path, node_modulesFolderName);
19561956
}
19571957
}
19581958

0 commit comments

Comments
 (0)