Skip to content

Commit 9549cf9

Browse files
authored
refactor: tweaks for landing in Node.js (#89)
1 parent 835b17a commit 9549cf9

File tree

5 files changed

+49
-8
lines changed

5 files changed

+49
-8
lines changed

errors.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ class ERR_INVALID_ARG_TYPE extends TypeError {
77
}
88
}
99

10+
class ERR_INVALID_SHORT_OPTION extends TypeError {
11+
constructor(longOption, shortOption) {
12+
super(`options.${longOption}.short must be a single character, got '${shortOption}'`);
13+
this.code = 'ERR_INVALID_SHORT_OPTION';
14+
}
15+
}
16+
1017
module.exports = {
1118
codes: {
1219
ERR_INVALID_ARG_TYPE,
20+
ERR_INVALID_SHORT_OPTION
1321
}
1422
};

index.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const {
66
ArrayPrototypeShift,
77
ArrayPrototypeSlice,
88
ArrayPrototypePush,
9-
ObjectHasOwn,
9+
ObjectPrototypeHasOwnProperty: ObjectHasOwn,
1010
ObjectEntries,
1111
StringPrototypeCharAt,
1212
StringPrototypeIncludes,
@@ -32,6 +32,12 @@ const {
3232
isShortOptionGroup
3333
} = require('./utils');
3434

35+
const {
36+
codes: {
37+
ERR_INVALID_SHORT_OPTION,
38+
},
39+
} = require('./errors');
40+
3541
function getMainArgs() {
3642
// This function is a placeholder for proposed process.mainArgs.
3743
// Work out where to slice process.argv for user supplied arguments.
@@ -66,12 +72,17 @@ function getMainArgs() {
6672
return ArrayPrototypeSlice(process.argv, 2);
6773
}
6874

75+
const protoKey = '__proto__';
6976
function storeOptionValue(options, longOption, value, result) {
7077
const optionConfig = options[longOption] || {};
7178

7279
// Flags
7380
result.flags[longOption] = true;
7481

82+
if (longOption === protoKey) {
83+
return;
84+
}
85+
7586
// Values
7687
if (optionConfig.multiple) {
7788
// Always store value in array, including for flags.
@@ -97,7 +108,7 @@ const parseArgs = ({
97108
validateObject(options, 'options');
98109
ArrayPrototypeForEach(
99110
ObjectEntries(options),
100-
([longOption, optionConfig]) => {
111+
({ 0: longOption, 1: optionConfig }) => {
101112
validateObject(optionConfig, `options.${longOption}`);
102113

103114
if (ObjectHasOwn(optionConfig, 'type')) {
@@ -108,7 +119,7 @@ const parseArgs = ({
108119
const shortOption = optionConfig.short;
109120
validateString(shortOption, `options.${longOption}.short`);
110121
if (shortOption.length !== 1) {
111-
throw new Error(`options.${longOption}.short must be a single character, got '${shortOption}'`);
122+
throw new ERR_INVALID_SHORT_OPTION(longOption, shortOption);
112123
}
113124
}
114125

test/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,9 @@ test('invalid short option length', function(t) {
385385
const passedArgs = [];
386386
const passedOptions = { foo: { short: 'fo' } };
387387

388-
t.throws(function() { parseArgs({ args: passedArgs, options: passedOptions }); });
388+
t.throws(function() { parseArgs({ args: passedArgs, options: passedOptions }); }, {
389+
code: 'ERR_INVALID_SHORT_OPTION'
390+
});
389391

390392
t.end();
391393
});

test/prototype-pollution.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
/* eslint max-len: 0 */
3+
4+
const test = require('tape');
5+
const { parseArgs } = require('../index.js');
6+
7+
test('should not allow __proto__ key to be set on object', (t) => {
8+
const passedArgs = ['--__proto__=hello'];
9+
const expected = { flags: {}, values: {}, positionals: [] };
10+
11+
const result = parseArgs({ args: passedArgs });
12+
13+
t.deepEqual(result, expected);
14+
t.end();
15+
});

utils.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ const {
99
StringPrototypeStartsWith,
1010
} = require('./primordials');
1111

12+
const {
13+
validateObject
14+
} = require('./validators');
15+
1216
// These are internal utilities to make the parsing logic easier to read, and
1317
// add lots of detail for the curious. They are in a separate file to allow
1418
// unit testing, although that is not essential (this could be rolled into
@@ -116,7 +120,8 @@ function isShortOptionGroup(arg, options) {
116120
* }) // returns true
117121
*/
118122
function isShortOptionAndValue(arg, options) {
119-
if (!options) throw new Error('Internal error, missing options argument');
123+
validateObject(options, 'options');
124+
120125
if (arg.length <= 2) return false;
121126
if (StringPrototypeCharAt(arg, 0) !== '-') return false;
122127
if (StringPrototypeCharAt(arg, 1) === '-') return false;
@@ -136,10 +141,10 @@ function isShortOptionAndValue(arg, options) {
136141
* }) // returns 'bar'
137142
*/
138143
function findLongOptionForShort(shortOption, options) {
139-
if (!options) throw new Error('Internal error, missing options argument');
140-
const [longOption] = ArrayPrototypeFind(
144+
validateObject(options, 'options');
145+
const { 0: longOption } = ArrayPrototypeFind(
141146
ObjectEntries(options),
142-
([, optionConfig]) => optionConfig.short === shortOption
147+
({ 1: optionConfig }) => optionConfig.short === shortOption
143148
) || [];
144149
return longOption || shortOption;
145150
}

0 commit comments

Comments
 (0)