1import '../common/index.mjs'; 2import assert from 'node:assert'; 3import { test } from 'node:test'; 4import { parseArgs } from 'node:util'; 5 6test('when short option used as flag then stored as flag', () => { 7 const args = ['-f']; 8 const expected = { values: { __proto__: null, f: true }, positionals: [] }; 9 const result = parseArgs({ strict: false, args }); 10 assert.deepStrictEqual(result, expected); 11}); 12 13test('when short option used as flag before positional then stored as flag and positional (and not value)', () => { 14 const args = ['-f', 'bar']; 15 const expected = { values: { __proto__: null, f: true }, positionals: [ 'bar' ] }; 16 const result = parseArgs({ strict: false, args }); 17 assert.deepStrictEqual(result, expected); 18}); 19 20test('when short option `type: "string"` used with value then stored as value', () => { 21 const args = ['-f', 'bar']; 22 const options = { f: { type: 'string' } }; 23 const expected = { values: { __proto__: null, f: 'bar' }, positionals: [] }; 24 const result = parseArgs({ args, options }); 25 assert.deepStrictEqual(result, expected); 26}); 27 28test('when short option listed in short used as flag then long option stored as flag', () => { 29 const args = ['-f']; 30 const options = { foo: { short: 'f', type: 'boolean' } }; 31 const expected = { values: { __proto__: null, foo: true }, positionals: [] }; 32 const result = parseArgs({ args, options }); 33 assert.deepStrictEqual(result, expected); 34}); 35 36test('when short option listed in short and long listed in `type: "string"` and ' + 37 'used with value then long option stored as value', () => { 38 const args = ['-f', 'bar']; 39 const options = { foo: { short: 'f', type: 'string' } }; 40 const expected = { values: { __proto__: null, foo: 'bar' }, positionals: [] }; 41 const result = parseArgs({ args, options }); 42 assert.deepStrictEqual(result, expected); 43}); 44 45test('when short option `type: "string"` used without value then stored as flag', () => { 46 const args = ['-f']; 47 const options = { f: { type: 'string' } }; 48 const expected = { values: { __proto__: null, f: true }, positionals: [] }; 49 const result = parseArgs({ strict: false, args, options }); 50 assert.deepStrictEqual(result, expected); 51}); 52 53test('short option group behaves like multiple short options', () => { 54 const args = ['-rf']; 55 const options = { }; 56 const expected = { values: { __proto__: null, r: true, f: true }, positionals: [] }; 57 const result = parseArgs({ strict: false, args, options }); 58 assert.deepStrictEqual(result, expected); 59}); 60 61test('short option group does not consume subsequent positional', () => { 62 const args = ['-rf', 'foo']; 63 const options = { }; 64 const expected = { values: { __proto__: null, r: true, f: true }, positionals: ['foo'] }; 65 const result = parseArgs({ strict: false, args, options }); 66 assert.deepStrictEqual(result, expected); 67}); 68 69// See: Guideline 5 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html 70test('if terminal of short-option group configured `type: "string"`, subsequent positional is stored', () => { 71 const args = ['-rvf', 'foo']; 72 const options = { f: { type: 'string' } }; 73 const expected = { values: { __proto__: null, r: true, v: true, f: 'foo' }, positionals: [] }; 74 const result = parseArgs({ strict: false, args, options }); 75 assert.deepStrictEqual(result, expected); 76}); 77 78test('handles short-option groups in conjunction with long-options', () => { 79 const args = ['-rf', '--foo', 'foo']; 80 const options = { foo: { type: 'string' } }; 81 const expected = { values: { __proto__: null, r: true, f: true, foo: 'foo' }, positionals: [] }; 82 const result = parseArgs({ strict: false, args, options }); 83 assert.deepStrictEqual(result, expected); 84}); 85 86test('handles short-option groups with "short" alias configured', () => { 87 const args = ['-rf']; 88 const options = { remove: { short: 'r', type: 'boolean' } }; 89 const expected = { values: { __proto__: null, remove: true, f: true }, positionals: [] }; 90 const result = parseArgs({ strict: false, args, options }); 91 assert.deepStrictEqual(result, expected); 92}); 93 94test('handles short-option followed by its value', () => { 95 const args = ['-fFILE']; 96 const options = { foo: { short: 'f', type: 'string' } }; 97 const expected = { values: { __proto__: null, foo: 'FILE' }, positionals: [] }; 98 const result = parseArgs({ strict: false, args, options }); 99 assert.deepStrictEqual(result, expected); 100}); 101 102test('Everything after a bare `--` is considered a positional argument', () => { 103 const args = ['--', 'barepositionals', 'mopositionals']; 104 const expected = { values: { __proto__: null }, positionals: ['barepositionals', 'mopositionals'] }; 105 const result = parseArgs({ allowPositionals: true, args }); 106 assert.deepStrictEqual(result, expected, Error('testing bare positionals')); 107}); 108 109test('args are true', () => { 110 const args = ['--foo', '--bar']; 111 const expected = { values: { __proto__: null, foo: true, bar: true }, positionals: [] }; 112 const result = parseArgs({ strict: false, args }); 113 assert.deepStrictEqual(result, expected, Error('args are true')); 114}); 115 116test('arg is true and positional is identified', () => { 117 const args = ['--foo=a', '--foo', 'b']; 118 const expected = { values: { __proto__: null, foo: true }, positionals: ['b'] }; 119 const result = parseArgs({ strict: false, args }); 120 assert.deepStrictEqual(result, expected, Error('arg is true and positional is identified')); 121}); 122 123test('args equals are passed `type: "string"`', () => { 124 const args = ['--so=wat']; 125 const options = { so: { type: 'string' } }; 126 const expected = { values: { __proto__: null, so: 'wat' }, positionals: [] }; 127 const result = parseArgs({ args, options }); 128 assert.deepStrictEqual(result, expected, Error('arg value is passed')); 129}); 130 131test('when args include single dash then result stores dash as positional', () => { 132 const args = ['-']; 133 const expected = { values: { __proto__: null }, positionals: ['-'] }; 134 const result = parseArgs({ allowPositionals: true, args }); 135 assert.deepStrictEqual(result, expected); 136}); 137 138test('zero config args equals are parsed as if `type: "string"`', () => { 139 const args = ['--so=wat']; 140 const options = { }; 141 const expected = { values: { __proto__: null, so: 'wat' }, positionals: [] }; 142 const result = parseArgs({ strict: false, args, options }); 143 assert.deepStrictEqual(result, expected, Error('arg value is passed')); 144}); 145 146test('same arg is passed twice `type: "string"` and last value is recorded', () => { 147 const args = ['--foo=a', '--foo', 'b']; 148 const options = { foo: { type: 'string' } }; 149 const expected = { values: { __proto__: null, foo: 'b' }, positionals: [] }; 150 const result = parseArgs({ args, options }); 151 assert.deepStrictEqual(result, expected, Error('last arg value is passed')); 152}); 153 154test('args equals pass string including more equals', () => { 155 const args = ['--so=wat=bing']; 156 const options = { so: { type: 'string' } }; 157 const expected = { values: { __proto__: null, so: 'wat=bing' }, positionals: [] }; 158 const result = parseArgs({ args, options }); 159 assert.deepStrictEqual(result, expected, Error('arg value is passed')); 160}); 161 162test('first arg passed for `type: "string"` and "multiple" is in array', () => { 163 const args = ['--foo=a']; 164 const options = { foo: { type: 'string', multiple: true } }; 165 const expected = { values: { __proto__: null, foo: ['a'] }, positionals: [] }; 166 const result = parseArgs({ args, options }); 167 assert.deepStrictEqual(result, expected, Error('first multiple in array')); 168}); 169 170test('args are passed `type: "string"` and "multiple"', () => { 171 const args = ['--foo=a', '--foo', 'b']; 172 const options = { 173 foo: { 174 type: 'string', 175 multiple: true, 176 }, 177 }; 178 const expected = { values: { __proto__: null, foo: ['a', 'b'] }, positionals: [] }; 179 const result = parseArgs({ args, options }); 180 assert.deepStrictEqual(result, expected, Error('both arg values are passed')); 181}); 182 183test('when expecting `multiple:true` boolean option and option used multiple times then result includes array of ' + 184 'booleans matching usage', () => { 185 const args = ['--foo', '--foo']; 186 const options = { 187 foo: { 188 type: 'boolean', 189 multiple: true, 190 }, 191 }; 192 const expected = { values: { __proto__: null, foo: [true, true] }, positionals: [] }; 193 const result = parseArgs({ args, options }); 194 assert.deepStrictEqual(result, expected); 195}); 196 197test('order of option and positional does not matter (per README)', () => { 198 const args1 = ['--foo=bar', 'baz']; 199 const args2 = ['baz', '--foo=bar']; 200 const options = { foo: { type: 'string' } }; 201 const expected = { values: { __proto__: null, foo: 'bar' }, positionals: ['baz'] }; 202 assert.deepStrictEqual( 203 parseArgs({ allowPositionals: true, args: args1, options }), 204 expected, 205 Error('option then positional') 206 ); 207 assert.deepStrictEqual( 208 parseArgs({ allowPositionals: true, args: args2, options }), 209 expected, 210 Error('positional then option') 211 ); 212}); 213 214test('correct default args when use node -p', () => { 215 const holdArgv = process.argv; 216 process.argv = [process.argv0, '--foo']; 217 const holdExecArgv = process.execArgv; 218 process.execArgv = ['-p', '0']; 219 const result = parseArgs({ strict: false }); 220 221 const expected = { values: { __proto__: null, foo: true }, 222 positionals: [] }; 223 assert.deepStrictEqual(result, expected); 224 process.argv = holdArgv; 225 process.execArgv = holdExecArgv; 226}); 227 228test('correct default args when use node --print', () => { 229 const holdArgv = process.argv; 230 process.argv = [process.argv0, '--foo']; 231 const holdExecArgv = process.execArgv; 232 process.execArgv = ['--print', '0']; 233 const result = parseArgs({ strict: false }); 234 235 const expected = { values: { __proto__: null, foo: true }, 236 positionals: [] }; 237 assert.deepStrictEqual(result, expected); 238 process.argv = holdArgv; 239 process.execArgv = holdExecArgv; 240}); 241 242test('correct default args when use node -e', () => { 243 const holdArgv = process.argv; 244 process.argv = [process.argv0, '--foo']; 245 const holdExecArgv = process.execArgv; 246 process.execArgv = ['-e', '0']; 247 const result = parseArgs({ strict: false }); 248 249 const expected = { values: { __proto__: null, foo: true }, 250 positionals: [] }; 251 assert.deepStrictEqual(result, expected); 252 process.argv = holdArgv; 253 process.execArgv = holdExecArgv; 254}); 255 256test('correct default args when use node --eval', () => { 257 const holdArgv = process.argv; 258 process.argv = [process.argv0, '--foo']; 259 const holdExecArgv = process.execArgv; 260 process.execArgv = ['--eval', '0']; 261 const result = parseArgs({ strict: false }); 262 const expected = { values: { __proto__: null, foo: true }, 263 positionals: [] }; 264 assert.deepStrictEqual(result, expected); 265 process.argv = holdArgv; 266 process.execArgv = holdExecArgv; 267}); 268 269test('correct default args when normal arguments', () => { 270 const holdArgv = process.argv; 271 process.argv = [process.argv0, 'script.js', '--foo']; 272 const holdExecArgv = process.execArgv; 273 process.execArgv = []; 274 const result = parseArgs({ strict: false }); 275 276 const expected = { values: { __proto__: null, foo: true }, 277 positionals: [] }; 278 assert.deepStrictEqual(result, expected); 279 process.argv = holdArgv; 280 process.execArgv = holdExecArgv; 281}); 282 283test('excess leading dashes on options are retained', () => { 284 // Enforce a design decision for an edge case. 285 const args = ['---triple']; 286 const options = { }; 287 const expected = { 288 values: { '__proto__': null, '-triple': true }, 289 positionals: [] 290 }; 291 const result = parseArgs({ strict: false, args, options }); 292 assert.deepStrictEqual(result, expected, Error('excess option dashes are retained')); 293}); 294 295test('positional arguments are allowed by default in strict:false', () => { 296 const args = ['foo']; 297 const options = { }; 298 const expected = { 299 values: { __proto__: null }, 300 positionals: ['foo'] 301 }; 302 const result = parseArgs({ strict: false, args, options }); 303 assert.deepStrictEqual(result, expected); 304}); 305 306test('positional arguments may be explicitly disallowed in strict:false', () => { 307 const args = ['foo']; 308 const options = { }; 309 assert.throws(() => { parseArgs({ strict: false, allowPositionals: false, args, options }); }, { 310 code: 'ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL' 311 }); 312}); 313 314// Test bad inputs 315 316test('invalid argument passed for options', () => { 317 const args = ['--so=wat']; 318 const options = 'bad value'; 319 assert.throws(() => { parseArgs({ args, options }); }, { 320 code: 'ERR_INVALID_ARG_TYPE' 321 }); 322}); 323 324test('type property missing for option then throw', () => { 325 const knownOptions = { foo: { } }; 326 assert.throws(() => { parseArgs({ options: knownOptions }); }, { 327 code: 'ERR_INVALID_ARG_TYPE' 328 }); 329}); 330 331test('boolean passed to "type" option', () => { 332 const args = ['--so=wat']; 333 const options = { foo: { type: true } }; 334 assert.throws(() => { parseArgs({ args, options }); }, { 335 code: 'ERR_INVALID_ARG_TYPE' 336 }); 337}); 338 339test('invalid union value passed to "type" option', () => { 340 const args = ['--so=wat']; 341 const options = { foo: { type: 'str' } }; 342 assert.throws(() => { parseArgs({ args, options }); }, { 343 code: 'ERR_INVALID_ARG_TYPE' 344 }); 345}); 346 347// Test strict mode 348 349test('unknown long option --bar', () => { 350 const args = ['--foo', '--bar']; 351 const options = { foo: { type: 'boolean' } }; 352 assert.throws(() => { parseArgs({ args, options }); }, { 353 code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' 354 }); 355}); 356 357test('unknown short option -b', () => { 358 const args = ['--foo', '-b']; 359 const options = { foo: { type: 'boolean' } }; 360 assert.throws(() => { parseArgs({ args, options }); }, { 361 code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' 362 }); 363}); 364 365test('unknown option -r in short option group -bar', () => { 366 const args = ['-bar']; 367 const options = { b: { type: 'boolean' }, a: { type: 'boolean' } }; 368 assert.throws(() => { parseArgs({ args, options }); }, { 369 code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' 370 }); 371}); 372 373test('unknown option with explicit value', () => { 374 const args = ['--foo', '--bar=baz']; 375 const options = { foo: { type: 'boolean' } }; 376 assert.throws(() => { parseArgs({ args, options }); }, { 377 code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' 378 }); 379}); 380 381test('unexpected positional', () => { 382 const args = ['foo']; 383 const options = { foo: { type: 'boolean' } }; 384 assert.throws(() => { parseArgs({ args, options }); }, { 385 code: 'ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL' 386 }); 387}); 388 389test('unexpected positional after --', () => { 390 const args = ['--', 'foo']; 391 const options = { foo: { type: 'boolean' } }; 392 assert.throws(() => { parseArgs({ args, options }); }, { 393 code: 'ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL' 394 }); 395}); 396 397test('-- by itself is not a positional', () => { 398 const args = ['--foo', '--']; 399 const options = { foo: { type: 'boolean' } }; 400 const result = parseArgs({ args, options }); 401 const expected = { values: { __proto__: null, foo: true }, 402 positionals: [] }; 403 assert.deepStrictEqual(result, expected); 404}); 405 406test('string option used as boolean', () => { 407 const args = ['--foo']; 408 const options = { foo: { type: 'string' } }; 409 assert.throws(() => { parseArgs({ args, options }); }, { 410 code: 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' 411 }); 412}); 413 414test('boolean option used with value', () => { 415 const args = ['--foo=bar']; 416 const options = { foo: { type: 'boolean' } }; 417 assert.throws(() => { parseArgs({ args, options }); }, { 418 code: 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' 419 }); 420}); 421 422test('invalid short option length', () => { 423 const args = []; 424 const options = { foo: { short: 'fo', type: 'boolean' } }; 425 assert.throws(() => { parseArgs({ args, options }); }, { 426 code: 'ERR_INVALID_ARG_VALUE' 427 }); 428}); 429 430test('null prototype: when no options then values.toString is undefined', () => { 431 const result = parseArgs({ args: [] }); 432 assert.strictEqual(result.values.toString, undefined); 433}); 434 435test('null prototype: when --toString then values.toString is true', () => { 436 const args = ['--toString']; 437 const options = { toString: { type: 'boolean' } }; 438 const expectedResult = { values: { __proto__: null, toString: true }, positionals: [] }; 439 440 const result = parseArgs({ args, options }); 441 assert.deepStrictEqual(result, expectedResult); 442}); 443 444const candidateGreedyOptions = [ 445 '', 446 '-', 447 '--', 448 'abc', 449 '123', 450 '-s', 451 '--foo', 452]; 453 454candidateGreedyOptions.forEach((value) => { 455 test(`greedy: when short option with value '${value}' then eaten`, () => { 456 const args = ['-w', value]; 457 const options = { with: { type: 'string', short: 'w' } }; 458 const expectedResult = { values: { __proto__: null, with: value }, positionals: [] }; 459 460 const result = parseArgs({ args, options, strict: false }); 461 assert.deepStrictEqual(result, expectedResult); 462 }); 463 464 test(`greedy: when long option with value '${value}' then eaten`, () => { 465 const args = ['--with', value]; 466 const options = { with: { type: 'string', short: 'w' } }; 467 const expectedResult = { values: { __proto__: null, with: value }, positionals: [] }; 468 469 const result = parseArgs({ args, options, strict: false }); 470 assert.deepStrictEqual(result, expectedResult); 471 }); 472}); 473 474test('strict: when candidate option value is plain text then does not throw', () => { 475 const args = ['--with', 'abc']; 476 const options = { with: { type: 'string' } }; 477 const expectedResult = { values: { __proto__: null, with: 'abc' }, positionals: [] }; 478 479 const result = parseArgs({ args, options, strict: true }); 480 assert.deepStrictEqual(result, expectedResult); 481}); 482 483test("strict: when candidate option value is '-' then does not throw", () => { 484 const args = ['--with', '-']; 485 const options = { with: { type: 'string' } }; 486 const expectedResult = { values: { __proto__: null, with: '-' }, positionals: [] }; 487 488 const result = parseArgs({ args, options, strict: true }); 489 assert.deepStrictEqual(result, expectedResult); 490}); 491 492test("strict: when candidate option value is '--' then throws", () => { 493 const args = ['--with', '--']; 494 const options = { with: { type: 'string' } }; 495 496 assert.throws(() => { 497 parseArgs({ args, options }); 498 }, { 499 code: 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' 500 }); 501}); 502 503test('strict: when candidate option value is short option then throws', () => { 504 const args = ['--with', '-a']; 505 const options = { with: { type: 'string' } }; 506 507 assert.throws(() => { 508 parseArgs({ args, options }); 509 }, { 510 code: 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' 511 }); 512}); 513 514test('strict: when candidate option value is short option digit then throws', () => { 515 const args = ['--with', '-1']; 516 const options = { with: { type: 'string' } }; 517 518 assert.throws(() => { 519 parseArgs({ args, options }); 520 }, { 521 code: 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' 522 }); 523}); 524 525test('strict: when candidate option value is long option then throws', () => { 526 const args = ['--with', '--foo']; 527 const options = { with: { type: 'string' } }; 528 529 assert.throws(() => { 530 parseArgs({ args, options }); 531 }, { 532 code: 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' 533 }); 534}); 535 536test('strict: when short option and suspect value then throws with short option in error message', () => { 537 const args = ['-w', '--foo']; 538 const options = { with: { type: 'string', short: 'w' } }; 539 540 assert.throws(() => { 541 parseArgs({ args, options }); 542 }, /for '-w'/ 543 ); 544}); 545 546test('strict: when long option and suspect value then throws with long option in error message', () => { 547 const args = ['--with', '--foo']; 548 const options = { with: { type: 'string' } }; 549 550 assert.throws(() => { 551 parseArgs({ args, options }); 552 }, /for '--with'/ 553 ); 554}); 555 556test('strict: when short option and suspect value then throws with whole expected message', () => { 557 const args = ['-w', '--foo']; 558 const options = { with: { type: 'string', short: 'w' } }; 559 560 try { 561 parseArgs({ args, options }); 562 } catch (err) { 563 console.info(err.message); 564 } 565 566 assert.throws(() => { 567 parseArgs({ args, options }); 568 }, /To specify an option argument starting with a dash use '--with=-XYZ' or '-w-XYZ'/ 569 ); 570}); 571 572test('strict: when long option and suspect value then throws with whole expected message', () => { 573 const args = ['--with', '--foo']; 574 const options = { with: { type: 'string', short: 'w' } }; 575 576 assert.throws(() => { 577 parseArgs({ args, options }); 578 }, /To specify an option argument starting with a dash use '--with=-XYZ'/ 579 ); 580}); 581 582test('tokens: positional', () => { 583 const args = ['one']; 584 const expectedTokens = [ 585 { kind: 'positional', index: 0, value: 'one' }, 586 ]; 587 const { tokens } = parseArgs({ strict: false, args, tokens: true }); 588 assert.deepStrictEqual(tokens, expectedTokens); 589}); 590 591test('tokens: -- followed by option-like', () => { 592 const args = ['--', '--foo']; 593 const expectedTokens = [ 594 { kind: 'option-terminator', index: 0 }, 595 { kind: 'positional', index: 1, value: '--foo' }, 596 ]; 597 const { tokens } = parseArgs({ strict: false, args, tokens: true }); 598 assert.deepStrictEqual(tokens, expectedTokens); 599}); 600 601test('tokens: strict:true boolean short', () => { 602 const args = ['-f']; 603 const options = { 604 file: { short: 'f', type: 'boolean' } 605 }; 606 const expectedTokens = [ 607 { kind: 'option', name: 'file', rawName: '-f', 608 index: 0, value: undefined, inlineValue: undefined }, 609 ]; 610 const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); 611 assert.deepStrictEqual(tokens, expectedTokens); 612}); 613 614test('tokens: strict:true boolean long', () => { 615 const args = ['--file']; 616 const options = { 617 file: { short: 'f', type: 'boolean' } 618 }; 619 const expectedTokens = [ 620 { kind: 'option', name: 'file', rawName: '--file', 621 index: 0, value: undefined, inlineValue: undefined }, 622 ]; 623 const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); 624 assert.deepStrictEqual(tokens, expectedTokens); 625}); 626 627test('tokens: strict:false boolean short', () => { 628 const args = ['-f']; 629 const expectedTokens = [ 630 { kind: 'option', name: 'f', rawName: '-f', 631 index: 0, value: undefined, inlineValue: undefined }, 632 ]; 633 const { tokens } = parseArgs({ strict: false, args, tokens: true }); 634 assert.deepStrictEqual(tokens, expectedTokens); 635}); 636 637test('tokens: strict:false boolean long', () => { 638 const args = ['--file']; 639 const expectedTokens = [ 640 { kind: 'option', name: 'file', rawName: '--file', 641 index: 0, value: undefined, inlineValue: undefined }, 642 ]; 643 const { tokens } = parseArgs({ strict: false, args, tokens: true }); 644 assert.deepStrictEqual(tokens, expectedTokens); 645}); 646 647test('tokens: strict:false boolean option group', () => { 648 const args = ['-ab']; 649 const expectedTokens = [ 650 { kind: 'option', name: 'a', rawName: '-a', 651 index: 0, value: undefined, inlineValue: undefined }, 652 { kind: 'option', name: 'b', rawName: '-b', 653 index: 0, value: undefined, inlineValue: undefined }, 654 ]; 655 const { tokens } = parseArgs({ strict: false, args, tokens: true }); 656 assert.deepStrictEqual(tokens, expectedTokens); 657}); 658 659test('tokens: strict:false boolean option group with repeated option', () => { 660 // Also positional to check index correct after grouop 661 const args = ['-aa', 'pos']; 662 const expectedTokens = [ 663 { kind: 'option', name: 'a', rawName: '-a', 664 index: 0, value: undefined, inlineValue: undefined }, 665 { kind: 'option', name: 'a', rawName: '-a', 666 index: 0, value: undefined, inlineValue: undefined }, 667 { kind: 'positional', index: 1, value: 'pos' }, 668 ]; 669 const { tokens } = parseArgs({ strict: false, allowPositionals: true, args, tokens: true }); 670 assert.deepStrictEqual(tokens, expectedTokens); 671}); 672 673test('tokens: strict:true string short with value after space', () => { 674 // Also positional to check index correct after out-of-line. 675 const args = ['-f', 'bar', 'ppp']; 676 const options = { 677 file: { short: 'f', type: 'string' } 678 }; 679 const expectedTokens = [ 680 { kind: 'option', name: 'file', rawName: '-f', 681 index: 0, value: 'bar', inlineValue: false }, 682 { kind: 'positional', index: 2, value: 'ppp' }, 683 ]; 684 const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); 685 assert.deepStrictEqual(tokens, expectedTokens); 686}); 687 688test('tokens: strict:true string short with value inline', () => { 689 const args = ['-fBAR']; 690 const options = { 691 file: { short: 'f', type: 'string' } 692 }; 693 const expectedTokens = [ 694 { kind: 'option', name: 'file', rawName: '-f', 695 index: 0, value: 'BAR', inlineValue: true }, 696 ]; 697 const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); 698 assert.deepStrictEqual(tokens, expectedTokens); 699}); 700 701test('tokens: strict:false string short missing value', () => { 702 const args = ['-f']; 703 const options = { 704 file: { short: 'f', type: 'string' } 705 }; 706 const expectedTokens = [ 707 { kind: 'option', name: 'file', rawName: '-f', 708 index: 0, value: undefined, inlineValue: undefined }, 709 ]; 710 const { tokens } = parseArgs({ strict: false, args, options, tokens: true }); 711 assert.deepStrictEqual(tokens, expectedTokens); 712}); 713 714test('tokens: strict:true string long with value after space', () => { 715 // Also positional to check index correct after out-of-line. 716 const args = ['--file', 'bar', 'ppp']; 717 const options = { 718 file: { short: 'f', type: 'string' } 719 }; 720 const expectedTokens = [ 721 { kind: 'option', name: 'file', rawName: '--file', 722 index: 0, value: 'bar', inlineValue: false }, 723 { kind: 'positional', index: 2, value: 'ppp' }, 724 ]; 725 const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); 726 assert.deepStrictEqual(tokens, expectedTokens); 727}); 728 729test('tokens: strict:true string long with value inline', () => { 730 // Also positional to check index correct after out-of-line. 731 const args = ['--file=bar', 'pos']; 732 const options = { 733 file: { short: 'f', type: 'string' } 734 }; 735 const expectedTokens = [ 736 { kind: 'option', name: 'file', rawName: '--file', 737 index: 0, value: 'bar', inlineValue: true }, 738 { kind: 'positional', index: 1, value: 'pos' }, 739 ]; 740 const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); 741 assert.deepStrictEqual(tokens, expectedTokens); 742}); 743 744test('tokens: strict:false string long with value inline', () => { 745 const args = ['--file=bar']; 746 const expectedTokens = [ 747 { kind: 'option', name: 'file', rawName: '--file', 748 index: 0, value: 'bar', inlineValue: true }, 749 ]; 750 const { tokens } = parseArgs({ strict: false, args, tokens: true }); 751 assert.deepStrictEqual(tokens, expectedTokens); 752}); 753 754test('tokens: strict:false string long missing value', () => { 755 const args = ['--file']; 756 const options = { 757 file: { short: 'f', type: 'string' } 758 }; 759 const expectedTokens = [ 760 { kind: 'option', name: 'file', rawName: '--file', 761 index: 0, value: undefined, inlineValue: undefined }, 762 ]; 763 const { tokens } = parseArgs({ strict: false, args, options, tokens: true }); 764 assert.deepStrictEqual(tokens, expectedTokens); 765}); 766 767test('tokens: strict:true complex option group with value after space', () => { 768 // Also positional to check index correct afterwards. 769 const args = ['-ab', 'c', 'pos']; 770 const options = { 771 alpha: { short: 'a', type: 'boolean' }, 772 beta: { short: 'b', type: 'string' }, 773 }; 774 const expectedTokens = [ 775 { kind: 'option', name: 'alpha', rawName: '-a', 776 index: 0, value: undefined, inlineValue: undefined }, 777 { kind: 'option', name: 'beta', rawName: '-b', 778 index: 0, value: 'c', inlineValue: false }, 779 { kind: 'positional', index: 2, value: 'pos' }, 780 ]; 781 const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); 782 assert.deepStrictEqual(tokens, expectedTokens); 783}); 784 785test('tokens: strict:true complex option group with inline value', () => { 786 // Also positional to check index correct afterwards. 787 const args = ['-abc', 'pos']; 788 const options = { 789 alpha: { short: 'a', type: 'boolean' }, 790 beta: { short: 'b', type: 'string' }, 791 }; 792 const expectedTokens = [ 793 { kind: 'option', name: 'alpha', rawName: '-a', 794 index: 0, value: undefined, inlineValue: undefined }, 795 { kind: 'option', name: 'beta', rawName: '-b', 796 index: 0, value: 'c', inlineValue: true }, 797 { kind: 'positional', index: 1, value: 'pos' }, 798 ]; 799 const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); 800 assert.deepStrictEqual(tokens, expectedTokens); 801}); 802 803test('tokens: strict:false with single dashes', () => { 804 const args = ['--file', '-', '-']; 805 const options = { 806 file: { short: 'f', type: 'string' }, 807 }; 808 const expectedTokens = [ 809 { kind: 'option', name: 'file', rawName: '--file', 810 index: 0, value: '-', inlineValue: false }, 811 { kind: 'positional', index: 2, value: '-' }, 812 ]; 813 const { tokens } = parseArgs({ strict: false, args, options, tokens: true }); 814 assert.deepStrictEqual(tokens, expectedTokens); 815}); 816 817test('tokens: strict:false with -- --', () => { 818 const args = ['--', '--']; 819 const expectedTokens = [ 820 { kind: 'option-terminator', index: 0 }, 821 { kind: 'positional', index: 1, value: '--' }, 822 ]; 823 const { tokens } = parseArgs({ strict: false, args, tokens: true }); 824 assert.deepStrictEqual(tokens, expectedTokens); 825}); 826 827test('default must be a boolean when option type is boolean', () => { 828 const args = []; 829 const options = { alpha: { type: 'boolean', default: 'not a boolean' } }; 830 assert.throws(() => { 831 parseArgs({ args, options }); 832 }, /"options\.alpha\.default" property must be of type boolean/ 833 ); 834}); 835 836test('default must accept undefined value', () => { 837 const args = []; 838 const options = { alpha: { type: 'boolean', default: undefined } }; 839 const result = parseArgs({ args, options }); 840 const expected = { 841 values: { 842 __proto__: null, 843 }, 844 positionals: [] 845 }; 846 assert.deepStrictEqual(result, expected); 847}); 848 849test('default must be a boolean array when option type is boolean and multiple', () => { 850 const args = []; 851 const options = { alpha: { type: 'boolean', multiple: true, default: 'not an array' } }; 852 assert.throws(() => { 853 parseArgs({ args, options }); 854 }, /"options\.alpha\.default" property must be an instance of Array/ 855 ); 856}); 857 858test('default must be a boolean array when option type is string and multiple is true', () => { 859 const args = []; 860 const options = { alpha: { type: 'boolean', multiple: true, default: [true, true, 42] } }; 861 assert.throws(() => { 862 parseArgs({ args, options }); 863 }, /"options\.alpha\.default\[2\]" property must be of type boolean/ 864 ); 865}); 866 867test('default must be a string when option type is string', () => { 868 const args = []; 869 const options = { alpha: { type: 'string', default: true } }; 870 assert.throws(() => { 871 parseArgs({ args, options }); 872 }, /"options\.alpha\.default" property must be of type string/ 873 ); 874}); 875 876test('default must be an array when option type is string and multiple is true', () => { 877 const args = []; 878 const options = { alpha: { type: 'string', multiple: true, default: 'not an array' } }; 879 assert.throws(() => { 880 parseArgs({ args, options }); 881 }, /"options\.alpha\.default" property must be an instance of Array/ 882 ); 883}); 884 885test('default must be a string array when option type is string and multiple is true', () => { 886 const args = []; 887 const options = { alpha: { type: 'string', multiple: true, default: ['str', 42] } }; 888 assert.throws(() => { 889 parseArgs({ args, options }); 890 }, /"options\.alpha\.default\[1\]" property must be of type string/ 891 ); 892}); 893 894test('default accepted input when multiple is true', () => { 895 const args = ['--inputStringArr', 'c', '--inputStringArr', 'd', '--inputBoolArr', '--inputBoolArr']; 896 const options = { 897 inputStringArr: { type: 'string', multiple: true, default: ['a', 'b'] }, 898 emptyStringArr: { type: 'string', multiple: true, default: [] }, 899 fullStringArr: { type: 'string', multiple: true, default: ['a', 'b'] }, 900 inputBoolArr: { type: 'boolean', multiple: true, default: [false, true, false] }, 901 emptyBoolArr: { type: 'boolean', multiple: true, default: [] }, 902 fullBoolArr: { type: 'boolean', multiple: true, default: [false, true, false] }, 903 }; 904 const expected = { values: { __proto__: null, 905 inputStringArr: ['c', 'd'], 906 inputBoolArr: [true, true], 907 emptyStringArr: [], 908 fullStringArr: ['a', 'b'], 909 emptyBoolArr: [], 910 fullBoolArr: [false, true, false] }, 911 positionals: [] }; 912 const result = parseArgs({ args, options }); 913 assert.deepStrictEqual(result, expected); 914}); 915 916test('when default is set, the option must be added as result', () => { 917 const args = []; 918 const options = { 919 a: { type: 'string', default: 'HELLO' }, 920 b: { type: 'boolean', default: false }, 921 c: { type: 'boolean', default: true } 922 }; 923 const expected = { values: { __proto__: null, a: 'HELLO', b: false, c: true }, positionals: [] }; 924 925 const result = parseArgs({ args, options }); 926 assert.deepStrictEqual(result, expected); 927}); 928 929test('when default is set, the args value takes precedence', () => { 930 const args = ['--a', 'WORLD', '--b', '-c']; 931 const options = { 932 a: { type: 'string', default: 'HELLO' }, 933 b: { type: 'boolean', default: false }, 934 c: { type: 'boolean', default: true } 935 }; 936 const expected = { values: { __proto__: null, a: 'WORLD', b: true, c: true }, positionals: [] }; 937 938 const result = parseArgs({ args, options }); 939 assert.deepStrictEqual(result, expected); 940}); 941 942test('tokens should not include the default options', () => { 943 const args = []; 944 const options = { 945 a: { type: 'string', default: 'HELLO' }, 946 b: { type: 'boolean', default: false }, 947 c: { type: 'boolean', default: true } 948 }; 949 950 const expectedTokens = []; 951 952 const { tokens } = parseArgs({ args, options, tokens: true }); 953 assert.deepStrictEqual(tokens, expectedTokens); 954}); 955 956test('tokens:true should not include the default options after the args input', () => { 957 const args = ['--z', 'zero', 'positional-item']; 958 const options = { 959 z: { type: 'string' }, 960 a: { type: 'string', default: 'HELLO' }, 961 b: { type: 'boolean', default: false }, 962 c: { type: 'boolean', default: true } 963 }; 964 965 const expectedTokens = [ 966 { kind: 'option', name: 'z', rawName: '--z', index: 0, value: 'zero', inlineValue: false }, 967 { kind: 'positional', index: 2, value: 'positional-item' }, 968 ]; 969 970 const { tokens } = parseArgs({ args, options, tokens: true, allowPositionals: true }); 971 assert.deepStrictEqual(tokens, expectedTokens); 972}); 973 974test('proto as default value must be ignored', () => { 975 const args = []; 976 const options = Object.create(null); 977 978 // eslint-disable-next-line no-proto 979 options.__proto__ = { type: 'string', default: 'HELLO' }; 980 981 const result = parseArgs({ args, options, allowPositionals: true }); 982 const expected = { values: { __proto__: null }, positionals: [] }; 983 assert.deepStrictEqual(result, expected); 984}); 985 986 987test('multiple as false should expect a String', () => { 988 const args = []; 989 const options = { alpha: { type: 'string', multiple: false, default: ['array'] } }; 990 assert.throws(() => { 991 parseArgs({ args, options }); 992 }, /"options\.alpha\.default" property must be of type string/ 993 ); 994}); 995