Skip to content
  • Rate limit · GitHub

    Whoa there!

    You have triggered an abuse detection mechanism.

    Please wait a few minutes before you try again;
    in some cases this may take up to an hour.

  • Notifications You must be signed in to change notification settings
  • Fork 1.7k
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check command-arguments even without action handler #1502

Rate limit · GitHub

Whoa there!

You have triggered an abuse detection mechanism.

Please wait a few minutes before you try again;
in some cases this may take up to an hour.

Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Check command-arguments even without action handler
Rate limit · GitHub

Whoa there!

You have triggered an abuse detection mechanism.

Please wait a few minutes before you try again;
in some cases this may take up to an hour.

shadowspawn committed Apr 10, 2021
commit b5737c21f676009250f1c8da5578a9df03a2c465
17 changes: 11 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -1559,11 +1559,8 @@ class Command extends EventEmitter {
this.unknownOption(parsed.unknown[0]);
}
};

const commandEvent = `command:${this.name()}`;
if (this._actionHandler) {
checkForUnknownOptions();
// Check expected arguments and collect variadic together.
// Check for missing or extra arguments, and refactor the arguments for passing to action handler.
const getCheckedArguments = () => {
const args = this.args.slice();
this._args.forEach((arg, i) => {
if (arg.required && args[i] == null) {
@@ -1576,11 +1573,17 @@ class Command extends EventEmitter {
if (args.length > this._args.length) {
this._excessArguments(args);
}
return args;
};

this._actionHandler(args);
const commandEvent = `command:${this.name()}`;
if (this._actionHandler) {
checkForUnknownOptions();
this._actionHandler(getCheckedArguments());
if (this.parent) this.parent.emit(commandEvent, operands, unknown); // legacy
} else if (this.parent && this.parent.listenerCount(commandEvent)) {
checkForUnknownOptions();
getCheckedArguments();
this.parent.emit(commandEvent, operands, unknown); // legacy
} else if (operands.length) {
if (this._findCommand('*')) { // legacy default command
@@ -1592,12 +1595,14 @@ class Command extends EventEmitter {
this.unknownCommand();
} else {
checkForUnknownOptions();
getCheckedArguments();
}
} else if (this.commands.length) {
// This command has subcommands and nothing hooked up at this level, so display help.
this.help({ error: true });
} else {
checkForUnknownOptions();
getCheckedArguments();
// fall through for caller to handle after calling .parse()
}
}
80 changes: 32 additions & 48 deletions tests/command.allowExcessArguments.test.js
Original file line number Diff line number Diff line change
@@ -2,27 +2,21 @@ const commander = require('../');

// Not testing output, just testing whether an error is detected.

describe('allowUnknownOption', () => {
// Optional. Use internal knowledge to suppress output to keep test output clean.
let writeErrorSpy;

beforeAll(() => {
writeErrorSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
});

afterEach(() => {
writeErrorSpy.mockClear();
});

afterAll(() => {
writeErrorSpy.mockRestore();
});
describe.each([true, false])('allowExcessArguments when action handler: %s', (hasActionHandler) => {
function configureCommand(cmd) {
cmd
.exitOverride()
.configureOutput({
writeErr: () => {}
});
if (hasActionHandler) {
cmd.action(() => {});
}
}

test('when specify excess program argument then no error by default', () => {
const program = new commander.Command();
program
.exitOverride()
.action(() => {});
configureCommand(program);

expect(() => {
program.parse(['excess'], { from: 'user' });
@@ -31,10 +25,9 @@ describe('allowUnknownOption', () => {

test('when specify excess program argument and allowExcessArguments(false) then error', () => {
const program = new commander.Command();
configureCommand(program);
program
.exitOverride()
.allowExcessArguments(false)
.action(() => {});
.allowExcessArguments(false);

expect(() => {
program.parse(['excess'], { from: 'user' });
@@ -43,10 +36,9 @@ describe('allowUnknownOption', () => {

test('when specify excess program argument and allowExcessArguments() then no error', () => {
const program = new commander.Command();
configureCommand(program);
program
.exitOverride()
.allowExcessArguments()
.action(() => {});
.allowExcessArguments();

expect(() => {
program.parse(['excess'], { from: 'user' });
@@ -55,10 +47,9 @@ describe('allowUnknownOption', () => {

test('when specify excess program argument and allowExcessArguments(true) then no error', () => {
const program = new commander.Command();
configureCommand(program);
program
.exitOverride()
.allowExcessArguments(true)
.action(() => {});
.allowExcessArguments(true);

expect(() => {
program.parse(['excess'], { from: 'user' });
@@ -67,10 +58,9 @@ describe('allowUnknownOption', () => {

test('when specify excess command argument then no error (by default)', () => {
const program = new commander.Command();
program
.exitOverride()
.command('sub')
.action(() => { });
const sub = program
.command('sub');
configureCommand(sub);

expect(() => {
program.parse(['sub', 'excess'], { from: 'user' });
@@ -79,12 +69,10 @@ describe('allowUnknownOption', () => {

test('when specify excess command argument and allowExcessArguments(false) then error', () => {
const program = new commander.Command();
program
.exitOverride()
const sub = program
.command('sub')
.allowUnknownOption()
.allowExcessArguments(false)
.action(() => { });
.allowExcessArguments(false);
configureCommand(sub);

expect(() => {
program.parse(['sub', 'excess'], { from: 'user' });
@@ -93,11 +81,10 @@ describe('allowUnknownOption', () => {

test('when specify expected arg and allowExcessArguments(false) then no error', () => {
const program = new commander.Command();
configureCommand(program);
program
.argument('<file>')
.exitOverride()
.allowExcessArguments(false)
.action(() => {});
.allowExcessArguments(false);

expect(() => {
program.parse(['file'], { from: 'user' });
@@ -106,11 +93,10 @@ describe('allowUnknownOption', () => {

test('when specify excess after <arg> and allowExcessArguments(false) then error', () => {
const program = new commander.Command();
configureCommand(program);
program
.argument('<file>')
.exitOverride()
.allowExcessArguments(false)
.action(() => {});
.allowExcessArguments(false);

expect(() => {
program.parse(['file', 'excess'], { from: 'user' });
@@ -119,11 +105,10 @@ describe('allowUnknownOption', () => {

test('when specify excess after [arg] and allowExcessArguments(false) then error', () => {
const program = new commander.Command();
configureCommand(program);
program
.argument('[file]')
.exitOverride()
.allowExcessArguments(false)
.action(() => {});
.allowExcessArguments(false);

expect(() => {
program.parse(['file', 'excess'], { from: 'user' });
@@ -132,11 +117,10 @@ describe('allowUnknownOption', () => {

test('when specify args for [args...] and allowExcessArguments(false) then no error', () => {
const program = new commander.Command();
configureCommand(program);
program
.argument('[files...]')
.exitOverride()
.allowExcessArguments(false)
.action(() => {});
.allowExcessArguments(false);

expect(() => {
program.parse(['file1', 'file2', 'file3'], { from: 'user' });
17 changes: 17 additions & 0 deletions tests/command.exitOverride.test.js
Original file line number Diff line number Diff line change
@@ -120,6 +120,23 @@ describe('.exitOverride and error details', () => {
expectCommanderError(caughtErr, 1, 'commander.missingArgument', "error: missing required argument 'arg-name'");
});

test('when specify program without required argument and no action handler then throw CommanderError', () => {
const program = new commander.Command();
program
.exitOverride()
.argument('<arg-name>');

let caughtErr;
try {
program.parse(['node', 'test']);
} catch (err) {
caughtErr = err;
}

expect(stderrSpy).toHaveBeenCalled();
expectCommanderError(caughtErr, 1, 'commander.missingArgument', "error: missing required argument 'arg-name'");
});

test('when specify excess argument then throw CommanderError', () => {
const program = new commander.Command();
program