From 2b072f8478a17f888eaabd97fef600ef3f47498c Mon Sep 17 00:00:00 2001 From: Tim Viiding-Spader Date: Sat, 30 Jan 2016 12:17:55 -0600 Subject: [PATCH 1/6] Don't choke on switches in --example=VALUE format Issue #6758 --- src/compiler/commandLineParser.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index d5bf95a64058a..6504cad37906d 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -371,13 +371,25 @@ namespace ts { parseResponseFile(s.slice(1)); } else if (s.charCodeAt(0) === CharacterCodes.minus) { - s = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase(); + s = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1); // Try to translate short option names to their full equivalents. - if (hasProperty(shortOptionNames, s)) { + if (hasProperty(shortOptionNames, s.toLowerCase())) { s = shortOptionNames[s]; } + else { + // When using long-form switches, we follow standard command-line conventions and accept + // "--example=VALUE", but we also accept "--example VALUE". + var [ longFormSwitch ] = s.split("=", 1); + if (longFormSwitch.length < s.length && hasProperty(optionNameMap, longFormSwitch.toLowerCase())) { + // It's in "--example=VALUE" format. Replace it in the arg list with the separated format. + var value = s.substring(longFormSwitch.length + 1); + args.splice(i - 1, 1, longFormSwitch, value); + s = longFormSwitch; + } + } + s = s.toLowerCase(); if (hasProperty(optionNameMap, s)) { const opt = optionNameMap[s]; From 1f45c438cfac4ce9c0ecbccd14722e84adfae5f1 Mon Sep 17 00:00:00 2001 From: Tim Viiding-Spader Date: Sat, 30 Jan 2016 13:24:28 -0600 Subject: [PATCH 2/6] Address pull review comments For issue #6758 --- src/compiler/commandLineParser.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 6504cad37906d..96d11437a0b50 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -375,15 +375,15 @@ namespace ts { // Try to translate short option names to their full equivalents. if (hasProperty(shortOptionNames, s.toLowerCase())) { - s = shortOptionNames[s]; + s = shortOptionNames[s.toLowerCase()]; } else { // When using long-form switches, we follow standard command-line conventions and accept // "--example=VALUE", but we also accept "--example VALUE". - var [ longFormSwitch ] = s.split("=", 1); + const [ longFormSwitch ] = s.split("=", 1); if (longFormSwitch.length < s.length && hasProperty(optionNameMap, longFormSwitch.toLowerCase())) { // It's in "--example=VALUE" format. Replace it in the arg list with the separated format. - var value = s.substring(longFormSwitch.length + 1); + const value = s.substring(longFormSwitch.length + 1); args.splice(i - 1, 1, longFormSwitch, value); s = longFormSwitch; } From 279f15e026ccebe888881a59a92dfb0898469f8c Mon Sep 17 00:00:00 2001 From: Tim Viiding-Spader Date: Sat, 30 Jan 2016 21:30:31 -0600 Subject: [PATCH 3/6] Don't shift values for boolean options into args --- src/compiler/commandLineParser.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 96d11437a0b50..51e213af4b002 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -380,11 +380,16 @@ namespace ts { else { // When using long-form switches, we follow standard command-line conventions and accept // "--example=VALUE", but we also accept "--example VALUE". - const [ longFormSwitch ] = s.split("=", 1); - if (longFormSwitch.length < s.length && hasProperty(optionNameMap, longFormSwitch.toLowerCase())) { + const longFormSwitch = s.split("=", 1).toString().toLowerCase(); + if (longFormSwitch.length < s.length && hasProperty(optionNameMap, longFormSwitch)) { // It's in "--example=VALUE" format. Replace it in the arg list with the separated format. const value = s.substring(longFormSwitch.length + 1); - args.splice(i - 1, 1, longFormSwitch, value); + const opt = optionNameMap[longFormSwitch]; + if (opt.type === "boolean") { + errors.push(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, opt.name, value)); + continue; + } + args.splice(i - 1, 1, opt.name, value); s = longFormSwitch; } } From abad47102f0036faae95485a3a083e594b16e63c Mon Sep 17 00:00:00 2001 From: Tim Viiding-Spader Date: Tue, 2 Feb 2016 08:50:45 -0600 Subject: [PATCH 4/6] Address review comments for superficial changes Issue #6758 --- src/compiler/commandLineParser.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 51e213af4b002..e73cd7c3ee417 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -380,17 +380,17 @@ namespace ts { else { // When using long-form switches, we follow standard command-line conventions and accept // "--example=VALUE", but we also accept "--example VALUE". - const longFormSwitch = s.split("=", 1).toString().toLowerCase(); - if (longFormSwitch.length < s.length && hasProperty(optionNameMap, longFormSwitch)) { + const optionName = s.split("=", 1)[0].toLowerCase(); + if (optionName.length < s.length && hasProperty(optionNameMap, optionName)) { // It's in "--example=VALUE" format. Replace it in the arg list with the separated format. - const value = s.substring(longFormSwitch.length + 1); - const opt = optionNameMap[longFormSwitch]; + const value = s.substring(optionName.length + 1); + const opt = optionNameMap[optionName]; if (opt.type === "boolean") { - errors.push(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, opt.name, value)); - continue; + errors.push(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, opt.name, value)); + continue; } args.splice(i - 1, 1, opt.name, value); - s = longFormSwitch; + s = optionName; } } From d3f75d8078bd560bbe60c396ca65647962d39949 Mon Sep 17 00:00:00 2001 From: Tim Viiding-Spader Date: Tue, 2 Feb 2016 08:52:24 -0600 Subject: [PATCH 5/6] Diagnostic for longform boolean flag given a value Issue #6758 --- src/compiler/commandLineParser.ts | 2 +- src/compiler/diagnosticMessages.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index e73cd7c3ee417..b39e117e7fb6a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -386,7 +386,7 @@ namespace ts { const value = s.substring(optionName.length + 1); const opt = optionNameMap[optionName]; if (opt.type === "boolean") { - errors.push(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, opt.name, value)); + errors.push(createCompilerDiagnostic(Diagnostics.Option_0_does_not_expect_a_parameter_but_was_given_1, opt.name, value)); continue; } args.splice(i - 1, 1, opt.name, value); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index ca5a6d9d41527..57270dd6942ff 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2187,6 +2187,10 @@ "category": "Error", "code": 5062 }, + "Option '{0}' does not expect a parameter, but was given '{1}'.": { + "category": "Error", + "code": 5063 + }, "Concatenate and emit output to single file.": { "category": "Message", "code": 6001 From 47ed17af0355d4975d654e2be34bd52674ea6554 Mon Sep 17 00:00:00 2001 From: Tim Viiding-Spader Date: Tue, 2 Feb 2016 09:00:22 -0600 Subject: [PATCH 6/6] Don't inject synthesized strings into command line Issue #6758 --- src/compiler/commandLineParser.ts | 53 +++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index b39e117e7fb6a..26fc1b142691b 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -349,6 +349,12 @@ namespace ts { return optionNameMapCache; } + interface ArgumentStream { + synthesizedArgs: string[]; + commandLineArgs: string[]; + cliPosition: number; + } + export function parseCommandLine(commandLine: string[], readFile?: (path: string) => string): ParsedCommandLine { const options: CompilerOptions = {}; const fileNames: string[] = []; @@ -363,10 +369,13 @@ namespace ts { }; function parseStrings(args: string[]) { - let i = 0; - while (i < args.length) { - let s = args[i]; - i++; + const strings: ArgumentStream = { + synthesizedArgs: [], + commandLineArgs: args, + cliPosition: 0 + }; + let s = getNextString(strings); + while (s !== undefined) { if (s.charCodeAt(0) === CharacterCodes.at) { parseResponseFile(s.slice(1)); } @@ -382,14 +391,15 @@ namespace ts { // "--example=VALUE", but we also accept "--example VALUE". const optionName = s.split("=", 1)[0].toLowerCase(); if (optionName.length < s.length && hasProperty(optionNameMap, optionName)) { - // It's in "--example=VALUE" format. Replace it in the arg list with the separated format. + // It's in "--example=VALUE" format. Separate the value and inject it into the arg stream + // so it's present for the next read. const value = s.substring(optionName.length + 1); const opt = optionNameMap[optionName]; if (opt.type === "boolean") { errors.push(createCompilerDiagnostic(Diagnostics.Option_0_does_not_expect_a_parameter_but_was_given_1, opt.name, value)); continue; } - args.splice(i - 1, 1, opt.name, value); + strings.synthesizedArgs.unshift(value); s = optionName; } } @@ -403,27 +413,24 @@ namespace ts { } else { // Check to see if no argument was provided (e.g. "--locale" is the last command-line argument). - if (!args[i] && opt.type !== "boolean") { + if (!getNextString(strings, /*holdPosition*/ true) && opt.type !== "boolean") { errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_expects_an_argument, opt.name)); } switch (opt.type) { case "number": - options[opt.name] = parseInt(args[i]); - i++; + options[opt.name] = parseInt(getNextString(strings)); break; case "boolean": options[opt.name] = true; break; case "string": - options[opt.name] = args[i] || ""; - i++; + options[opt.name] = getNextString(strings) || ""; break; // If not a primitive, the possible types are specified in what is effectively a map of options. default: let map = >opt.type; - let key = (args[i] || "").toLowerCase(); - i++; + let key = (getNextString(strings) || "").toLowerCase(); if (hasProperty(map, key)) { options[opt.name] = map[key]; } @@ -440,9 +447,29 @@ namespace ts { else { fileNames.push(s); } + + s = getNextString(strings); } } + function getNextString(stream: ArgumentStream, holdPosition = false) { + if (stream.synthesizedArgs.length > 0) { + const next = stream.synthesizedArgs[0]; + if (!holdPosition) { + stream.synthesizedArgs.shift(); + } + + return next; + } + + const next = stream.commandLineArgs[stream.cliPosition]; + if (!holdPosition) { + stream.cliPosition++; + } + + return next; + } + function parseResponseFile(fileName: string) { const text = readFile ? readFile(fileName) : sys.readFile(fileName);