From 85c9325eaf6dc2edb9b11b5a91863501387427d2 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 15 Apr 2022 17:38:37 +1200 Subject: [PATCH 1/7] create result.values with null prototype --- index.js | 2 +- test/index.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 9e25e05..c0261af 100644 --- a/index.js +++ b/index.js @@ -158,7 +158,7 @@ const parseArgs = ({ ); const result = { - values: {}, + values: { __proto__: null }, positionals: [] }; diff --git a/test/index.js b/test/index.js index 492eeb9..a0db6c2 100644 --- a/test/index.js +++ b/test/index.js @@ -378,3 +378,17 @@ test('invalid short option length', () => { code: 'ERR_INVALID_ARG_VALUE' }); }); + +test('null prototype: when no options then values.toString is undefined', () => { + const result = parseArgs({ args: [] }); + assert.strictEqual(result.values.toString, undefined); +}); + +test('null prototype: when --toString then values.toString is true', () => { + const args = ['--toString']; + const options = { toString: { type: 'boolean' } }; + const expectedResult = { __proto__: null, values: { toString: true }, positionals: [] }; + + const result = parseArgs({ args, options }); + assert.deepStrictEqual(result, expectedResult); +}); From aaca554d1c98246416dff8a8054b264de06cf6d6 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 15 Apr 2022 17:47:04 +1200 Subject: [PATCH 2/7] Fix where applying null prototype --- test/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index a0db6c2..bc2236a 100644 --- a/test/index.js +++ b/test/index.js @@ -387,7 +387,7 @@ test('null prototype: when no options then values.toString is undefined', () => test('null prototype: when --toString then values.toString is true', () => { const args = ['--toString']; const options = { toString: { type: 'boolean' } }; - const expectedResult = { __proto__: null, values: { toString: true }, positionals: [] }; + const expectedResult = { values: { __proto__: null, toString: true }, positionals: [] }; const result = parseArgs({ args, options }); assert.deepStrictEqual(result, expectedResult); From 1b4b853d31558f24f97601613bb28e12643732c8 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 16 Apr 2022 15:21:48 +1200 Subject: [PATCH 3/7] Tests churn - add __proto__:null in expected values - rename passedArgs to args - rename passedOptions to options - rename args (result) to result --- test/dash.js | 14 +- test/index.js | 298 +++++++++++------------ test/prototype-pollution.js | 6 +- test/short-option-combined-with-value.js | 56 ++--- test/short-option-groups.js | 38 +-- test/store-user-intent.js | 18 +- 6 files changed, 215 insertions(+), 215 deletions(-) diff --git a/test/dash.js b/test/dash.js index 931b04f..dab82b2 100644 --- a/test/dash.js +++ b/test/dash.js @@ -12,10 +12,10 @@ const { parseArgs } = require('../index.js'); // A different usage and example is `git switch -` to switch back to the previous branch. test("dash: when args include '-' used as positional then result has '-' in positionals", (t) => { - const passedArgs = ['-']; - const expected = { values: {}, positionals: ['-'] }; + const args = ['-']; + const expected = { values: { __proto__: null }, positionals: ['-'] }; - const result = parseArgs({ args: passedArgs }); + const result = parseArgs({ args }); t.deepEqual(result, expected); t.end(); @@ -23,11 +23,11 @@ test("dash: when args include '-' used as positional then result has '-' in posi // If '-' is a valid positional, it is symmetrical to allow it as an option value too. test("dash: when args include '-' used as space-separated option value then result has '-' in option value", (t) => { - const passedArgs = ['-v', '-']; - const passedOptions = { v: { type: 'string' } }; - const expected = { values: { v: '-' }, positionals: [] }; + const args = ['-v', '-']; + const options = { v: { type: 'string' } }; + const expected = { values: { __proto__: null, v: '-' }, positionals: [] }; - const result = parseArgs({ args: passedArgs, options: passedOptions }); + const result = parseArgs({ args, options }); t.deepEqual(result, expected); t.end(); diff --git a/test/index.js b/test/index.js index bc2236a..0d2be1b 100644 --- a/test/index.js +++ b/test/index.js @@ -8,200 +8,200 @@ const { parseArgs } = require('../index'); // Test results are as we expect test('when short option used as flag then stored as flag', () => { - const passedArgs = ['-f']; - const expected = { values: { f: true }, positionals: [] }; - const args = parseArgs({ strict: false, args: passedArgs }); - assert.deepStrictEqual(args, expected); + const args = ['-f']; + const expected = { values: { __proto__: null, f: true }, positionals: [] }; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result, expected); }); test('when short option used as flag before positional then stored as flag and positional (and not value)', () => { - const passedArgs = ['-f', 'bar']; - const expected = { values: { f: true }, positionals: [ 'bar' ] }; - const args = parseArgs({ strict: false, args: passedArgs }); - assert.deepStrictEqual(args, expected); + const args = ['-f', 'bar']; + const expected = { values: { __proto__: null, f: true }, positionals: [ 'bar' ] }; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result, expected); }); test('when short option `type: "string"` used with value then stored as value', () => { - const passedArgs = ['-f', 'bar']; - const passedOptions = { f: { type: 'string' } }; - const expected = { values: { f: 'bar' }, positionals: [] }; - const args = parseArgs({ args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected); + const args = ['-f', 'bar']; + const options = { f: { type: 'string' } }; + const expected = { values: { __proto__: null, f: 'bar' }, positionals: [] }; + const result = parseArgs({ args, options }); + assert.deepStrictEqual(result, expected); }); test('when short option listed in short used as flag then long option stored as flag', () => { - const passedArgs = ['-f']; - const passedOptions = { foo: { short: 'f', type: 'boolean' } }; - const expected = { values: { foo: true }, positionals: [] }; - const args = parseArgs({ args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected); + const args = ['-f']; + const options = { foo: { short: 'f', type: 'boolean' } }; + const expected = { values: { __proto__: null, foo: true }, positionals: [] }; + const result = parseArgs({ args, options }); + assert.deepStrictEqual(result, expected); }); test('when short option listed in short and long listed in `type: "string"` and ' + 'used with value then long option stored as value', () => { - const passedArgs = ['-f', 'bar']; - const passedOptions = { foo: { short: 'f', type: 'string' } }; - const expected = { values: { foo: 'bar' }, positionals: [] }; - const args = parseArgs({ args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected); + const args = ['-f', 'bar']; + const options = { foo: { short: 'f', type: 'string' } }; + const expected = { values: { __proto__: null, foo: 'bar' }, positionals: [] }; + const result = parseArgs({ args, options }); + assert.deepStrictEqual(result, expected); }); test('when short option `type: "string"` used without value then stored as flag', () => { - const passedArgs = ['-f']; - const passedOptions = { f: { type: 'string' } }; - const expected = { values: { f: true }, positionals: [] }; - const args = parseArgs({ strict: false, args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected); + const args = ['-f']; + const options = { f: { type: 'string' } }; + const expected = { values: { __proto__: null, f: true }, positionals: [] }; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result, expected); }); test('short option group behaves like multiple short options', () => { - const passedArgs = ['-rf']; - const passedOptions = { }; - const expected = { values: { r: true, f: true }, positionals: [] }; - const args = parseArgs({ strict: false, args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected); + const args = ['-rf']; + const options = { }; + const expected = { values: { __proto__: null, r: true, f: true }, positionals: [] }; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result, expected); }); test('short option group does not consume subsequent positional', () => { - const passedArgs = ['-rf', 'foo']; - const passedOptions = { }; - const expected = { values: { r: true, f: true }, positionals: ['foo'] }; - const args = parseArgs({ strict: false, args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected); + const args = ['-rf', 'foo']; + const options = { }; + const expected = { values: { __proto__: null, r: true, f: true }, positionals: ['foo'] }; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result, expected); }); // See: Guideline 5 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html test('if terminal of short-option group configured `type: "string"`, subsequent positional is stored', () => { - const passedArgs = ['-rvf', 'foo']; - const passedOptions = { f: { type: 'string' } }; - const expected = { values: { r: true, v: true, f: 'foo' }, positionals: [] }; - const args = parseArgs({ strict: false, args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected); + const args = ['-rvf', 'foo']; + const options = { f: { type: 'string' } }; + const expected = { values: { __proto__: null, r: true, v: true, f: 'foo' }, positionals: [] }; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result, expected); }); test('handles short-option groups in conjunction with long-options', () => { - const passedArgs = ['-rf', '--foo', 'foo']; - const passedOptions = { foo: { type: 'string' } }; - const expected = { values: { r: true, f: true, foo: 'foo' }, positionals: [] }; - const args = parseArgs({ strict: false, args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected); + const args = ['-rf', '--foo', 'foo']; + const options = { foo: { type: 'string' } }; + const expected = { values: { __proto__: null, r: true, f: true, foo: 'foo' }, positionals: [] }; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result, expected); }); test('handles short-option groups with "short" alias configured', () => { - const passedArgs = ['-rf']; - const passedOptions = { remove: { short: 'r', type: 'boolean' } }; - const expected = { values: { remove: true, f: true }, positionals: [] }; - const args = parseArgs({ strict: false, args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected); + const args = ['-rf']; + const options = { remove: { short: 'r', type: 'boolean' } }; + const expected = { values: { __proto__: null, remove: true, f: true }, positionals: [] }; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result, expected); }); test('Everything after a bare `--` is considered a positional argument', () => { - const passedArgs = ['--', 'barepositionals', 'mopositionals']; - const expected = { values: {}, positionals: ['barepositionals', 'mopositionals'] }; - const args = parseArgs({ args: passedArgs }); - assert.deepStrictEqual(args, expected, Error('testing bare positionals')); + const args = ['--', 'barepositionals', 'mopositionals']; + const expected = { values: { __proto__: null }, positionals: ['barepositionals', 'mopositionals'] }; + const result = parseArgs({ args }); + assert.deepStrictEqual(result, expected, Error('testing bare positionals')); }); test('args are true', () => { - const passedArgs = ['--foo', '--bar']; - const expected = { values: { foo: true, bar: true }, positionals: [] }; - const args = parseArgs({ strict: false, args: passedArgs }); - assert.deepStrictEqual(args, expected, Error('args are true')); + const args = ['--foo', '--bar']; + const expected = { values: { __proto__: null, foo: true, bar: true }, positionals: [] }; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result, expected, Error('args are true')); }); test('arg is true and positional is identified', () => { - const passedArgs = ['--foo=a', '--foo', 'b']; - const expected = { values: { foo: true }, positionals: ['b'] }; - const args = parseArgs({ strict: false, args: passedArgs }); - assert.deepStrictEqual(args, expected, Error('arg is true and positional is identified')); + const args = ['--foo=a', '--foo', 'b']; + const expected = { values: { __proto__: null, foo: true }, positionals: ['b'] }; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result, expected, Error('arg is true and positional is identified')); }); test('args equals are passed `type: "string"`', () => { - const passedArgs = ['--so=wat']; - const passedOptions = { so: { type: 'string' } }; - const expected = { values: { so: 'wat' }, positionals: [] }; - const args = parseArgs({ args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected, Error('arg value is passed')); + const args = ['--so=wat']; + const options = { so: { type: 'string' } }; + const expected = { values: { __proto__: null, so: 'wat' }, positionals: [] }; + const result = parseArgs({ args, options }); + assert.deepStrictEqual(result, expected, Error('arg value is passed')); }); test('when args include single dash then result stores dash as positional', () => { - const passedArgs = ['-']; - const expected = { values: { }, positionals: ['-'] }; - const args = parseArgs({ args: passedArgs }); - assert.deepStrictEqual(args, expected); + const args = ['-']; + const expected = { values: { __proto__: null }, positionals: ['-'] }; + const result = parseArgs({ args }); + assert.deepStrictEqual(result, expected); }); test('zero config args equals are parsed as if `type: "string"`', () => { - const passedArgs = ['--so=wat']; - const passedOptions = { }; - const expected = { values: { so: 'wat' }, positionals: [] }; - const args = parseArgs({ strict: false, args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected, Error('arg value is passed')); + const args = ['--so=wat']; + const options = { }; + const expected = { values: { __proto__: null, so: 'wat' }, positionals: [] }; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result, expected, Error('arg value is passed')); }); test('same arg is passed twice `type: "string"` and last value is recorded', () => { - const passedArgs = ['--foo=a', '--foo', 'b']; - const passedOptions = { foo: { type: 'string' } }; - const expected = { values: { foo: 'b' }, positionals: [] }; - const args = parseArgs({ args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected, Error('last arg value is passed')); + const args = ['--foo=a', '--foo', 'b']; + const options = { foo: { type: 'string' } }; + const expected = { values: { __proto__: null, foo: 'b' }, positionals: [] }; + const result = parseArgs({ args, options }); + assert.deepStrictEqual(result, expected, Error('last arg value is passed')); }); test('args equals pass string including more equals', () => { - const passedArgs = ['--so=wat=bing']; - const passedOptions = { so: { type: 'string' } }; - const expected = { values: { so: 'wat=bing' }, positionals: [] }; - const args = parseArgs({ args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected, Error('arg value is passed')); + const args = ['--so=wat=bing']; + const options = { so: { type: 'string' } }; + const expected = { values: { __proto__: null, so: 'wat=bing' }, positionals: [] }; + const result = parseArgs({ args, options }); + assert.deepStrictEqual(result, expected, Error('arg value is passed')); }); test('first arg passed for `type: "string"` and "multiple" is in array', () => { - const passedArgs = ['--foo=a']; - const passedOptions = { foo: { type: 'string', multiple: true } }; - const expected = { values: { foo: ['a'] }, positionals: [] }; - const args = parseArgs({ args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected, Error('first multiple in array')); + const args = ['--foo=a']; + const options = { foo: { type: 'string', multiple: true } }; + const expected = { values: { __proto__: null, foo: ['a'] }, positionals: [] }; + const result = parseArgs({ args, options }); + assert.deepStrictEqual(result, expected, Error('first multiple in array')); }); test('args are passed `type: "string"` and "multiple"', () => { - const passedArgs = ['--foo=a', '--foo', 'b']; - const passedOptions = { + const args = ['--foo=a', '--foo', 'b']; + const options = { foo: { type: 'string', multiple: true, }, }; - const expected = { values: { foo: ['a', 'b'] }, positionals: [] }; - const args = parseArgs({ args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected, Error('both arg values are passed')); + const expected = { values: { __proto__: null, foo: ['a', 'b'] }, positionals: [] }; + const result = parseArgs({ args, options }); + assert.deepStrictEqual(result, expected, Error('both arg values are passed')); }); test('when expecting `multiple:true` boolean option and option used multiple times then result includes array of ' + 'booleans matching usage', () => { - const passedArgs = ['--foo', '--foo']; - const passedOptions = { + const args = ['--foo', '--foo']; + const options = { foo: { type: 'boolean', multiple: true, }, }; - const expected = { values: { foo: [true, true] }, positionals: [] }; - const args = parseArgs({ args: passedArgs, options: passedOptions }); - assert.deepStrictEqual(args, expected); + const expected = { values: { __proto__: null, foo: [true, true] }, positionals: [] }; + const result = parseArgs({ args, options }); + assert.deepStrictEqual(result, expected); }); test('order of option and positional does not matter (per README)', () => { - const passedArgs1 = ['--foo=bar', 'baz']; - const passedArgs2 = ['baz', '--foo=bar']; - const passedOptions = { foo: { type: 'string' } }; - const expected = { values: { foo: 'bar' }, positionals: ['baz'] }; + const args1 = ['--foo=bar', 'baz']; + const args2 = ['baz', '--foo=bar']; + const options = { foo: { type: 'string' } }; + const expected = { values: { __proto__: null, foo: 'bar' }, positionals: ['baz'] }; assert.deepStrictEqual( - parseArgs({ args: passedArgs1, options: passedOptions }), + parseArgs({ args: args1, options }), expected, Error('option then positional') ); assert.deepStrictEqual( - parseArgs({ args: passedArgs2, options: passedOptions }), + parseArgs({ args: args2, options }), expected, Error('positional then option') ); @@ -214,7 +214,7 @@ test('correct default args when use node -p', () => { process.execArgv = ['-p', '0']; const result = parseArgs({ strict: false }); - const expected = { values: { foo: true }, + const expected = { values: { __proto__: null, foo: true }, positionals: [] }; assert.deepStrictEqual(result, expected); process.argv = holdArgv; @@ -228,7 +228,7 @@ test('correct default args when use node --print', () => { process.execArgv = ['--print', '0']; const result = parseArgs({ strict: false }); - const expected = { values: { foo: true }, + const expected = { values: { __proto__: null, foo: true }, positionals: [] }; assert.deepStrictEqual(result, expected); process.argv = holdArgv; @@ -242,7 +242,7 @@ test('correct default args when use node -e', () => { process.execArgv = ['-e', '0']; const result = parseArgs({ strict: false }); - const expected = { values: { foo: true }, + const expected = { values: { __proto__: null, foo: true }, positionals: [] }; assert.deepStrictEqual(result, expected); process.argv = holdArgv; @@ -255,7 +255,7 @@ test('correct default args when use node --eval', () => { const holdExecArgv = process.execArgv; process.execArgv = ['--eval', '0']; const result = parseArgs({ strict: false }); - const expected = { values: { foo: true }, + const expected = { values: { __proto__: null, foo: true }, positionals: [] }; assert.deepStrictEqual(result, expected); process.argv = holdArgv; @@ -269,7 +269,7 @@ test('correct default args when normal arguments', () => { process.execArgv = []; const result = parseArgs({ strict: false }); - const expected = { values: { foo: true }, + const expected = { values: { __proto__: null, foo: true }, positionals: [] }; assert.deepStrictEqual(result, expected); process.argv = holdArgv; @@ -278,22 +278,22 @@ test('correct default args when normal arguments', () => { test('excess leading dashes on options are retained', () => { // Enforce a design decision for an edge case. - const passedArgs = ['---triple']; - const passedOptions = { }; + const args = ['---triple']; + const options = { }; const expected = { - values: { '-triple': true }, + values: { '__proto__': null, '-triple': true }, positionals: [] }; - const result = parseArgs({ strict: false, args: passedArgs, options: passedOptions }); + const result = parseArgs({ strict: false, args, options }); assert.deepStrictEqual(result, expected, Error('excess option dashes are retained')); }); // Test bad inputs test('invalid argument passed for options', () => { - const passedArgs = ['--so=wat']; - const passedOptions = 'bad value'; - assert.throws(() => { parseArgs({ args: passedArgs, options: passedOptions }); }, { + const args = ['--so=wat']; + const options = 'bad value'; + assert.throws(() => { parseArgs({ args, options }); }, { code: 'ERR_INVALID_ARG_TYPE' }); }); @@ -306,17 +306,17 @@ test('type property missing for option then throw', () => { }); test('boolean passed to "type" option', () => { - const passedArgs = ['--so=wat']; - const passedOptions = { foo: { type: true } }; - assert.throws(() => { parseArgs({ args: passedArgs, options: passedOptions }); }, { + const args = ['--so=wat']; + const options = { foo: { type: true } }; + assert.throws(() => { parseArgs({ args, options }); }, { code: 'ERR_INVALID_ARG_TYPE' }); }); test('invalid union value passed to "type" option', () => { - const passedArgs = ['--so=wat']; - const passedOptions = { foo: { type: 'str' } }; - assert.throws(() => { parseArgs({ args: passedArgs, options: passedOptions }); }, { + const args = ['--so=wat']; + const options = { foo: { type: 'str' } }; + assert.throws(() => { parseArgs({ args, options }); }, { code: 'ERR_INVALID_ARG_TYPE' }); }); @@ -324,57 +324,57 @@ test('invalid union value passed to "type" option', () => { // Test strict mode test('unknown long option --bar', () => { - const passedArgs = ['--foo', '--bar']; - const passedOptions = { foo: { type: 'boolean' } }; - assert.throws(() => { parseArgs({ args: passedArgs, options: passedOptions }); }, { + const args = ['--foo', '--bar']; + const options = { foo: { type: 'boolean' } }; + assert.throws(() => { parseArgs({ args, options }); }, { code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' }); }); test('unknown short option -b', () => { - const passedArgs = ['--foo', '-b']; - const passedOptions = { foo: { type: 'boolean' } }; - assert.throws(() => { parseArgs({ args: passedArgs, options: passedOptions }); }, { + const args = ['--foo', '-b']; + const options = { foo: { type: 'boolean' } }; + assert.throws(() => { parseArgs({ args, options }); }, { code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' }); }); test('unknown option -r in short option group -bar', () => { - const passedArgs = ['-bar']; - const passedOptions = { b: { type: 'boolean' }, a: { type: 'boolean' } }; - assert.throws(() => { parseArgs({ args: passedArgs, options: passedOptions }); }, { + const args = ['-bar']; + const options = { b: { type: 'boolean' }, a: { type: 'boolean' } }; + assert.throws(() => { parseArgs({ args, options }); }, { code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' }); }); test('unknown option with explicit value', () => { - const passedArgs = ['--foo', '--bar=baz']; - const passedOptions = { foo: { type: 'boolean' } }; - assert.throws(() => { parseArgs({ args: passedArgs, options: passedOptions }); }, { + const args = ['--foo', '--bar=baz']; + const options = { foo: { type: 'boolean' } }; + assert.throws(() => { parseArgs({ args, options }); }, { code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' }); }); test('string option used as boolean', () => { - const passedArgs = ['--foo']; - const passedOptions = { foo: { type: 'string' } }; - assert.throws(() => { parseArgs({ args: passedArgs, options: passedOptions }); }, { + const args = ['--foo']; + const options = { foo: { type: 'string' } }; + assert.throws(() => { parseArgs({ args, options }); }, { code: 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' }); }); test('boolean option used with value', () => { - const passedArgs = ['--foo=bar']; - const passedOptions = { foo: { type: 'boolean' } }; - assert.throws(() => { parseArgs({ args: passedArgs, options: passedOptions }); }, { + const args = ['--foo=bar']; + const options = { foo: { type: 'boolean' } }; + assert.throws(() => { parseArgs({ args, options }); }, { code: 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' }); }); test('invalid short option length', () => { - const passedArgs = []; - const passedOptions = { foo: { short: 'fo', type: 'boolean' } }; - assert.throws(() => { parseArgs({ args: passedArgs, options: passedOptions }); }, { + const args = []; + const options = { foo: { short: 'fo', type: 'boolean' } }; + assert.throws(() => { parseArgs({ args, options }); }, { code: 'ERR_INVALID_ARG_VALUE' }); }); diff --git a/test/prototype-pollution.js b/test/prototype-pollution.js index 3f28f59..546444b 100644 --- a/test/prototype-pollution.js +++ b/test/prototype-pollution.js @@ -5,11 +5,11 @@ const test = require('tape'); const { parseArgs } = require('../index.js'); test('should not allow __proto__ key to be set on object', (t) => { - const passedArgs = ['--__proto__=hello']; + const args = ['--__proto__=hello']; const expected = { values: {}, positionals: [] }; - const result = parseArgs({ strict: false, args: passedArgs }); + const result = parseArgs({ strict: false, args }); - t.deepEqual(result, expected); + t.deepEqual({ ...result.values }, expected.values); t.end(); }); diff --git a/test/short-option-combined-with-value.js b/test/short-option-combined-with-value.js index 61255b1..ca0ec6a 100644 --- a/test/short-option-combined-with-value.js +++ b/test/short-option-combined-with-value.js @@ -5,55 +5,55 @@ const test = require('tape'); const { parseArgs } = require('../index.js'); test('when combine string short with plain text then parsed as value', (t) => { - const passedArgs = ['-aHELLO']; - const passedOptions = { alpha: { short: 'a', type: 'string' } }; - const expected = { values: { alpha: 'HELLO' }, positionals: [] }; + const args = ['-aHELLO']; + const options = { alpha: { short: 'a', type: 'string' } }; + const expected = { values: { __proto__: null, alpha: 'HELLO' }, positionals: [] }; - const result = parseArgs({ args: passedArgs, options: passedOptions }); + const result = parseArgs({ args, options }); t.deepEqual(result, expected); t.end(); }); test('when combine low-config string short with plain text then parsed as value', (t) => { - const passedArgs = ['-aHELLO']; - const passedOptions = { a: { type: 'string' } }; - const expected = { values: { a: 'HELLO' }, positionals: [] }; + const args = ['-aHELLO']; + const options = { a: { type: 'string' } }; + const expected = { values: { __proto__: null, a: 'HELLO' }, positionals: [] }; - const result = parseArgs({ args: passedArgs, options: passedOptions }); + const result = parseArgs({ args, options }); t.deepEqual(result, expected); t.end(); }); test('when combine string short with value like short option then parsed as value', (t) => { - const passedArgs = ['-a-b']; - const passedOptions = { alpha: { short: 'a', type: 'string' } }; - const expected = { values: { alpha: '-b' }, positionals: [] }; + const args = ['-a-b']; + const options = { alpha: { short: 'a', type: 'string' } }; + const expected = { values: { __proto__: null, alpha: '-b' }, positionals: [] }; - const result = parseArgs({ args: passedArgs, options: passedOptions }); + const result = parseArgs({ args, options }); t.deepEqual(result, expected); t.end(); }); test('when combine string short with value like long option then parsed as value', (t) => { - const passedArgs = ['-a--bar']; - const passedOptions = { alpha: { short: 'a', type: 'string' } }; - const expected = { values: { alpha: '--bar' }, positionals: [] }; + const args = ['-a--bar']; + const options = { alpha: { short: 'a', type: 'string' } }; + const expected = { values: { __proto__: null, alpha: '--bar' }, positionals: [] }; - const result = parseArgs({ args: passedArgs, options: passedOptions }); + const result = parseArgs({ args, options }); t.deepEqual(result, expected); t.end(); }); test('when combine string short with value like negative number then parsed as value', (t) => { - const passedArgs = ['-a-5']; - const passedOptions = { alpha: { short: 'a', type: 'string' } }; - const expected = { values: { alpha: '-5' }, positionals: [] }; + const args = ['-a-5']; + const options = { alpha: { short: 'a', type: 'string' } }; + const expected = { values: { __proto__: null, alpha: '-5' }, positionals: [] }; - const result = parseArgs({ args: passedArgs, options: passedOptions }); + const result = parseArgs({ args, options }); t.deepEqual(result, expected); t.end(); @@ -61,21 +61,21 @@ test('when combine string short with value like negative number then parsed as v test('when combine string short with value which matches configured flag then parsed as value', (t) => { - const passedArgs = ['-af']; - const passedOptions = { alpha: { short: 'a', type: 'string' }, file: { short: 'f', type: 'boolean' } }; - const expected = { values: { alpha: 'f' }, positionals: [] }; - const result = parseArgs({ args: passedArgs, options: passedOptions }); + const args = ['-af']; + const options = { alpha: { short: 'a', type: 'string' }, file: { short: 'f', type: 'boolean' } }; + const expected = { values: { __proto__: null, alpha: 'f' }, positionals: [] }; + const result = parseArgs({ args, options }); t.deepEqual(result, expected); t.end(); }); test('when combine string short with value including equals then parsed with equals in value', (t) => { - const passedArgs = ['-a=5']; - const passedOptions = { alpha: { short: 'a', type: 'string' } }; - const expected = { values: { alpha: '=5' }, positionals: [] }; + const args = ['-a=5']; + const options = { alpha: { short: 'a', type: 'string' } }; + const expected = { values: { __proto__: null, alpha: '=5' }, positionals: [] }; - const result = parseArgs({ args: passedArgs, options: passedOptions }); + const result = parseArgs({ args, options }); t.deepEqual(result, expected); t.end(); diff --git a/test/short-option-groups.js b/test/short-option-groups.js index f95b6fa..7641549 100644 --- a/test/short-option-groups.js +++ b/test/short-option-groups.js @@ -5,44 +5,44 @@ const test = require('tape'); const { parseArgs } = require('../index.js'); test('when pass zero-config group of booleans then parsed as booleans', (t) => { - const passedArgs = ['-rf', 'p']; - const passedOptions = { }; - const expected = { values: { r: true, f: true }, positionals: ['p'] }; + const args = ['-rf', 'p']; + const options = { }; + const expected = { values: { __proto__: null, r: true, f: true }, positionals: ['p'] }; - const result = parseArgs({ strict: false, args: passedArgs, options: passedOptions }); + const result = parseArgs({ strict: false, args, options }); t.deepEqual(result, expected); t.end(); }); test('when pass full-config group of booleans then parsed as booleans', (t) => { - const passedArgs = ['-rf', 'p']; - const passedOptions = { r: { type: 'boolean' }, f: { type: 'boolean' } }; - const expected = { values: { r: true, f: true }, positionals: ['p'] }; + const args = ['-rf', 'p']; + const options = { r: { type: 'boolean' }, f: { type: 'boolean' } }; + const expected = { values: { __proto__: null, r: true, f: true }, positionals: ['p'] }; - const result = parseArgs({ args: passedArgs, options: passedOptions }); + const result = parseArgs({ args, options }); t.deepEqual(result, expected); t.end(); }); test('when pass group with string option on end then parsed as booleans and string option', (t) => { - const passedArgs = ['-rf', 'p']; - const passedOptions = { r: { type: 'boolean' }, f: { type: 'string' } }; - const expected = { values: { r: true, f: 'p' }, positionals: [] }; + const args = ['-rf', 'p']; + const options = { r: { type: 'boolean' }, f: { type: 'string' } }; + const expected = { values: { __proto__: null, r: true, f: 'p' }, positionals: [] }; - const result = parseArgs({ args: passedArgs, options: passedOptions }); + const result = parseArgs({ args, options }); t.deepEqual(result, expected); t.end(); }); test('when pass group with string option in middle and strict:false then parsed as booleans and string option with trailing value', (t) => { - const passedArgs = ['-afb', 'p']; - const passedOptions = { f: { type: 'string' } }; - const expected = { values: { a: true, f: 'b' }, positionals: ['p'] }; + const args = ['-afb', 'p']; + const options = { f: { type: 'string' } }; + const expected = { values: { __proto__: null, a: true, f: 'b' }, positionals: ['p'] }; - const result = parseArgs({ args: passedArgs, options: passedOptions, strict: false }); + const result = parseArgs({ args, options, strict: false }); t.deepEqual(result, expected); t.end(); @@ -50,11 +50,11 @@ test('when pass group with string option in middle and strict:false then parsed // Hopefully coming: // test('when pass group with string option in middle and strict:true then error', (t) => { -// const passedArgs = ['-afb', 'p']; -// const passedOptions = { f: { type: 'string' } }; +// const args = ['-afb', 'p']; +// const options = { f: { type: 'string' } }; // // t.throws(() => { -// parseArgs({ args: passedArgs, options: passedOptions, strict: true }); +// parseArgs({ args, options, strict: true }); // }); // t.end(); // }); diff --git a/test/store-user-intent.js b/test/store-user-intent.js index d5340a9..661acb0 100644 --- a/test/store-user-intent.js +++ b/test/store-user-intent.js @@ -17,36 +17,36 @@ const { parseArgs } = require('../index.js'); // user's intentions. test('when use string short option used as boolean then result as if boolean', (t) => { - const passedArgs = ['-o']; + const args = ['-o']; const stringOptions = { opt: { short: 'o', type: 'string' } }; const booleanOptions = { opt: { short: 'o', type: 'boolean' } }; - const stringConfigResult = parseArgs({ args: passedArgs, options: stringOptions, strict: false }); - const booleanConfigResult = parseArgs({ args: passedArgs, options: booleanOptions, strict: false }); + const stringConfigResult = parseArgs({ args, options: stringOptions, strict: false }); + const booleanConfigResult = parseArgs({ args, options: booleanOptions, strict: false }); t.deepEqual(stringConfigResult, booleanConfigResult); t.end(); }); test('when use string long option used as boolean then result as if boolean', (t) => { - const passedArgs = ['--opt']; + const args = ['--opt']; const stringOptions = { opt: { short: 'o', type: 'string' } }; const booleanOptions = { opt: { short: 'o', type: 'boolean' } }; - const stringConfigResult = parseArgs({ args: passedArgs, options: stringOptions, strict: false }); - const booleanConfigResult = parseArgs({ args: passedArgs, options: booleanOptions, strict: false }); + const stringConfigResult = parseArgs({ args, options: stringOptions, strict: false }); + const booleanConfigResult = parseArgs({ args, options: booleanOptions, strict: false }); t.deepEqual(stringConfigResult, booleanConfigResult); t.end(); }); test('when use boolean long option used as string then result as if string', (t) => { - const passedArgs = ['--bool=OOPS']; + const args = ['--bool=OOPS']; const stringOptions = { bool: { type: 'string' } }; const booleanOptions = { bool: { type: 'boolean' } }; - const stringConfigResult = parseArgs({ args: passedArgs, options: stringOptions, strict: false }); - const booleanConfigResult = parseArgs({ args: passedArgs, options: booleanOptions, strict: false }); + const stringConfigResult = parseArgs({ args, options: stringOptions, strict: false }); + const booleanConfigResult = parseArgs({ args, options: booleanOptions, strict: false }); t.deepEqual(booleanConfigResult, stringConfigResult); t.end(); From bb4b9170e51d99ed5eced111c42ca882fefead7f Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 16 Apr 2022 15:22:20 +1200 Subject: [PATCH 4/7] Remove test for unimplemented feature --- test/short-option-groups.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/test/short-option-groups.js b/test/short-option-groups.js index 7641549..7b528f0 100644 --- a/test/short-option-groups.js +++ b/test/short-option-groups.js @@ -47,14 +47,3 @@ test('when pass group with string option in middle and strict:false then parsed t.deepEqual(result, expected); t.end(); }); - -// Hopefully coming: -// test('when pass group with string option in middle and strict:true then error', (t) => { -// const args = ['-afb', 'p']; -// const options = { f: { type: 'string' } }; -// -// t.throws(() => { -// parseArgs({ args, options, strict: true }); -// }); -// t.end(); -// }); From e0e3a3d2344d7455135bc77c3d910c41f970af77 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 16 Apr 2022 15:27:40 +1200 Subject: [PATCH 5/7] Add __proto__ in new test after merge --- test/prototype-pollution.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/prototype-pollution.js b/test/prototype-pollution.js index f1679d9..8bcd67e 100644 --- a/test/prototype-pollution.js +++ b/test/prototype-pollution.js @@ -34,7 +34,7 @@ test('should not allow __proto__ key to be set on object', (t) => { test('when prototype has multiple then ignored', (t) => { const args = ['--foo', '1', '--foo', '2']; const options = { foo: { type: 'string' } }; - const expectedResult = { values: { foo: '2' }, positionals: [] }; + const expectedResult = { values: { __proto__: null, foo: '2' }, positionals: [] }; const holdDescriptor = setObjectPrototype('multiple', true); const result = parseArgs({ args, options }); From f49f7e56199b0aef938ee43d0fb656d63593462c Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 16 Apr 2022 15:35:17 +1200 Subject: [PATCH 6/7] Simple values access now has null prototype --- index.js | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index f365745..87f12bd 100644 --- a/index.js +++ b/index.js @@ -6,7 +6,6 @@ const { ArrayPrototypeShift, ArrayPrototypeSlice, ArrayPrototypePush, - ObjectDefineProperty, ObjectEntries, ObjectPrototypeHasOwnProperty: ObjectHasOwn, StringPrototypeCharAt, @@ -117,19 +116,9 @@ function checkOptionUsage(longOption, optionValue, options, */ function storeOption(longOption, optionValue, options, values) { if (longOption === '__proto__') { - return; + return; // No. Just no. } - // Can be removed when value has a null prototype - const safeAssignProperty = (obj, prop, value) => { - ObjectDefineProperty(obj, prop, { - value, - writable: true, - enumerable: true, - configurable: true - }); - }; - // We store based on the option value rather than option type, // preserving the users intent for author to deal with. const newValue = optionValue ?? true; @@ -138,13 +127,14 @@ function storeOption(longOption, optionValue, options, values) { // values[longOption] starts out not present, // first value is added as new array [newValue], // subsequent values are pushed to existing array. - if (ObjectHasOwn(values, longOption)) { + // (note: values has null prototype, so simpler usage) + if (values[longOption]) { ArrayPrototypePush(values[longOption], newValue); } else { - safeAssignProperty(values, longOption, [newValue]); + values[longOption] = [newValue]; } } else { - safeAssignProperty(values, longOption, newValue); + values[longOption] = newValue; } } From 7095573eb1c184522af30c91ff9e40b964dad580 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 16 Apr 2022 15:40:07 +1200 Subject: [PATCH 7/7] Use same style as other tests for __proto__ test --- test/prototype-pollution.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/prototype-pollution.js b/test/prototype-pollution.js index 8bcd67e..a8a5d2f 100644 --- a/test/prototype-pollution.js +++ b/test/prototype-pollution.js @@ -23,11 +23,11 @@ function restoreObjectPrototype(prop, oldDescriptor) { test('should not allow __proto__ key to be set on object', (t) => { const args = ['--__proto__=hello']; - const expected = { values: {}, positionals: [] }; + const expected = { values: { __proto__: null }, positionals: [] }; const result = parseArgs({ strict: false, args }); - t.deepEqual({ ...result.values }, expected.values); + t.deepEqual(result, expected); t.end(); });