diff --git a/Readme.md b/Readme.md index 7c0976444..de9750ddc 100644 --- a/Readme.md +++ b/Readme.md @@ -303,7 +303,7 @@ Options: -h, --help display help for command $ extra --drink huge -error: option '-d, --drink ' argument of 'huge' not in allowed choices: small, medium, large +error: option '-d, --drink ' argument 'huge' is invalid. Allowed choices are small, medium, large. ``` ### Custom option processing @@ -319,8 +319,12 @@ Example file: [options-custom-processing.js](./examples/options-custom-processin ```js function myParseInt(value, dummyPrevious) { - // parseInt takes a string and an optional radix - return parseInt(value); + // parseInt takes a string and a radix + const parsedValue = parseInt(value, 10); + if (isNaN(parsedValue)) { + throw new commander.InvalidOptionArgumentError('Not a number.'); + } + return parsedValue; } function increaseVerbosity(dummyValue, previous) { diff --git a/examples/options-custom-processing.js b/examples/options-custom-processing.js index e9d44fa28..5a44ba1b8 100755 --- a/examples/options-custom-processing.js +++ b/examples/options-custom-processing.js @@ -16,6 +16,8 @@ // [ 'a', 'b', 'c' ] // $ custom --list x,y,z // [ 'x', 'y', 'z' ] +// $ custom --integer oops +// error: option '-i, --integer ' argument 'oops' is invalid. Not a number. // const commander = require('commander'); // (normal include) const commander = require('../'); // include commander in git clone of commander repo @@ -23,7 +25,11 @@ const program = new commander.Command(); function myParseInt(value, dummyPrevious) { // parseInt takes a string and a radix - return parseInt(value, 10); + const parsedValue = parseInt(value, 10); + if (isNaN(parsedValue)) { + throw new commander.InvalidOptionArgumentError('Not a number.'); + } + return parsedValue; } function increaseVerbosity(dummyValue, previous) { diff --git a/index.js b/index.js index 5817acaac..47a1c0a9b 100644 --- a/index.js +++ b/index.js @@ -422,16 +422,6 @@ class Option { return this; }; - /** - * Validation of option argument failed. - * Intended for use from custom argument processing functions. - * - * @param {string} message - */ - argumentRejected(message) { - throw new CommanderError(1, 'commander.optionArgumentRejected', message); - } - /** * Only allow option value to be one of choices. * @@ -443,7 +433,7 @@ class Option { this.argChoices = values; this.parseArg = (arg) => { if (!values.includes(arg)) { - this.argumentRejected(`error: option '${this.flags}' argument of '${arg}' not in allowed choices: ${values.join(', ')}`); + throw new InvalidOptionArgumentError(`Allowed choices are ${values.join(', ')}.`); } return arg; }; @@ -511,6 +501,24 @@ class CommanderError extends Error { } } +/** + * InvalidOptionArgumentError class + * @class + */ +class InvalidOptionArgumentError extends CommanderError { + /** + * Constructs the InvalidOptionArgumentError class + * @param {string} [message] explanation of why argument is invalid + * @constructor + */ + constructor(message) { + super(1, 'commander.invalidOptionArgument', message); + // properly capture stack trace in Node.js + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + } +} + class Command extends EventEmitter { /** * Initialize a new `Command`. @@ -1002,8 +1010,9 @@ Read more on https://git.io/JJc0W`); try { val = option.parseArg(val, oldValue === undefined ? defaultValue : oldValue); } catch (err) { - if (err.code === 'commander.optionArgumentRejected') { - this._displayError(err.exitCode, err.code, err.message); + if (err.code === 'commander.invalidOptionArgument') { + const message = `error: option '${option.flags}' argument '${val}' is invalid. ${err.message}`; + this._displayError(err.exitCode, err.code, message); } throw err; } @@ -2040,6 +2049,7 @@ exports.program = exports; // More explicit access to global command. exports.Command = Command; exports.Option = Option; exports.CommanderError = CommanderError; +exports.InvalidOptionArgumentError = InvalidOptionArgumentError; exports.Help = Help; /** diff --git a/tests/command.exitOverride.test.js b/tests/command.exitOverride.test.js index f542a5d81..40ab1783f 100644 --- a/tests/command.exitOverride.test.js +++ b/tests/command.exitOverride.test.js @@ -218,6 +218,26 @@ describe('.exitOverride and error details', () => { caughtErr = err; } - expectCommanderError(caughtErr, 1, 'commander.optionArgumentRejected', `error: option '${optionFlags}' argument of 'green' not in allowed choices: red, blue`); + expectCommanderError(caughtErr, 1, 'commander.invalidOptionArgument', "error: option '--colour ' argument 'green' is invalid. Allowed choices are red, blue."); + }); + + test('when custom processing throws InvalidOptionArgumentError then throw CommanderError', () => { + function justSayNo(value) { + throw new commander.InvalidOptionArgumentError('NO'); + } + const optionFlags = '--colour '; + const program = new commander.Command(); + program + .exitOverride() + .option(optionFlags, 'specify shade', justSayNo); + + let caughtErr; + try { + program.parse(['--colour', 'green'], { from: 'user' }); + } catch (err) { + caughtErr = err; + } + + expectCommanderError(caughtErr, 1, 'commander.invalidOptionArgument', "error: option '--colour ' argument 'green' is invalid. NO"); }); }); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index d90474179..b9a967163 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -30,6 +30,7 @@ const commandInstance1 = new commander.Command(); const commandInstance2 = new commander.Command('name'); const optionsInstance = new commander.Option('-f'); const errorInstance = new commander.CommanderError(1, 'code', 'message'); +const invalidOptionErrorInstance = new commander.InvalidOptionArgumentError('message'); // Command properties console.log(programWithOptions.someOption); diff --git a/typings/index.d.ts b/typings/index.d.ts index e921ffbb1..0eb6300af 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -14,6 +14,10 @@ declare namespace commander { } type CommanderErrorConstructor = new (exitCode: number, code: string, message: string) => CommanderError; + interface InvalidOptionArgumentError extends CommanderError { + } + type InvalidOptionArgumentErrorConstructor = new (message: string) => InvalidOptionArgumentError; + interface Option { flags: string; description: string; @@ -578,6 +582,7 @@ declare namespace commander { Command: CommandConstructor; Option: OptionConstructor; CommanderError: CommanderErrorConstructor; + InvalidOptionArgumentError: InvalidOptionArgumentErrorConstructor; Help: HelpConstructor; }