Skip to content

Commit 1d3dd47

Browse files
shadowspawnaweebit
andauthored
Add check for overlapping command names or aliases (#2059)
Co-authored-by: aweebit <aweebit64@gmail.com>
1 parent b96af40 commit 1d3dd47

File tree

2 files changed

+83
-4
lines changed

2 files changed

+83
-4
lines changed

lib/command.js

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class Command extends EventEmitter {
165165
cmd._hidden = !!(opts.noHelp || opts.hidden); // noHelp is deprecated old name for hidden
166166
cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor
167167
if (args) cmd.arguments(args);
168-
this.commands.push(cmd);
168+
this._registerCommand(cmd);
169169
cmd.parent = this;
170170
cmd.copyInheritedSettings(this);
171171

@@ -282,7 +282,7 @@ class Command extends EventEmitter {
282282
if (opts.isDefault) this._defaultCommandName = cmd._name;
283283
if (opts.noHelp || opts.hidden) cmd._hidden = true; // modifying passed command due to existing implementation
284284

285-
this.commands.push(cmd);
285+
this._registerCommand(cmd);
286286
cmd.parent = this;
287287
cmd._checkForBrokenPassThrough();
288288

@@ -535,10 +535,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
535535
throw err;
536536
}
537537
}
538+
538539
/**
539540
* Check for option flag conflicts.
540-
* Register option if no conflicts found.
541-
* Throw otherwise.
541+
* Register option if no conflicts found, or throw on conflict.
542542
*
543543
* @param {Option} option
544544
* @api private
@@ -552,9 +552,33 @@ Expecting one of '${allowedValues.join("', '")}'`);
552552
throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'
553553
- already used by option '${matchingOption.flags}'`);
554554
}
555+
555556
this.options.push(option);
556557
}
557558

559+
/**
560+
* Check for command name and alias conflicts with existing commands.
561+
* Register command if no conflicts found, or throw on conflict.
562+
*
563+
* @param {Command} command
564+
* @api private
565+
*/
566+
567+
_registerCommand(command) {
568+
const knownBy = (cmd) => {
569+
return [cmd.name()].concat(cmd.aliases());
570+
};
571+
572+
const alreadyUsed = knownBy(command).find((name) => this._findCommand(name));
573+
if (alreadyUsed) {
574+
const existingCmd = knownBy(this._findCommand(alreadyUsed)).join('|');
575+
const newCmd = knownBy(command).join('|');
576+
throw new Error(`cannot add command '${newCmd}' as already have command '${existingCmd}'`);
577+
}
578+
579+
this.commands.push(command);
580+
}
581+
558582
/**
559583
* Add an option.
560584
*
@@ -1900,6 +1924,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
19001924
}
19011925

19021926
if (alias === command._name) throw new Error('Command alias can\'t be the same as its name');
1927+
const matchingCommand = this.parent?._findCommand(alias);
1928+
if (matchingCommand) {
1929+
// c.f. _registerCommand
1930+
const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join('|');
1931+
throw new Error(`cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`);
1932+
}
19031933

19041934
command._aliases.push(alias);
19051935
return this;

tests/command.registerClash.test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const { Command } = require('../');
2+
3+
test('when command name conflicts with existing name then throw', () => {
4+
expect(() => {
5+
const program = new Command();
6+
program.command('one');
7+
program.command('one');
8+
}).toThrow('cannot add command');
9+
});
10+
11+
test('when command name conflicts with existing alias then throw', () => {
12+
expect(() => {
13+
const program = new Command();
14+
program.command('one').alias('1');
15+
program.command('1');
16+
}).toThrow('cannot add command');
17+
});
18+
19+
test('when command alias conflicts with existing name then throw', () => {
20+
expect(() => {
21+
const program = new Command();
22+
program.command('one');
23+
program.command('1').alias('one');
24+
}).toThrow('cannot add alias');
25+
});
26+
27+
test('when command alias conflicts with existing alias then throw', () => {
28+
expect(() => {
29+
const program = new Command();
30+
program.command('one').alias('1');
31+
program.command('unity').alias('1');
32+
}).toThrow('cannot add alias');
33+
});
34+
35+
test('when .addCommand name conflicts with existing name then throw', () => {
36+
expect(() => {
37+
const program = new Command();
38+
program.command('one');
39+
program.addCommand(new Command('one'));
40+
}).toThrow('cannot add command');
41+
});
42+
43+
test('when .addCommand alias conflicts with existing name then throw', () => {
44+
expect(() => {
45+
const program = new Command();
46+
program.command('one');
47+
program.addCommand(new Command('unity').alias('one'));
48+
}).toThrow('cannot add command');
49+
});

0 commit comments

Comments
 (0)