add optional 'directoryExists' method to hosts to reduce amount of disk probings that are known to fail

This commit is contained in:
Vladimir Matveev
2016-01-06 12:37:52 -08:00
parent b168da9248
commit 36af815bba
7 changed files with 222 additions and 110 deletions

View File

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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -100,7 +100,8 @@ namespace ts.server {
this.filenameToScript = createFileMap<ScriptInfo>();
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)
};
}

View File

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

View File

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