diff --git a/lib/completion.ts b/lib/completion.ts index 60cc486e0..4b850a2d9 100644 --- a/lib/completion.ts +++ b/lib/completion.ts @@ -153,21 +153,33 @@ export class Completion implements CompletionInstance { let previousArg = args[args.length - 1]; let filter = ''; // use second to last argument if the last one is not an option starting with -- - if (!previousArg.startsWith('--') && args.length > 1) { + if (!previousArg.startsWith('-') && args.length > 1) { filter = previousArg; // use last arg as filter for choices previousArg = args[args.length - 2]; } - if (!previousArg.startsWith('--')) return; // still no valid arg, abort - const previousArgKey = previousArg.replace(/-/g, ''); + if (!previousArg.startsWith('-')) return; // still no valid arg, abort + const previousArgKey = previousArg.replace(/^-+/, ''); const options = this.yargs.getOptions(); - if ( - Object.keys(options.key).some(key => key === previousArgKey) && - Array.isArray(options.choices[previousArgKey]) - ) { - return options.choices[previousArgKey].filter( - choice => !filter || choice.startsWith(filter) - ); + + const possibleAliases = [ + previousArgKey, + ...(this.yargs.getAliases()[previousArgKey] || []), + ]; + let choices: string[] | undefined; + // Find choices across all possible aliases + for (const possibleAlias of possibleAliases) { + if ( + Object.prototype.hasOwnProperty.call(options.key, possibleAlias) && + Array.isArray(options.choices[possibleAlias]) + ) { + choices = options.choices[possibleAlias]; + break; + } + } + + if (choices) { + return choices.filter(choice => !filter || choice.startsWith(filter)); } } diff --git a/test/completion.cjs b/test/completion.cjs index df3e07072..9cb45a906 100644 --- a/test/completion.cjs +++ b/test/completion.cjs @@ -373,6 +373,86 @@ describe('Completion', () => { r.logs.should.not.include('banana'); r.logs.should.not.include('pear'); }); + + it('completes choices if previous option or one of its aliases requires a choice', () => { + process.env.SHELL = '/bin/bash'; + const r = checkUsage(() => { + return yargs([ + './completion', + '--get-yargs-completions', + './completion', + '-f', + ]) + .options({ + fruit: { + alias: ['f', 'not-a-vegetable'], + describe: 'fruit option', + choices: ['apple', 'banana', 'pear'], + }, + amount: {describe: 'amount', type: 'number'}, + }) + .completion('completion', false).argv; + }); + + r.logs.should.have.length(3); + r.logs.should.include('apple'); + r.logs.should.include('banana'); + r.logs.should.include('pear'); + }); + + it('completes choices if previous option or one of its aliases requires a choice and space has been entered', () => { + process.env.SHELL = '/bin/bash'; + const r = checkUsage(() => { + return yargs([ + './completion', + '--get-yargs-completions', + './completion', + '--not-a-vegetable', + '', + ]) + .options({ + fruit: { + alias: ['f', 'not-a-vegetable'], + describe: 'fruit option', + choices: ['apple', 'banana', 'pear'], + }, + amount: {describe: 'amount', type: 'number'}, + }) + .completion('completion', false).argv; + }); + + r.logs.should.have.length(3); + r.logs.should.include('apple'); + r.logs.should.include('banana'); + r.logs.should.include('pear'); + }); + + it('completes choices if previous option or one of its aliases requires a choice and a partial choice has been entered', () => { + process.env.SHELL = '/bin/bash'; + const r = checkUsage(() => { + return yargs([ + './completion', + '--get-yargs-completions', + './completion', + '-f', + 'ap', + ]) + .options({ + fruit: { + alias: 'f', + describe: 'fruit option', + choices: ['apple', 'banana', 'pear'], + }, + amount: {describe: 'amount', type: 'number'}, + }) + .completion('completion', false).argv; + }); + + r.logs.should.have.length(1); + r.logs.should.include('apple'); + r.logs.should.not.include('banana'); + r.logs.should.not.include('pear'); + }); }); describe('generateCompletionScript()', () => {