diff --git a/docs/deprecated.md b/docs/deprecated.md index 2ead1ab81..6719f8065 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -15,6 +15,7 @@ They are currently still available for backwards compatibility, but should not b - [InvalidOptionArgumentError](#invalidoptionargumenterror) - [Short option flag longer than a single character](#short-option-flag-longer-than-a-single-character) - [Import from `commander/esm.mjs`](#import-from-commanderesmmjs) + - [.\_args](#_args) ## RegExp .option() parameter @@ -207,3 +208,17 @@ import { Command } from 'commander'; ``` README updated in Commander v9. Deprecated from Commander v9. + +## ._args + +The registered command argument instances have been made accessible via `.registeredArguments`. + +```js +const program = new Command().arguments('arg1 [arg3]'); +const argumentNamesForUsage = program.registeredArguments + .map(arg => arg.required ? `< ${arg.name()} >` : `[ ${arg.name()} ]`) + .join(' '); +program.usage('program ' + argumentNamesForUsage); +``` + +The older name of the property was `_args`. It was never documented but is still available so that old code relying on it keeps working. Do not use it. diff --git a/lib/command.js b/lib/command.js index 590a271dd..d6554b31a 100644 --- a/lib/command.js +++ b/lib/command.js @@ -29,7 +29,8 @@ class Command extends EventEmitter { this._allowUnknownOption = false; this._allowExcessArguments = true; /** @type {Argument[]} */ - this._args = []; + this.registeredArguments = []; + this._args = this.registeredArguments; // deprecated /** @type {string[]} */ this.args = []; // cli args with options removed this.rawArgs = []; @@ -343,14 +344,14 @@ class Command extends EventEmitter { * @return {Command} `this` command for chaining */ addArgument(argument) { - const previousArgument = this._args.slice(-1)[0]; + const previousArgument = this.registeredArguments.slice(-1)[0]; if (previousArgument && previousArgument.variadic) { throw new Error(`only the last argument can be variadic '${previousArgument.name()}'`); } if (argument.required && argument.defaultValue !== undefined && argument.parseArg === undefined) { throw new Error(`a default value for a required argument is never used: '${argument.name()}'`); } - this._args.push(argument); + this.registeredArguments.push(argument); return this; } @@ -470,7 +471,7 @@ Expecting one of '${allowedValues.join("', '")}'`); action(fn) { const listener = (args) => { // The .action callback takes an extra parameter which is the command or options. - const expectedArgsCount = this._args.length; + const expectedArgsCount = this.registeredArguments.length; const actionArgs = args.slice(0, expectedArgsCount); if (this._storeOptionsAsProperties) { actionArgs[expectedArgsCount] = this; // backwards compatible "options" @@ -1105,29 +1106,29 @@ Expecting one of '${allowedValues.join("', '")}'`); } /** - * Check this.args against expected this._args. + * Check this.args against expected this.registeredArguments. * * @api private */ _checkNumberOfArguments() { // too few - this._args.forEach((arg, i) => { + this.registeredArguments.forEach((arg, i) => { if (arg.required && this.args[i] == null) { this.missingArgument(arg.name()); } }); // too many - if (this._args.length > 0 && this._args[this._args.length - 1].variadic) { - return; - } - if (this.args.length > this._args.length) { + if (this.registeredArguments.length > 0 && + this.registeredArguments[this.registeredArguments.length - 1].variadic + ) return; + if (this.args.length > this.registeredArguments.length) { this._excessArguments(this.args); } } /** - * Process this.args using this._args and save as this.processedArgs! + * Process this.args using this.registeredArguments and save as this.processedArgs! * * @api private */ @@ -1153,7 +1154,7 @@ Expecting one of '${allowedValues.join("', '")}'`); this._checkNumberOfArguments(); const processedArgs = []; - this._args.forEach((declaredArg, index) => { + this.registeredArguments.forEach((declaredArg, index) => { let value = declaredArg.defaultValue; if (declaredArg.variadic) { // Collect together remaining arguments for passing together as an array. @@ -1768,7 +1769,7 @@ Expecting one of '${allowedValues.join("', '")}'`); _excessArguments(receivedArgs) { if (this._allowExcessArguments) return; - const expected = this._args.length; + const expected = this.registeredArguments.length; const s = (expected === 1) ? '' : 's'; const forSubcommand = this.parent ? ` for '${this.name()}'` : ''; const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`; @@ -1909,13 +1910,13 @@ Expecting one of '${allowedValues.join("', '")}'`); if (str === undefined) { if (this._usage) return this._usage; - const args = this._args.map((arg) => { + const args = this.registeredArguments.map((arg) => { return humanReadableArgName(arg); }); return [].concat( (this.options.length || this._hasHelpOption ? '[options]' : []), (this.commands.length ? '[command]' : []), - (this._args.length ? args : []) + (this.registeredArguments.length ? args : []) ).join(' '); } diff --git a/lib/help.js b/lib/help.js index 14e0fb9f3..af8f836e6 100644 --- a/lib/help.js +++ b/lib/help.js @@ -121,14 +121,14 @@ class Help { visibleArguments(cmd) { // Side effect! Apply the legacy descriptions before the arguments are displayed. if (cmd._argsDescription) { - cmd._args.forEach(argument => { + cmd.registeredArguments.forEach(argument => { argument.description = argument.description || cmd._argsDescription[argument.name()] || ''; }); } // If there are any arguments with a description then return all the arguments. - if (cmd._args.find(argument => argument.description)) { - return cmd._args; + if (cmd.registeredArguments.find(argument => argument.description)) { + return cmd.registeredArguments; } return []; } @@ -142,7 +142,7 @@ class Help { subcommandTerm(cmd) { // Legacy. Ignores custom usage string, and nested commands. - const args = cmd._args.map(arg => humanReadableArgName(arg)).join(' '); + const args = cmd.registeredArguments.map(arg => humanReadableArgName(arg)).join(' '); return cmd._name + (cmd._aliases[0] ? '|' + cmd._aliases[0] : '') + (cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option diff --git a/tests/command.argumentVariations.test.js b/tests/command.argumentVariations.test.js index a8a781625..a78464aaf 100644 --- a/tests/command.argumentVariations.test.js +++ b/tests/command.argumentVariations.test.js @@ -4,7 +4,7 @@ const commander = require('../'); // and not exhaustively testing all methods elsewhere. test.each(getSingleArgCases(''))('when add "" using %s then argument required', (methodName, cmd) => { - const argument = cmd._args[0]; + const argument = cmd.registeredArguments[0]; const expectedShape = { _name: 'explicit-required', required: true, @@ -15,7 +15,7 @@ test.each(getSingleArgCases(''))('when add "" using %s t }); test.each(getSingleArgCases('implicit-required'))('when add "arg" using %s then argument required', (methodName, cmd) => { - const argument = cmd._args[0]; + const argument = cmd.registeredArguments[0]; const expectedShape = { _name: 'implicit-required', required: true, @@ -26,7 +26,7 @@ test.each(getSingleArgCases('implicit-required'))('when add "arg" using %s then }); test.each(getSingleArgCases('[optional]'))('when add "[arg]" using %s then argument optional', (methodName, cmd) => { - const argument = cmd._args[0]; + const argument = cmd.registeredArguments[0]; const expectedShape = { _name: 'optional', required: false, @@ -37,7 +37,7 @@ test.each(getSingleArgCases('[optional]'))('when add "[arg]" using %s then argum }); test.each(getSingleArgCases(''))('when add "" using %s then argument required and variadic', (methodName, cmd) => { - const argument = cmd._args[0]; + const argument = cmd.registeredArguments[0]; const expectedShape = { _name: 'explicit-required', required: true, @@ -48,7 +48,7 @@ test.each(getSingleArgCases(''))('when add "" usin }); test.each(getSingleArgCases('implicit-required...'))('when add "arg..." using %s then argument required and variadic', (methodName, cmd) => { - const argument = cmd._args[0]; + const argument = cmd.registeredArguments[0]; const expectedShape = { _name: 'implicit-required', required: true, @@ -59,7 +59,7 @@ test.each(getSingleArgCases('implicit-required...'))('when add "arg..." using %s }); test.each(getSingleArgCases('[optional...]'))('when add "[arg...]" using %s then argument optional and variadic', (methodName, cmd) => { - const argument = cmd._args[0]; + const argument = cmd.registeredArguments[0]; const expectedShape = { _name: 'optional', required: false, @@ -79,8 +79,8 @@ function getSingleArgCases(arg) { } test.each(getMultipleArgCases('', '[second]'))('when add two arguments using %s then two arguments', (methodName, cmd) => { - expect(cmd._args[0].name()).toEqual('first'); - expect(cmd._args[1].name()).toEqual('second'); + expect(cmd.registeredArguments[0].name()).toEqual('first'); + expect(cmd.registeredArguments[1].name()).toEqual('second'); }); function getMultipleArgCases(arg1, arg2) { @@ -99,6 +99,6 @@ test('when add arguments using multiple methods then all added', () => { cmd.arguments(' '); cmd.argument(''); cmd.addArgument(new commander.Argument('arg6')); - const argNames = cmd._args.map(arg => arg.name()); + const argNames = cmd.registeredArguments.map(arg => arg.name()); expect(argNames).toEqual(['arg1', 'arg2', 'arg3', 'arg4', 'arg5', 'arg6']); }); diff --git a/tests/command.createArgument.test.js b/tests/command.createArgument.test.js index da69c721f..599efdfc4 100644 --- a/tests/command.createArgument.test.js +++ b/tests/command.createArgument.test.js @@ -21,20 +21,20 @@ class MyCommand extends commander.Command { test('when override createArgument then used for argument()', () => { const program = new MyCommand(); program.argument(''); - expect(program._args.length).toEqual(1); - expect(program._args[0].myProperty).toEqual('MyArgument'); + expect(program.registeredArguments.length).toEqual(1); + expect(program.registeredArguments[0].myProperty).toEqual('MyArgument'); }); test('when override createArgument then used for arguments()', () => { const program = new MyCommand(); program.arguments(''); - expect(program._args.length).toEqual(1); - expect(program._args[0].myProperty).toEqual('MyArgument'); + expect(program.registeredArguments.length).toEqual(1); + expect(program.registeredArguments[0].myProperty).toEqual('MyArgument'); }); test('when override createArgument and createCommand then used for argument of command()', () => { const program = new MyCommand(); const sub = program.command('sub '); - expect(sub._args.length).toEqual(1); - expect(sub._args[0].myProperty).toEqual('MyArgument'); + expect(sub.registeredArguments.length).toEqual(1); + expect(sub.registeredArguments[0].myProperty).toEqual('MyArgument'); }); diff --git a/typings/index.d.ts b/typings/index.d.ts index 695c3bd25..a1b738f00 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -281,6 +281,9 @@ export class Command { processedArgs: any[]; readonly commands: readonly Command[]; readonly options: readonly Option[]; + readonly registeredArguments: readonly Argument[]; + /** @deprecated Use `.registeredArguments` instead. */ + readonly _args: readonly Argument[]; // added here for strikethrough highlighting in editors parent: Command | null; constructor(name?: string);