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

Allow --composite false or --composite null on the command line #36997

Merged
merged 10 commits into from
Feb 26, 2020
93 changes: 60 additions & 33 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,8 @@ namespace ts {
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
}

interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics {
/*@internal*/
export interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics {
getOptionsNameMap: () => OptionsNameMap;
optionTypeMismatchDiagnostic: DiagnosticMessage;
}
Expand All @@ -1189,7 +1190,8 @@ namespace ts {
createDiagnostics(diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
}

function parseCommandLineWorker(
/*@internal*/
export function parseCommandLineWorker(
diagnostics: ParseCommandLineWorkerDiagnostics,
commandLine: readonly string[],
readFile?: (path: string) => string | undefined) {
Expand Down Expand Up @@ -1279,50 +1281,75 @@ 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).
if (!args[i] && opt.type !== "boolean") {
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,
Expand Down Expand Up @@ -2170,7 +2197,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) {
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down Expand Up @@ -4292,6 +4292,10 @@
"category": "Message",
"code": 6228
},
"Option '{0}' can only be specified in 'tsconfig.json' file or set to 'false' or 'null' on command line.": {
"category": "Error",
"code": 6229
},

"Projects to reference": {
"category": "Message",
Expand Down
1 change: 1 addition & 0 deletions src/testRunner/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
181 changes: 179 additions & 2 deletions src/testRunner/unittests/config/commandLineParsing.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
namespace ts {
describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => {

function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine) {
const parsed = parseCommandLine(commandLine);
function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine, workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics) {
const parsed = parseCommandLineWorker(workerDiagnostic?.() || compilerOptionsDidYouMeanDiagnostics, commandLine);
const parsedCompilerOptions = JSON.stringify(parsed.options);
const expectedCompilerOptions = JSON.stringify(expectedParsedCommandLine.options);
assert.equal(parsedCompilerOptions, expectedCompilerOptions);
Expand Down Expand Up @@ -414,6 +414,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 }
sheetalkamat marked this conversation as resolved.
Show resolved Hide resolved
},
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"],
Expand Down
Loading