diff --git a/lib/completion.js b/lib/completion.js index 8c2363e3d..4f041ce77 100644 --- a/lib/completion.js +++ b/lib/completion.js @@ -76,18 +76,27 @@ module.exports = function completion (yargs, usage, command) { if (current.match(/^-/) || (current === '' && completions.length === 0)) { const descs = usage.getDescriptions() - Object.keys(yargs.getOptions().key).forEach((key) => { + const options = yargs.getOptions() + Object.keys(options.key).forEach((key) => { + const negable = !!options.configuration['boolean-negation'] && options.boolean.includes(key) // If the key and its aliases aren't in 'args', add the key to 'completions' - const keyAndAliases = [key].concat(aliases[key] || []) - const notInArgs = keyAndAliases.every(val => args.indexOf(`--${val}`) === -1) - if (notInArgs) { - if (!zshShell) { - completions.push(`--${key}`) - } else { - const desc = descs[key] || '' - completions.push(`--${key.replace(/:/g, '\\:')}:${desc.replace('__yargsString__:', '')}`) + let keyAndAliases = [key].concat(aliases[key] || []) + if (negable) keyAndAliases = keyAndAliases.concat(keyAndAliases.map(key => `no-${key}`)) + + function completeOptionKey (key) { + const notInArgs = keyAndAliases.every(val => args.indexOf(`--${val}`) === -1) + if (notInArgs) { + if (!zshShell) { + completions.push(`--${key}`) + } else { + const desc = descs[key] || '' + completions.push(`--${key.replace(/:/g, '\\:')}:${desc.replace('__yargsString__:', '')}`) + } } } + + completeOptionKey(key) + if (negable && !!options.default[key]) completeOptionKey(`no-${key}`) }) } diff --git a/test/completion.js b/test/completion.js index a2036af83..79ab6b7a4 100644 --- a/test/completion.js +++ b/test/completion.js @@ -58,6 +58,55 @@ describe('Completion', () => { r.logs.should.not.include('--foo') }) + it('completes with no- prefix flags defaulting to true when boolean-negation is set', () => { + const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '']) + .options({ + foo: { describe: 'foo flag', type: 'boolean', default: true }, + bar: { describe: 'bar flag', type: 'boolean' } + }) + .parserConfiguration({ 'boolean-negation': true }) + .argv + ) + + r.logs.should.include('--no-foo') + r.logs.should.include('--foo') + r.logs.should.not.include('--no-bar') + r.logs.should.include('--bar') + }) + + it('avoids repeating flags whose negated counterparts are already included', () => { + const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '--no-foo', '--no-bar', '']) + .options({ + foo: { describe: 'foo flag', type: 'boolean', default: true }, + bar: { describe: 'bar flag', type: 'boolean' }, + baz: { describe: 'bar flag', type: 'boolean' } + }) + .parserConfiguration({ 'boolean-negation': true }) + .argv + ) + + r.logs.should.not.include('--no-foo') + r.logs.should.not.include('--foo') + r.logs.should.not.include('--no-bar') + r.logs.should.not.include('--bar') + r.logs.should.include('--baz') + }) + + it('ignores no- prefix flags when boolean-negation is not set', () => { + const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '--no-bar', '']) + .options({ + foo: { describe: 'foo flag', type: 'boolean', default: true }, + bar: { describe: 'bar flag', type: 'boolean' } + }) + .argv + ) + + r.logs.should.not.include('--no-foo') + r.logs.should.include('--foo') + r.logs.should.not.include('--no-bar') + r.logs.should.include('--bar') + }) + it('completes options for the correct command', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', 'cmd2', '--o'])