Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix(29118): tsconfig.extends as array #50403

Merged
merged 15 commits into from
Dec 13, 2022
Merged
227 changes: 172 additions & 55 deletions src/compiler/commandLineParser.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6598,7 +6598,7 @@ namespace ts {
/* @internal */
export interface CommandLineOptionBase {
name: string;
type: "string" | "number" | "boolean" | "object" | "list" | ESMap<string, number | string>; // a value of a primitive type, or an object literal mapping named values to actual values
type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | ESMap<string, number | string> ; // 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.
Expand Down Expand Up @@ -6669,7 +6669,7 @@ namespace ts {

/* @internal */
export interface CommandLineOptionOfListType extends CommandLineOptionBase {
type: "list";
type: "list" | "listOrElement";
element: CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | TsConfigOnlyOption;
listPreserveFalsyValues?: boolean;
}
Expand Down
8 changes: 8 additions & 0 deletions src/executeCommandLine/executeCommandLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,14 @@ namespace ts {
// TODO: check infinite loop
possibleValues = getPossibleValues(option.element);
break;
case "listOrElement":
if (option.element.type === "string"){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (option.element.type === "string"){
if (option.element.type === "string") {

possibleValues = option.type;
}
else {
possibleValues = getPossibleValues(option.element);
}
break;
case "object":
possibleValues = "";
break;
Expand Down
79 changes: 76 additions & 3 deletions src/testRunner/unittests/config/configurationExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,46 @@ namespace ts {
"dev/tests/unit/spec.ts": "",
"dev/tests/utils.ts": "",
"dev/tests/scenarios/first.json": "",
"dev/tests/baselines/first/output.ts": ""
"dev/tests/baselines/first/output.ts": "",
"dev/configs/extendsArrayFirst.json": JSON.stringify({
compilerOptions: {
allowJs: true,
noImplicitAny: true,
strictNullChecks: true
}
}),
"dev/configs/extendsArraySecond.json": JSON.stringify({
compilerOptions: {
module: "amd"
},
include: ["../supplemental.*"]
}),
"dev/configs/extendsArrayThird.json": JSON.stringify({
compilerOptions: {
module: null, // eslint-disable-line no-null/no-null
noImplicitAny: false
},
include: ["../supplemental.*"]
}),
"dev/configs/extendsArrayFourth.json": JSON.stringify({
compilerOptions: {
module: "system",
strictNullChecks: false
},
include: null, // eslint-disable-line no-null/no-null
files: ["../main.ts"]
}),
"dev/configs/extendsArrayFifth.json": JSON.stringify({
extends: ["./extendsArrayFirst", "./extendsArraySecond", "./extendsArrayThird", "./extendsArrayFourth"],
files: [],
}),
"dev/extendsArrayFails.json": JSON.stringify({
extends: [""],
compilerOptions: {
types: []
}
}),
"dev/extendsArrayFails2.json": JSON.stringify({ extends: [42] }),
}
}
});
Expand Down Expand Up @@ -292,9 +331,9 @@ namespace ts {
messageText: `Unknown option 'excludes'. Did you mean 'exclude'?`
}]);

testFailure("can error when 'extends' is not a string", "extends.json", [{
testFailure("can error when 'extends' is not a string or Array", "extends.json", [{
code: 5024,
messageText: `Compiler option 'extends' requires a value of type string.`
messageText: `Compiler option 'extends' requires a value of type string or Array.`
}]);

testSuccess("can overwrite compiler options using extended 'null'", "configs/third.json", {
Expand Down Expand Up @@ -349,6 +388,40 @@ namespace ts {
assert.deepEqual(sourceFile.extendedSourceFiles, expected);
});
});

describe(testName, () => {
it("adds extendedSourceFiles from an array only once", () => {
const sourceFile = readJsonConfigFile("configs/extendsArrayFifth.json", (path) => host.readFile(path));
const dir = combinePaths(basePath, "configs");
const expected = [
combinePaths(dir, "extendsArrayFirst.json"),
combinePaths(dir, "extendsArraySecond.json"),
combinePaths(dir, "extendsArrayThird.json"),
combinePaths(dir, "extendsArrayFourth.json"),
];
parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "extendsArrayFifth.json");
assert.deepEqual(sourceFile.extendedSourceFiles, expected);
parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "extendsArrayFifth.json");
assert.deepEqual(sourceFile.extendedSourceFiles, expected);
});

testSuccess("can overwrite top-level compilerOptions", "configs/extendsArrayFifth.json", {
allowJs: true,
noImplicitAny: false,
strictNullChecks: false,
module: ModuleKind.System
}, []);

testFailure("can report missing configurations", "extendsArrayFails.json", [{
code: 6053,
messageText: `File '' not found.`
}]);

testFailure("can error when 'extends' is not a string or Array2", "extendsArrayFails2.json", [{
code: 5024,
messageText: `Compiler option 'extends' requires a value of type string.`
}]);
});
});
});
}
20 changes: 20 additions & 0 deletions src/testRunner/unittests/config/showConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,26 @@ namespace ts {
}
break;
}
case "listOrElement": {
if (option.isTSConfigOnly) {
args = ["-p", "tsconfig.json"];
if(option.element.type === "string"){
optionValue = { [option.name]: "someString" };
}
else{
optionValue = { [option.name]: [] };
}
}
else {
if(option.element.type === "string"){
args = [`--${option.name}`, "someString"];
}
else{
args = [`--${option.name}`];
}
}
break;
}
case "string": {
if (option.isTSConfigOnly) {
args = ["-p", "tsconfig.json"];
Expand Down
57 changes: 55 additions & 2 deletions src/testRunner/unittests/tsbuildWatch/programUpdates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ export function someFn() { }`),
verifyTscWatch({
scenario: "programUpdates",
subScenario: "works with extended source files",
commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"],
commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json", "project3.tsconfig.json"],
sys: () => {
const alphaExtendedConfigFile: File = {
path: "/a/b/alpha.tsconfig.json",
Expand Down Expand Up @@ -602,10 +602,49 @@ export function someFn() { }`),
files: [otherFile.path]
})
};
const otherFile2: File = {
path: "/a/b/other2.ts",
content: "let k = 0;",
};
const extendsConfigFile1: File = {
path: "/a/b/extendsConfig1.tsconfig.json",
content: JSON.stringify({
compilerOptions: {
composite: true,
}
})
};
const extendsConfigFile2: File = {
path: "/a/b/extendsConfig2.tsconfig.json",
content: JSON.stringify({
compilerOptions: {
strictNullChecks: false,
}
})
};
const extendsConfigFile3: File = {
path: "/a/b/extendsConfig3.tsconfig.json",
content: JSON.stringify({
compilerOptions: {
noImplicitAny: true,
}
})
};
const project3Config: File = {
path: "/a/b/project3.tsconfig.json",
content: JSON.stringify({
extends: ["./extendsCnfig1.tsconfig.json", "./extendsConfig2.tsconfig.json", "./extendsConfig3.tsconfig.json"],
compilerOptions: {
composite: false,
},
files: [otherFile.path]
})
};
return createWatchedSystem([
libFile,
alphaExtendedConfigFile, project1Config, commonFile1, commonFile2,
bravoExtendedConfigFile, project2Config, otherFile
bravoExtendedConfigFile, project2Config, otherFile, otherFile2,
extendsConfigFile1, extendsConfigFile2, extendsConfigFile3, project3Config
], { currentDirectory: "/a/b" });
},
changes: [
Expand Down Expand Up @@ -646,6 +685,20 @@ export function someFn() { }`),
change: noop,
timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
},
{
caption: "Modify extendsConfigFile2",
change: sys => sys.writeFile("/a/b/extendsConfig2.tsconfig.json", JSON.stringify({
compilerOptions: { strictNullChecks: true }
})),
timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project1
},
{
caption: "Modify project 3",
change: sys => sys.writeFile("/a/b/project3.tsconfig.json", JSON.stringify({
extends: ["./extendsConfig1.tsconfig.json", "./extendsConfig2.tsconfig.json"],
})),
timeouts: checkSingleTimeoutQueueLengthAndRun // Build project1
},
]
});

Expand Down
Loading