Allow --composite false or --composite null on the command line (#36997)
* Add tests for specifying composite as command line option * Allow passing --composite false on commandline * Add test to verify tsc --composite false from command line * Handle "undefined" as option value to be set to undefined for that option * Support "null" as option to be converted to undefined which is normally end result from our config file as well * Support null as option for any tsconfig only option as well, and dont support undefined * Fix public api test case * Validates objects instead of stringify result * Add composite true to base source
This commit is contained in:
parent
05c9ec3f12
commit
d07761fe39
|
@ -1164,11 +1164,13 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
interface OptionsBase {
|
||||
/*@internal*/
|
||||
export interface OptionsBase {
|
||||
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
|
||||
}
|
||||
|
||||
interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics {
|
||||
/*@internal*/
|
||||
export interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics {
|
||||
getOptionsNameMap: () => OptionsNameMap;
|
||||
optionTypeMismatchDiagnostic: DiagnosticMessage;
|
||||
}
|
||||
|
@ -1189,7 +1191,8 @@ namespace ts {
|
|||
createDiagnostics(diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
|
||||
}
|
||||
|
||||
function parseCommandLineWorker(
|
||||
/*@internal*/
|
||||
export function parseCommandLineWorker(
|
||||
diagnostics: ParseCommandLineWorkerDiagnostics,
|
||||
commandLine: readonly string[],
|
||||
readFile?: (path: string) => string | undefined) {
|
||||
|
@ -1279,7 +1282,25 @@ namespace ts {
|
|||
errors: Diagnostic[]
|
||||
) {
|
||||
if (opt.isTSConfigOnly) {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name));
|
||||
const optValue = args[i];
|
||||
if (optValue === "null") {
|
||||
options[opt.name] = undefined;
|
||||
i++;
|
||||
}
|
||||
else if (opt.type === "boolean") {
|
||||
if (optValue === "false") {
|
||||
options[opt.name] = false;
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
if (optValue === "true") i++;
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line, opt.name));
|
||||
}
|
||||
}
|
||||
else {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line, opt.name));
|
||||
if (optValue && !startsWith(optValue, "-")) i++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
|
||||
|
@ -1287,42 +1308,49 @@ namespace ts {
|
|||
errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt)));
|
||||
}
|
||||
|
||||
switch (opt.type) {
|
||||
case "number":
|
||||
options[opt.name] = parseInt(args[i]);
|
||||
i++;
|
||||
break;
|
||||
case "boolean":
|
||||
// boolean flag has optional value true, false, others
|
||||
const optValue = args[i];
|
||||
options[opt.name] = optValue !== "false";
|
||||
// consume next argument as boolean flag value
|
||||
if (optValue === "false" || optValue === "true") {
|
||||
if (args[i] !== "null") {
|
||||
switch (opt.type) {
|
||||
case "number":
|
||||
options[opt.name] = parseInt(args[i]);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
options[opt.name] = args[i] || "";
|
||||
i++;
|
||||
break;
|
||||
case "list":
|
||||
const result = parseListTypeOption(opt, args[i], errors);
|
||||
options[opt.name] = result || [];
|
||||
if (result) {
|
||||
break;
|
||||
case "boolean":
|
||||
// boolean flag has optional value true, false, others
|
||||
const optValue = args[i];
|
||||
options[opt.name] = optValue !== "false";
|
||||
// consume next argument as boolean flag value
|
||||
if (optValue === "false" || optValue === "true") {
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
options[opt.name] = args[i] || "";
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
// If not a primitive, the possible types are specified in what is effectively a map of options.
|
||||
default:
|
||||
options[opt.name] = parseCustomTypeOption(<CommandLineOptionOfCustomType>opt, args[i], errors);
|
||||
i++;
|
||||
break;
|
||||
break;
|
||||
case "list":
|
||||
const result = parseListTypeOption(opt, args[i], errors);
|
||||
options[opt.name] = result || [];
|
||||
if (result) {
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
// If not a primitive, the possible types are specified in what is effectively a map of options.
|
||||
default:
|
||||
options[opt.name] = parseCustomTypeOption(<CommandLineOptionOfCustomType>opt, args[i], errors);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
options[opt.name] = undefined;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = {
|
||||
/*@internal*/
|
||||
export const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = {
|
||||
getOptionsNameMap,
|
||||
optionDeclarations,
|
||||
unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0,
|
||||
|
@ -2170,7 +2198,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function convertToOptionValueWithAbsolutePaths(option: CommandLineOption | undefined, value: CompilerOptionsValue, toAbsolutePath: (path: string) => string) {
|
||||
if (option) {
|
||||
if (option && !isNullOrUndefined(value)) {
|
||||
if (option.type === "list") {
|
||||
const values = value as readonly (string | number)[];
|
||||
if (option.element.isFilePath && values.length) {
|
||||
|
|
|
@ -3642,7 +3642,7 @@
|
|||
"category": "Message",
|
||||
"code": 6061
|
||||
},
|
||||
"Option '{0}' can only be specified in 'tsconfig.json' file.": {
|
||||
"Option '{0}' can only be specified in 'tsconfig.json' file or set to 'null' on command line.": {
|
||||
"category": "Error",
|
||||
"code": 6064
|
||||
},
|
||||
|
@ -4296,6 +4296,10 @@
|
|||
"category": "Error",
|
||||
"code": 6229
|
||||
},
|
||||
"Option '{0}' can only be specified in 'tsconfig.json' file or set to 'false' or 'null' on command line.": {
|
||||
"category": "Error",
|
||||
"code": 6230
|
||||
},
|
||||
|
||||
"Projects to reference": {
|
||||
"category": "Message",
|
||||
|
|
|
@ -126,6 +126,7 @@
|
|||
"unittests/tsbuild/transitiveReferences.ts",
|
||||
"unittests/tsbuild/watchEnvironment.ts",
|
||||
"unittests/tsbuild/watchMode.ts",
|
||||
"unittests/tsc/composite.ts",
|
||||
"unittests/tsc/declarationEmit.ts",
|
||||
"unittests/tsc/incremental.ts",
|
||||
"unittests/tsc/listFilesOnly.ts",
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
namespace ts {
|
||||
describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => {
|
||||
|
||||
function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine) {
|
||||
const parsed = parseCommandLine(commandLine);
|
||||
const parsedCompilerOptions = JSON.stringify(parsed.options);
|
||||
const expectedCompilerOptions = JSON.stringify(expectedParsedCommandLine.options);
|
||||
assert.equal(parsedCompilerOptions, expectedCompilerOptions);
|
||||
function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine, workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics) {
|
||||
const parsed = parseCommandLineWorker(workerDiagnostic?.() || compilerOptionsDidYouMeanDiagnostics, commandLine);
|
||||
assert.deepEqual(parsed.options, expectedParsedCommandLine.options);
|
||||
assert.deepEqual(parsed.watchOptions, expectedParsedCommandLine.watchOptions);
|
||||
|
||||
const parsedErrors = parsed.errors;
|
||||
|
@ -120,7 +118,7 @@ namespace ts {
|
|||
length: undefined,
|
||||
}],
|
||||
fileNames: ["0.ts"],
|
||||
options: {}
|
||||
options: { jsx: undefined }
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -146,7 +144,7 @@ namespace ts {
|
|||
length: undefined,
|
||||
}],
|
||||
fileNames: ["0.ts"],
|
||||
options: {}
|
||||
options: { module: undefined }
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -172,7 +170,7 @@ namespace ts {
|
|||
length: undefined,
|
||||
}],
|
||||
fileNames: ["0.ts"],
|
||||
options: {}
|
||||
options: { newLine: undefined }
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -198,7 +196,7 @@ namespace ts {
|
|||
length: undefined,
|
||||
}],
|
||||
fileNames: ["0.ts"],
|
||||
options: {}
|
||||
options: { target: undefined }
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -224,7 +222,7 @@ namespace ts {
|
|||
length: undefined,
|
||||
}],
|
||||
fileNames: ["0.ts"],
|
||||
options: {}
|
||||
options: { moduleResolution: undefined }
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -414,6 +412,183 @@ namespace ts {
|
|||
});
|
||||
});
|
||||
|
||||
describe("parses command line null for tsconfig only option", () => {
|
||||
interface VerifyNull {
|
||||
optionName: string;
|
||||
nonNullValue?: string;
|
||||
workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics;
|
||||
diagnosticMessage: DiagnosticMessage;
|
||||
}
|
||||
function verifyNull({ optionName, nonNullValue, workerDiagnostic, diagnosticMessage }: VerifyNull) {
|
||||
it("allows setting it to null", () => {
|
||||
assertParseResult(
|
||||
[`--${optionName}`, "null", "0.ts"],
|
||||
{
|
||||
errors: [],
|
||||
fileNames: ["0.ts"],
|
||||
options: { [optionName]: undefined }
|
||||
},
|
||||
workerDiagnostic
|
||||
);
|
||||
});
|
||||
|
||||
if (nonNullValue) {
|
||||
it("errors if non null value is passed", () => {
|
||||
assertParseResult(
|
||||
[`--${optionName}`, nonNullValue, "0.ts"],
|
||||
{
|
||||
errors: [{
|
||||
messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]),
|
||||
category: diagnosticMessage.category,
|
||||
code: diagnosticMessage.code,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined
|
||||
}],
|
||||
fileNames: ["0.ts"],
|
||||
options: {}
|
||||
},
|
||||
workerDiagnostic
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
it("errors if its followed by another option", () => {
|
||||
assertParseResult(
|
||||
["0.ts", "--strictNullChecks", `--${optionName}`],
|
||||
{
|
||||
errors: [{
|
||||
messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]),
|
||||
category: diagnosticMessage.category,
|
||||
code: diagnosticMessage.code,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined
|
||||
}],
|
||||
fileNames: ["0.ts"],
|
||||
options: { strictNullChecks: true }
|
||||
},
|
||||
workerDiagnostic
|
||||
);
|
||||
});
|
||||
|
||||
it("errors if its last option", () => {
|
||||
assertParseResult(
|
||||
["0.ts", `--${optionName}`],
|
||||
{
|
||||
errors: [{
|
||||
messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]),
|
||||
category: diagnosticMessage.category,
|
||||
code: diagnosticMessage.code,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined
|
||||
}],
|
||||
fileNames: ["0.ts"],
|
||||
options: {}
|
||||
},
|
||||
workerDiagnostic
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
interface VerifyNullNonIncludedOption {
|
||||
type: () => "string" | "number" | Map<number | string>;
|
||||
nonNullValue?: string;
|
||||
}
|
||||
function verifyNullNonIncludedOption({ type, nonNullValue }: VerifyNullNonIncludedOption) {
|
||||
verifyNull({
|
||||
optionName: "optionName",
|
||||
nonNullValue,
|
||||
diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line,
|
||||
workerDiagnostic: () => {
|
||||
const optionDeclarations = [
|
||||
...compilerOptionsDidYouMeanDiagnostics.optionDeclarations,
|
||||
{
|
||||
name: "optionName",
|
||||
type: type(),
|
||||
isTSConfigOnly: true,
|
||||
category: Diagnostics.Basic_Options,
|
||||
description: Diagnostics.Enable_project_compilation,
|
||||
}
|
||||
];
|
||||
return {
|
||||
...compilerOptionsDidYouMeanDiagnostics,
|
||||
optionDeclarations,
|
||||
getOptionsNameMap: () => createOptionNameMap(optionDeclarations)
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe("option of type boolean", () => {
|
||||
it("allows setting it to false", () => {
|
||||
assertParseResult(
|
||||
["--composite", "false", "0.ts"],
|
||||
{
|
||||
errors: [],
|
||||
fileNames: ["0.ts"],
|
||||
options: { composite: false }
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
verifyNull({
|
||||
optionName: "composite",
|
||||
nonNullValue: "true",
|
||||
diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line
|
||||
});
|
||||
});
|
||||
|
||||
describe("option of type object", () => {
|
||||
verifyNull({
|
||||
optionName: "paths",
|
||||
diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line
|
||||
});
|
||||
});
|
||||
|
||||
describe("option of type list", () => {
|
||||
verifyNull({
|
||||
optionName: "rootDirs",
|
||||
nonNullValue: "abc,xyz",
|
||||
diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line
|
||||
});
|
||||
});
|
||||
|
||||
describe("option of type string", () => {
|
||||
verifyNullNonIncludedOption({
|
||||
type: () => "string",
|
||||
nonNullValue: "hello"
|
||||
});
|
||||
});
|
||||
|
||||
describe("option of type number", () => {
|
||||
verifyNullNonIncludedOption({
|
||||
type: () => "number",
|
||||
nonNullValue: "10"
|
||||
});
|
||||
});
|
||||
|
||||
describe("option of type Map<number | string>", () => {
|
||||
verifyNullNonIncludedOption({
|
||||
type: () => createMapFromTemplate({
|
||||
node: ModuleResolutionKind.NodeJs,
|
||||
classic: ModuleResolutionKind.Classic,
|
||||
}),
|
||||
nonNullValue: "node"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("allows tsconfig only option to be set to null", () => {
|
||||
assertParseResult(["--composite", "null", "-tsBuildInfoFile", "null", "0.ts"],
|
||||
{
|
||||
errors: [],
|
||||
fileNames: ["0.ts"],
|
||||
options: { composite: undefined, tsBuildInfoFile: undefined }
|
||||
});
|
||||
});
|
||||
|
||||
describe("Watch options", () => {
|
||||
it("parse --watchFile", () => {
|
||||
assertParseResult(["--watchFile", "UseFsEvents", "0.ts"],
|
||||
|
@ -487,9 +662,7 @@ namespace ts {
|
|||
describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => {
|
||||
function assertParseResult(commandLine: string[], expectedParsedBuildCommand: ParsedBuildCommand) {
|
||||
const parsed = parseBuildCommand(commandLine);
|
||||
const parsedBuildOptions = JSON.stringify(parsed.buildOptions);
|
||||
const expectedBuildOptions = JSON.stringify(expectedParsedBuildCommand.buildOptions);
|
||||
assert.equal(parsedBuildOptions, expectedBuildOptions);
|
||||
assert.deepEqual(parsed.buildOptions, expectedParsedBuildCommand.buildOptions);
|
||||
assert.deepEqual(parsed.watchOptions, expectedParsedBuildCommand.watchOptions);
|
||||
|
||||
const parsedErrors = parsed.errors;
|
||||
|
|
85
src/testRunner/unittests/tsc/composite.ts
Normal file
85
src/testRunner/unittests/tsc/composite.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
namespace ts {
|
||||
describe("unittests:: tsc:: composite::", () => {
|
||||
verifyTsc({
|
||||
scenario: "composite",
|
||||
subScenario: "when setting composite false on command line",
|
||||
fs: () => loadProjectFromFiles({
|
||||
"/src/project/src/main.ts": "export const x = 10;",
|
||||
"/src/project/tsconfig.json": Utils.dedent`
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"composite": true,
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}`,
|
||||
}),
|
||||
commandLineArgs: ["--composite", "false", "--p", "src/project"],
|
||||
});
|
||||
|
||||
verifyTsc({
|
||||
scenario: "composite",
|
||||
subScenario: "when setting composite null on command line",
|
||||
fs: () => loadProjectFromFiles({
|
||||
"/src/project/src/main.ts": "export const x = 10;",
|
||||
"/src/project/tsconfig.json": Utils.dedent`
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"composite": true,
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}`,
|
||||
}),
|
||||
commandLineArgs: ["--composite", "null", "--p", "src/project"],
|
||||
});
|
||||
|
||||
verifyTsc({
|
||||
scenario: "composite",
|
||||
subScenario: "when setting composite false on command line but has tsbuild info in config",
|
||||
fs: () => loadProjectFromFiles({
|
||||
"/src/project/src/main.ts": "export const x = 10;",
|
||||
"/src/project/tsconfig.json": Utils.dedent`
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "tsconfig.json.tsbuildinfo"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}`,
|
||||
}),
|
||||
commandLineArgs: ["--composite", "false", "--p", "src/project"],
|
||||
});
|
||||
|
||||
verifyTsc({
|
||||
scenario: "composite",
|
||||
subScenario: "when setting composite false and tsbuildinfo as null on command line but has tsbuild info in config",
|
||||
fs: () => loadProjectFromFiles({
|
||||
"/src/project/src/main.ts": "export const x = 10;",
|
||||
"/src/project/tsconfig.json": Utils.dedent`
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "tsconfig.json.tsbuildinfo"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}`,
|
||||
}),
|
||||
commandLineArgs: ["--composite", "false", "--p", "src/project", "--tsBuildInfoFile", "null"],
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
//// [/lib/initial-buildOutput.txt]
|
||||
/lib/tsc --composite false --p src/project --tsBuildInfoFile null
|
||||
exitCode:: ExitStatus.Success
|
||||
|
||||
|
||||
//// [/src/project/src/main.js]
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.x = 10;
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
//// [/lib/initial-buildOutput.txt]
|
||||
/lib/tsc --composite false --p src/project
|
||||
src/project/tsconfig.json(6,9): error TS5069: Option 'tsBuildInfoFile' cannot be specified without specifying option 'incremental' or option 'composite'.
|
||||
exitCode:: ExitStatus.DiagnosticsPresent_OutputsGenerated
|
||||
|
||||
|
||||
//// [/src/project/src/main.js]
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.x = 10;
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
//// [/lib/initial-buildOutput.txt]
|
||||
/lib/tsc --composite false --p src/project
|
||||
exitCode:: ExitStatus.Success
|
||||
|
||||
|
||||
//// [/src/project/src/main.js]
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.x = 10;
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
//// [/lib/initial-buildOutput.txt]
|
||||
/lib/tsc --composite null --p src/project
|
||||
exitCode:: ExitStatus.Success
|
||||
|
||||
|
||||
//// [/src/project/src/main.js]
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.x = 10;
|
||||
|
||||
|
Loading…
Reference in a new issue