diff --git a/lib/command.js b/lib/command.js index 0bc48ed20..0c78b94b9 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1598,6 +1598,14 @@ Expecting one of '${allowedValues.join("', '")}'`); * @api private */ _conflictingOption(option, conflictingOption) { + const getNegatedOption = (option) => { + const optionKey = option.attributeName(); + const negatedLongFlag = option.long && `--no-${optionKey}`; + const negatedOption = negatedLongFlag && this._findOption(negatedLongFlag); + + return negatedOption; + }; + const getErrorMessage = (option) => { const optionKey = option.attributeName(); const source = this.getOptionValueSource(optionKey); @@ -1607,7 +1615,10 @@ Expecting one of '${allowedValues.join("', '")}'`); return `option '${option.flags}'`; }; - const message = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`; + const originOption = getNegatedOption(option) || option; + const originConflictingOption = getNegatedOption(conflictingOption) || conflictingOption; + + const message = `error: ${getErrorMessage(originOption)} cannot be used with ${getErrorMessage(originConflictingOption)}`; this.error(message, { code: 'commander.conflictingOption' }); } diff --git a/tests/command.conflicts.test.js b/tests/command.conflicts.test.js index a1e55d103..e6ea10d63 100644 --- a/tests/command.conflicts.test.js +++ b/tests/command.conflicts.test.js @@ -24,6 +24,7 @@ describe('command with conflicting options', () => { beforeEach(() => { delete process.env.SILENT; delete process.env.JSON; + delete process.env.NO_COLOR; }); test('should call action if there are no explicit conflicting options set', () => { @@ -92,4 +93,34 @@ describe('command with conflicting options', () => { expect(actionMock).toHaveBeenCalledTimes(1); expect(actionMock).toHaveBeenCalledWith({ debug: true, silent: true }, expect.any(Object)); }); + + test('should report conflict on negated option flag', () => { + const { program } = makeProgram(); + + program + .command('bar') + .addOption(new commander.Option('--red').conflicts(['color'])) + .addOption(new commander.Option('--color')) + .addOption(new commander.Option('-N, --no-color')); + + expect(() => { + program.parse('node test.js bar --red -N'.split(' ')); + }).toThrow("error: option '--red' cannot be used with option '-N, --no-color'"); + }); + + test('should report conflict on negated option env variable', () => { + const { program } = makeProgram(); + + process.env.NO_COLOR = true; + + program + .command('bar') + .addOption(new commander.Option('--red').conflicts(['color'])) + .addOption(new commander.Option('--color')) + .addOption(new commander.Option('-N, --no-color').env('NO_COLOR')); + + expect(() => { + program.parse('node test.js bar --red'.split(' ')); + }).toThrow("error: option '--red' cannot be used with environment variable 'NO_COLOR'"); + }); });