mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-19 01:33:15 -05:00
initial implementation of path mapping based module resolution
This commit is contained in:
@@ -250,10 +250,12 @@ namespace ts {
|
||||
name: "moduleResolution",
|
||||
type: {
|
||||
"node": ModuleResolutionKind.NodeJs,
|
||||
"classic": ModuleResolutionKind.Classic
|
||||
"classic": ModuleResolutionKind.Classic,
|
||||
// name is lowercased so we can still use hasProperty(userValue.toLower()) to check if user has entered the right value
|
||||
"baseurl": ModuleResolutionKind.BaseUrl,
|
||||
},
|
||||
description: Diagnostics.Specifies_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6,
|
||||
error: Diagnostics.Argument_for_moduleResolution_option_must_be_node_or_classic,
|
||||
error: Diagnostics.Argument_for_moduleResolution_option_must_be_node_classic_or_baseUrl,
|
||||
},
|
||||
{
|
||||
name: "allowUnusedLabels",
|
||||
@@ -279,6 +281,26 @@ namespace ts {
|
||||
name: "forceConsistentCasingInFileNames",
|
||||
type: "boolean",
|
||||
description: Diagnostics.Disallow_inconsistently_cased_references_to_the_same_file
|
||||
},
|
||||
{
|
||||
name: "baseUrl",
|
||||
type: "string",
|
||||
isFilePath: true,
|
||||
description: Diagnostics.Base_directory_to_resolve_relative_module_names
|
||||
},
|
||||
{
|
||||
// this option can only be specified in tsconfig.json
|
||||
// use type = object to copy the value as-is
|
||||
name: "paths",
|
||||
type: "object",
|
||||
isTSConfigOnly: true
|
||||
},
|
||||
{
|
||||
// this option can only be specified in tsconfig.json
|
||||
// use type = object to copy the value as-is
|
||||
name: "rootDirs",
|
||||
type: "object",
|
||||
isTSConfigOnly: true
|
||||
}
|
||||
];
|
||||
|
||||
@@ -339,31 +361,36 @@ namespace ts {
|
||||
if (hasProperty(optionNameMap, s)) {
|
||||
const opt = optionNameMap[s];
|
||||
|
||||
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
|
||||
if (!args[i] && opt.type !== "boolean") {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_expects_an_argument, opt.name));
|
||||
if (opt.isTSConfigOnly) {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name));
|
||||
}
|
||||
else {
|
||||
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
|
||||
if (!args[i] && opt.type !== "boolean") {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_expects_an_argument, opt.name));
|
||||
}
|
||||
|
||||
switch (opt.type) {
|
||||
case "number":
|
||||
options[opt.name] = parseInt(args[i++]);
|
||||
break;
|
||||
case "boolean":
|
||||
options[opt.name] = true;
|
||||
break;
|
||||
case "string":
|
||||
options[opt.name] = args[i++] || "";
|
||||
break;
|
||||
// If not a primitive, the possible types are specified in what is effectively a map of options.
|
||||
default:
|
||||
let map = <Map<number>>opt.type;
|
||||
let key = (args[i++] || "").toLowerCase();
|
||||
if (hasProperty(map, key)) {
|
||||
options[opt.name] = map[key];
|
||||
}
|
||||
else {
|
||||
errors.push(createCompilerDiagnostic((<CommandLineOptionOfCustomType>opt).error));
|
||||
}
|
||||
switch (opt.type) {
|
||||
case "number":
|
||||
options[opt.name] = parseInt(args[i++]);
|
||||
break;
|
||||
case "boolean":
|
||||
options[opt.name] = true;
|
||||
break;
|
||||
case "string":
|
||||
options[opt.name] = args[i++] || "";
|
||||
break;
|
||||
// If not a primitive, the possible types are specified in what is effectively a map of options.
|
||||
default:
|
||||
let map = <Map<number>>opt.type;
|
||||
let key = (args[i++] || "").toLowerCase();
|
||||
if (hasProperty(map, key)) {
|
||||
options[opt.name] = map[key];
|
||||
}
|
||||
else {
|
||||
errors.push(createCompilerDiagnostic((<CommandLineOptionOfCustomType>opt).error));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -466,7 +493,6 @@ namespace ts {
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the contents of a config file (tsconfig.json).
|
||||
* @param json The contents of the config file to parse
|
||||
@@ -477,6 +503,9 @@ namespace ts {
|
||||
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string): ParsedCommandLine {
|
||||
const { options, errors } = convertCompilerOptionsFromJson(json["compilerOptions"], basePath);
|
||||
|
||||
// set basePath as inferredBaseUrl so baseUrl module resolution strategy can still work even if user have not specified baseUrl explicity
|
||||
options.inferredBaseUrl = basePath;
|
||||
|
||||
return {
|
||||
options,
|
||||
fileNames: getFileNames(),
|
||||
|
||||
@@ -2060,7 +2060,26 @@
|
||||
"category": "Error",
|
||||
"code": 5054
|
||||
},
|
||||
|
||||
"Option '{0}' can only be used when option 'moduleResolution' is 'baseUrl'.": {
|
||||
"category": "Error",
|
||||
"code": 5055
|
||||
},
|
||||
"Pattern '{0}' can have at most one '*' character": {
|
||||
"category": "Error",
|
||||
"code": 5056
|
||||
},
|
||||
"Substitution '{0}' in pattern '{1}' in can have at most one '*' character": {
|
||||
"category": "Error",
|
||||
"code": 5057
|
||||
},
|
||||
"'moduleResolution' kind 'baseUrl' cannot be used without specifying '--baseUrl' option.": {
|
||||
"category": "Error",
|
||||
"code": 5058
|
||||
},
|
||||
"Module resolution kind cannot be determined automatically. Please specify module resolution explicitly via 'moduleResolution' option.": {
|
||||
"category": "Error",
|
||||
"code": 5059
|
||||
},
|
||||
"Concatenate and emit output to single file.": {
|
||||
"category": "Message",
|
||||
"code": 6001
|
||||
@@ -2253,10 +2272,14 @@
|
||||
"category": "Error",
|
||||
"code": 6062
|
||||
},
|
||||
"Argument for '--moduleResolution' option must be 'node' or 'classic'.": {
|
||||
"Argument for '--moduleResolution' option must be 'node', 'classic' or 'baseUrl'.": {
|
||||
"category": "Error",
|
||||
"code": 6063
|
||||
},
|
||||
"Option '{0}' can only be specified in 'tsconfig.json' file.": {
|
||||
"category": "Error",
|
||||
"code": 6064
|
||||
},
|
||||
|
||||
"Enables experimental support for ES7 decorators.": {
|
||||
"category": "Message",
|
||||
@@ -2322,6 +2345,10 @@
|
||||
"category": "Error",
|
||||
"code": 6082
|
||||
},
|
||||
"Base directory to resolve relative module names.": {
|
||||
"category": "Message",
|
||||
"code": 6083
|
||||
},
|
||||
"Variable '{0}' implicitly has an '{1}' type.": {
|
||||
"category": "Error",
|
||||
"code": 7005
|
||||
|
||||
@@ -37,23 +37,221 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const moduleResolution = compilerOptions.moduleResolution !== undefined
|
||||
? compilerOptions.moduleResolution
|
||||
: compilerOptions.module === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic;
|
||||
let moduleResolution = compilerOptions.moduleResolution;
|
||||
if (moduleResolution === undefined) {
|
||||
if (compilerOptions.module === ModuleKind.CommonJS) {
|
||||
moduleResolution = ModuleResolutionKind.NodeJs;
|
||||
}
|
||||
else if (compilerOptions.baseUrl !== undefined || compilerOptions.paths || compilerOptions.rootDirs) {
|
||||
moduleResolution = ModuleResolutionKind.BaseUrl;
|
||||
}
|
||||
else {
|
||||
moduleResolution = ModuleResolutionKind.Classic;
|
||||
}
|
||||
}
|
||||
|
||||
switch (moduleResolution) {
|
||||
case ModuleResolutionKind.NodeJs: return nodeModuleNameResolver(moduleName, containingFile, host);
|
||||
case ModuleResolutionKind.Classic: return classicNameResolver(moduleName, containingFile, compilerOptions, host);
|
||||
case ModuleResolutionKind.BaseUrl: return baseUrlModuleNameResolver(moduleName, containingFile, compilerOptions, host);
|
||||
}
|
||||
}
|
||||
|
||||
// Path mapping based module resolution strategy uses base url to resolve relative file names. This resolution strategy can be enabled
|
||||
// by setting 'moduleResolution' option to 'baseUrl' and as it implied by the name it uses base url to resolve module names.
|
||||
// Base url can be specified:
|
||||
// - explicitly via command line option 'baseUrl'. If this value is relative it will be computed relative to current directory.
|
||||
// - explicitly in 'tsconfig.json' via 'baseUrl' compiler option. If this value is relative it will be computed relative to the location
|
||||
// of 'tsconfig.json'.
|
||||
// - if value is not provided explicitly but project uses tsconfig.json then 'baseUrl' will be set of folder that contains 'tsconfig.json'
|
||||
// If baseUrl is the only provided option then non-relative module names will be resolved relative to baseUrl. Relative module names
|
||||
// will still be resolved relative to the folder that contains file with import.
|
||||
// User can specify additional options in tsconfig.json to tweak module resolution.
|
||||
// - 'paths' option allows to tune how non-relative module names will be resolved based on the content of the module name.
|
||||
// Structure of 'paths' compiler options
|
||||
// 'paths': {
|
||||
// pattern-1: list of substitutions 1,
|
||||
// pattern-2: list of substitutions 2,
|
||||
// ...
|
||||
// pattern-n: list of substitutions-n
|
||||
// }
|
||||
// Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against
|
||||
// all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case.
|
||||
// If pattern contains '*' then to match pattern "<prefix>*<suffix>" module name must start with the <prefix> and end with <suffix>.
|
||||
// <MatchedStar> denotes part of the module name between <prefix> and <suffix>.
|
||||
// If module name can be matches with multiple patterns then pattern with the longest prefix will be picked.
|
||||
// After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module from the candidate location.
|
||||
// Substitiution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every substitution in the list
|
||||
// and replace '*' with <MatchedStar> string. If candidate location is not rooted it will be converted to absolute using baseUrl.
|
||||
// For example:
|
||||
// baseUrl: /a/b/c
|
||||
// "paths": {
|
||||
// // match all module names
|
||||
// "*": [
|
||||
// "*", // use matched name as is,
|
||||
// // <matched name> will be looked as /a/b/c/<matched name>
|
||||
//
|
||||
// "folder1/*" // substitution will convert matched name to 'folder1/<matched name>',
|
||||
// // since it is not rooted then final candidate location will be /a/b/c/folder1/<matched name>
|
||||
// ],
|
||||
// // match module names that start with 'components/'
|
||||
// "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/<matched name> to '/root/components/folder1/<matched name>',
|
||||
// // it is rooted so it will be final candidate location
|
||||
// }
|
||||
// 'paths' allows to remap locations of non-relative module names. Relative module names usually are resolved relative to the folder
|
||||
// that contains import. However sometimes it might be useful to apply mappings to relative module names as well.
|
||||
// Since the there might be different relative file names that refer to the same file we need to convert relative module name to non-relative
|
||||
// so we can just apply path mapping to it. This conversion can be performed using 'rootDirs' compiler option - this options stores list of root directories
|
||||
// for the project (if root directory is not absolute it will be converted to absolute using baseUrl as a base path).
|
||||
// First - we convert relative module name to absolute using location of file that contains import.
|
||||
// Then we try to find what element is rootDirs is the longest prefix for this absolute file name (respecting case):
|
||||
// <absoluteFileName> = <longestRootDir><suffix>
|
||||
// Once it is done - value of <suffix> is a be non-relative name for initially relative module name.
|
||||
// Note that if element in rootDirs should always end with '/' so it will be appended automatically if value in configuration does not have it.
|
||||
// For example:
|
||||
// baseUrl: /a/b/c
|
||||
// "paths": {
|
||||
// "*": [ "*", "generated/*" ] // try to resolve module name as '/a/b/c/<name>' then look it in '/a/b/c/generated/<name>'
|
||||
// }
|
||||
// "rootDirs": [
|
||||
// "./src", // root dir is project baseUrl/src
|
||||
// "./generated" // root dir is baseUrl/generated
|
||||
// "/e/f" // root dir is /e/f
|
||||
// ]
|
||||
// @file: /a/b/c/src/form1.ts
|
||||
// import {x} from "./form.content.ts"
|
||||
// @file: /a/b/c/generated/form1.content.ts
|
||||
// export var x = ...
|
||||
// first './form.content.ts' needs to be converted to non-relative name.
|
||||
// 1. convert it to absolute name '/a/b/c/src/form1.content.ts' ->
|
||||
// find longest prefix in rootDirs that match this path, it will be './src' (after expansion '/a/b/c/src/') ->
|
||||
// suffix is form1.content.ts
|
||||
// 2. apply path mappings to 'form1.content.ts' ->
|
||||
// '*' pattern will be matched ->
|
||||
// '*' substitution yields '/a/b/c/form1.content.ts' - file does not exists
|
||||
// 'generated/*' substitution yields '/a/b/c/generated/form1.content' - file exists - OK
|
||||
export function baseUrlModuleNameResolver(moduleName: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const baseUrl = options.baseUrl !== undefined ? options.baseUrl : options.inferredBaseUrl;
|
||||
Debug.assert(baseUrl !== undefined);
|
||||
|
||||
if (isRootedDiskPath(moduleName)) {
|
||||
return { resolvedModule: { resolvedFileName: moduleName }, failedLookupLocations: emptyArray };
|
||||
}
|
||||
|
||||
if (nameStartsWithDotSlashOrDotDotSlash(moduleName)) {
|
||||
// relative name
|
||||
return baseUrlResolveRelativeModuleName(moduleName, containingFile, baseUrl, options, host);
|
||||
}
|
||||
else {
|
||||
// non-relative name
|
||||
return baseUrlResolveNonRelativeModuleName(moduleName, baseUrl, options, host);
|
||||
}
|
||||
}
|
||||
|
||||
export function baseUrlResolveRelativeModuleName(moduleName: string, containingFile: string, baseUrl: string, options: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const failedLookupLocations: string[] = [];
|
||||
|
||||
const containingDirectory = getDirectoryPath(containingFile);
|
||||
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
|
||||
|
||||
if (options.rootDirs) {
|
||||
let matchedPrefix: string;
|
||||
for (const rootDir of options.rootDirs) {
|
||||
let normalizedRoot = getNormalizedAbsolutePath(rootDir, baseUrl);
|
||||
if (!endsWith(normalizedRoot, directorySeparator)) {
|
||||
normalizedRoot += directorySeparator;
|
||||
}
|
||||
if (startsWith(candidate, normalizedRoot) && (matchedPrefix === undefined || matchedPrefix.length < normalizedRoot.length)) {
|
||||
matchedPrefix = normalizedRoot;
|
||||
}
|
||||
}
|
||||
if (matchedPrefix) {
|
||||
const suffix = candidate.substr(matchedPrefix.length);
|
||||
return baseUrlResolveNonRelativeModuleName(suffix, baseUrl, options, host);
|
||||
}
|
||||
return { resolvedModule: undefined, failedLookupLocations };
|
||||
}
|
||||
else {
|
||||
const resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host);
|
||||
return {
|
||||
resolvedModule: resolvedFileName ? { resolvedFileName } : undefined,
|
||||
failedLookupLocations
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function baseUrlResolveNonRelativeModuleName(moduleName: string, baseUrl: string, options: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
let longestMatchPrefixLength = -1;
|
||||
let matchedPattern: string;
|
||||
let matchedStar: string;
|
||||
|
||||
const failedLookupLocations: string[] = [];
|
||||
if (options.paths) {
|
||||
for (const key in options.paths) {
|
||||
const pattern: string = key;
|
||||
const indexOfStar = pattern.indexOf("*");
|
||||
if (indexOfStar !== -1) {
|
||||
const prefix = pattern.substr(0, indexOfStar);
|
||||
const suffix = pattern.substr(indexOfStar + 1);
|
||||
if (moduleName.length >= prefix.length + suffix.length &&
|
||||
startsWith(moduleName, prefix) &&
|
||||
endsWith(moduleName, suffix)) {
|
||||
|
||||
// use length of prefix as betterness criteria
|
||||
if (prefix.length > longestMatchPrefixLength) {
|
||||
longestMatchPrefixLength = prefix.length;
|
||||
matchedPattern = pattern;
|
||||
matchedStar = moduleName.substr(prefix.length, moduleName.length - suffix.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pattern === moduleName) {
|
||||
// pattern was matched as is - no need to seatch further
|
||||
matchedPattern = pattern;
|
||||
matchedStar = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedPattern) {
|
||||
for (const subst of options.paths[matchedPattern]) {
|
||||
const path = matchedStar ? subst.replace("\*", matchedStar) : subst;
|
||||
const candidate = normalizePath(combinePaths(baseUrl, path));
|
||||
const resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host);
|
||||
if (resolvedFileName) {
|
||||
return { resolvedModule: { resolvedFileName }, failedLookupLocations };
|
||||
}
|
||||
}
|
||||
|
||||
return { resolvedModule: undefined, failedLookupLocations };
|
||||
}
|
||||
else {
|
||||
const candidate = normalizePath(combinePaths(baseUrl, moduleName));
|
||||
const resolvedFileName = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host);
|
||||
return {
|
||||
resolvedModule: resolvedFileName ? { resolvedFileName } : undefined,
|
||||
failedLookupLocations
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function startsWith(str: string, prefix: string): boolean {
|
||||
return str.lastIndexOf(prefix, 0) === 0;
|
||||
}
|
||||
|
||||
function endsWith(str: string, suffix: string): boolean {
|
||||
const expectedPos = str.length - suffix.length;
|
||||
return str.indexOf(suffix, expectedPos) === expectedPos;
|
||||
}
|
||||
|
||||
export function nodeModuleNameResolver(moduleName: string, containingFile: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const containingDirectory = getDirectoryPath(containingFile);
|
||||
|
||||
if (getRootLength(moduleName) !== 0 || nameStartsWithDotSlashOrDotDotSlash(moduleName)) {
|
||||
if (isRootedDiskPath(moduleName) || nameStartsWithDotSlashOrDotDotSlash(moduleName)) {
|
||||
const failedLookupLocations: string[] = [];
|
||||
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
|
||||
let resolvedFileName = loadNodeModuleFromFile(supportedJsExtensions, candidate, failedLookupLocations, host);
|
||||
let resolvedFileName = loadModuleFromFile(supportedJsExtensions, candidate, failedLookupLocations, host);
|
||||
|
||||
if (resolvedFileName) {
|
||||
return { resolvedModule: { resolvedFileName }, failedLookupLocations };
|
||||
@@ -69,7 +267,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function loadNodeModuleFromFile(extensions: string[], candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost): string {
|
||||
function loadModuleFromFile(extensions: string[], candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost): string {
|
||||
return forEach(extensions, tryLoad);
|
||||
|
||||
function tryLoad(ext: string): string {
|
||||
@@ -100,7 +298,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
if (jsonContent.typings) {
|
||||
const result = loadNodeModuleFromFile(extensions, normalizePath(combinePaths(candidate, jsonContent.typings)), failedLookupLocation, host);
|
||||
const result = loadModuleFromFile(extensions, normalizePath(combinePaths(candidate, jsonContent.typings)), failedLookupLocation, host);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
@@ -111,7 +309,7 @@ namespace ts {
|
||||
failedLookupLocation.push(packageJsonPath);
|
||||
}
|
||||
|
||||
return loadNodeModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocation, host);
|
||||
return loadModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocation, host);
|
||||
}
|
||||
|
||||
function loadModuleFromNodeModules(moduleName: string, directory: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
@@ -122,7 +320,7 @@ namespace ts {
|
||||
if (baseName !== "node_modules") {
|
||||
const nodeModulesFolder = combinePaths(directory, "node_modules");
|
||||
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
|
||||
let result = loadNodeModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host);
|
||||
let result = loadModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host);
|
||||
if (result) {
|
||||
return { resolvedModule: { resolvedFileName: result, isExternalLibraryImport: true }, failedLookupLocations };
|
||||
}
|
||||
@@ -149,6 +347,22 @@ namespace ts {
|
||||
return i === 0 || (i === 1 && name.charCodeAt(0) === CharacterCodes.dot);
|
||||
}
|
||||
|
||||
function hasZeroOrOneAsteriskCharacter(str: string): boolean {
|
||||
let seenAsterisk = false;
|
||||
for (let i = 0; i < str.length; ++i) {
|
||||
if (str.charCodeAt(i) === CharacterCodes.asterisk) {
|
||||
if (!seenAsterisk) {
|
||||
seenAsterisk = true;
|
||||
}
|
||||
else {
|
||||
// have already seen asterisk
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
|
||||
// module names that contain '!' are used to reference resources and are not resolved to actual files on disk
|
||||
@@ -997,6 +1211,47 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
if (options.moduleResolution === ModuleResolutionKind.BaseUrl) {
|
||||
if (options.baseUrl === undefined && options.inferredBaseUrl === undefined) {
|
||||
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.moduleResolution_kind_baseUrl_cannot_be_used_without_specifying_baseUrl_option));
|
||||
}
|
||||
}
|
||||
else if (options.moduleResolution === undefined) {
|
||||
// if module resolution kind is not specified it is an error to have baseUrl\paths\rootDirs and module === CommonJs
|
||||
// since the former one implies moduleResolutionKind to be BaseUrl and the latter one - Node
|
||||
if (options.module === ModuleKind.CommonJS && (options.baseUrl || options.paths || options.rootDirs)) {
|
||||
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Module_resolution_kind_cannot_be_determined_automatically_Please_specify_module_resolution_explicitly_via_moduleResolution_option));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// here it is known that moduleResolution is not baseurl and is not undefined
|
||||
if (options.baseUrl !== undefined) {
|
||||
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_used_when_option_moduleResolution_is_baseUrl, "baseUrl"));
|
||||
}
|
||||
if (options.paths) {
|
||||
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_used_when_option_moduleResolution_is_baseUrl, "paths"));
|
||||
}
|
||||
if (options.rootDirs) {
|
||||
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_used_when_option_moduleResolution_is_baseUrl, "rootDirs"));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.paths) {
|
||||
for (const key in options.paths) {
|
||||
if (!hasProperty(options.paths, key)) {
|
||||
continue;
|
||||
}
|
||||
if (!hasZeroOrOneAsteriskCharacter(key)) {
|
||||
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key));
|
||||
}
|
||||
for (const subst of options.paths[key]) {
|
||||
if (!hasZeroOrOneAsteriskCharacter(subst)) {
|
||||
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Substitution_0_in_pattern_1_in_can_have_at_most_one_Asterisk_character, subst, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.inlineSources) {
|
||||
if (!options.sourceMap && !options.inlineSourceMap) {
|
||||
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_inlineSources_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided));
|
||||
|
||||
@@ -360,7 +360,7 @@ namespace ts {
|
||||
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
return;
|
||||
}
|
||||
const configParseResult = parseJsonConfigFileContent(configObject, sys, getDirectoryPath(configFileName));
|
||||
const configParseResult = parseJsonConfigFileContent(configObject, sys, getNormalizedAbsolutePath(getDirectoryPath(configFileName), sys.getCurrentDirectory()));
|
||||
if (configParseResult.errors.length > 0) {
|
||||
reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined);
|
||||
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
@@ -723,7 +723,9 @@ namespace ts {
|
||||
|
||||
for (const name in options) {
|
||||
if (hasProperty(options, name)) {
|
||||
const value = options[name];
|
||||
// tsconfig only options cannot be specified via command line,
|
||||
// so we can assume that only types that can appear here string | number | boolean
|
||||
const value = <string | number | boolean>options[name];
|
||||
switch (name) {
|
||||
case "init":
|
||||
case "watch":
|
||||
|
||||
@@ -2310,9 +2310,14 @@ namespace ts {
|
||||
|
||||
export const enum ModuleResolutionKind {
|
||||
Classic = 1,
|
||||
NodeJs = 2
|
||||
NodeJs = 2,
|
||||
BaseUrl = 3
|
||||
}
|
||||
|
||||
export type RootPaths = string[];
|
||||
export type PathSubstitutions = Map<string[]>;
|
||||
export type TsConfigOnlyOptions = RootPaths | PathSubstitutions;
|
||||
|
||||
export interface CompilerOptions {
|
||||
allowNonTsExtensions?: boolean;
|
||||
charset?: string;
|
||||
@@ -2360,12 +2365,17 @@ namespace ts {
|
||||
noImplicitReturns?: boolean;
|
||||
noFallthroughCasesInSwitch?: boolean;
|
||||
forceConsistentCasingInFileNames?: boolean;
|
||||
baseUrl?: string;
|
||||
paths?: PathSubstitutions;
|
||||
rootDirs?: RootPaths;
|
||||
/* @internal */ stripInternal?: boolean;
|
||||
|
||||
// Skip checking lib.d.ts to help speed up tests.
|
||||
/* @internal */ skipDefaultLibCheck?: boolean;
|
||||
// inferred baseUrl - currently this will be set in 'parseJsonConfigFileContent' to 'baseDir'
|
||||
/* @internal */ inferredBaseUrl?: string;
|
||||
|
||||
[option: string]: string | number | boolean;
|
||||
[option: string]: string | number | boolean | TsConfigOnlyOptions;
|
||||
}
|
||||
|
||||
export const enum ModuleKind {
|
||||
@@ -2425,12 +2435,13 @@ namespace ts {
|
||||
/* @internal */
|
||||
export interface CommandLineOptionBase {
|
||||
name: string;
|
||||
type: "string" | "number" | "boolean" | Map<number>; // a value of a primitive type, or an object literal mapping named values to actual values
|
||||
type: "string" | "number" | "boolean" | "object" | Map<number>; // a value of a primitive type, or an object literal mapping named values to actual values
|
||||
isFilePath?: boolean; // True if option value is a path or fileName
|
||||
shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help'
|
||||
description?: DiagnosticMessage; // The message describing what the command line switch does
|
||||
paramType?: DiagnosticMessage; // The name to be used for a non-boolean option's parameter
|
||||
experimental?: boolean;
|
||||
isTSConfigOnly?: boolean; // True if option can only be specified via tsconfig.json file
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -2445,7 +2456,12 @@ namespace ts {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type CommandLineOption = CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType;
|
||||
export interface TsConfigOnlyOption extends CommandLineOptionBase {
|
||||
type: "object";
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type CommandLineOption = CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType | TsConfigOnlyOption;
|
||||
|
||||
/* @internal */
|
||||
export const enum CharacterCodes {
|
||||
|
||||
@@ -49,7 +49,6 @@ class CompilerBaselineRunner extends RunnerBase {
|
||||
// Mocha holds onto the closure environment of the describe callback even after the test is done.
|
||||
// Everything declared here should be cleared out in the "after" callback.
|
||||
let justName: string;
|
||||
|
||||
let lastUnit: Harness.TestCaseParser.TestUnitData;
|
||||
let harnessSettings: Harness.TestCaseParser.CompilerSettings;
|
||||
|
||||
@@ -63,16 +62,30 @@ class CompilerBaselineRunner extends RunnerBase {
|
||||
before(() => {
|
||||
justName = fileName.replace(/^.*[\\\/]/, ""); // strips the fileName from the path.
|
||||
const content = Harness.IO.readFile(fileName);
|
||||
const testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, fileName);
|
||||
const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/";
|
||||
const testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, fileName, rootDir);
|
||||
const units = testCaseContent.testUnitData;
|
||||
harnessSettings = testCaseContent.settings;
|
||||
let tsConfigOptions: ts.CompilerOptions;
|
||||
if (testCaseContent.tsConfig) {
|
||||
assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`);
|
||||
|
||||
tsConfigOptions = ts.clone(testCaseContent.tsConfig.options);
|
||||
}
|
||||
else {
|
||||
const baseUrl = harnessSettings["baseUrl"];
|
||||
if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) {
|
||||
harnessSettings["baseUrl"] = ts.getNormalizedAbsolutePath(baseUrl, rootDir);
|
||||
}
|
||||
}
|
||||
|
||||
lastUnit = units[units.length - 1];
|
||||
const rootDir = lastUnit.originalFilePath.indexOf("conformance") === -1 ? "tests/cases/compiler/" : lastUnit.originalFilePath.substring(0, lastUnit.originalFilePath.lastIndexOf("/")) + "/";
|
||||
// We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test)
|
||||
// If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references,
|
||||
// otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another.
|
||||
toBeCompiled = [];
|
||||
otherFiles = [];
|
||||
|
||||
if (/require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) {
|
||||
toBeCompiled.push({ unitName: this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content });
|
||||
units.forEach(unit => {
|
||||
@@ -88,7 +101,7 @@ class CompilerBaselineRunner extends RunnerBase {
|
||||
}
|
||||
|
||||
const output = Harness.Compiler.compileFiles(
|
||||
toBeCompiled, otherFiles, harnessSettings, /* options */ undefined, /* currentDirectory */ undefined);
|
||||
toBeCompiled, otherFiles, harnessSettings, /*options*/ tsConfigOptions, /*currentDirectory*/ undefined);
|
||||
|
||||
options = output.options;
|
||||
result = output.result;
|
||||
|
||||
@@ -1000,6 +1000,9 @@ namespace Harness {
|
||||
if (options.useCaseSensitiveFileNames !== undefined) {
|
||||
useCaseSensitiveFileNames = options.useCaseSensitiveFileNames;
|
||||
}
|
||||
if (options.inferredBaseUrl) {
|
||||
options.inferredBaseUrl = ts.getNormalizedAbsolutePath(options.inferredBaseUrl, currentDirectory);
|
||||
}
|
||||
|
||||
const programFiles: TestFile[] = inputFiles.slice();
|
||||
// Files from built\local that are requested by test "@includeBuiltFiles" to be in the context.
|
||||
@@ -1372,7 +1375,7 @@ namespace Harness {
|
||||
}
|
||||
|
||||
/** Given a test file containing // @FileName directives, return an array of named units of code to be added to an existing compiler instance */
|
||||
export function makeUnitsFromTest(code: string, fileName: string): { settings: CompilerSettings; testUnitData: TestUnitData[]; } {
|
||||
export function makeUnitsFromTest(code: string, fileName: string, rootDir?: string): { settings: CompilerSettings; testUnitData: TestUnitData[]; tsConfig: ts.ParsedCommandLine } {
|
||||
const settings = extractCompilerSettings(code);
|
||||
|
||||
// List of all the subfiles we've parsed out
|
||||
@@ -1450,7 +1453,31 @@ namespace Harness {
|
||||
};
|
||||
testUnitData.push(newTestFile2);
|
||||
|
||||
return { settings, testUnitData };
|
||||
// unit tests always list files explicitly
|
||||
const parseConfigHost: ts.ParseConfigHost = {
|
||||
readDirectory: (name) => []
|
||||
};
|
||||
|
||||
// check if project has tsconfig.json in the list of files
|
||||
let tsConfig: ts.ParsedCommandLine;
|
||||
for (let i = 0; i < testUnitData.length; ++i) {
|
||||
const data = testUnitData[i];
|
||||
if (ts.getBaseFileName(data.name).toLowerCase() === "tsconfig.json") {
|
||||
const configJson = ts.parseConfigFileTextToJson(data.name, data.content);
|
||||
assert.isTrue(configJson.config !== undefined);
|
||||
let baseDir = ts.normalizePath(ts.getDirectoryPath(data.name));
|
||||
if (rootDir) {
|
||||
baseDir = ts.getNormalizedAbsolutePath(baseDir, rootDir);
|
||||
}
|
||||
tsConfig = ts.parseJsonConfigFileContent(configJson.config, parseConfigHost, baseDir);
|
||||
|
||||
// delete entry from the list
|
||||
testUnitData.splice(i, 1);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { settings, testUnitData, tsConfig };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user