Skip to content

Commit

Permalink
Add public Arguments property (#2010)
Browse files Browse the repository at this point in the history

Co-authored-by: Wee Bit <aweebit64@gmail.com>
  • Loading branch information
shadowspawn and aweebit committed Sep 11, 2023
1 parent 8edcfd9 commit 7d23437
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 32 deletions.
17 changes: 17 additions & 0 deletions docs/deprecated.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
27 changes: 14 additions & 13 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
*/
Expand All @@ -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.
Expand Down Expand Up @@ -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}.`;
Expand Down Expand Up @@ -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(' ');
}

Expand Down
8 changes: 4 additions & 4 deletions lib/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 [];
}
Expand All @@ -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
Expand Down
18 changes: 9 additions & 9 deletions tests/command.argumentVariations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const commander = require('../');
// and not exhaustively testing all methods elsewhere.

test.each(getSingleArgCases('<explicit-required>'))('when add "<arg>" using %s then argument required', (methodName, cmd) => {
const argument = cmd._args[0];
const argument = cmd.registeredArguments[0];
const expectedShape = {
_name: 'explicit-required',
required: true,
Expand All @@ -15,7 +15,7 @@ test.each(getSingleArgCases('<explicit-required>'))('when add "<arg>" 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,
Expand All @@ -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,
Expand All @@ -37,7 +37,7 @@ test.each(getSingleArgCases('[optional]'))('when add "[arg]" using %s then argum
});

test.each(getSingleArgCases('<explicit-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: 'explicit-required',
required: true,
Expand All @@ -48,7 +48,7 @@ test.each(getSingleArgCases('<explicit-required...>'))('when add "<arg...>" 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,
Expand All @@ -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,
Expand All @@ -79,8 +79,8 @@ function getSingleArgCases(arg) {
}

test.each(getMultipleArgCases('<first>', '[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) {
Expand All @@ -99,6 +99,6 @@ test('when add arguments using multiple methods then all added', () => {
cmd.arguments('<arg3> <arg4>');
cmd.argument('<arg5>');
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']);
});
12 changes: 6 additions & 6 deletions tests/command.createArgument.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@ class MyCommand extends commander.Command {
test('when override createArgument then used for argument()', () => {
const program = new MyCommand();
program.argument('<file>');
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('<file>');
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 <file>');
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');
});
1 change: 1 addition & 0 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions typings/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ expectType<string[]>(program.args);
expectType<any[]>(program.processedArgs);
expectType<readonly commander.Command[]>(program.commands);
expectType<readonly commander.Option[]>(program.options);
expectType<readonly commander.Argument[]>(program.registeredArguments);
expectType<commander.Command | null>(program.parent);

// version
Expand Down

0 comments on commit 7d23437

Please sign in to comment.