introduce ModuleResolutionHost interface

This commit is contained in:
Vladimir Matveev
2015-07-15 11:40:49 -07:00
parent e15c700549
commit 9332f7e1e3
8 changed files with 204 additions and 73 deletions
+79 -33
View File
@@ -27,6 +27,52 @@ namespace ts {
}
return undefined;
}
export function getDefaultModuleNameResolver(options: CompilerOptions): ModuleNameResolver {
// TODO: return different resolver based on compiler options (i.e. module kind)
return resolveModuleName;
}
export function resolveTripleslashReference(moduleName: string, containingFile: string): string {
let basePath = getDirectoryPath(containingFile);
let referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName);
return normalizePath(referencedFileName);
}
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule {
let searchPath = getDirectoryPath(containingFile);
let searchName: string;
let failedLookupLocations: string[] = [];
let referencedSourceFile: string;
while (true) {
searchName = normalizePath(combinePaths(searchPath, moduleName));
referencedSourceFile = forEach(supportedExtensions, extension => {
let candidate = searchName + extension;
let ok = host.fileExists(candidate) ? candidate : undefined;
if (host.fileExists(candidate)) {
return candidate;
}
else {
failedLookupLocations.push(candidate);
}
});
if (referencedSourceFile) {
break;
}
let parentPath = getDirectoryPath(searchPath);
if (parentPath === searchPath) {
break;
}
searchPath = parentPath;
}
return { resolvedFileName: referencedSourceFile, failedLookupLocations };
}
export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost {
let currentDirectory: string;
@@ -94,7 +140,11 @@ namespace ts {
}
const newLine = getNewLineCharacter(options);
let moduleResolutionHost: ModuleResolutionHost = {
fileExists: fileName => sys.fileExists(fileName),
}
return {
getSourceFile,
getDefaultLibFileName: options => combinePaths(getDirectoryPath(normalizePath(sys.getExecutingFilePath())), getDefaultLibFileName(options)),
@@ -102,7 +152,8 @@ namespace ts {
getCurrentDirectory: () => currentDirectory || (currentDirectory = sys.getCurrentDirectory()),
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
getCanonicalFileName,
getNewLine: () => newLine
getNewLine: () => newLine,
getModuleResolutionHost: () => moduleResolutionHost
};
}
@@ -160,6 +211,20 @@ namespace ts {
let start = new Date().getTime();
host = host || createCompilerHost(options);
// initialize resolveModuleNameWorker only if noResolve is false
let resolveModuleNameWorker: (moduleName: string, containingFile: string) => string;
if (!options.noResolve) {
resolveModuleNameWorker = host.resolveModuleName;
if (!resolveModuleNameWorker) {
Debug.assert(host.getModuleResolutionHost !== undefined);
let defaultResolver = getDefaultModuleNameResolver(options);
resolveModuleNameWorker = (moduleName, containingFile) => {
let moduleResolution = defaultResolver(moduleName, containingFile, options, host.getModuleResolutionHost());
return moduleResolution.resolvedFileName;
}
}
}
let filesByName = createFileMap<SourceFile>(fileName => host.getCanonicalFileName(fileName));
@@ -622,8 +687,8 @@ namespace ts {
function processReferencedFiles(file: SourceFile, basePath: string) {
forEach(file.referencedFiles, ref => {
let referencedFileName = isRootedDiskPath(ref.fileName) ? ref.fileName : combinePaths(basePath, ref.fileName);
processSourceFile(normalizePath(referencedFileName), /* isDefaultLib */ false, file, ref.pos, ref.end);
let referencedFileName = resolveTripleslashReference(ref.fileName, file.fileName);
processSourceFile(referencedFileName, /* isDefaultLib */ false, file, ref.pos, ref.end);
});
}
@@ -647,36 +712,17 @@ namespace ts {
return findSourceFile(fileName, /* isDefaultLib */ false, file, nameLiteral.pos, nameLiteral.end - nameLiteral.pos);
}
function resolveModule(moduleNameExpr: LiteralExpression, existingResolutions: Map<string>): void {
let searchPath = basePath;
let searchName: string;
if (existingResolutions && hasProperty(existingResolutions, moduleNameExpr.text)) {
let fileName = existingResolutions[moduleNameExpr.text];
// use existing resolution
setResolvedModuleName(file, moduleNameExpr.text, fileName);
if (fileName) {
findModuleSourceFile(fileName, moduleNameExpr);
}
return;
function resolveModule(moduleNameExpr: LiteralExpression, existingResolutions: Map<string>): void {
Debug.assert(resolveModuleNameWorker !== undefined);
let resolvedModuleName = existingResolutions && hasProperty(existingResolutions, moduleNameExpr.text)
? existingResolutions[moduleNameExpr.text]
: resolveModuleNameWorker(moduleNameExpr.text, file.fileName);
setResolvedModuleName(file, moduleNameExpr.text, resolvedModuleName);
if (resolvedModuleName) {
findModuleSourceFile(resolvedModuleName, moduleNameExpr);
}
while (true) {
searchName = normalizePath(combinePaths(searchPath, moduleNameExpr.text));
let referencedSourceFile = forEach(supportedExtensions, extension => findModuleSourceFile(searchName + extension, moduleNameExpr));
if (referencedSourceFile) {
setResolvedModuleName(file, moduleNameExpr.text, referencedSourceFile.fileName);
return;
}
let parentPath = getDirectoryPath(searchPath);
if (parentPath === searchPath) {
break;
}
searchPath = parentPath;
}
// mark reference as non-resolved
setResolvedModuleName(file, moduleNameExpr.text, undefined);
}
}
+16 -1
View File
@@ -1285,7 +1285,7 @@ namespace ts {
getCurrentDirectory(): string;
}
export interface ParseConfigHost {
export interface ParseConfigHost extends ModuleResolutionHost {
readDirectory(rootDir: string, extension: string, exclude: string[]): string[];
}
@@ -2197,7 +2197,18 @@ namespace ts {
byteOrderMark = 0xFEFF,
tab = 0x09, // \t
verticalTab = 0x0B, // \v
}
export interface ModuleResolutionHost {
fileExists(fileName: string): boolean;
}
export interface ResolvedModule {
resolvedFileName: string;
failedLookupLocations: string[];
}
export type ModuleNameResolver = (moduleName: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => ResolvedModule;
export interface CompilerHost {
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
@@ -2207,6 +2218,10 @@ namespace ts {
getCanonicalFileName(fileName: string): string;
useCaseSensitiveFileNames(): boolean;
getNewLine(): string;
// compiler host should implement one of these methods
resolveModuleName?(moduleName: string, containingFile: string): string;
getModuleResolutionHost?(): ModuleResolutionHost;
}
export interface TextSpan {
+31 -24
View File
@@ -866,41 +866,48 @@ module Harness {
}
};
inputFiles.forEach(register);
function getSourceFile(fn: string, languageVersion: ts.ScriptTarget) {
fn = ts.normalizePath(fn);
if (Object.prototype.hasOwnProperty.call(filemap, getCanonicalFileName(fn))) {
return filemap[getCanonicalFileName(fn)];
}
else if (currentDirectory) {
var canonicalAbsolutePath = getCanonicalFileName(ts.getNormalizedAbsolutePath(fn, currentDirectory));
return Object.prototype.hasOwnProperty.call(filemap, getCanonicalFileName(canonicalAbsolutePath)) ? filemap[canonicalAbsolutePath] : undefined;
}
else if (fn === fourslashFileName) {
var tsFn = 'tests/cases/fourslash/' + fourslashFileName;
fourslashSourceFile = fourslashSourceFile || createSourceFileAndAssertInvariants(tsFn, Harness.IO.readFile(tsFn), scriptTarget);
return fourslashSourceFile;
}
else {
if (fn === defaultLibFileName) {
return languageVersion === ts.ScriptTarget.ES6 ? defaultES6LibSourceFile : defaultLibSourceFile;
}
// Don't throw here -- the compiler might be looking for a test that actually doesn't exist as part of the TC
return undefined;
}
}
let newLine =
newLineKind === ts.NewLineKind.CarriageReturnLineFeed ? carriageReturnLineFeed :
newLineKind === ts.NewLineKind.LineFeed ? lineFeed :
ts.sys.newLine;
let moduleResolutionHost: ts.ModuleResolutionHost = {
fileExists: fileName => getSourceFile(fileName, ts.ScriptTarget.ES5) !== undefined,
}
return {
getCurrentDirectory,
getSourceFile: (fn, languageVersion) => {
fn = ts.normalizePath(fn);
if (Object.prototype.hasOwnProperty.call(filemap, getCanonicalFileName(fn))) {
return filemap[getCanonicalFileName(fn)];
}
else if (currentDirectory) {
var canonicalAbsolutePath = getCanonicalFileName(ts.getNormalizedAbsolutePath(fn, currentDirectory));
return Object.prototype.hasOwnProperty.call(filemap, getCanonicalFileName(canonicalAbsolutePath)) ? filemap[canonicalAbsolutePath] : undefined;
}
else if (fn === fourslashFileName) {
var tsFn = 'tests/cases/fourslash/' + fourslashFileName;
fourslashSourceFile = fourslashSourceFile || createSourceFileAndAssertInvariants(tsFn, Harness.IO.readFile(tsFn), scriptTarget);
return fourslashSourceFile;
}
else {
if (fn === defaultLibFileName) {
return languageVersion === ts.ScriptTarget.ES6 ? defaultES6LibSourceFile : defaultLibSourceFile;
}
// Don't throw here -- the compiler might be looking for a test that actually doesn't exist as part of the TC
return undefined;
}
},
getSourceFile,
getDefaultLibFileName: options => defaultLibFileName,
writeFile,
getCanonicalFileName,
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getNewLine: () => newLine
getNewLine: () => newLine,
getModuleResolutionHost: () => moduleResolutionHost
};
}
+2 -1
View File
@@ -228,7 +228,8 @@ module Harness.LanguageService {
readDirectory(rootDir: string, extension: string): string {
throw new Error("NYI");
}
}
fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; }
log(s: string): void { this.nativeHost.log(s); }
trace(s: string): void { this.nativeHost.trace(s); }
+6 -1
View File
@@ -128,6 +128,10 @@ class ProjectRunner extends RunnerBase {
getSourceFileText: (fileName: string) => string,
writeFile: (fileName: string, data: string, writeByteOrderMark: boolean) => void): CompileProjectFilesResult {
let moduleResolutionHost: ts.ModuleResolutionHost = {
fileExists: fileName => getSourceFile(fileName, ts.ScriptTarget.ES5) !== undefined,
}
var program = ts.createProgram(getInputFiles(), createCompilerOptions(), createCompilerHost());
var errors = ts.getPreEmitDiagnostics(program);
@@ -190,7 +194,8 @@ class ProjectRunner extends RunnerBase {
getCurrentDirectory,
getCanonicalFileName: Harness.Compiler.getCanonicalFileName,
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
getNewLine: () => ts.sys.newLine
getNewLine: () => ts.sys.newLine,
getModuleResolutionHost: () => moduleResolutionHost
};
}
}
+36 -5
View File
@@ -122,7 +122,7 @@ namespace ts {
referencedFiles: FileReference[];
importedFiles: FileReference[];
isLibFile: boolean
}
}
let scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true);
@@ -967,6 +967,10 @@ namespace ts {
trace? (s: string): void;
error? (s: string): void;
useCaseSensitiveFileNames? (): boolean;
// LS host should implement one of these methods
resolveModuleName?(moduleName: string, containingFile: string): string;
getModuleResolutionHost?(): ModuleResolutionHost;
}
//
@@ -1783,6 +1787,10 @@ namespace ts {
// Output
let outputText: string;
let moduleResolutionHost: ModuleResolutionHost = {
fileExists: fileName => fileName === inputFileName,
}
// Create a compilerHost object to allow the compiler to read and write files
let compilerHost: CompilerHost = {
@@ -1795,7 +1803,8 @@ namespace ts {
useCaseSensitiveFileNames: () => false,
getCanonicalFileName: fileName => fileName,
getCurrentDirectory: () => "",
getNewLine: () => newLine
getNewLine: () => newLine,
getModuleResolutionHost: () => moduleResolutionHost
};
let program = createProgram([inputFileName], options, compilerHost);
@@ -1989,6 +1998,11 @@ namespace ts {
reportStats
};
}
export function resolveModuleName(fileName: string, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule {
let resolver = getDefaultModuleNameResolver(compilerOptions);
return resolver(moduleName, fileName, compilerOptions, host);
}
export function preProcessFile(sourceText: string, readImportFiles = true): PreProcessedFileInfo {
let referencedFiles: FileReference[] = [];
@@ -2472,9 +2486,9 @@ namespace ts {
let oldSettings = program && program.getCompilerOptions();
let newSettings = hostCache.compilationSettings();
let changesInCompilationSettingsAffectSyntax = oldSettings && oldSettings.target !== newSettings.target;
// Now create a new compiler
let newProgram = createProgram(hostCache.getRootFileNames(), newSettings, {
let compilerHost: CompilerHost = {
getSourceFile: getOrCreateSourceFile,
getCancellationToken: () => cancellationToken,
getCanonicalFileName,
@@ -2483,7 +2497,24 @@ namespace ts {
getDefaultLibFileName: (options) => host.getDefaultLibFileName(options),
writeFile: (fileName, data, writeByteOrderMark) => { },
getCurrentDirectory: () => host.getCurrentDirectory(),
}, program);
};
if (host.resolveModuleName) {
compilerHost.resolveModuleName = (moduleName, containingFile) => host.resolveModuleName(moduleName, containingFile)
}
else if (host.getModuleResolutionHost) {
compilerHost.getModuleResolutionHost = () => host.getModuleResolutionHost()
}
else {
compilerHost.getModuleResolutionHost = () => {
// stub missing host functionality
return {
fileExists: fileName => hostCache.getOrCreateEntry(fileName) != undefined,
};
}
}
let newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program);
// Release any files we have acquired in the old program but are
// not part of the new program.
+28 -3
View File
@@ -57,10 +57,12 @@ namespace ts {
getNewLine?(): string;
getProjectVersion?(): string;
useCaseSensitiveFileNames?(): boolean;
getModuleResolutionsForFile?(fileName: string): string;
}
/** Public interface of the the of a config service shim instance.*/
export interface CoreServicesShimHost extends Logger {
export interface CoreServicesShimHost extends Logger, ModuleResolutionHost {
/** Returns a JSON-encoded value of the type: string[] */
readDirectory(rootDir: string, extension: string): string;
}
@@ -249,8 +251,20 @@ namespace ts {
private files: string[];
private loggingEnabled = false;
private tracingEnabled = false;
private lastRequestedFile: string;
private lastRequestedModuleResolutions: Map<string>;
constructor(private shimHost: LanguageServiceShimHost) {
if ("getModuleResolutionsForFile" in this.shimHost) {
(<any>this).resolveModuleName = (moduleName: string, containingFile: string) => {
if (this.lastRequestedFile !== containingFile) {
this.lastRequestedModuleResolutions = <Map<string>>JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile));
this.lastRequestedFile = containingFile;
}
return this.lastRequestedModuleResolutions[moduleName];
};
}
}
public log(s: string): void {
@@ -267,7 +281,7 @@ namespace ts {
public error(s: string): void {
this.shimHost.error(s);
}
}
public getProjectVersion(): string {
if (!this.shimHost.getProjectVersion) {
@@ -379,6 +393,10 @@ namespace ts {
var encoded = this.shimHost.readDirectory(rootDir, extension);
return JSON.parse(encoded);
}
public fileExists(fileName: string): boolean {
return this.shimHost.fileExists(fileName);
}
}
function simpleForwardCall(logger: Logger, actionDescription: string, action: () => any, logPerformance: boolean): any {
@@ -505,7 +523,7 @@ namespace ts {
private realizeDiagnostics(diagnostics: Diagnostic[]): { message: string; start: number; length: number; category: string; }[]{
var newLine = this.getNewLine();
return ts.realizeDiagnostics(diagnostics, newLine);
}
}
public getSyntacticClassifications(fileName: string, start: number, length: number): string {
return this.forwardJSONCall(
@@ -881,6 +899,13 @@ namespace ts {
private forwardJSONCall(actionDescription: string, action: () => any): any {
return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance);
}
public resolveModuleName(fileName: string, moduleName: string, compilerOptionsJson: string): string {
return this.forwardJSONCall(`resolveModuleName('${fileName}')`, () => {
let compilerOptions = <CompilerOptions>JSON.parse(compilerOptionsJson);
return resolveModuleName(fileName, moduleName, compilerOptions, this.host);
});
}
public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string {
return this.forwardJSONCall(
@@ -103,6 +103,11 @@ module ts {
file.sourceText = t.text;
files[t.name] = file;
}
let moduleResolutionHost: ModuleResolutionHost = {
fileExists: fileName => hasProperty(files, fileName)
}
return {
getSourceFile(fileName): SourceFile {
return files[fileName];
@@ -125,11 +130,7 @@ module ts {
getNewLine(): string {
return sys.newLine;
},
hasChanges(oldFile: SourceFileWithText): boolean {
let current = files[oldFile.fileName];
return !current || oldFile.sourceText.getVersion() !== current.sourceText.getVersion();
}
getModuleResolutionHost: () => moduleResolutionHost
}
}