Skip to content

Commit bf205d1

Browse files
authored
Check for conflicts in parent commands (#1711)
* Rename test file * Add test for confilcts in parent command of called command * Check for conflicts up hierarchy of commands * Suppress help output from test * Remove bogus parameter
1 parent 7d7a674 commit bf205d1

2 files changed

Lines changed: 73 additions & 4 deletions

File tree

lib/command.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
946946

947947
// Not checking for help first. Unlikely to have mandatory and executable, and can't robustly test for help flags in external command.
948948
this._checkForMissingMandatoryOptions();
949+
this._checkForConflictingOptions();
949950

950951
// executableFile and executableDir might be full path, or just a name
951952
let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
@@ -1294,7 +1295,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
12941295

12951296
/**
12961297
* Display an error message if a mandatory option does not have a value.
1297-
* Lazy calling after checking for help flags from leaf subcommand.
1298+
* Called after checking for help flags in leaf subcommand.
12981299
*
12991300
* @api private
13001301
*/
@@ -1311,11 +1312,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
13111312
}
13121313

13131314
/**
1314-
* Display an error message if conflicting options are used together.
1315+
* Display an error message if conflicting options are used together in this.
13151316
*
13161317
* @api private
13171318
*/
1318-
_checkForConflictingOptions() {
1319+
_checkForConflictingLocalOptions() {
13191320
const definedNonDefaultOptions = this.options.filter(
13201321
(option) => {
13211322
const optionKey = option.attributeName();
@@ -1340,6 +1341,19 @@ Expecting one of '${allowedValues.join("', '")}'`);
13401341
});
13411342
}
13421343

1344+
/**
1345+
* Display an error message if conflicting options are used together.
1346+
* Called after checking for help flags in leaf subcommand.
1347+
*
1348+
* @api private
1349+
*/
1350+
_checkForConflictingOptions() {
1351+
// Walk up hierarchy so can call in subcommand after checking for displaying help.
1352+
for (let cmd = this; cmd; cmd = cmd.parent) {
1353+
cmd._checkForConflictingLocalOptions();
1354+
}
1355+
}
1356+
13431357
/**
13441358
* Parse options from `argv` removing known options,
13451359
* and return argv split into operands and unknown arguments.
Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const path = require('path');
12
const commander = require('../');
23

34
describe('command with conflicting options', () => {
@@ -7,7 +8,8 @@ describe('command with conflicting options', () => {
78
program
89
.exitOverride()
910
.configureOutput({
10-
writeErr: () => {}
11+
writeErr: () => {},
12+
writeOut: () => {}
1113
})
1214
.command('foo')
1315
.addOption(new commander.Option('-s, --silent', "Don't print anything").env('SILENT'))
@@ -253,4 +255,57 @@ describe('command with conflicting options', () => {
253255
program.parse('node test.js bar --a --b'.split(' '));
254256
}).toThrow("error: option '--b' cannot be used with option '--a'");
255257
});
258+
259+
test('when conflict on program calling action subcommand then throw conflict', () => {
260+
const { program } = makeProgram();
261+
let exception;
262+
263+
program
264+
.addOption(new commander.Option('--black'))
265+
.addOption(new commander.Option('--white').conflicts('black'));
266+
267+
try {
268+
program.parse('--white --black foo'.split(' '), { from: 'user' });
269+
} catch (err) {
270+
exception = err;
271+
}
272+
expect(exception).not.toBeUndefined();
273+
expect(exception.code).toBe('commander.conflictingOption');
274+
});
275+
276+
test('when conflict on program calling action subcommand with help then show help', () => {
277+
const { program } = makeProgram();
278+
let exception;
279+
280+
program
281+
.addOption(new commander.Option('--black'))
282+
.addOption(new commander.Option('--white').conflicts('black'));
283+
284+
try {
285+
program.parse('--white --black foo --help'.split(' '), { from: 'user' });
286+
} catch (err) {
287+
exception = err;
288+
}
289+
expect(exception).not.toBeUndefined();
290+
expect(exception.code).toBe('commander.helpDisplayed');
291+
});
292+
293+
test('when conflict on program calling external subcommand then throw conflict', () => {
294+
const { program } = makeProgram();
295+
let exception;
296+
297+
program
298+
.addOption(new commander.Option('--black'))
299+
.addOption(new commander.Option('--white').conflicts('black'));
300+
const pm = path.join(__dirname, './fixtures/pm');
301+
program.command('ext', 'external command', { executableFile: pm });
302+
303+
try {
304+
program.parse('--white --black ext'.split(' '), { from: 'user' });
305+
} catch (err) {
306+
exception = err;
307+
}
308+
expect(exception).not.toBeUndefined();
309+
expect(exception.code).toBe('commander.conflictingOption');
310+
});
256311
});

0 commit comments

Comments
 (0)