From 1bdab66139ad96e088ba94b9d7aece7779468ae2 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 10 Sep 2023 18:21:30 +1200 Subject: [PATCH 1/2] Add public property for command arguments Co-authored-by: Wee Bit --- docs/deprecated.md | 17 +++++++++++++++ lib/command.js | 27 ++++++++++++------------ lib/help.js | 8 +++---- tests/command.argumentVariations.test.js | 18 ++++++++-------- tests/command.createArgument.test.js | 12 +++++------ typings/index.d.ts | 1 + 6 files changed, 51 insertions(+), 32 deletions(-) diff --git a/docs/deprecated.md b/docs/deprecated.md index 2ead1ab81..8ff745341 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) + - [cmd.\_args](#cmd_args) ## RegExp .option() parameter @@ -207,3 +208,19 @@ import { Command } from 'commander'; ``` README updated in Commander v9. Deprecated from Commander v9. + +## cmd._args + +This was always private, but was previously the only way to access the command `Argument` array. + +```js +const registeredArguments = program._args; +``` + +The registered command arguments are now accessible via `.registeredArguments`. + +```js +const registeredArguments = program.registeredArguments; +``` + +Deprecated from Commander v11. diff --git a/lib/command.js b/lib/command.js index e9f655f6b..9cdbb25f1 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 old name /** @type {string[]} */ this.args = []; // cli args with options removed this.rawArgs = []; @@ -356,14 +357,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; } @@ -483,7 +484,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" @@ -1109,29 +1110,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) { + if (this.registeredArguments.length > 0 && this.registeredArguments[this.registeredArguments.length - 1].variadic) { return; } - if (this.args.length > this._args.length) { + 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 */ @@ -1150,7 +1151,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. @@ -1765,7 +1766,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}.`; @@ -1905,13 +1906,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 560801bd3..32f409161 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 cc6b9ca8a..8d1aead56 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -290,6 +290,7 @@ export class Command { processedArgs: any[]; readonly commands: readonly Command[]; readonly options: readonly Option[]; + readonly registeredArguments: readonly Argument[]; parent: Command | null; constructor(name?: string); From a8a3895dd22122fafb49a67ec94513b733491108 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 10 Sep 2023 18:25:24 +1200 Subject: [PATCH 2/2] Add typing test --- typings/index.test-d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index 7296e4cf2..c8db518bb 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -30,6 +30,7 @@ expectType(program.args); expectType(program.processedArgs); expectType(program.commands); expectType(program.options); +expectType(program.registeredArguments); expectType(program.parent); // version