diff --git a/lib/internal/util/parse_args/parse_args.js b/lib/internal/util/parse_args/parse_args.js index e8751c6e2225..e1a205f2c280 100644 --- a/lib/internal/util/parse_args/parse_args.js +++ b/lib/internal/util/parse_args/parse_args.js @@ -192,11 +192,22 @@ function argsToTokens(args, options) { while (remainingArgs.length > 0) { const arg = ArrayPrototypeShift(remainingArgs); const nextArg = remainingArgs[0]; - if (groupCount > 0) + const isFromGroup = groupCount > 0; + if (isFromGroup) groupCount--; else index++; + if (isFromGroup && arg === '--') { + // A '-' inside a short option group, e.g. '-a-b'. It was expanded to + // '--' but is not the options terminator, so report it as an option. + ArrayPrototypePush( + tokens, + { kind: 'option', name: findLongOptionForShort('-', options), + rawName: '-', index, value: undefined, inlineValue: undefined }); + continue; + } + // Check if `arg` is an options terminator. // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html if (arg === '--') { diff --git a/test/parallel/test-parse-args.mjs b/test/parallel/test-parse-args.mjs index 706a3b24f1d0..4180cd339ea8 100644 --- a/test/parallel/test-parse-args.mjs +++ b/test/parallel/test-parse-args.mjs @@ -336,6 +336,24 @@ test('unknown option -r in short option group -bar', () => { }); }); +test("strict: '-' inside short option group -a-b is an unknown option", () => { + const args = ['-a-b']; + const options = { a: { type: 'boolean' }, b: { type: 'boolean' } }; + assert.throws(() => { parseArgs({ args, options }); }, { + code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION', + message: /Unknown option '-'/, + }); +}); + +test("strict: trailing '-' in short option group -a- is an unknown option", () => { + const args = ['-a-']; + const options = { a: { type: 'boolean' } }; + assert.throws(() => { parseArgs({ args, options }); }, { + code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION', + message: /Unknown option '-'/, + }); +}); + test('unknown option with explicit value', () => { const args = ['--foo', '--bar=baz']; const options = { foo: { type: 'boolean' } }; @@ -748,6 +766,26 @@ test('tokens: strict:true complex option group with value after space', () => { assert.deepStrictEqual(tokens, expectedTokens); }); +test("tokens: '-' inside short option group is not an option terminator", () => { + // Also positional to check index correct afterwards. + const args = ['-a-b', 'pos']; + const options = { + alpha: { short: 'a', type: 'boolean' }, + beta: { short: 'b', type: 'boolean' }, + }; + const expectedTokens = [ + { kind: 'option', name: 'alpha', rawName: '-a', + index: 0, value: undefined, inlineValue: undefined }, + { kind: 'option', name: '-', rawName: '-', + index: 0, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'beta', rawName: '-b', + index: 0, value: undefined, inlineValue: undefined }, + { kind: 'positional', index: 1, value: 'pos' }, + ]; + const { tokens } = parseArgs({ strict: false, allowPositionals: true, args, options, tokens: true }); + assert.deepStrictEqual(tokens, expectedTokens); +}); + test('tokens: strict:true complex option group with inline value', () => { // Also positional to check index correct afterwards. const args = ['-abc', 'pos'];