Skip to content

Commit 72cbc12

Browse files
authored
Allow undefined/null to override all parameters (#18058)
1 parent c3e0906 commit 72cbc12

File tree

3 files changed

+58
-13
lines changed

3 files changed

+58
-13
lines changed

src/compiler/commandLineParser.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,7 +1057,7 @@ namespace ts {
10571057
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnosticMessage, keyText));
10581058
}
10591059
const value = convertPropertyValueToJson(element.initializer, option);
1060-
if (typeof keyText !== "undefined" && typeof value !== "undefined") {
1060+
if (typeof keyText !== "undefined") {
10611061
result[keyText] = value;
10621062
// Notify key value set, if user asked for it
10631063
if (jsonConversionNotifier &&
@@ -1104,7 +1104,7 @@ namespace ts {
11041104
return false;
11051105

11061106
case SyntaxKind.NullKeyword:
1107-
reportInvalidOptionValue(!!option);
1107+
reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for
11081108
return null; // tslint:disable-line:no-null-keyword
11091109

11101110
case SyntaxKind.StringLiteral:
@@ -1189,6 +1189,7 @@ namespace ts {
11891189

11901190
function isCompilerOptionsValue(option: CommandLineOption, value: any): value is CompilerOptionsValue {
11911191
if (option) {
1192+
if (isNullOrUndefined(value)) return true; // All options are undefinable/nullable
11921193
if (option.type === "list") {
11931194
return isArray(value);
11941195
}
@@ -1379,6 +1380,11 @@ namespace ts {
13791380
}
13801381
}
13811382

1383+
function isNullOrUndefined(x: any): x is null | undefined {
1384+
// tslint:disable-next-line:no-null-keyword
1385+
return x === undefined || x === null;
1386+
}
1387+
13821388
/**
13831389
* Parse the contents of a config file from json or json source file (tsconfig.json).
13841390
* @param json The contents of the config file to parse
@@ -1419,7 +1425,7 @@ namespace ts {
14191425

14201426
function getFileNames(): ExpandResult {
14211427
let fileNames: ReadonlyArray<string>;
1422-
if (hasProperty(raw, "files")) {
1428+
if (hasProperty(raw, "files") && !isNullOrUndefined(raw["files"])) {
14231429
if (isArray(raw["files"])) {
14241430
fileNames = <ReadonlyArray<string>>raw["files"];
14251431
if (fileNames.length === 0) {
@@ -1432,7 +1438,7 @@ namespace ts {
14321438
}
14331439

14341440
let includeSpecs: ReadonlyArray<string>;
1435-
if (hasProperty(raw, "include")) {
1441+
if (hasProperty(raw, "include") && !isNullOrUndefined(raw["include"])) {
14361442
if (isArray(raw["include"])) {
14371443
includeSpecs = <ReadonlyArray<string>>raw["include"];
14381444
}
@@ -1442,7 +1448,7 @@ namespace ts {
14421448
}
14431449

14441450
let excludeSpecs: ReadonlyArray<string>;
1445-
if (hasProperty(raw, "exclude")) {
1451+
if (hasProperty(raw, "exclude") && !isNullOrUndefined(raw["exclude"])) {
14461452
if (isArray(raw["exclude"])) {
14471453
excludeSpecs = <ReadonlyArray<string>>raw["exclude"];
14481454
}
@@ -1461,7 +1467,7 @@ namespace ts {
14611467
includeSpecs = ["**/*"];
14621468
}
14631469

1464-
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, extraFileExtensions, sourceFile);
1470+
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, configFileName ? getDirectoryPath(toPath(configFileName, basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames))) : basePath, options, host, errors, extraFileExtensions, sourceFile);
14651471

14661472
if (result.fileNames.length === 0 && !hasProperty(raw, "files") && resolutionStack.length === 0) {
14671473
errors.push(
@@ -1552,7 +1558,7 @@ namespace ts {
15521558
host: ParseConfigHost,
15531559
basePath: string,
15541560
getCanonicalFileName: (fileName: string) => string,
1555-
configFileName: string,
1561+
configFileName: string | undefined,
15561562
errors: Push<Diagnostic>
15571563
): ParsedTsconfig {
15581564
if (hasProperty(json, "excludes")) {
@@ -1571,7 +1577,8 @@ namespace ts {
15711577
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"));
15721578
}
15731579
else {
1574-
extendedConfigPath = getExtendsConfigPath(json.extends, host, basePath, getCanonicalFileName, errors, createCompilerDiagnostic);
1580+
const newBase = configFileName ? getDirectoryPath(toPath(configFileName, basePath, getCanonicalFileName)) : basePath;
1581+
extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, getCanonicalFileName, errors, createCompilerDiagnostic);
15751582
}
15761583
}
15771584
return { raw: json, options, typeAcquisition, extendedConfigPath };
@@ -1582,7 +1589,7 @@ namespace ts {
15821589
host: ParseConfigHost,
15831590
basePath: string,
15841591
getCanonicalFileName: (fileName: string) => string,
1585-
configFileName: string,
1592+
configFileName: string | undefined,
15861593
errors: Push<Diagnostic>
15871594
): ParsedTsconfig {
15881595
const options = getDefaultCompilerOptions(configFileName);
@@ -1603,10 +1610,11 @@ namespace ts {
16031610
onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) {
16041611
switch (key) {
16051612
case "extends":
1613+
const newBase = configFileName ? getDirectoryPath(toPath(configFileName, basePath, getCanonicalFileName)) : basePath;
16061614
extendedConfigPath = getExtendsConfigPath(
16071615
<string>value,
16081616
host,
1609-
basePath,
1617+
newBase,
16101618
getCanonicalFileName,
16111619
errors,
16121620
(message, arg0) =>
@@ -1803,6 +1811,7 @@ namespace ts {
18031811
}
18041812

18051813
function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
1814+
if (isNullOrUndefined(value)) return undefined;
18061815
if (option.type === "list") {
18071816
const listOption = <CommandLineOptionOfListType>option;
18081817
if (listOption.element.isFilePath || typeof listOption.element.type !== "string") {
@@ -1827,6 +1836,7 @@ namespace ts {
18271836
}
18281837

18291838
function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push<Diagnostic>) {
1839+
if (isNullOrUndefined(value)) return undefined;
18301840
const key = value.toLowerCase();
18311841
const val = opt.type.get(key);
18321842
if (val !== undefined) {
@@ -1977,7 +1987,7 @@ namespace ts {
19771987
// remove a literal file.
19781988
if (fileNames) {
19791989
for (const fileName of fileNames) {
1980-
const file = combinePaths(basePath, fileName);
1990+
const file = getNormalizedAbsolutePath(fileName, basePath);
19811991
literalFileMap.set(keyMapper(file), file);
19821992
}
19831993
}

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3583,7 +3583,7 @@ namespace ts {
35833583
name: string;
35843584
}
35853585

3586-
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[];
3586+
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | null | undefined;
35873587

35883588
export interface CompilerOptions {
35893589
/*@internal*/ all?: boolean;

src/harness/unittests/configurationExtension.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,23 @@ namespace ts {
7878
},
7979
include: ["../supplemental.*"]
8080
},
81+
"/dev/configs/third.json": {
82+
extends: "./second",
83+
compilerOptions: {
84+
// tslint:disable-next-line:no-null-keyword
85+
module: null
86+
},
87+
include: ["../supplemental.*"]
88+
},
89+
"/dev/configs/fourth.json": {
90+
extends: "./third",
91+
compilerOptions: {
92+
module: "system"
93+
},
94+
// tslint:disable-next-line:no-null-keyword
95+
include: null,
96+
files: ["../main.ts"]
97+
},
8198
"/dev/extends.json": { extends: 42 },
8299
"/dev/extends2.json": { extends: "configs/base" },
83100
"/dev/main.ts": "",
@@ -106,7 +123,7 @@ namespace ts {
106123
}
107124
}
108125

109-
describe("Configuration Extension", () => {
126+
describe("configurationExtension", () => {
110127
forEach<[string, string, Utils.MockParseConfigHost], void>([
111128
["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost],
112129
["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost]
@@ -206,6 +223,24 @@ namespace ts {
206223
category: DiagnosticCategory.Error,
207224
messageText: `A path in an 'extends' option must be relative or rooted, but 'configs/base' is not.`
208225
}]);
226+
227+
testSuccess("can overwrite compiler options using extended 'null'", "configs/third.json", {
228+
allowJs: true,
229+
noImplicitAny: true,
230+
strictNullChecks: true,
231+
module: undefined // Technically, this is distinct from the key never being set; but within the compiler we don't make the distinction
232+
}, [
233+
combinePaths(basePath, "supplemental.ts")
234+
]);
235+
236+
testSuccess("can overwrite top-level options using extended 'null'", "configs/fourth.json", {
237+
allowJs: true,
238+
noImplicitAny: true,
239+
strictNullChecks: true,
240+
module: ModuleKind.System
241+
}, [
242+
combinePaths(basePath, "main.ts")
243+
]);
209244
});
210245
});
211246
});

0 commit comments

Comments
 (0)