Skip to content

Commit 7a30d9a

Browse files
committed
util: support 'option.required' in parseArgs
Fixes: #44564
1 parent 1f54fc2 commit 7a30d9a

File tree

5 files changed

+108
-2
lines changed

5 files changed

+108
-2
lines changed

doc/api/errors.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2464,6 +2464,17 @@ added:
24642464
When `strict` set to `true`, thrown by [`util.parseArgs()`][] if an argument
24652465
is not configured in `options`.
24662466

2467+
<a id="ERR_PARSE_ARGS_REQUIRED_OPTION"></a>
2468+
2469+
### `ERR_PARSE_ARGS_REQUIRED_OPTION`
2470+
2471+
<!--YAML
2472+
added: REPLACEME
2473+
-->
2474+
2475+
When `required` set to `true`, thrown by [`util.parseArgs()`][] if an option
2476+
is not provided in `args`.
2477+
24672478
<a id="ERR_PERFORMANCE_INVALID_TIMESTAMP"></a>
24682479

24692480
### `ERR_PERFORMANCE_INVALID_TIMESTAMP`

doc/api/util.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,9 @@ added:
10311031
- v18.3.0
10321032
- v16.17.0
10331033
changes:
1034+
- version: REPLACEME
1035+
pr-url: https://github.com/nodejs/node/pull/44565
1036+
description: support `required` field in option
10341037
- version:
10351038
- v18.7.0
10361039
- v16.17.0
@@ -1053,6 +1056,7 @@ changes:
10531056
times. If `true`, all values will be collected in an array. If
10541057
`false`, values for the option are last-wins. **Default:** `false`.
10551058
* `short` {string} A single character alias for the option.
1059+
* `required` {boolean} Whether this option is required. **Default:** `false`.
10561060
* `strict` {boolean} Should an error be thrown when unknown arguments
10571061
are encountered, or when arguments are passed that do not match the
10581062
`type` configured in `options`.
@@ -1085,7 +1089,8 @@ const options = {
10851089
short: 'f'
10861090
},
10871091
bar: {
1088-
type: 'string'
1092+
type: 'string',
1093+
required: true
10891094
}
10901095
};
10911096
const {
@@ -1105,7 +1110,8 @@ const options = {
11051110
short: 'f'
11061111
},
11071112
bar: {
1108-
type: 'string'
1113+
type: 'string',
1114+
required: true
11091115
}
11101116
};
11111117
const {

lib/internal/errors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,7 @@ E('ERR_PARSE_ARGS_UNKNOWN_OPTION', (option, allowPositionals) => {
15191519
`'--', as in '-- ${JSONStringify(option)}` : '';
15201520
return `Unknown option '${option}'${suggestDashDash}`;
15211521
}, TypeError);
1522+
E('ERR_PARSE_ARGS_REQUIRED_OPTION', 'Required option \'%s\'.', TypeError);
15221523
E('ERR_PERFORMANCE_INVALID_TIMESTAMP',
15231524
'%d is not a valid timestamp', TypeError);
15241525
E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS', '%s', TypeError);

lib/internal/util/parse_args/parse_args.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const {
4444
ERR_PARSE_ARGS_INVALID_OPTION_VALUE,
4545
ERR_PARSE_ARGS_UNKNOWN_OPTION,
4646
ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL,
47+
ERR_PARSE_ARGS_REQUIRED_OPTION
4748
},
4849
} = require('internal/errors');
4950

@@ -336,6 +337,18 @@ const parseArgs = (config = kEmptyObject) => {
336337
}
337338
});
338339

340+
// Phase 3: check if some options is required
341+
ArrayPrototypeForEach(
342+
ObjectEntries(options),
343+
({ 0: longOption, 1: optionConfig }) => {
344+
if (optionConfig.required) {
345+
if (result.values[longOption] == null) {
346+
throw new ERR_PARSE_ARGS_REQUIRED_OPTION(longOption);
347+
}
348+
}
349+
}
350+
);
351+
339352
return result;
340353
};
341354

test/parallel/test-parse-args.mjs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,3 +823,78 @@ test('tokens: strict:false with -- --', () => {
823823
const { tokens } = parseArgs({ strict: false, args, tokens: true });
824824
assert.deepStrictEqual(tokens, expectedTokens);
825825
});
826+
827+
test('strict: required option', () => {
828+
const args = ['--foo']
829+
parseArgs({
830+
args,
831+
options: {
832+
foo: {
833+
type: 'boolean',
834+
required: true
835+
}
836+
}
837+
})
838+
})
839+
840+
test('required option', () => {
841+
const args = ['--foo', '--goo']
842+
parseArgs({
843+
strict: false,
844+
args,
845+
options: {
846+
foo: {
847+
type: 'boolean',
848+
required: true
849+
}
850+
}
851+
})
852+
})
853+
854+
test('strict: false required option fail', () => {
855+
const args = []
856+
assert.throws(() => {
857+
parseArgs({
858+
strict: false,
859+
args,
860+
options: {
861+
foo: {
862+
type: 'boolean',
863+
required: true
864+
}
865+
}
866+
}, {
867+
code: 'ERR_PARSE_ARGS_REQUIRED_OPTION'
868+
})
869+
})
870+
})
871+
872+
test('strict: no input but has required option', () => {
873+
const args = []
874+
assert.throws(() => {
875+
parseArgs({
876+
args,
877+
options: {
878+
foo: {
879+
type: 'boolean',
880+
required: true
881+
}
882+
}
883+
}, {
884+
code: 'ERR_PARSE_ARGS_REQUIRED_OPTION'
885+
})
886+
})
887+
})
888+
889+
test('strict: no input and no required option', () => {
890+
const args = []
891+
parseArgs({
892+
args,
893+
options: {
894+
foo: {
895+
type: 'boolean',
896+
required: false
897+
}
898+
}
899+
})
900+
})

0 commit comments

Comments
 (0)