Skip to content

Commit 8267d02

Browse files
feat: Add strict mode to parser (#74)
This PR introduces a strict mode parser config that is enabled by default. Errors on: - Unknown option encountered - Option of type:'string' used like a boolean option e.g. lone --string - Option of type:'boolean' used like a string option e.g. --boolean=foo
1 parent 8232d36 commit 8267d02

6 files changed

+175
-48
lines changed

README.md

+20-20
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ process.mainArgs = process.argv.slice(process._exec ? 1 : 2)
8484
* `type` {'string'|'boolean'} (Required) Type of known option
8585
* `multiple` {boolean} (Optional) If true, when appearing one or more times in `args`, results are collected in an `Array`
8686
* `short` {string} (Optional) A single character alias for an option; When appearing one or more times in `args`; Respects the `multiple` configuration
87-
* `strict` {Boolean} (Optional) A `Boolean` on wheather or not to throw an error when unknown args are encountered
87+
* `strict` {Boolean} (Optional) A `Boolean` for whether or not to throw an error when unknown options are encountered, `type:'string'` options are missing an options-argument, or `type:'boolean'` options are passed an options-argument; defaults to `true`
8888
* Returns: {Object} An object having properties:
8989
* `values` {Object}, key:value for each option found. Value is a string for string options, or `true` for boolean options, or an array (of strings or booleans) for options configured as `multiple:true`.
9090
* `positionals` {string[]}, containing [Positionals][]
@@ -98,59 +98,59 @@ const { parseArgs } = require('@pkgjs/parseargs');
9898
```
9999

100100
```js
101-
// unconfigured
102101
const { parseArgs } = require('@pkgjs/parseargs');
103-
const args = ['-f', '--foo=a', '--bar', 'b'];
104-
const options = {};
105-
const { values, positionals } = parseArgs({ args, options });
106-
// values = { f: true, foo: 'a', bar: true }
107-
// positionals = ['b']
108-
```
109-
110-
```js
111-
const { parseArgs } = require('@pkgjs/parseargs');
112-
// type:string
113-
const args = ['-f', '--foo=a', '--bar', 'b'];
102+
// specify the options that may be used
114103
const options = {
115-
bar: {
116-
type: 'string',
117-
},
104+
foo: { type: 'string'},
105+
bar: { type: 'boolean' },
118106
};
107+
const args = ['--foo=a', '--bar'];
119108
const { values, positionals } = parseArgs({ args, options });
120-
// values = { f: true, foo: 'a', bar: 'b' }
109+
// values = { foo: 'a', bar: true }
121110
// positionals = []
122111
```
123112

124113
```js
125114
const { parseArgs } = require('@pkgjs/parseargs');
126115
// type:string & multiple
127-
const args = ['-f', '--foo=a', '--foo', 'b'];
128116
const options = {
129117
foo: {
130118
type: 'string',
131119
multiple: true,
132120
},
133121
};
122+
const args = ['--foo=a', '--foo', 'b'];
134123
const { values, positionals } = parseArgs({ args, options });
135-
// values = { f: true, foo: [ 'a', 'b' ] }
124+
// values = { foo: [ 'a', 'b' ] }
136125
// positionals = []
137126
```
138127

139128
```js
140129
const { parseArgs } = require('@pkgjs/parseargs');
141130
// shorts
142-
const args = ['-f', 'b'];
143131
const options = {
144132
foo: {
145133
short: 'f',
146134
type: 'boolean'
147135
},
148136
};
137+
const args = ['-f', 'b'];
149138
const { values, positionals } = parseArgs({ args, options });
150139
// values = { foo: true }
151140
// positionals = ['b']
152141
```
153142

143+
```js
144+
const { parseArgs } = require('@pkgjs/parseargs');
145+
// unconfigured
146+
const options = {};
147+
const args = ['-f', '--foo=a', '--bar', 'b'];
148+
const { values, positionals } = parseArgs({ strict: false, args, options });
149+
// values = { f: true, foo: 'a', bar: true }
150+
// positionals = ['b']
151+
```
152+
153+
154154
### F.A.Qs
155155

156156
- Is `cmd --foo=bar baz` the same as `cmd baz --foo=bar`?

errors.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,25 @@ class ERR_INVALID_ARG_VALUE extends TypeError {
1414
}
1515
}
1616

17+
class ERR_PARSE_ARGS_INVALID_OPTION_VALUE extends Error {
18+
constructor(message) {
19+
super(message);
20+
this.code = 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE';
21+
}
22+
}
23+
24+
class ERR_PARSE_ARGS_UNKNOWN_OPTION extends Error {
25+
constructor(option) {
26+
super(`Unknown option '${option}'`);
27+
this.code = 'ERR_PARSE_ARGS_UNKNOWN_OPTION';
28+
}
29+
}
30+
1731
module.exports = {
1832
codes: {
1933
ERR_INVALID_ARG_TYPE,
20-
ERR_INVALID_ARG_VALUE
34+
ERR_INVALID_ARG_VALUE,
35+
ERR_PARSE_ARGS_INVALID_OPTION_VALUE,
36+
ERR_PARSE_ARGS_UNKNOWN_OPTION,
2137
}
2238
};

index.js

+51-8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const {
3535
const {
3636
codes: {
3737
ERR_INVALID_ARG_VALUE,
38+
ERR_PARSE_ARGS_INVALID_OPTION_VALUE,
39+
ERR_PARSE_ARGS_UNKNOWN_OPTION,
3840
},
3941
} = require('./errors');
4042

@@ -73,16 +75,41 @@ function getMainArgs() {
7375
}
7476

7577
const protoKey = '__proto__';
76-
function storeOptionValue(options, longOption, value, result) {
77-
const optionConfig = options[longOption] || {};
78+
79+
function storeOption({
80+
strict,
81+
options,
82+
result,
83+
longOption,
84+
shortOption,
85+
optionValue,
86+
}) {
87+
const hasOptionConfig = ObjectHasOwn(options, longOption);
88+
const optionConfig = hasOptionConfig ? options[longOption] : {};
89+
90+
if (strict) {
91+
if (!hasOptionConfig) {
92+
throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(shortOption == null ? `--${longOption}` : `-${shortOption}`);
93+
}
94+
95+
const shortOptionErr = ObjectHasOwn(optionConfig, 'short') ? `-${optionConfig.short}, ` : '';
96+
97+
if (options[longOption].type === 'string' && optionValue == null) {
98+
throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortOptionErr}--${longOption} <value>' argument missing`);
99+
}
100+
101+
if (options[longOption].type === 'boolean' && optionValue != null) {
102+
throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortOptionErr}--${longOption}' does not take an argument`);
103+
}
104+
}
78105

