From 6844285782edca96d9591678580aeb8a4423205e Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Thu, 19 Nov 2015 21:33:33 -0800 Subject: [PATCH] verbose module resolution --- src/compiler/commandLineParser.ts | 5 + src/compiler/core.ts | 13 +- src/compiler/diagnosticMessages.json | 114 +++++++++++++++- src/compiler/program.ts | 189 +++++++++++++++++++++++---- src/compiler/types.ts | 6 +- src/services/shims.ts | 2 + 6 files changed, 292 insertions(+), 37 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 4fd29de142d..f462c7c1e91 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -302,6 +302,11 @@ namespace ts { type: "object", isTSConfigOnly: true }, + { + name: "traceModuleResolution", + type: "boolean", + description: Diagnostics.Enable_tracing_of_the_module_resolution_process + }, { name: "allowJs", type: "boolean", diff --git a/src/compiler/core.ts b/src/compiler/core.ts index c93f3aebc2d..50b10ac5a90 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -410,6 +410,17 @@ namespace ts { }; } + /* internal */ + export function formatMessage(dummy: any, message: DiagnosticMessage): string { + let text = getLocaleSpecificMessage(message); + + if (arguments.length > 2) { + text = formatStringFromArgs(text, arguments, 2); + } + + return text; + } + export function createCompilerDiagnostic(message: DiagnosticMessage, ...args: any[]): Diagnostic; export function createCompilerDiagnostic(message: DiagnosticMessage): Diagnostic { let text = getLocaleSpecificMessage(message); @@ -860,4 +871,4 @@ namespace ts { } return copiedList; } -} +} diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 96e48766f71..3d696ff8172 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2220,10 +2220,10 @@ "category": "Error", "code": 6046 }, - "Argument for '--target' option must be 'ES3', 'ES5', or 'ES2015'.": { - "category": "Error", - "code": 6047 - }, + "Argument for '--target' option must be 'ES3', 'ES5', or 'ES2015'.": { + "category": "Error", + "code": 6047 + }, "Locale must be of the form or -. For example '{0}' or '{1}'.": { "category": "Error", "code": 6048 @@ -2353,14 +2353,114 @@ "category": "Error", "code": 6082 }, - "Allow javascript files to be compiled.": { + "Base directory to resolve relative module names.": { "category": "Message", "code": 6083 }, - "Base directory to resolve relative module names.": { + "Enable tracing of the module resolution process.": { "category": "Message", "code": 6084 - }, + }, + "======== Resolving module '{0}' from '{1}'. ========": { + "category": "Message", + "code": 6085 + }, + "Explicitly specified module resolution kind: '{0}'.": { + "category": "Message", + "code": 6086 + }, + "Module resolution kind is not specified, using '{0}'.": { + "category": "Message", + "code": 6087 + }, + "======== Module name '{0}' was successfully resolved to '{1}'. ========": { + "category": "Message", + "code": 6088 + }, + "======== Module name '{0}' was not resolved. ========": { + "category": "Message", + "code": 6089 + }, + "Resolving rooted module name '{0}' - use it as a candidate location.": { + "category": "Message", + "code": 6090 + }, + "Resolving relative module name '{0}'.": { + "category": "Message", + "code": 6091 + }, + "Resolving non-relative module name '{0}'.": { + "category": "Message", + "code": 6092 + }, + "Converting relative module name '{0}' to absolute using '{1}' as a base directory => '{2}'.": { + "category": "Message", + "code": 6093 + }, + "'rootDirs' option is specified, searching for a longest matching prefix...": { + "category": "Message", + "code": 6094 + }, + "Found longest matching rootDir '{0}', 'converted relative path '{1}', to non-relative path '{2}'": { + "category": "Message", + "code": 6095 + }, + "'rootDirs' option is not specified, using '{0}' as candidate location.": { + "category": "Message", + "code": 6096 + }, + "'paths' option is specified, looking for a pattern to match module name '{0}'.": { + "category": "Message", + "code": 6097 + }, + "Module name '{0}', matched pattern '{1}'.": { + "category": "Message", + "code": 6098 + }, + "Trying substitution '{0}', candidate module location: '{1}'.": { + "category": "Message", + "code": 6099 + }, + "Resolving module name '{0}' relative to base url '{1}' - '{2}'.": { + "category": "Message", + "code": 6100 + }, + "Loading module '{0}' as file / folder, candidate module location '{1}'.": { + "category": "Message", + "code": 6101 + }, + "File '{0}' does not exist.": { + "category": "Message", + "code": 6102 + }, + "File '{0}' exist - use it as a module resolution result.": { + "category": "Message", + "code": 6103 + }, + "Loading module '{0}' from 'node_modules' folder.": { + "category": "Message", + "code": 6104 + }, + "Found 'package.json' at '{0}'.": { + "category": "Message", + "code": 6105 + }, + "'package.json' does not have 'typings' field.": { + "category": "Message", + "code": 6106 + }, + "'package.json' has 'typings' field '{0}' that references '{1}'.": { + "category": "Message", + "code": 6107 + }, + "Module name '{0}' contains '!' character.": { + "category": "Message", + "code": 6108 + }, + "Allow javascript files to be compiled.": { + "category": "Message", + "code": 6109 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 6d99b6e1dda..b9ebef7c27f 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -36,7 +36,21 @@ namespace ts { return normalizePath(referencedFileName); } + function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; + function trace(host: ModuleResolutionHost, message: DiagnosticMessage): void { + host.trace(formatMessage.apply(undefined, arguments)); + } + + function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { + return compilerOptions.traceModuleResolution && host.trace !== undefined; + } + export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + if (traceEnabled) { + trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); + } + let moduleResolution = compilerOptions.moduleResolution; if (moduleResolution === undefined) { if (compilerOptions.module === ModuleKind.CommonJS) { @@ -48,13 +62,40 @@ namespace ts { else { moduleResolution = ModuleResolutionKind.Classic; } + + if (traceEnabled) { + trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); + } + } + else { + if (traceEnabled) { + trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); + } } + let result: ResolvedModuleWithFailedLookupLocations; switch (moduleResolution) { - case ModuleResolutionKind.NodeJs: return nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host); - case ModuleResolutionKind.Classic: return classicNameResolver(moduleName, containingFile, compilerOptions, host); - case ModuleResolutionKind.BaseUrl: return baseUrlModuleNameResolver(moduleName, containingFile, compilerOptions, host); + case ModuleResolutionKind.NodeJs: + result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host); + break; + case ModuleResolutionKind.Classic: + result = classicNameResolver(moduleName, containingFile, compilerOptions, host); + break; + case ModuleResolutionKind.BaseUrl: + result = baseUrlModuleNameResolver(moduleName, containingFile, compilerOptions, host); + break; } + + if (traceEnabled) { + if (result.resolvedModule) { + trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); + } + else { + trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); + } + } + + return result; } // Path mapping based module resolution strategy uses base url to resolve relative file names. This resolution strategy can be enabled @@ -135,28 +176,52 @@ namespace ts { Debug.assert(baseUrl !== undefined); const supportedExtensions = getSupportedExtensions(compilerOptions); + const traceEnabled = isTraceEnabled(compilerOptions, host); + if (isRootedDiskPath(moduleName)) { - return { resolvedModule: { resolvedFileName: moduleName }, failedLookupLocations: emptyArray }; + if (traceEnabled) { + trace(host, Diagnostics.Resolving_rooted_module_name_0_use_it_as_a_candidate_location, moduleName); + } + + const failedLookupLocations: string[] = []; + const resolvedFileName = loadModuleFromFile(supportedExtensions, moduleName, failedLookupLocations, host, traceEnabled); + return { + resolvedModule: resolvedFileName ? { resolvedFileName } : undefined, + failedLookupLocations + }; } if (nameStartsWithDotSlashOrDotDotSlash(moduleName)) { // relative name - return baseUrlResolveRelativeModuleName(moduleName, containingFile, baseUrl, compilerOptions, host); + if (traceEnabled) { + trace(host, Diagnostics.Resolving_relative_module_name_0, moduleName); + } + return baseUrlResolveRelativeModuleName(moduleName, containingFile, baseUrl, supportedExtensions, compilerOptions, host, traceEnabled); } else { // non-relative name - return baseUrlResolveNonRelativeModuleName(moduleName, baseUrl, supportedExtensions, compilerOptions, host); + if (traceEnabled) { + trace(host, Diagnostics.Resolving_non_relative_module_name_0, moduleName); + } + return baseUrlResolveNonRelativeModuleName(moduleName, baseUrl, supportedExtensions, compilerOptions, host, traceEnabled); } } - export function baseUrlResolveRelativeModuleName(moduleName: string, containingFile: string, baseUrl: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + function baseUrlResolveRelativeModuleName(moduleName: string, containingFile: string, baseUrl: string, supportedExtensions: string[], compilerOptions: CompilerOptions, host: ModuleResolutionHost, traceEnabled: boolean): ResolvedModuleWithFailedLookupLocations { const failedLookupLocations: string[] = []; const containingDirectory = getDirectoryPath(containingFile); const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); - const supportedExtensions = getSupportedExtensions(compilerOptions); + + if (traceEnabled) { + trace(host, Diagnostics.Converting_relative_module_name_0_to_absolute_using_1_as_a_base_directory_2, moduleName, containingDirectory, candidate); + } if (compilerOptions.rootDirs) { + if (traceEnabled) { + trace(host, Diagnostics.rootDirs_option_is_specified_searching_for_a_longest_matching_prefix); + } + let matchedPrefix: string; for (const rootDir of compilerOptions.rootDirs) { let normalizedRoot = getNormalizedAbsolutePath(rootDir, baseUrl); @@ -169,12 +234,20 @@ namespace ts { } if (matchedPrefix) { const suffix = candidate.substr(matchedPrefix.length); - return baseUrlResolveNonRelativeModuleName(suffix, baseUrl, supportedExtensions, compilerOptions, host); + if (traceEnabled) { + trace(host, Diagnostics.Found_longest_matching_rootDir_0_converted_relative_path_1_to_non_relative_path_2, matchedPrefix, moduleName, suffix); + } + + return baseUrlResolveNonRelativeModuleName(suffix, baseUrl, supportedExtensions, compilerOptions, host, traceEnabled); } return { resolvedModule: undefined, failedLookupLocations }; } else { - const resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host); + if (traceEnabled) { + trace(host, Diagnostics.rootDirs_option_is_not_specified_using_0_as_candidate_location, candidate); + } + + const resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host, traceEnabled); return { resolvedModule: resolvedFileName ? { resolvedFileName } : undefined, failedLookupLocations @@ -182,14 +255,18 @@ namespace ts { } } - export function baseUrlResolveNonRelativeModuleName(moduleName: string, baseUrl: string, supportedExtensions: string[], options: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + function baseUrlResolveNonRelativeModuleName(moduleName: string, baseUrl: string, supportedExtensions: string[], compilerOptions: CompilerOptions, host: ModuleResolutionHost, traceEnabled: boolean): ResolvedModuleWithFailedLookupLocations { let longestMatchPrefixLength = -1; let matchedPattern: string; let matchedStar: string; const failedLookupLocations: string[] = []; - if (options.paths) { - for (const key in options.paths) { + if (compilerOptions.paths) { + if (traceEnabled) { + trace(host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); + } + + for (const key in compilerOptions.paths) { const pattern: string = key; const indexOfStar = pattern.indexOf("*"); if (indexOfStar !== -1) { @@ -217,10 +294,19 @@ namespace ts { } if (matchedPattern) { - for (const subst of options.paths[matchedPattern]) { + if (traceEnabled) { + trace(host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPattern); + } + + for (const subst of compilerOptions.paths[matchedPattern]) { const path = matchedStar ? subst.replace("\*", matchedStar) : subst; const candidate = normalizePath(combinePaths(baseUrl, path)); - const resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host); + + if (traceEnabled) { + trace(host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); + } + + const resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host, traceEnabled); if (resolvedFileName) { return { resolvedModule: { resolvedFileName }, failedLookupLocations }; } @@ -230,7 +316,12 @@ namespace ts { } else { const candidate = normalizePath(combinePaths(baseUrl, moduleName)); - const resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host); + + if (traceEnabled) { + trace(host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate); + } + + const resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host, traceEnabled); return { resolvedModule: resolvedFileName ? { resolvedFileName } : undefined, failedLookupLocations @@ -250,44 +341,64 @@ namespace ts { export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { const containingDirectory = getDirectoryPath(containingFile); const supportedExtensions = getSupportedExtensions(compilerOptions); + const traceEnabled = isTraceEnabled(compilerOptions, host); if (isRootedDiskPath(moduleName) || nameStartsWithDotSlashOrDotDotSlash(moduleName)) { const failedLookupLocations: string[] = []; const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); - let resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host); + + if (traceEnabled) { + trace(host, Diagnostics.Loading_module_0_as_file_Slash_folder_candidate_module_location_1, moduleName, candidate); + } + + let resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host, traceEnabled); if (resolvedFileName) { return { resolvedModule: { resolvedFileName }, failedLookupLocations }; } - resolvedFileName = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, host); + resolvedFileName = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, host, traceEnabled); return resolvedFileName ? { resolvedModule: { resolvedFileName }, failedLookupLocations } : { resolvedModule: undefined, failedLookupLocations }; } else { - return loadModuleFromNodeModules(moduleName, containingDirectory, host); + if (traceEnabled) { + trace(host, Diagnostics.Loading_module_0_from_node_modules_folder, moduleName); + } + + return loadModuleFromNodeModules(moduleName, containingDirectory, host, traceEnabled); } } - function loadModuleFromFile(extensions: string[], candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost): string { + function loadModuleFromFile(extensions: string[], candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost, traceEnabled: boolean): string { return forEach(extensions, tryLoad); function tryLoad(ext: string): string { const fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext; + if (host.fileExists(fileName)) { + if (traceEnabled) { + trace(host, Diagnostics.File_0_exist_use_it_as_a_module_resolution_result, fileName); + } return fileName; } else { + if (traceEnabled) { + trace(host, Diagnostics.File_0_does_not_exist, fileName); + } failedLookupLocation.push(fileName); return undefined; } } } - function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost): string { + function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost, traceEnabled: boolean): string { const packageJsonPath = combinePaths(candidate, "package.json"); if (host.fileExists(packageJsonPath)) { + if (traceEnabled) { + trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); + } let jsonContent: { typings?: string }; @@ -301,21 +412,33 @@ namespace ts { } if (jsonContent.typings) { - const result = loadModuleFromFile(extensions, normalizePath(combinePaths(candidate, jsonContent.typings)), failedLookupLocation, host); + const typingsFile = normalizePath(combinePaths(candidate, jsonContent.typings)); + if (traceEnabled) { + trace(host, Diagnostics.package_json_has_typings_field_0_that_references_1, jsonContent.typings, typingsFile); + } + const result = loadModuleFromFile(extensions, typingsFile , failedLookupLocation, host, traceEnabled); if (result) { return result; } } + else { + if (traceEnabled) { + trace(host, Diagnostics.package_json_does_not_have_typings_field); + } + } } else { + if (traceEnabled) { + trace(host, Diagnostics.package_json_does_not_have_typings_field); + } // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results failedLookupLocation.push(packageJsonPath); } - return loadModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocation, host); + return loadModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocation, host, traceEnabled); } - function loadModuleFromNodeModules(moduleName: string, directory: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + function loadModuleFromNodeModules(moduleName: string, directory: string, host: ModuleResolutionHost, traceEnabled: boolean): ResolvedModuleWithFailedLookupLocations { const failedLookupLocations: string[] = []; directory = normalizeSlashes(directory); while (true) { @@ -324,12 +447,12 @@ namespace ts { const nodeModulesFolder = combinePaths(directory, "node_modules"); const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName)); // Load only typescript files irrespective of allowJs option if loading from node modules - let result = loadModuleFromFile(supportedTypeScriptExtensions, candidate, failedLookupLocations, host); + let result = loadModuleFromFile(supportedTypeScriptExtensions, candidate, failedLookupLocations, host, traceEnabled); if (result) { return { resolvedModule: { resolvedFileName: result, isExternalLibraryImport: true }, failedLookupLocations }; } - result = loadNodeModuleFromDirectory(supportedTypeScriptExtensions, candidate, failedLookupLocations, host); + result = loadNodeModuleFromDirectory(supportedTypeScriptExtensions, candidate, failedLookupLocations, host, traceEnabled); if (result) { return { resolvedModule: { resolvedFileName: result, isExternalLibraryImport: true }, failedLookupLocations }; } @@ -369,8 +492,13 @@ namespace ts { export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + // module names that contain '!' are used to reference resources and are not resolved to actual files on disk if (moduleName.indexOf("!") != -1) { + if (traceEnabled) { + trace(host, Diagnostics.Module_name_0_contains_character, moduleName); + } return { resolvedModule: undefined, failedLookupLocations: [] }; } @@ -392,9 +520,15 @@ namespace ts { const candidate = searchName + extension; if (host.fileExists(candidate)) { + if (traceEnabled) { + trace(host, Diagnostics.File_0_exist_use_it_as_a_module_resolution_result, candidate); + } return candidate; } else { + if (traceEnabled) { + trace(host, Diagnostics.File_0_does_not_exist, candidate); + } failedLookupLocations.push(candidate); } }); @@ -498,7 +632,8 @@ namespace ts { getCanonicalFileName, getNewLine: () => newLine, fileExists: fileName => sys.fileExists(fileName), - readFile: fileName => sys.readFile(fileName) + readFile: fileName => sys.readFile(fileName), + trace: (s: string) => sys.write(s + newLine) }; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8dc0b48e781..288532c27e6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2308,7 +2308,7 @@ namespace ts { Message, } - export const enum ModuleResolutionKind { + export enum ModuleResolutionKind { Classic = 1, NodeJs = 2, BaseUrl = 3 @@ -2368,6 +2368,7 @@ namespace ts { baseUrl?: string; paths?: PathSubstitutions; rootDirs?: RootPaths; + traceModuleResolution?: boolean; allowJs?: boolean; /* @internal */ stripInternal?: boolean; @@ -2379,7 +2380,7 @@ namespace ts { [option: string]: string | number | boolean | TsConfigOnlyOptions; } - export const enum ModuleKind { + export enum ModuleKind { None = 0, CommonJS = 1, AMD = 2, @@ -2606,6 +2607,7 @@ 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; + trace?(s: string): void; } export interface ResolvedModule { diff --git a/src/services/shims.ts b/src/services/shims.ts index 6b656ea2738..2f0f0de3e5d 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -73,6 +73,8 @@ namespace ts { * when enumerating the directory. */ readDirectory(rootDir: string, extension: string, exclude?: string): string; + + trace(s: string): void; } ///