• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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