6
6
ArrayPrototypeShift,
7
7
ArrayPrototypeSlice,
8
8
ArrayPrototypePush,
9
- ObjectPrototypeHasOwnProperty : ObjectHasOwn ,
9
+ ObjectDefineProperty ,
10
10
ObjectEntries,
11
+ ObjectPrototypeHasOwnProperty : ObjectHasOwn ,
11
12
StringPrototypeCharAt,
12
13
StringPrototypeIncludes,
13
14
StringPrototypeIndexOf,
@@ -29,7 +30,9 @@ const {
29
30
isLongOptionAndValue,
30
31
isOptionValue,
31
32
isShortOptionAndValue,
32
- isShortOptionGroup
33
+ isShortOptionGroup,
34
+ objectGetOwn,
35
+ optionsGetOwn
33
36
} = require ( './utils' ) ;
34
37
35
38
const {
@@ -74,61 +77,83 @@ function getMainArgs() {
74
77
return ArrayPrototypeSlice ( process . argv , 2 ) ;
75
78
}
76
79
77
- const protoKey = '__proto__' ;
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 } , ` : '' ;
80
+ /**
81
+ * In strict mode, throw for usage errors.
82
+ *
83
+ * @param {string } longOption - long option name e.g. 'foo'
84
+ * @param {string|undefined } optionValue - value from user args
85
+ * @param {Object } options - option configs, from parseArgs({ options })
86
+ * @param {string } shortOrLong - option used, with dashes e.g. `-l` or `--long`
87
+ * @param {boolean } strict - show errors, from parseArgs({ strict })
88
+ */
89
+ function checkOptionUsage ( longOption , optionValue , options ,
90
+ shortOrLong , strict ) {
91
+ // Strict and options are used from local context.
92
+ if ( ! strict ) return ;
96
93
97
- if ( options [ longOption ] . type === 'string' && optionValue == null ) {
98
- throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE ( `Option ' ${ shortOptionErr } -- ${ longOption } <value>' argument missing` ) ;
99
- }
94
+ if ( ! ObjectHasOwn ( options , longOption ) ) {
95
+ throw new ERR_PARSE_ARGS_UNKNOWN_OPTION ( shortOrLong ) ;
96
+ }
100
97
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
- }
98
+ const short = optionsGetOwn ( options , longOption , 'short' ) ;
99
+ const shortAndLong = short ? `-${ short } , --${ longOption } ` : `--${ longOption } ` ;
100
+ const type = optionsGetOwn ( options , longOption , 'type' ) ;
101
+ if ( type === 'string' && typeof optionValue !== 'string' ) {
102
+ throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE ( `Option '${ shortAndLong } <value>' argument missing` ) ;
103
+ }
104
+ // (Idiomatic test for undefined||null, expecting undefined.)
105
+ if ( type === 'boolean' && optionValue != null ) {
106
+ throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE ( `Option '${ shortAndLong } ' does not take an argument` ) ;
104
107
}
108
+ }
105
109
106
- if ( longOption === protoKey ) {
110
+ /**
111
+ * Store the option value in `values`.
112
+ *
113
+ * @param {string } longOption - long option name e.g. 'foo'
114
+ * @param {string|undefined } optionValue - value from user args
115
+ * @param {Object } options - option configs, from parseArgs({ options })
116
+ * @param {Object } values - option values returned in `values` by parseArgs
117
+ */
118
+ function storeOption ( longOption , optionValue , options , values ) {
119
+ if ( longOption === '__proto__' ) {
107
120
return ;
108
121
}
109
122
110
- // Values
111
- const usedAsFlag = optionValue === undefined ;
112
- const newValue = usedAsFlag ? true : optionValue ;
113
- if ( optionConfig . multiple ) {
114
- // Always store value in array, including for flags.
115
- // result.values[longOption] starts out not present,
123
+ // Can be removed when value has a null prototype
124
+ const safeAssignProperty = ( obj , prop , value ) => {
125
+ ObjectDefineProperty ( obj , prop , {
126
+ value,
127
+ writable : true ,
128
+ enumerable : true ,
129
+ configurable : true
130
+ } ) ;
131
+ } ;
132
+
133
+ // We store based on the option value rather than option type,
134
+ // preserving the users intent for author to deal with.
135
+ const newValue = optionValue ?? true ;
136
+ if ( optionsGetOwn ( options , longOption , 'multiple' ) ) {
137
+ // Always store value in array, including for boolean.
138
+ // values[longOption] starts out not present,
116
139
// first value is added as new array [newValue],
117
140
// subsequent values are pushed to existing array.
118
- if ( result . values [ longOption ] !== undefined )
119
- ArrayPrototypePush ( result . values [ longOption ] , newValue ) ;
120
- else
121
- result . values [ longOption ] = [ newValue ] ;
141
+ if ( ObjectHasOwn ( values , longOption ) ) {
142
+ ArrayPrototypePush ( values [ longOption ] , newValue ) ;
143
+ } else {
144
+ safeAssignProperty ( values , longOption , [ newValue ] ) ;
145
+ }
122
146
} else {
123
- result . values [ longOption ] = newValue ;
147
+ safeAssignProperty ( values , longOption , newValue ) ;
124
148
}
125
149
}
126
150
127
- const parseArgs = ( {
128
- args = getMainArgs ( ) ,
129
- strict = true ,
130
- options = { }
131
- } = { } ) => {
151
+ const parseArgs = ( config = { __proto__ : null } ) => {
152
+ const args = objectGetOwn ( config , 'args' ) ?? getMainArgs ( ) ;
153
+ const strict = objectGetOwn ( config , 'strict' ) ?? true ;
154
+ const options = objectGetOwn ( config , 'options' ) ?? { __proto__ : null } ;
155
+
156
+ // Validate input configuration.
132
157
validateArray ( args , 'args' ) ;
133
158
validateBoolean ( strict , 'strict' ) ;
134
159
validateObject ( options , 'options' ) ;
@@ -137,7 +162,8 @@ const parseArgs = ({
137
162
( { 0 : longOption , 1 : optionConfig } ) => {
138
163
validateObject ( optionConfig , `options.${ longOption } ` ) ;
139
164
140
- validateUnion ( optionConfig . type , `options.${ longOption } .type` , [ 'string' , 'boolean' ] ) ;
165
+ // type is required
166
+ validateUnion ( objectGetOwn ( optionConfig , 'type' ) , `options.${ longOption } .type` , [ 'string' , 'boolean' ] ) ;
141
167
142
168
if ( ObjectHasOwn ( optionConfig , 'short' ) ) {
143
169
const shortOption = optionConfig . short ;
@@ -183,18 +209,13 @@ const parseArgs = ({
183
209
const shortOption = StringPrototypeCharAt ( arg , 1 ) ;
184
210
const longOption = findLongOptionForShort ( shortOption , options ) ;
185
211
let optionValue ;
186
- if ( options [ longOption ] ?. type === 'string' && isOptionValue ( nextArg ) ) {
212
+ if ( optionsGetOwn ( options , longOption , 'type' ) === 'string' &&
213
+ isOptionValue ( nextArg ) ) {
187
214
// e.g. '-f', 'bar'
188
215
optionValue = ArrayPrototypeShift ( remainingArgs ) ;
189
216
}
190
- storeOption ( {
191
- strict,
192
- options,
193
- result,
194
- longOption,
195
- shortOption,
196
- optionValue,
197
- } ) ;
217
+ checkOptionUsage ( longOption , optionValue , options , arg , strict ) ;
218
+ storeOption ( longOption , optionValue , options , result . values ) ;
198
219
continue ;
199
220
}
200
221
@@ -204,7 +225,7 @@ const parseArgs = ({
204
225
for ( let index = 1 ; index < arg . length ; index ++ ) {
205
226
const shortOption = StringPrototypeCharAt ( arg , index ) ;
206
227
const longOption = findLongOptionForShort ( shortOption , options ) ;
207
- if ( options [ longOption ] ?. type !== 'string' ||
228
+ if ( optionsGetOwn ( options , longOption , ' type' ) !== 'string' ||
208
229
index === arg . length - 1 ) {
209
230
// Boolean option, or last short in group. Well formed.
210
231
ArrayPrototypePush ( expanded , `-${ shortOption } ` ) ;
@@ -224,26 +245,22 @@ const parseArgs = ({
224
245
const shortOption = StringPrototypeCharAt ( arg , 1 ) ;
225
246
const longOption = findLongOptionForShort ( shortOption , options ) ;
226
247
const optionValue = StringPrototypeSlice ( arg , 2 ) ;
227
- storeOption ( {
228
- strict,
229
- options,
230
- result,
231
- longOption,
232
- shortOption,
233
- optionValue,
234
- } ) ;
248
+ checkOptionUsage ( longOption , optionValue , options , `-${ shortOption } ` , strict ) ;
249
+ storeOption ( longOption , optionValue , options , result . values ) ;
235
250
continue ;
236
251
}
237
252
238
253
if ( isLoneLongOption ( arg ) ) {
239
254
// e.g. '--foo'
240
255
const longOption = StringPrototypeSlice ( arg , 2 ) ;
241
256
let optionValue ;
242
- if ( options [ longOption ] ?. type === 'string' && isOptionValue ( nextArg ) ) {
257
+ if ( optionsGetOwn ( options , longOption , 'type' ) === 'string' &&
258
+ isOptionValue ( nextArg ) ) {
243
259
// e.g. '--foo', 'bar'
244
260
optionValue = ArrayPrototypeShift ( remainingArgs ) ;
245
261
}
246
- storeOption ( { strict, options, result, longOption, optionValue } ) ;
262
+ checkOptionUsage ( longOption , optionValue , options , arg , strict ) ;
263
+ storeOption ( longOption , optionValue , options , result . values ) ;
247
264
continue ;
248
265
}
249
266
@@ -252,7 +269,8 @@ const parseArgs = ({
252
269
const index = StringPrototypeIndexOf ( arg , '=' ) ;
253
270
const longOption = StringPrototypeSlice ( arg , 2 , index ) ;
254
271
const optionValue = StringPrototypeSlice ( arg , index + 1 ) ;
255
- storeOption ( { strict, options, result, longOption, optionValue } ) ;
272
+ checkOptionUsage ( longOption , optionValue , options , `--${ longOption } ` , strict ) ;
273
+ storeOption ( longOption , optionValue , options , result . values ) ;
256
274
continue ;
257
275
}
258
276
0 commit comments