Merge branch 'master' into ownJsonParsing

This commit is contained in:
Sheetal Nandi
2017-05-11 09:06:01 -07:00
575 changed files with 145504 additions and 67143 deletions
+260 -179
View File
@@ -1328,221 +1328,82 @@ namespace ts {
}
/**
* Parse the contents of a config file (tsconfig.json).
* Parse the contents of a config file from json or json source file (tsconfig.json).
* @param json The contents of the config file to parse
* @param sourceFile sourceFile corresponding to the Json
* @param host Instance of ParseConfigHost used to enumerate files in folder.
* @param basePath A root directory to resolve relative path entries in the config
* file to. e.g. outDir
* @param resolutionStack Only present for backwards-compatibility. Should be empty.
*/
function parseJsonConfigFileContentWorker(json: any, sourceFile: JsonSourceFile, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = [], extraFileExtensions: JsFileExtensionInfo[] = []): ParsedCommandLine {
function parseJsonConfigFileContentWorker(
json: any,
sourceFile: JsonSourceFile,
host: ParseConfigHost,
basePath: string,
existingOptions: CompilerOptions = {},
configFileName?: string,
resolutionStack: Path[] = [],
extraFileExtensions: JsFileExtensionInfo[] = [],
): ParsedCommandLine {
Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined));
const errors: Diagnostic[] = [];
basePath = normalizeSlashes(basePath);
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName);
if (resolutionStack.indexOf(resolvedPath) >= 0) {
return {
options: {},
fileNames: [],
typeAcquisition: {},
raw: json || convertToObject(sourceFile, errors),
errors: errors.concat(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))),
wildcardDirectories: {}
};
}
let options: CompilerOptions;
let typeAcquisition: TypeAcquisition;
let compileOnSave: boolean;
let hasExtendsError: boolean, extendedConfigPath: Path;
if (json) {
options = convertCompilerOptionsFromJsonWorker(json["compilerOptions"], basePath, errors, configFileName);
// typingOptions has been deprecated and is only supported for backward compatibility purposes.
// It should be removed in future releases - use typeAcquisition instead.
const jsonOptions = json["typeAcquisition"] || json["typingOptions"];
typeAcquisition = convertTypeAcquisitionFromJsonWorker(jsonOptions, basePath, errors, configFileName);
compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
}
else {
options = getDefaultCompilerOptions(configFileName);
let typingOptionstypeAcquisition: TypeAcquisition;
const optionsIterator: JsonConversionNotifier = {
onSetOptionKeyValue(optionsObject: string, option: CommandLineOption, value: CompilerOptionsValue) {
Debug.assert(optionsObject === "compilerOptions" || optionsObject === "typeAcquisition" || optionsObject === "typingOptions");
const currentOption = optionsObject === "compilerOptions" ? options :
optionsObject === "typeAcquisition" ? (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName)) ) :
(typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName)));
currentOption[option.name] = normalizeOptionValue(option, basePath, value);
},
onRootKeyValue(key: string, propertyName: PropertyName, value: CompilerOptionsValue, node: Expression) {
switch (key) {
case "extends":
const extendsDiagnostic = getExtendsConfigPath(<string>value, (message, arg0) =>
createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0));
if ((<Diagnostic>extendsDiagnostic).messageText) {
errors.push(<Diagnostic>extendsDiagnostic);
hasExtendsError = true;
}
else {
extendedConfigPath = <Path>extendsDiagnostic;
}
return;
case "excludes":
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, propertyName, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude));
return;
case "files":
if ((<string[]>value).length === 0) {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"));
}
return;
case "compileOnSave":
compileOnSave = <boolean>value;
return;
}
}
};
json = convertToObjectWorker(sourceFile, errors, getTsconfigRootOptionsMap(), optionsIterator);
if (!typeAcquisition) {
if (typingOptionstypeAcquisition) {
typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ?
{
enable: typingOptionstypeAcquisition.enableAutoDiscovery,
include: typingOptionstypeAcquisition.include,
exclude: typingOptionstypeAcquisition.exclude
} :
typingOptionstypeAcquisition;
}
else {
typeAcquisition = getDefaultTypeAcquisition(configFileName);
}
}
}
if (json["extends"]) {
let [include, exclude, files, baseOptions]: [string[], string[], string[], CompilerOptions] = [undefined, undefined, undefined, {}];
if (!hasExtendsError && typeof json["extends"] === "string") {
[include, exclude, files, baseOptions] = (tryExtendsName(json["extends"], extendedConfigPath) || [include, exclude, files, baseOptions]);
}
else {
createCompilerDiagnosticForJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string");
}
if (include && !json["include"]) {
json["include"] = include;
}
if (exclude && !json["exclude"]) {
json["exclude"] = exclude;
}
if (files && !json["files"]) {
json["files"] = files;
}
options = assign({}, baseOptions, options);
}
options = extend(existingOptions, options);
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors);
const { raw } = parsedConfig;
const options = extend(existingOptions, parsedConfig.options || {});
options.configFilePath = configFileName;
options.configFile = sourceFile;
const { fileNames, wildcardDirectories } = getFileNames(errors);
const { fileNames, wildcardDirectories } = getFileNames();
return {
options,
fileNames,
typeAcquisition,
raw: json,
typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(),
raw,
errors,
wildcardDirectories,
compileOnSave
compileOnSave: !!raw.compileOnSave
};
function getExtendsConfigPath<T>(extendedConfig: string, createDiagnostic: (message: DiagnosticMessage, arg1?: string) => T): T | Path {
// If the path isn't a rooted or relative path, don't try to resolve it (we reserve the right to special case module-id like paths in the future)
if (!(isRootedDiskPath(extendedConfig) || startsWith(normalizeSlashes(extendedConfig), "./") || startsWith(normalizeSlashes(extendedConfig), "../"))) {
return createDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extendedConfig);
}
let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName);
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) {
extendedConfigPath = `${extendedConfigPath}.json` as Path;
if (!host.fileExists(extendedConfigPath)) {
return createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig);
}
}
return extendedConfigPath;
}
function tryExtendsName(extendedConfig: string, extendedConfigPath?: Path): [string[], string[], string[], CompilerOptions] {
if (!extendedConfigPath) {
const result = getExtendsConfigPath(extendedConfig, (message, arg1) => (createCompilerDiagnosticForJson(message, arg1), true));
if (result === true) {
return;
}
extendedConfigPath = <Path>result;
}
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) {
extendedConfigPath = `${extendedConfigPath}.json` as Path;
}
const extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path));
if (extendedResult.parseDiagnostics.length) {
errors.push(...extendedResult.parseDiagnostics);
return;
}
const extendedDirname = getDirectoryPath(extendedConfigPath);
const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName);
const updatePath: (path: string) => string = path => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path);
// Merge configs (copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios)
const result = parseJsonSourceFileConfigFileContent(extendedResult, host, extendedDirname, /*existingOptions*/ undefined, getBaseFileName(extendedConfigPath), resolutionStack.concat([resolvedPath]));
errors.push(...result.errors);
const [include, exclude, files] = map(["include", "exclude", "files"], key => {
if (!json[key] && result.raw[key]) {
return map(result.raw[key], updatePath);
}
});
return [include, exclude, files, result.options];
}
function getFileNames(errors: Diagnostic[]): ExpandResult {
function getFileNames(): ExpandResult {
let fileNames: string[];
if (hasProperty(json, "files")) {
if (isArray(json["files"])) {
fileNames = <string[]>json["files"];
if (hasProperty(raw, "files")) {
if (isArray(raw["files"])) {
fileNames = <string[]>raw["files"];
if (fileNames.length === 0) {
createCompilerDiagnosticForJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
}
}
else {
createCompilerDiagnosticForJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "files", "Array");
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "files", "Array");
}
}
let includeSpecs: string[];
if (hasProperty(json, "include")) {
if (isArray(json["include"])) {
includeSpecs = <string[]>json["include"];
if (hasProperty(raw, "include")) {
if (isArray(raw["include"])) {
includeSpecs = <string[]>raw["include"];
}
else {
createCompilerDiagnosticForJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array");
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array");
}
}
let excludeSpecs: string[];
if (hasProperty(json, "exclude")) {
if (isArray(json["exclude"])) {
excludeSpecs = <string[]>json["exclude"];
if (hasProperty(raw, "exclude")) {
if (isArray(raw["exclude"])) {
excludeSpecs = <string[]>raw["exclude"];
}
else {
createCompilerDiagnosticForJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array");
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array");
}
}
else if (hasProperty(json, "excludes")) {
createCompilerDiagnosticForJson(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude);
}
else {
// If no includes were specified, exclude common package folders and the outDir
excludeSpecs = includeSpecs ? [] : ["node_modules", "bower_components", "jspm_packages"];
const outDir = json["compilerOptions"] && json["compilerOptions"]["outDir"];
const outDir = raw["compilerOptions"] && raw["compilerOptions"]["outDir"];
if (outDir) {
excludeSpecs.push(outDir);
}
@@ -1554,7 +1415,7 @@ namespace ts {
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, extraFileExtensions, sourceFile);
if (result.fileNames.length === 0 && !hasProperty(json, "files") && resolutionStack.length === 0) {
if (result.fileNames.length === 0 && !hasProperty(raw, "files") && resolutionStack.length === 0) {
errors.push(
createCompilerDiagnostic(
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2,
@@ -1566,16 +1427,236 @@ namespace ts {
return result;
}
function createCompilerDiagnosticForJson(message: DiagnosticMessage, arg0?: string, arg1?: string) {
function createCompilerDiagnosticOnlyIfJson(message: DiagnosticMessage, arg0?: string, arg1?: string) {
if (!sourceFile) {
errors.push(createCompilerDiagnostic(message, arg0, arg1));
}
}
}
export function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Diagnostic[]): boolean {
interface ParsedTsconfig {
raw: any;
options?: CompilerOptions;
typeAcquisition?: TypeAcquisition;
extendedConfigPath?: Path;
}
function isSuccessfulParsedTsconfig(value: ParsedTsconfig) {
return !!value.options;
}
/**
* This *just* extracts options/include/exclude/files out of a config file.
* It does *not* resolve the included files.
*/
function parseConfig(
json: any,
sourceFile: JsonSourceFile,
host: ParseConfigHost,
basePath: string,
configFileName: string,
resolutionStack: Path[],
errors: Diagnostic[],
): ParsedTsconfig {
basePath = normalizeSlashes(basePath);
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName);
if (resolutionStack.indexOf(resolvedPath) >= 0) {
errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> ")));
return { raw: json || convertToObject(sourceFile, errors) };
}
const ownConfig = json ?
parseOwnConfigOfJson(json, host, basePath, getCanonicalFileName, configFileName, errors) :
parseOwnConfigOfJsonSourceFile(sourceFile, host, basePath, getCanonicalFileName, configFileName, errors);
if (ownConfig.extendedConfigPath) {
// copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios.
resolutionStack = resolutionStack.concat([resolvedPath]);
const extendedConfig = getExtendedConfig(ownConfig.extendedConfigPath, host, basePath, getCanonicalFileName,
resolutionStack, errors);
if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) {
const baseRaw = extendedConfig.raw;
const raw = ownConfig.raw;
const setPropertyInRawIfNotUndefined = (propertyName: string) => {
const value = raw[propertyName] || baseRaw[propertyName];
if (value) {
raw[propertyName] = value;
}
};
setPropertyInRawIfNotUndefined("include");
setPropertyInRawIfNotUndefined("exclude");
setPropertyInRawIfNotUndefined("files");
if (raw.compileOnSave === undefined) {
raw.compileOnSave = baseRaw.compileOnSave;
}
ownConfig.options = assign({}, extendedConfig.options, ownConfig.options);
// TODO extend type typeAcquisition
}
}
return ownConfig;
}
function parseOwnConfigOfJson(
json: any,
host: ParseConfigHost,
basePath: string,
getCanonicalFileName: (fileName: string) => string,
configFileName: string,
errors: Diagnostic[]
): ParsedTsconfig {
if (hasProperty(json, "excludes")) {
errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude));
}
const options = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName);
// typingOptions has been deprecated and is only supported for backward compatibility purposes.
// It should be removed in future releases - use typeAcquisition instead.
const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json["typeAcquisition"] || json["typingOptions"], basePath, errors, configFileName);
json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
let extendedConfigPath: Path;
if (json.extends) {
if (typeof json.extends !== "string") {
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"));
}
else {
extendedConfigPath = getExtendsConfigPath(json.extends, host, basePath, getCanonicalFileName, errors, createCompilerDiagnostic);
}
}
return { raw: json, options, typeAcquisition, extendedConfigPath };
}
function parseOwnConfigOfJsonSourceFile(
sourceFile: JsonSourceFile,
host: ParseConfigHost,
basePath: string,
getCanonicalFileName: (fileName: string) => string,
configFileName: string,
errors: Diagnostic[]
): ParsedTsconfig {
const options = getDefaultCompilerOptions(configFileName);
let typeAcquisition: TypeAcquisition, typingOptionstypeAcquisition: TypeAcquisition;
let extendedConfigPath: Path;
const optionsIterator: JsonConversionNotifier = {
onSetOptionKeyValue(optionsObject: string, option: CommandLineOption, value: CompilerOptionsValue) {
Debug.assert(optionsObject === "compilerOptions" || optionsObject === "typeAcquisition" || optionsObject === "typingOptions");
const currentOption = optionsObject === "compilerOptions" ? options :
optionsObject === "typeAcquisition" ? (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName))) :
(typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName)));
currentOption[option.name] = normalizeOptionValue(option, basePath, value);
},
onRootKeyValue(key: string, propertyName: PropertyName, value: CompilerOptionsValue, node: Expression) {
switch (key) {
case "extends":
extendedConfigPath = getExtendsConfigPath(
<string>value,
host,
basePath,
getCanonicalFileName,
errors,
(message, arg0) =>
createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0)
);
return;
case "excludes":
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, propertyName, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude));
return;
case "files":
if ((<string[]>value).length === 0) {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"));
}
return;
}
}
};
const json = convertToObjectWorker(sourceFile, errors, getTsconfigRootOptionsMap(), optionsIterator);
if (!typeAcquisition) {
if (typingOptionstypeAcquisition) {
typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ?
{
enable: typingOptionstypeAcquisition.enableAutoDiscovery,
include: typingOptionstypeAcquisition.include,
exclude: typingOptionstypeAcquisition.exclude
} :
typingOptionstypeAcquisition;
}
else {
typeAcquisition = getDefaultTypeAcquisition(configFileName);
}
}
return { raw: json, options, typeAcquisition, extendedConfigPath };
}
function getExtendsConfigPath(
extendedConfig: string,
host: ParseConfigHost,
basePath: string,
getCanonicalFileName: (fileName: string) => string,
errors: Diagnostic[],
createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) {
extendedConfig = normalizeSlashes(extendedConfig);
// If the path isn't a rooted or relative path, don't try to resolve it (we reserve the right to special case module-id like paths in the future)
if (!(isRootedDiskPath(extendedConfig) || startsWith(extendedConfig, "./") || startsWith(extendedConfig, "../"))) {
errors.push(createDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extendedConfig));
return undefined;
}
let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName);
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) {
extendedConfigPath = `${extendedConfigPath}.json` as Path;
if (!host.fileExists(extendedConfigPath)) {
errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig));
return undefined;
}
}
return extendedConfigPath;
}
function getExtendedConfig(
extendedConfigPath: Path,
host: ts.ParseConfigHost,
basePath: string,
getCanonicalFileName: (fileName: string) => string,
resolutionStack: Path[],
errors: Diagnostic[],
): ParsedTsconfig | undefined {
const extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path));
if (extendedResult.parseDiagnostics.length) {
errors.push(...extendedResult.parseDiagnostics);
return undefined;
}
const extendedDirname = getDirectoryPath(extendedConfigPath);
const extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname,
getBaseFileName(extendedConfigPath), resolutionStack, errors);
if (isSuccessfulParsedTsconfig(extendedConfig)) {
// Update the paths to reflect base path
const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName);
const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path);
const mapPropertiesInRawIfNotUndefined = (propertyName: string) => {
if (raw[propertyName]) {
raw[propertyName] = map(raw[propertyName], updatePath);
}
};
const { raw } = extendedConfig;
mapPropertiesInRawIfNotUndefined("include");
mapPropertiesInRawIfNotUndefined("exclude");
mapPropertiesInRawIfNotUndefined("files");
}
return extendedConfig;
}
function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Diagnostic[]): boolean {
if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) {
return false;
return undefined;
}
const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption["compileOnSave"], basePath, errors);
if (typeof result === "boolean" && result) {
@@ -2025,4 +2106,4 @@ namespace ts {
function caseInsensitiveKeyMapper(key: string) {
return key.toLowerCase();
}
}
}