Skip to content

Commit

Permalink
util: support undefined values in parse args
Browse files Browse the repository at this point in the history
  • Loading branch information
avivkeller committed Jun 12, 2024
1 parent 73fa9ab commit 4b37afe
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 1 deletion.
6 changes: 6 additions & 0 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -1390,6 +1390,10 @@ added:
- v18.3.0
- v16.17.0
changes:
- version: REPLACEME
pr-url: ...
description: Add support for default values when an undefined
value is supplied.
- version:
- v20.0.0
pr-url: https://github.com/nodejs/node/pull/46718
Expand Down Expand Up @@ -1422,6 +1426,8 @@ changes:
* `default` {string | boolean | string\[] | boolean\[]} The default option
value when it is not set by args. It must be of the same type as the
`type` property. When `multiple` is `true`, it must be an array.
* `useDefaultWhenUndefined` {boolean} Use the `default` value when a string
`type` argument is provided without a supplied value.
* `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`.
Expand Down
26 changes: 25 additions & 1 deletion lib/internal/util/parse_args/parse_args.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,13 @@ function checkOptionUsage(config, token) {
const short = optionsGetOwn(config.options, token.name, 'short');
const shortAndLong = `${short ? `-${short}, ` : ''}--${token.name}`;
const type = optionsGetOwn(config.options, token.name, 'type');
const useDefaultWhenUndefined = optionsGetOwn(config.options, token.name, 'useDefaultWhenUndefined');
if (type === 'string' && typeof token.value !== 'string') {
throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortAndLong} <value>' argument missing`);
if (useDefaultWhenUndefined) {
token.value = optionsGetOwn(config.options, token.name, 'default');
} else {
throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortAndLong} <value>' argument missing`);
}
}
// (Idiomatic test for undefined||null, expecting undefined.)
if (type === 'boolean' && token.value != null) {
Expand Down Expand Up @@ -340,6 +345,25 @@ const parseArgs = (config = kEmptyObject) => {
}
validator(defaultValue, `options.${longOption}.default`);
}

const useDefaultWhenUndefined = objectGetOwn(optionConfig, 'useDefaultWhenUndefined');
if (useDefaultWhenUndefined !== undefined) {
if (optionType !== 'string') {
throw new ERR_INVALID_ARG_VALUE(
`options.${longOption}.useDefaultWhenUndefined`,
useDefaultWhenUndefined,
'can only be set for string options',
);
};
validateBoolean(useDefaultWhenUndefined, `options.${longOption}.useDefaultWhenUndefined`);
if (defaultValue === undefined && useDefaultWhenUndefined) {
throw new ERR_INVALID_ARG_VALUE(
`options.${longOption}.default`,
defaultValue,
'must be set if useDefaultWhenUndefined is true',
);
}
}
},
);

Expand Down
35 changes: 35 additions & 0 deletions test/parallel/test-parse-args.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -992,3 +992,38 @@ test('multiple as false should expect a String', () => {
}, /"options\.alpha\.default" property must be of type string/
);
});

// Default when undefined
test('using default when undefined with a boolean should throw', () => {
const args = [];
const options = { alpha: { type: 'boolean', useDefaultWhenUndefined: true } };
assert.throws(() => {
parseArgs({ args, options });
}, /The property 'options\.alpha\.useDefaultWhenUndefined' can only be set for string options/
);
});

test('using default when undefined without default should throw', () => {
const args = [];
const options = { alpha: { type: 'string', useDefaultWhenUndefined: true } };
assert.throws(() => {
parseArgs({ args, options });
}, /The property 'options\.alpha\.default' must be set if useDefaultWhenUndefined is true/
);
});

test('using default when undefined with a string should use the default', () => {
const args = ['--alpha']; // --alpha is undefined, as it has no value
const options = { alpha: { type: 'string', useDefaultWhenUndefined: true, default: 'default' } };
const expected = { values: { __proto__: null, alpha: 'default' }, positionals: [] };
const result = parseArgs({ args, options });
assert.deepStrictEqual(result, expected);
});

test('using default when undefined with a string that has a value should not use the default', () => {
const args = ['--alpha', 'value'];
const options = { alpha: { type: 'string', useDefaultWhenUndefined: true, default: 'default' } };
const expected = { values: { __proto__: null, alpha: 'value' }, positionals: [] };
const result = parseArgs({ args, options });
assert.deepStrictEqual(result, expected);
});

0 comments on commit 4b37afe

Please sign in to comment.