Allow undefined/null to override all parameters (#18058)

This commit is contained in:
Wesley Wigham
2017-09-06 22:08:42 -07:00
committed by GitHub
parent c3e090695e
commit 72cbc12c9a
3 changed files with 58 additions and 13 deletions

View File

@@ -1057,7 +1057,7 @@ namespace ts {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnosticMessage, keyText));
}
const value = convertPropertyValueToJson(element.initializer, option);
if (typeof keyText !== "undefined" && typeof value !== "undefined") {
if (typeof keyText !== "undefined") {
result[keyText] = value;
// Notify key value set, if user asked for it
if (jsonConversionNotifier &&
@@ -1104,7 +1104,7 @@ namespace ts {
return false;
case SyntaxKind.NullKeyword:
reportInvalidOptionValue(!!option);
reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for
return null; // tslint:disable-line:no-null-keyword
case SyntaxKind.StringLiteral:
@@ -1189,6 +1189,7 @@ namespace ts {
function isCompilerOptionsValue(option: CommandLineOption, value: any): value is CompilerOptionsValue {
if (option) {
if (isNullOrUndefined(value)) return true; // All options are undefinable/nullable
if (option.type === "list") {
return isArray(value);
}
@@ -1379,6 +1380,11 @@ namespace ts {
}
}
function isNullOrUndefined(x: any): x is null | undefined {
// tslint:disable-next-line:no-null-keyword
return x === undefined || x === null;
}
/**
* 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
@@ -1419,7 +1425,7 @@ namespace ts {
function getFileNames(): ExpandResult {
let fileNames: ReadonlyArray<string>;
if (hasProperty(raw, "files")) {
if (hasProperty(raw, "files") && !isNullOrUndefined(raw["files"])) {
if (isArray(raw["files"])) {
fileNames = <ReadonlyArray<string>>raw["files"];
if (fileNames.length === 0) {
@@ -1432,7 +1438,7 @@ namespace ts {
}
let includeSpecs: ReadonlyArray<string>;
if (hasProperty(raw, "include")) {
if (hasProperty(raw, "include") && !isNullOrUndefined(raw["include"])) {
if (isArray(raw["include"])) {
includeSpecs = <ReadonlyArray<string>>raw["include"];
}
@@ -1442,7 +1448,7 @@ namespace ts {
}
let excludeSpecs: ReadonlyArray<string>;
if (hasProperty(raw, "exclude")) {
if (hasProperty(raw, "exclude") && !isNullOrUndefined(raw["exclude"])) {
if (isArray(raw["exclude"])) {
excludeSpecs = <ReadonlyArray<string>>raw["exclude"];
}
@@ -1461,7 +1467,7 @@ namespace ts {
includeSpecs = ["**/*"];
}
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, extraFileExtensions, sourceFile);
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, configFileName ? getDirectoryPath(toPath(configFileName, basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames))) : basePath, options, host, errors, extraFileExtensions, sourceFile);
if (result.fileNames.length === 0 && !hasProperty(raw, "files") && resolutionStack.length === 0) {
errors.push(
@@ -1552,7 +1558,7 @@ namespace ts {
host: ParseConfigHost,
basePath: string,
getCanonicalFileName: (fileName: string) => string,
configFileName: string,
configFileName: string | undefined,
errors: Push<Diagnostic>
): ParsedTsconfig {
if (hasProperty(json, "excludes")) {
@@ -1571,7 +1577,8 @@ namespace ts {
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);
const newBase = configFileName ? getDirectoryPath(toPath(configFileName, basePath, getCanonicalFileName)) : basePath;
extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, getCanonicalFileName, errors, createCompilerDiagnostic);
}
}
return { raw: json, options, typeAcquisition, extendedConfigPath };
@@ -1582,7 +1589,7 @@ namespace ts {
host: ParseConfigHost,
basePath: string,
getCanonicalFileName: (fileName: string) => string,
configFileName: string,
configFileName: string | undefined,
errors: Push<Diagnostic>
): ParsedTsconfig {
const options = getDefaultCompilerOptions(configFileName);
@@ -1603,10 +1610,11 @@ namespace ts {
onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) {
switch (key) {
case "extends":
const newBase = configFileName ? getDirectoryPath(toPath(configFileName, basePath, getCanonicalFileName)) : basePath;
extendedConfigPath = getExtendsConfigPath(
<string>value,
host,
basePath,
newBase,
getCanonicalFileName,
errors,
(message, arg0) =>
@@ -1803,6 +1811,7 @@ namespace ts {
}
function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
if (isNullOrUndefined(value)) return undefined;
if (option.type === "list") {
const listOption = <CommandLineOptionOfListType>option;
if (listOption.element.isFilePath || typeof listOption.element.type !== "string") {
@@ -1827,6 +1836,7 @@ namespace ts {
}
function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push<Diagnostic>) {
if (isNullOrUndefined(value)) return undefined;
const key = value.toLowerCase();
const val = opt.type.get(key);
if (val !== undefined) {
@@ -1977,7 +1987,7 @@ namespace ts {
// remove a literal file.
if (fileNames) {
for (const fileName of fileNames) {
const file = combinePaths(basePath, fileName);
const file = getNormalizedAbsolutePath(fileName, basePath);
literalFileMap.set(keyMapper(file), file);
}
}

View File

@@ -3583,7 +3583,7 @@ namespace ts {
name: string;
}
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[];
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | null | undefined;
export interface CompilerOptions {
/*@internal*/ all?: boolean;

View File

@@ -78,6 +78,23 @@ namespace ts {
},
include: ["../supplemental.*"]
},
"/dev/configs/third.json": {
extends: "./second",
compilerOptions: {
// tslint:disable-next-line:no-null-keyword
module: null
},
include: ["../supplemental.*"]
},
"/dev/configs/fourth.json": {
extends: "./third",
compilerOptions: {
module: "system"
},
// tslint:disable-next-line:no-null-keyword
include: null,
files: ["../main.ts"]
},
"/dev/extends.json": { extends: 42 },
"/dev/extends2.json": { extends: "configs/base" },
"/dev/main.ts": "",
@@ -106,7 +123,7 @@ namespace ts {
}
}
describe("Configuration Extension", () => {
describe("configurationExtension", () => {
forEach<[string, string, Utils.MockParseConfigHost], void>([
["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost],
["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost]
@@ -206,6 +223,24 @@ namespace ts {
category: DiagnosticCategory.Error,
messageText: `A path in an 'extends' option must be relative or rooted, but 'configs/base' is not.`
}]);
testSuccess("can overwrite compiler options using extended 'null'", "configs/third.json", {
allowJs: true,
noImplicitAny: true,
strictNullChecks: true,
module: undefined // Technically, this is distinct from the key never being set; but within the compiler we don't make the distinction
}, [
combinePaths(basePath, "supplemental.ts")
]);
testSuccess("can overwrite top-level options using extended 'null'", "configs/fourth.json", {
allowJs: true,
noImplicitAny: true,
strictNullChecks: true,
module: ModuleKind.System
}, [
combinePaths(basePath, "main.ts")
]);
});
});
});