79106
if (longOption === protoKey) {
80107
return;
81108
}
82109

83110
// Values
84-
const usedAsFlag = value === undefined;
85-
const newValue = usedAsFlag ? true : value;
111+
const usedAsFlag = optionValue === undefined;
112+
const newValue = usedAsFlag ? true : optionValue;
86113
if (optionConfig.multiple) {
87114
// Always store value in array, including for flags.
88115
// result.values[longOption] starts out not present,
@@ -99,9 +126,11 @@ function storeOptionValue(options, longOption, value, result) {
99126

100127
const parseArgs = ({
101128
args = getMainArgs(),
129+
strict = true,
102130
options = {}
103131
} = {}) => {
104132
validateArray(args, 'args');
133+
validateBoolean(strict, 'strict');
105134
validateObject(options, 'options');
106135
ArrayPrototypeForEach(
107136
ObjectEntries(options),
@@ -158,7 +187,14 @@ const parseArgs = ({
158187
// e.g. '-f', 'bar'
159188
optionValue = ArrayPrototypeShift(remainingArgs);
160189
}
161-
storeOptionValue(options, longOption, optionValue, result);
190+
storeOption({
191+
strict,
192+
options,
193+
result,
194+
longOption,
195+
shortOption,
196+
optionValue,
197+
});
162198
continue;
163199
}
164200

@@ -188,7 +224,14 @@ const parseArgs = ({
188224
const shortOption = StringPrototypeCharAt(arg, 1);
189225
const longOption = findLongOptionForShort(shortOption, options);
190226
const optionValue = StringPrototypeSlice(arg, 2);
191-
storeOptionValue(options, longOption, optionValue, result);
227+
storeOption({
228+
strict,
229+
options,
230+
result,
231+
longOption,
232+
shortOption,
233+
optionValue,
234+
});
192235
continue;
193236
}
194237

@@ -200,7 +243,7 @@ const parseArgs = ({
200243
// e.g. '--foo', 'bar'
201244
optionValue = ArrayPrototypeShift(remainingArgs);
202245
}
203-
storeOptionValue(options, longOption, optionValue, result);
246+
storeOption({ strict, options, result, longOption, optionValue });
204247
continue;
205248
}
206249

@@ -209,7 +252,7 @@ const parseArgs = ({
209252
const index = StringPrototypeIndexOf(arg, '=');
210253
const longOption = StringPrototypeSlice(arg, 2, index);
211254
const optionValue = StringPrototypeSlice(arg, index + 1);
212-
storeOptionValue(options, longOption, optionValue, result);
255+
storeOption({ strict, options, result, longOption, optionValue });
213256
continue;
214257
}
215258

0 commit comments

Comments
 (0)