mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
introduce ModuleResolutionHost interface
This commit is contained in:
+79
-33
@@ -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
@@ -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
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user