From 36af815bbabfc9d109336543ebb0fd4d85c67a12 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 6 Jan 2016 12:37:52 -0800 Subject: [PATCH] add optional 'directoryExists' method to hosts to reduce amount of disk probings that are known to fail --- src/compiler/program.ts | 36 ++- src/compiler/types.ts | 2 + src/harness/harnessLanguageService.ts | 4 + src/server/editorServices.ts | 3 +- src/services/services.ts | 8 +- src/services/shims.ts | 16 +- tests/cases/unittests/moduleResolution.ts | 263 ++++++++++++++-------- 7 files changed, 222 insertions(+), 110 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index c9de6a0ed96..349c4a86efa 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -53,13 +53,13 @@ namespace ts { if (getRootLength(moduleName) !== 0 || nameStartsWithDotSlashOrDotDotSlash(moduleName)) { const failedLookupLocations: string[] = []; const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); - let resolvedFileName = loadNodeModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host); + let resolvedFileName = loadNodeModuleFromFile(supportedExtensions, candidate, failedLookupLocations, /*onlyRecordFailures*/ false, host); if (resolvedFileName) { return { resolvedModule: { resolvedFileName }, failedLookupLocations }; } - resolvedFileName = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, host); + resolvedFileName = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, /*onlyRecordFailures*/ false, host); return resolvedFileName ? { resolvedModule: { resolvedFileName }, failedLookupLocations } : { resolvedModule: undefined, failedLookupLocations }; @@ -69,12 +69,22 @@ namespace ts { } } - function loadNodeModuleFromFile(extensions: string[], candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost): string { + /* @internal */ + export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean } ): boolean { + // if host does not support 'directoryExists' assume that directory will exist + return !host.directoryExists || host.directoryExists(directoryName); + } + + /** + * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary + * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. + */ + function loadNodeModuleFromFile(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, host: ModuleResolutionHost): string { return forEach(extensions, tryLoad); function tryLoad(ext: string): string { const fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext; - if (host.fileExists(fileName)) { + if (!onlyRecordFailures && host.fileExists(fileName)) { return fileName; } else { @@ -84,9 +94,10 @@ namespace ts { } } - function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost): string { + function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, host: ModuleResolutionHost): string { const packageJsonPath = combinePaths(candidate, "package.json"); - if (host.fileExists(packageJsonPath)) { + const directoryExists = !onlyRecordFailures && directoryProbablyExists(candidate, host); + if (directoryExists && host.fileExists(packageJsonPath)) { let jsonContent: { typings?: string }; @@ -100,7 +111,8 @@ namespace ts { } if (typeof jsonContent.typings === "string") { - const result = loadNodeModuleFromFile(extensions, normalizePath(combinePaths(candidate, jsonContent.typings)), failedLookupLocation, host); + const path = normalizePath(combinePaths(candidate, jsonContent.typings)); + const result = loadNodeModuleFromFile(extensions, path, failedLookupLocation, !directoryProbablyExists(getDirectoryPath(path), host), host); if (result) { return result; } @@ -111,7 +123,7 @@ namespace ts { failedLookupLocation.push(packageJsonPath); } - return loadNodeModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocation, host); + return loadNodeModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocation, !directoryExists, host); } function loadModuleFromNodeModules(moduleName: string, directory: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { @@ -121,14 +133,15 @@ namespace ts { const baseName = getBaseFileName(directory); if (baseName !== "node_modules") { const nodeModulesFolder = combinePaths(directory, "node_modules"); + const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, host); const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName)); // Load only typescript files irrespective of allowJs option if loading from node modules - let result = loadNodeModuleFromFile(supportedTypeScriptExtensions, candidate, failedLookupLocations, host); + let result = loadNodeModuleFromFile(supportedTypeScriptExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, host); if (result) { return { resolvedModule: { resolvedFileName: result, isExternalLibraryImport: true }, failedLookupLocations }; } - result = loadNodeModuleFromDirectory(supportedTypeScriptExtensions, candidate, failedLookupLocations, host); + result = loadNodeModuleFromDirectory(supportedTypeScriptExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, host); if (result) { return { resolvedModule: { resolvedFileName: result, isExternalLibraryImport: true }, failedLookupLocations }; } @@ -281,7 +294,8 @@ namespace ts { getCanonicalFileName, getNewLine: () => newLine, fileExists: fileName => sys.fileExists(fileName), - readFile: fileName => sys.readFile(fileName) + readFile: fileName => sys.readFile(fileName), + directoryExists: directoryName => sys.directoryExists(directoryName) }; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 24f70373a8a..8fc5f5079fb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2649,6 +2649,8 @@ namespace ts { // readFile function is used to read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json' // to determine location of bundled typings for node module readFile(fileName: string): string; + + directoryExists?(directoryName: string): boolean; } export interface ResolvedModule { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index fb5b6ce92aa..a0cac439729 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -267,6 +267,10 @@ namespace Harness.LanguageService { log(s: string): void { this.nativeHost.log(s); } trace(s: string): void { this.nativeHost.trace(s); } error(s: string): void { this.nativeHost.error(s); } + directoryExists(directoryName: string): boolean { + // for tests pessimistically assume that directory always exists + return true; + } } class ClassifierShimProxy implements ts.Classifier { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d78f7d40ef6..2c021106c74 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -100,7 +100,8 @@ namespace ts.server { this.filenameToScript = createFileMap(); this.moduleResolutionHost = { fileExists: fileName => this.fileExists(fileName), - readFile: fileName => this.host.readFile(fileName) + readFile: fileName => this.host.readFile(fileName), + directoryExists: directoryName => this.host.directoryExists(directoryName) }; } diff --git a/src/services/services.ts b/src/services/services.ts index 0fd4df19907..f2651163610 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1034,6 +1034,7 @@ namespace ts { * host specific questions using 'getScriptSnapshot'. */ resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; + directoryExists?(directoryName: string): boolean; } // @@ -1911,7 +1912,8 @@ namespace ts { getCurrentDirectory: () => "", getNewLine: () => newLine, fileExists: (fileName): boolean => fileName === inputFileName, - readFile: (fileName): string => "" + readFile: (fileName): string => "", + directoryExists: directoryExists => true }; const program = createProgram([inputFileName], options, compilerHost); @@ -2768,6 +2770,10 @@ namespace ts { // stub missing host functionality const entry = hostCache.getOrCreateEntry(fileName); return entry && entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength()); + }, + directoryExists: directoryName => { + Debug.assert(!host.resolveModuleNames); + return directoryProbablyExists(directoryName, host); } }; diff --git a/src/services/shims.ts b/src/services/shims.ts index 6b656ea2738..397f86f871f 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -62,6 +62,7 @@ namespace ts { useCaseSensitiveFileNames?(): boolean; getModuleResolutionsForFile?(fileName: string): string; + directoryExists(directoryName: string): boolean; } /** Public interface of the the of a config service shim instance.*/ @@ -274,6 +275,7 @@ namespace ts { private tracingEnabled = false; public resolveModuleNames: (moduleName: string[], containingFile: string) => ResolvedModule[]; + public directoryExists: (directoryName: string) => boolean; constructor(private shimHost: LanguageServiceShimHost) { // if shimHost is a COM object then property check will become method call with no arguments. @@ -287,6 +289,9 @@ namespace ts { }); }; } + if ("directoryExists" in this.shimHost) { + this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName); + } } public log(s: string): void { @@ -405,9 +410,14 @@ namespace ts { } } - export class CoreServicesShimHostAdapter implements ParseConfigHost { + export class CoreServicesShimHostAdapter implements ParseConfigHost, ModuleResolutionHost { + public directoryExists: (directoryName: string) => boolean; + constructor(private shimHost: CoreServicesShimHost) { + if ("directoryExists" in this.shimHost) { + this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName); + } } public readDirectory(rootDir: string, extension: string, exclude: string[]): string[] { @@ -424,11 +434,11 @@ namespace ts { } return JSON.parse(encoded); } - + public fileExists(fileName: string): boolean { return this.shimHost.fileExists(fileName); } - + public readFile(fileName: string): string { return this.shimHost.readFile(fileName); } diff --git a/tests/cases/unittests/moduleResolution.ts b/tests/cases/unittests/moduleResolution.ts index 5b924650401..81d7c5f8b2e 100644 --- a/tests/cases/unittests/moduleResolution.ts +++ b/tests/cases/unittests/moduleResolution.ts @@ -26,15 +26,36 @@ module ts { content?: string } - function createModuleResolutionHost(...files: File[]): ModuleResolutionHost { + function createModuleResolutionHost(hasDirectoryExists: boolean, ...files: File[]): ModuleResolutionHost { let map = arrayToMap(files, f => f.name); - return { fileExists, readFile }; - - function fileExists(path: string): boolean { - return hasProperty(map, path); + if (hasDirectoryExists) { + const directories: Map = {}; + for (const f of files) { + let name = getDirectoryPath(f.name); + while (true) { + directories[name] = name; + let baseName = getDirectoryPath(name); + if (baseName === name) { + break; + } + name = baseName; + } + } + return { + readFile, + directoryExists: path => { + return hasProperty(directories, path); + }, + fileExists: path => { + assert.isTrue(hasProperty(directories, getDirectoryPath(path)), "'fileExists' request in non-existing directory"); + return hasProperty(map, path); + } + } + } + else { + return { readFile, fileExists: path => hasProperty(map, path), }; } - function readFile(path: string): string { return hasProperty(map, path) ? map[path].content : undefined; } @@ -51,9 +72,14 @@ module ts { function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void { for (let ext of supportedTypeScriptExtensions) { + test(ext, /*hasDirectoryExists*/ false); + test(ext, /*hasDirectoryExists*/ true); + } + + function test(ext: string, hasDirectoryExists: boolean) { let containingFile = { name: containingFileName } let moduleFile = { name: moduleFileNameNoExt + ext } - let resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(containingFile, moduleFile)); + let resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name); assert.equal(!!resolution.resolvedModule.isExternalLibraryImport, false); @@ -69,6 +95,7 @@ module ts { } assert.deepEqual(resolution.failedLookupLocations, failedLookupLocations); + } } @@ -89,14 +116,19 @@ module ts { }); function testLoadingFromPackageJson(containingFileName: string, packageJsonFileName: string, fieldRef: string, moduleFileName: string, moduleName: string): void { - let containingFile = { name: containingFileName }; - let packageJson = { name: packageJsonFileName, content: JSON.stringify({ "typings": fieldRef }) }; - let moduleFile = { name: moduleFileName }; - let resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(containingFile, packageJson, moduleFile)); - assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name); - assert.equal(!!resolution.resolvedModule.isExternalLibraryImport, false); - // expect three failed lookup location - attempt to load module as file with all supported extensions - assert.equal(resolution.failedLookupLocations.length, supportedTypeScriptExtensions.length); + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + let containingFile = { name: containingFileName }; + let packageJson = { name: packageJsonFileName, content: JSON.stringify({ "typings": fieldRef }) }; + let moduleFile = { name: moduleFileName }; + let resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile)); + assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name); + assert.equal(!!resolution.resolvedModule.isExternalLibraryImport, false); + // expect three failed lookup location - attempt to load module as file with all supported extensions + assert.equal(resolution.failedLookupLocations.length, supportedTypeScriptExtensions.length); + } } it("module name as directory - load from 'typings'", () => { @@ -107,16 +139,21 @@ module ts { }); function testTypingsIgnored(typings: any): void { - let containingFile = { name: "/a/b.ts" }; - let packageJson = { name: "/node_modules/b/package.json", content: JSON.stringify({ "typings": typings }) }; - let moduleFile = { name: "/a/b.d.ts" }; + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - let indexPath = "/node_modules/b/index.d.ts"; - let indexFile = { name: indexPath } + function test(hasDirectoryExists: boolean) { + let containingFile = { name: "/a/b.ts" }; + let packageJson = { name: "/node_modules/b/package.json", content: JSON.stringify({ "typings": typings }) }; + let moduleFile = { name: "/a/b.d.ts" }; - let resolution = nodeModuleNameResolver("b", containingFile.name, {}, createModuleResolutionHost(containingFile, packageJson, moduleFile, indexFile)); + let indexPath = "/node_modules/b/index.d.ts"; + let indexFile = { name: indexPath } - assert.equal(resolution.resolvedModule.resolvedFileName, indexPath); + let resolution = nodeModuleNameResolver("b", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile, indexFile)); + + assert.equal(resolution.resolvedModule.resolvedFileName, indexPath); + } } it("module name as directory - handle invalid 'typings'", () => { @@ -128,89 +165,110 @@ module ts { }); it("module name as directory - load index.d.ts", () => { - let containingFile = { name: "/a/b/c.ts" }; - let packageJson = { name: "/a/b/foo/package.json", content: JSON.stringify({ main: "/c/d" }) }; - let indexFile = { name: "/a/b/foo/index.d.ts" }; - let resolution = nodeModuleNameResolver("./foo", containingFile.name, {}, createModuleResolutionHost(containingFile, packageJson, indexFile)); - assert.equal(resolution.resolvedModule.resolvedFileName, indexFile.name); - assert.equal(!!resolution.resolvedModule.isExternalLibraryImport, false); - assert.deepEqual(resolution.failedLookupLocations, [ - "/a/b/foo.ts", - "/a/b/foo.tsx", - "/a/b/foo.d.ts", - "/a/b/foo/index.ts", - "/a/b/foo/index.tsx", - ]); + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + let containingFile = { name: "/a/b/c.ts" }; + let packageJson = { name: "/a/b/foo/package.json", content: JSON.stringify({ main: "/c/d" }) }; + let indexFile = { name: "/a/b/foo/index.d.ts" }; + let resolution = nodeModuleNameResolver("./foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, indexFile)); + assert.equal(resolution.resolvedModule.resolvedFileName, indexFile.name); + assert.equal(!!resolution.resolvedModule.isExternalLibraryImport, false); + assert.deepEqual(resolution.failedLookupLocations, [ + "/a/b/foo.ts", + "/a/b/foo.tsx", + "/a/b/foo.d.ts", + "/a/b/foo/index.ts", + "/a/b/foo/index.tsx", + ]); + } }); }); describe("Node module resolution - non-relative paths", () => { it("load module as file - ts files not loaded", () => { - let containingFile = { name: "/a/b/c/d/e.ts" }; - let moduleFile = { name: "/a/b/node_modules/foo.ts" }; - let resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(containingFile, moduleFile)); - assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name); - assert.deepEqual(resolution.failedLookupLocations, [ - "/a/b/c/d/node_modules/foo.ts", - "/a/b/c/d/node_modules/foo.tsx", - "/a/b/c/d/node_modules/foo.d.ts", - "/a/b/c/d/node_modules/foo/package.json", - "/a/b/c/d/node_modules/foo/index.ts", - "/a/b/c/d/node_modules/foo/index.tsx", - "/a/b/c/d/node_modules/foo/index.d.ts", - "/a/b/c/node_modules/foo.ts", - "/a/b/c/node_modules/foo.tsx", - "/a/b/c/node_modules/foo.d.ts", - "/a/b/c/node_modules/foo/package.json", - "/a/b/c/node_modules/foo/index.ts", - "/a/b/c/node_modules/foo/index.tsx", - "/a/b/c/node_modules/foo/index.d.ts", - ]) + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + let containingFile = { name: "/a/b/c/d/e.ts" }; + let moduleFile = { name: "/a/b/node_modules/foo.ts" }; + let resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name); + assert.deepEqual(resolution.failedLookupLocations, [ + "/a/b/c/d/node_modules/foo.ts", + "/a/b/c/d/node_modules/foo.tsx", + "/a/b/c/d/node_modules/foo.d.ts", + "/a/b/c/d/node_modules/foo/package.json", + "/a/b/c/d/node_modules/foo/index.ts", + "/a/b/c/d/node_modules/foo/index.tsx", + "/a/b/c/d/node_modules/foo/index.d.ts", + "/a/b/c/node_modules/foo.ts", + "/a/b/c/node_modules/foo.tsx", + "/a/b/c/node_modules/foo.d.ts", + "/a/b/c/node_modules/foo/package.json", + "/a/b/c/node_modules/foo/index.ts", + "/a/b/c/node_modules/foo/index.tsx", + "/a/b/c/node_modules/foo/index.d.ts", + ]) + } }); it("load module as file", () => { - let containingFile = { name: "/a/b/c/d/e.ts" }; - let moduleFile = { name: "/a/b/node_modules/foo.d.ts" }; - let resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(containingFile, moduleFile)); - assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name); - assert.equal(resolution.resolvedModule.isExternalLibraryImport, true); + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + let containingFile = { name: "/a/b/c/d/e.ts" }; + let moduleFile = { name: "/a/b/node_modules/foo.d.ts" }; + let resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name); + assert.equal(resolution.resolvedModule.isExternalLibraryImport, true); + } }); it("load module as directory", () => { - let containingFile = { name: "/a/node_modules/b/c/node_modules/d/e.ts" }; - let moduleFile = { name: "/a/node_modules/foo/index.d.ts" }; - let resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(containingFile, moduleFile)); - assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name); - assert.equal(resolution.resolvedModule.isExternalLibraryImport, true); - assert.deepEqual(resolution.failedLookupLocations, [ - "/a/node_modules/b/c/node_modules/d/node_modules/foo.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/foo.tsx", - "/a/node_modules/b/c/node_modules/d/node_modules/foo.d.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/foo/package.json", - "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.tsx", - "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.d.ts", - "/a/node_modules/b/c/node_modules/foo.ts", - "/a/node_modules/b/c/node_modules/foo.tsx", - "/a/node_modules/b/c/node_modules/foo.d.ts", - "/a/node_modules/b/c/node_modules/foo/package.json", - "/a/node_modules/b/c/node_modules/foo/index.ts", - "/a/node_modules/b/c/node_modules/foo/index.tsx", - "/a/node_modules/b/c/node_modules/foo/index.d.ts", - "/a/node_modules/b/node_modules/foo.ts", - "/a/node_modules/b/node_modules/foo.tsx", - "/a/node_modules/b/node_modules/foo.d.ts", - "/a/node_modules/b/node_modules/foo/package.json", - "/a/node_modules/b/node_modules/foo/index.ts", - "/a/node_modules/b/node_modules/foo/index.tsx", - "/a/node_modules/b/node_modules/foo/index.d.ts", - "/a/node_modules/foo.ts", - "/a/node_modules/foo.tsx", - "/a/node_modules/foo.d.ts", - "/a/node_modules/foo/package.json", - "/a/node_modules/foo/index.ts", - "/a/node_modules/foo/index.tsx" - ]); + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + let containingFile = { name: "/a/node_modules/b/c/node_modules/d/e.ts" }; + let moduleFile = { name: "/a/node_modules/foo/index.d.ts" }; + let resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name); + assert.equal(resolution.resolvedModule.isExternalLibraryImport, true); + assert.deepEqual(resolution.failedLookupLocations, [ + "/a/node_modules/b/c/node_modules/d/node_modules/foo.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/foo.tsx", + "/a/node_modules/b/c/node_modules/d/node_modules/foo.d.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/foo/package.json", + "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.tsx", + "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.d.ts", + "/a/node_modules/b/c/node_modules/foo.ts", + "/a/node_modules/b/c/node_modules/foo.tsx", + "/a/node_modules/b/c/node_modules/foo.d.ts", + "/a/node_modules/b/c/node_modules/foo/package.json", + "/a/node_modules/b/c/node_modules/foo/index.ts", + "/a/node_modules/b/c/node_modules/foo/index.tsx", + "/a/node_modules/b/c/node_modules/foo/index.d.ts", + "/a/node_modules/b/node_modules/foo.ts", + "/a/node_modules/b/node_modules/foo.tsx", + "/a/node_modules/b/node_modules/foo.d.ts", + "/a/node_modules/b/node_modules/foo/package.json", + "/a/node_modules/b/node_modules/foo/index.ts", + "/a/node_modules/b/node_modules/foo/index.tsx", + "/a/node_modules/b/node_modules/foo/index.d.ts", + "/a/node_modules/foo.ts", + "/a/node_modules/foo.tsx", + "/a/node_modules/foo.d.ts", + "/a/node_modules/foo/package.json", + "/a/node_modules/foo/index.ts", + "/a/node_modules/foo/index.tsx" + ]); + + } }); }); @@ -400,4 +458,21 @@ import b = require("./moduleB.ts"); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /* useCaseSensitiveFileNames */ false, ["moduleD.ts"], []); }) }); + + function notImplemented(name: string): () => any { + return () => assert(`${name} is not implemented and should not be called`); + } + + describe("ModuleResolutionHost.directoryExists", () => { + it("No 'fileExists' calls if containing directory is missing", () => { + const host: ModuleResolutionHost = { + readFile: notImplemented("readFile"), + fileExists: notImplemented("fileExists"), + directoryExists: _ => false + }; + + const result = resolveModuleName("someName", "/a/b/c/d", { moduleResolution: ModuleResolutionKind.NodeJs }, host); + assert(!result.resolvedModule); + }); + }); } \ No newline at end of file