From 7a30d9a92e83aaf2ed6e46841f6a903f92ea10a1 Mon Sep 17 00:00:00 2001 From: himself65 Date: Wed, 7 Sep 2022 17:09:09 -0500 Subject: [PATCH] util: support 'option.required' in `parseArgs` Fixes: https://github.com/nodejs/node/issues/44564 --- doc/api/errors.md | 11 ++++ doc/api/util.md | 10 ++- lib/internal/errors.js | 1 + lib/internal/util/parse_args/parse_args.js | 13 ++++ test/parallel/test-parse-args.mjs | 75 ++++++++++++++++++++++ 5 files changed, 108 insertions(+), 2 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index c6c8140e259102..2a11a1c5441a74 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -2464,6 +2464,17 @@ added: When `strict` set to `true`, thrown by [`util.parseArgs()`][] if an argument is not configured in `options`. + + +### `ERR_PARSE_ARGS_REQUIRED_OPTION` + + + +When `required` set to `true`, thrown by [`util.parseArgs()`][] if an option +is not provided in `args`. + ### `ERR_PERFORMANCE_INVALID_TIMESTAMP` diff --git a/doc/api/util.md b/doc/api/util.md index 48b32712cf83c1..5b9f94c34e4d00 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -1031,6 +1031,9 @@ added: - v18.3.0 - v16.17.0 changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44565 + description: support `required` field in option - version: - v18.7.0 - v16.17.0 @@ -1053,6 +1056,7 @@ changes: times. If `true`, all values will be collected in an array. If `false`, values for the option are last-wins. **Default:** `false`. * `short` {string} A single character alias for the option. + * `required` {boolean} Whether this option is required. **Default:** `false`. * `strict` {boolean} Should an error be thrown when unknown arguments are encountered, or when arguments are passed that do not match the `type` configured in `options`. @@ -1085,7 +1089,8 @@ const options = { short: 'f' }, bar: { - type: 'string' + type: 'string', + required: true } }; const { @@ -1105,7 +1110,8 @@ const options = { short: 'f' }, bar: { - type: 'string' + type: 'string', + required: true } }; const { diff --git a/lib/internal/errors.js b/lib/internal/errors.js index de861b813a4fe9..f6a035f61dc9f4 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1519,6 +1519,7 @@ E('ERR_PARSE_ARGS_UNKNOWN_OPTION', (option, allowPositionals) => { `'--', as in '-- ${JSONStringify(option)}` : ''; return `Unknown option '${option}'${suggestDashDash}`; }, TypeError); +E('ERR_PARSE_ARGS_REQUIRED_OPTION', 'Required option \'%s\'.', TypeError); E('ERR_PERFORMANCE_INVALID_TIMESTAMP', '%d is not a valid timestamp', TypeError); E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS', '%s', TypeError); diff --git a/lib/internal/util/parse_args/parse_args.js b/lib/internal/util/parse_args/parse_args.js index 05f4c6cffbdbd7..452ad5faa4295d 100644 --- a/lib/internal/util/parse_args/parse_args.js +++ b/lib/internal/util/parse_args/parse_args.js @@ -44,6 +44,7 @@ const { ERR_PARSE_ARGS_INVALID_OPTION_VALUE, ERR_PARSE_ARGS_UNKNOWN_OPTION, ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL, + ERR_PARSE_ARGS_REQUIRED_OPTION }, } = require('internal/errors'); @@ -336,6 +337,18 @@ const parseArgs = (config = kEmptyObject) => { } }); + // Phase 3: check if some options is required + ArrayPrototypeForEach( + ObjectEntries(options), + ({ 0: longOption, 1: optionConfig }) => { + if (optionConfig.required) { + if (result.values[longOption] == null) { + throw new ERR_PARSE_ARGS_REQUIRED_OPTION(longOption); + } + } + } + ); + return result; }; diff --git a/test/parallel/test-parse-args.mjs b/test/parallel/test-parse-args.mjs index 98cf9403743a41..1758409f1dcf92 100644 --- a/test/parallel/test-parse-args.mjs +++ b/test/parallel/test-parse-args.mjs @@ -823,3 +823,78 @@ test('tokens: strict:false with -- --', () => { const { tokens } = parseArgs({ strict: false, args, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); + +test('strict: required option', () => { + const args = ['--foo'] + parseArgs({ + args, + options: { + foo: { + type: 'boolean', + required: true + } + } + }) +}) + +test('required option', () => { + const args = ['--foo', '--goo'] + parseArgs({ + strict: false, + args, + options: { + foo: { + type: 'boolean', + required: true + } + } + }) +}) + +test('strict: false required option fail', () => { + const args = [] + assert.throws(() => { + parseArgs({ + strict: false, + args, + options: { + foo: { + type: 'boolean', + required: true + } + } + }, { + code: 'ERR_PARSE_ARGS_REQUIRED_OPTION' + }) + }) +}) + +test('strict: no input but has required option', () => { + const args = [] + assert.throws(() => { + parseArgs({ + args, + options: { + foo: { + type: 'boolean', + required: true + } + } + }, { + code: 'ERR_PARSE_ARGS_REQUIRED_OPTION' + }) + }) +}) + +test('strict: no input and no required option', () => { + const args = [] + parseArgs({ + args, + options: { + foo: { + type: 'boolean', + required: false + } + } + }) +})