util: support `--no-` for argument with boolean type for parseArgs · nodejs/node@50733a1 · GitHub
Skip to content

Commit 50733a1

Browse files
kylo5abytargos
authored andcommitted
util: support --no- for argument with boolean type for parseArgs
PR-URL: #53107 Refs: #53095 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent befabe5 commit 50733a1

3 files changed

Lines changed: 111 additions & 14 deletions

File tree

doc/api/util.md

Lines changed: 9 additions & 3 deletions

lib/internal/util/parse_args/parse_args.js

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,24 @@ To specify an option argument starting with a dash use ${example}.`;
9494
* @param {object} token - from tokens as available from parseArgs
9595
*/
9696
function checkOptionUsage(config, token) {
97-
if (!ObjectHasOwn(config.options, token.name)) {
98-
throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(
99-
token.rawName, config.allowPositionals);
97+
let tokenName = token.name;
98+
if (!ObjectHasOwn(config.options, tokenName)) {
99+
// Check for negated boolean option.
100+
if (config.allowNegative && StringPrototypeStartsWith(tokenName, 'no-')) {
101+
tokenName = StringPrototypeSlice(tokenName, 3);
102+
if (!ObjectHasOwn(config.options, tokenName) || optionsGetOwn(config.options, tokenName, 'type') !== 'boolean') {
103+
throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(
104+
token.rawName, config.allowPositionals);
105+
}
106+
} else {
107+
throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(
108+
token.rawName, config.allowPositionals);
109+
}
100110
}
101111

102-
const short = optionsGetOwn(config.options, token.name, 'short');
103-
const shortAndLong = `${short ? `-${short}, ` : ''}--${token.name}`;
104-
const type = optionsGetOwn(config.options, token.name, 'type');
112+
const short = optionsGetOwn(config.options, tokenName, 'short');
113+
const shortAndLong = `${short ? `-${short}, ` : ''}--${tokenName}`;
114+
const type = optionsGetOwn(config.options, tokenName, 'type');
105115
if (type === 'string' && typeof token.value !== 'string') {
106116
throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortAndLong} <value>' argument missing`);
107117
}
@@ -114,16 +124,25 @@ function checkOptionUsage(config, token) {
114124

115125
/**
116126
* Store the option value in `values`.
117-
* @param {string} longOption - long option name e.g. 'foo'
118-
* @param {string|undefined} optionValue - value from user args
127+
* @param {object} token - from tokens as available from parseArgs
119128
* @param {object} options - option configs, from parseArgs({ options })
120129
* @param {object} values - option values returned in `values` by parseArgs
130+
* @param {boolean} allowNegative - allow negative optinons if true
121131
*/
122-
function storeOption(longOption, optionValue, options, values) {
132+
function storeOption(token, options, values, allowNegative) {
133+
let longOption = token.name;
134+
let optionValue = token.value;
123135
if (longOption === '__proto__') {
124136
return; // No. Just no.
125137
}
126138

139+
if (allowNegative && StringPrototypeStartsWith(longOption, 'no-') && optionValue === undefined) {
140+
// Boolean option negation: --no-foo
141+
longOption = StringPrototypeSlice(longOption, 3);
142+
token.name = longOption;
143+
optionValue = false;
144+
}
145+
127146
// We store based on the option value rather than option type,
128147
// preserving the users intent for author to deal with.
129148
const newValue = optionValue ?? true;
@@ -290,15 +309,17 @@ const parseArgs = (config = kEmptyObject) => {
290309
const strict = objectGetOwn(config, 'strict') ?? true;
291310
const allowPositionals = objectGetOwn(config, 'allowPositionals') ?? !strict;
292311
const returnTokens = objectGetOwn(config, 'tokens') ?? false;
312+
const allowNegative = objectGetOwn(config, 'allowNegative') ?? false;
293313
const options = objectGetOwn(config, 'options') ?? { __proto__: null };
294314
// Bundle these up for passing to strict-mode checks.
295-
const parseConfig = { args, strict, options, allowPositionals };
315+
const parseConfig = { args, strict, options, allowPositionals, allowNegative };
296316

297317
// Validate input configuration.
298318
validateArray(args, 'args');
299319
validateBoolean(strict, 'strict');
300320
validateBoolean(allowPositionals, 'allowPositionals');
301321
validateBoolean(returnTokens, 'tokens');
322+
validateBoolean(allowNegative, 'allowNegative');
302323
validateObject(options, 'options');
303324
ArrayPrototypeForEach(
304325
ObjectEntries(options),
@@ -360,7 +381,7 @@ const parseArgs = (config = kEmptyObject) => {
360381
checkOptionUsage(parseConfig, token);
361382
checkOptionLikeValue(token);
362383
}
363-
storeOption(token.name, token.value, options, result.values);
384+
storeOption(token, options, result.values, parseConfig.allowNegative);
364385
} else if (token.kind === 'positional') {
365386
if (!allowPositionals) {
366387
throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(token.value);

test/parallel/test-parse-args.mjs

Lines changed: 70 additions & 0 deletions

0 commit comments

Comments
 (0)