diff --git a/lib/completion.js b/lib/completion.js index b48bba507..3c5e81df9 100644 --- a/lib/completion.js +++ b/lib/completion.js @@ -76,21 +76,30 @@ 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) { - const startsByTwoDashes = s => /^--/.test(s) - const isShortOption = s => /^[^0-9]$/.test(s) - const dashes = !startsByTwoDashes(current) && isShortOption(key) ? '-' : '--' - if (!zshShell) { - completions.push(dashes + key) - } else { - const desc = descs[key] || '' - completions.push(dashes + `${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) { + const startsByTwoDashes = s => /^--/.test(s) + const isShortOption = s => /^[^0-9]$/.test(s) + const dashes = !startsByTwoDashes(current) && isShortOption(key) ? '-' : '--' + if (!zshShell) { + completions.push(dashes + key) + } else { + const desc = descs[key] || '' + completions.push(dashes + `${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 f55380480..fd777aecf 100644 --- a/test/completion.js +++ b/test/completion.js @@ -94,6 +94,55 @@ describe('Completion', () => { r.logs.should.not.include('-1') }) + 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'])