| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent befabe5 commit 50733a1
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1390,6 +1390,9 @@ added: | |||
| 1390 | 1390 | - v18.3.0 | |
| 1391 | 1391 | - v16.17.0 | |
| 1392 | 1392 | changes: | |
| 1393 | + - version: REPLACEME | ||
| 1394 | + pr-url: https://github.com/nodejs/node/pull/53107 | ||
| 1395 | + description: add support for allowing negative options in input `config`. | ||
| 1393 | 1396 | - version: | |
| 1394 | 1397 | - v20.0.0 | |
| 1395 | 1398 | pr-url: https://github.com/nodejs/node/pull/46718 | |
@@ -1429,6 +1432,9 @@ changes: | |||
| 1429 | 1432 | * `allowPositionals` {boolean} Whether this command accepts positional | |
| 1430 | 1433 | arguments. | |
| 1431 | 1434 | **Default:** `false` if `strict` is `true`, otherwise `true`. | |
| 1435 | + * `allowNegative` {boolean} If `true`, allows explicitly setting boolean | ||
| 1436 | + options to `false` by prefixing the option name with `--no-`. | ||
| 1437 | + **Default:** `false`. | ||
| 1432 | 1438 | * `tokens` {boolean} Return the parsed tokens. This is useful for extending | |
| 1433 | 1439 | the built-in behavior, from adding additional checks through to reprocessing | |
| 1434 | 1440 | the tokens in different ways. | |
@@ -1511,9 +1517,9 @@ that appear more than once in args produce a token for each use. Short option | |||
| 1511 | 1517 | groups like `-xy` expand to a token for each option. So `-xxx` produces | |
| 1512 | 1518 | three tokens. | |
| 1513 | 1519 | ||
| 1514 | - For example to use the returned tokens to add support for a negated option | ||
| 1515 | - like `--no-color`, the tokens can be reprocessed to change the value stored | ||
| 1516 | - for the negated option. | ||
| 1520 | + For example, to add support for a negated option like `--no-color` (which | ||
| 1521 | + `allowNegative` supports when the option is of `boolean` type), the returned | ||
| 1522 | + tokens can be reprocessed to change the value stored for the negated option. | ||
| 1517 | 1523 | ||
| 1518 | 1524 | ```mjs | |
| 1519 | 1525 | import { parseArgs } from 'node:util'; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -94,14 +94,24 @@ To specify an option argument starting with a dash use ${example}.`; | |||
| 94 | 94 | * @param {object} token - from tokens as available from parseArgs | |
| 95 | 95 | */ | |
| 96 | 96 | 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 | + } | ||
| 100 | 110 | } | |
| 101 | 111 | ||
| 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'); | ||
| 105 | 115 | if (type === 'string' && typeof token.value !== 'string') { | |
| 106 | 116 | throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortAndLong} <value>' argument missing`); | |
| 107 | 117 | } | |
@@ -114,16 +124,25 @@ function checkOptionUsage(config, token) { | |||
| 114 | 124 | ||
| 115 | 125 | /** | |
| 116 | 126 | * 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 | ||
| 119 | 128 | * @param {object} options - option configs, from parseArgs({ options }) | |
| 120 | 129 | * @param {object} values - option values returned in `values` by parseArgs | |
| 130 | + * @param {boolean} allowNegative - allow negative optinons if true | ||
| 121 | 131 | */ | |
| 122 | - function storeOption(longOption, optionValue, options, values) { | ||
| 132 | + function storeOption(token, options, values, allowNegative) { | ||
| 133 | + let longOption = token.name; | ||
| 134 | + let optionValue = token.value; | ||
| 123 | 135 | if (longOption === '__proto__') { | |
| 124 | 136 | return; // No. Just no. | |
| 125 | 137 | } | |
| 126 | 138 | ||
| 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 | + | ||
| 127 | 146 | // We store based on the option value rather than option type, | |
| 128 | 147 | // preserving the users intent for author to deal with. | |
| 129 | 148 | const newValue = optionValue ?? true; | |
@@ -290,15 +309,17 @@ const parseArgs = (config = kEmptyObject) => { | |||
| 290 | 309 | const strict = objectGetOwn(config, 'strict') ?? true; | |
| 291 | 310 | const allowPositionals = objectGetOwn(config, 'allowPositionals') ?? !strict; | |
| 292 | 311 | const returnTokens = objectGetOwn(config, 'tokens') ?? false; | |
| 312 | + const allowNegative = objectGetOwn(config, 'allowNegative') ?? false; | ||
| 293 | 313 | const options = objectGetOwn(config, 'options') ?? { __proto__: null }; | |
| 294 | 314 | // Bundle these up for passing to strict-mode checks. | |
| 295 | - const parseConfig = { args, strict, options, allowPositionals }; | ||
| 315 | + const parseConfig = { args, strict, options, allowPositionals, allowNegative }; | ||
| 296 | 316 | ||
| 297 | 317 | // Validate input configuration. | |
| 298 | 318 | validateArray(args, 'args'); | |
| 299 | 319 | validateBoolean(strict, 'strict'); | |
| 300 | 320 | validateBoolean(allowPositionals, 'allowPositionals'); | |
| 301 | 321 | validateBoolean(returnTokens, 'tokens'); | |
| 322 | + validateBoolean(allowNegative, 'allowNegative'); | ||
| 302 | 323 | validateObject(options, 'options'); | |
| 303 | 324 | ArrayPrototypeForEach( | |
| 304 | 325 | ObjectEntries(options), | |
@@ -360,7 +381,7 @@ const parseArgs = (config = kEmptyObject) => { | |||
| 360 | 381 | checkOptionUsage(parseConfig, token); | |
| 361 | 382 | checkOptionLikeValue(token); | |
| 362 | 383 | } | |
| 363 | - storeOption(token.name, token.value, options, result.values); | ||
| 384 | + storeOption(token, options, result.values, parseConfig.allowNegative); | ||
| 364 | 385 | } else if (token.kind === 'positional') { | |
| 365 | 386 | if (!allowPositionals) { | |
| 366 | 387 | throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(token.value); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -992,3 +992,73 @@ test('multiple as false should expect a String', () => { | |||
| 992 | 992 | }, /"options\.alpha\.default" property must be of type string/ | |
| 993 | 993 | ); | |
| 994 | 994 | }); | |
| 995 | + | ||
| 996 | + // Test negative options | ||
| 997 | + test('disable negative options and args are started with "--no-" prefix', () => { | ||
| 998 | + const args = ['--no-alpha']; | ||
| 999 | + const options = { alpha: { type: 'boolean' } }; | ||
| 1000 | + assert.throws(() => { | ||
| 1001 | + parseArgs({ args, options }); | ||
| 1002 | + }, { | ||
| 1003 | + code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' | ||
| 1004 | + }); | ||
| 1005 | + }); | ||
| 1006 | + | ||
| 1007 | + test('args are passed `type: "string"` and allow negative options', () => { | ||
| 1008 | + const args = ['--no-alpha', 'value']; | ||
| 1009 | + const options = { alpha: { type: 'string' } }; | ||
| 1010 | + assert.throws(() => { | ||
| 1011 | + parseArgs({ args, options, allowNegative: true }); | ||
| 1012 | + }, { | ||
| 1013 | + code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' | ||
| 1014 | + }); | ||
| 1015 | + }); | ||
| 1016 | + | ||
| 1017 | + test('args are passed `type: "boolean"` and allow negative options', () => { | ||
| 1018 | + const args = ['--no-alpha']; | ||
| 1019 | + const options = { alpha: { type: 'boolean' } }; | ||
| 1020 | + const expected = { values: { __proto__: null, alpha: false }, positionals: [] }; | ||
| 1021 | + assert.deepStrictEqual(parseArgs({ args, options, allowNegative: true }), expected); | ||
| 1022 | + }); | ||
| 1023 | + | ||
| 1024 | + test('args are passed `default: "true"` and allow negative options', () => { | ||
| 1025 | + const args = ['--no-alpha']; | ||
| 1026 | + const options = { alpha: { type: 'boolean', default: true } }; | ||
| 1027 | + const expected = { values: { __proto__: null, alpha: false }, positionals: [] }; | ||
| 1028 | + assert.deepStrictEqual(parseArgs({ args, options, allowNegative: true }), expected); | ||
| 1029 | + }); | ||
| 1030 | + | ||
| 1031 | + test('args are passed `default: "false" and allow negative options', () => { | ||
| 1032 | + const args = ['--no-alpha']; | ||
| 1033 | + const options = { alpha: { type: 'boolean', default: false } }; | ||
| 1034 | + const expected = { values: { __proto__: null, alpha: false }, positionals: [] }; | ||
| 1035 | + assert.deepStrictEqual(parseArgs({ args, options, allowNegative: true }), expected); | ||
| 1036 | + }); | ||
| 1037 | + | ||
| 1038 | + test('allow negative options and multiple as true', () => { | ||
| 1039 | + const args = ['--no-alpha', '--alpha', '--no-alpha']; | ||
| 1040 | + const options = { alpha: { type: 'boolean', multiple: true } }; | ||
| 1041 | + const expected = { values: { __proto__: null, alpha: [false, true, false] }, positionals: [] }; | ||
| 1042 | + assert.deepStrictEqual(parseArgs({ args, options, allowNegative: true }), expected); | ||
| 1043 | + }); | ||
| 1044 | + | ||
| 1045 | + test('allow negative options and passed multiple arguments', () => { | ||
| 1046 | + const args = ['--no-alpha', '--alpha']; | ||
| 1047 | + const options = { alpha: { type: 'boolean' } }; | ||
| 1048 | + const expected = { values: { __proto__: null, alpha: true }, positionals: [] }; | ||
| 1049 | + assert.deepStrictEqual(parseArgs({ args, options, allowNegative: true }), expected); | ||
| 1050 | + }); | ||
| 1051 | + | ||
| 1052 | + test('auto-detect --no-foo as negated when strict:false and allowNegative', () => { | ||
| 1053 | + const holdArgv = process.argv; | ||
| 1054 | + process.argv = [process.argv0, 'script.js', '--no-foo']; | ||
| 1055 | + const holdExecArgv = process.execArgv; | ||
| 1056 | + process.execArgv = []; | ||
| 1057 | + const result = parseArgs({ strict: false, allowNegative: true }); | ||
| 1058 | + | ||
| 1059 | + const expected = { values: { __proto__: null, foo: false }, | ||
| 1060 | + positionals: [] }; | ||
| 1061 | + assert.deepStrictEqual(result, expected); | ||
| 1062 | + process.argv = holdArgv; | ||
| 1063 | + process.execArgv = holdExecArgv; | ||
| 1064 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments