66 ArrayPrototypeShift,
77 ArrayPrototypeSlice,
88 ArrayPrototypePush,
9- ObjectPrototypeHasOwnProperty : ObjectHasOwn ,
9+ ObjectDefineProperty ,
1010 ObjectEntries,
11+ ObjectPrototypeHasOwnProperty : ObjectHasOwn ,
1112 StringPrototypeCharAt,
1213 StringPrototypeIncludes,
1314 StringPrototypeIndexOf,
@@ -29,7 +30,9 @@ const {
2930 isLongOptionAndValue,
3031 isOptionValue,
3132 isShortOptionAndValue,
32- isShortOptionGroup
33+ isShortOptionGroup,
34+ objectGetOwn,
35+ optionsGetOwn
3336} = require ( './utils' ) ;
3437
3538const {
@@ -74,61 +77,83 @@ function getMainArgs() {
7477 return ArrayPrototypeSlice ( process . argv , 2 ) ;
7578}
7679
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 ;
9693
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+ }
10097
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` ) ;
104107 }
108+ }
105109
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__' ) {
107120 return ;
108121 }
109122
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,
116139 // first value is added as new array [newValue],
117140 // 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+ }
122146 } else {
123- result . values [ longOption ] = newValue ;
147+ safeAssignProperty ( values , longOption , newValue ) ;
124148 }
125149}
126150
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.
132157 validateArray ( args , 'args' ) ;
133158 validateBoolean ( strict , 'strict' ) ;
134159 validateObject ( options , 'options' ) ;
@@ -137,7 +162,8 @@ const parseArgs = ({
137162 ( { 0 : longOption , 1 : optionConfig } ) => {
138163 validateObject ( optionConfig , `options.${ longOption } ` ) ;
139164
140- validateUnion ( optionConfig . type , `options.${ longOption } .type` , [ 'string' , 'boolean' ] ) ;
165+ // type is required
166+ validateUnion ( objectGetOwn ( optionConfig , 'type' ) , `options.${ longOption } .type` , [ 'string' , 'boolean' ] ) ;
141167
142168 if ( ObjectHasOwn ( optionConfig , 'short' ) ) {
143169 const shortOption = optionConfig . short ;
@@ -183,18 +209,13 @@ const parseArgs = ({
183209 const shortOption = StringPrototypeCharAt ( arg , 1 ) ;
184210 const longOption = findLongOptionForShort ( shortOption , options ) ;
185211 let optionValue ;
186- if ( options [ longOption ] ?. type === 'string' && isOptionValue ( nextArg ) ) {
212+ if ( optionsGetOwn ( options , longOption , 'type' ) === 'string' &&
213+ isOptionValue ( nextArg ) ) {
187214 // e.g. '-f', 'bar'
188215 optionValue = ArrayPrototypeShift ( remainingArgs ) ;
189216 }
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 ) ;
198219 continue ;
199220 }
200221
@@ -204,7 +225,7 @@ const parseArgs = ({
204225 for ( let index = 1 ; index < arg . length ; index ++ ) {
205226 const shortOption = StringPrototypeCharAt ( arg , index ) ;
206227 const longOption = findLongOptionForShort ( shortOption , options ) ;
207- if ( options [ longOption ] ?. type !== 'string' ||
228+ if ( optionsGetOwn ( options , longOption , ' type' ) !== 'string' ||
208229 index === arg . length - 1 ) {
209230 // Boolean option, or last short in group. Well formed.
210231 ArrayPrototypePush ( expanded , `-${ shortOption } ` ) ;
@@ -224,26 +245,22 @@ const parseArgs = ({
224245 const shortOption = StringPrototypeCharAt ( arg , 1 ) ;
225246 const longOption = findLongOptionForShort ( shortOption , options ) ;
226247 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 ) ;
235250 continue ;
236251 }
237252
238253 if ( isLoneLongOption ( arg ) ) {
239254 // e.g. '--foo'
240255 const longOption = StringPrototypeSlice ( arg , 2 ) ;
241256 let optionValue ;
242- if ( options [ longOption ] ?. type === 'string' && isOptionValue ( nextArg ) ) {
257+ if ( optionsGetOwn ( options , longOption , 'type' ) === 'string' &&
258+ isOptionValue ( nextArg ) ) {
243259 // e.g. '--foo', 'bar'
244260 optionValue = ArrayPrototypeShift ( remainingArgs ) ;
245261 }
246- storeOption ( { strict, options, result, longOption, optionValue } ) ;
262+ checkOptionUsage ( longOption , optionValue , options , arg , strict ) ;
263+ storeOption ( longOption , optionValue , options , result . values ) ;
247264 continue ;
248265 }
249266
@@ -252,7 +269,8 @@ const parseArgs = ({
252269 const index = StringPrototypeIndexOf ( arg , '=' ) ;
253270 const longOption = StringPrototypeSlice ( arg , 2 , index ) ;
254271 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 ) ;
256274 continue ;
257275 }
258276
0 commit comments