From a35c9b711a30be50be19912ab460a69937e60c7b Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Mon, 17 Jan 2022 13:55:33 +0000 Subject: [PATCH] refactor: use primordials (#37) --- .eslintrc | 6 +- .npmignore | 6 + README.md | 4 + index.js | 89 ++++++---- package.json | 4 +- primordials.js | 432 +++++++++++++++++++++++++++++++++++++++++++++++++ test/index.js | 121 +++++++------- 7 files changed, 566 insertions(+), 96 deletions(-) create mode 100644 .npmignore create mode 100644 primordials.js diff --git a/.eslintrc b/.eslintrc index ca55697..0fce6bd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,9 +1,11 @@ { "extends": ["plugin:eslint-plugin-node-core/recommended"], "env": { - "node": true + "node": true, + "es6": true }, "rules": { "linebreak-style": 0 - } + }, + "ignorePatterns": ["README.md"] } diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..af75441 --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +coverage +test +.nycrc +.eslintrc +.github +CONTRIBUTING.md diff --git a/README.md b/README.md index 100f47f..e11fcfa 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ const { parseArgs } = require('@pkgjs/parseargs'); ```js // unconfigured +const { parseArgs } = require('@pkgjs/parseargs'); const argv = ['-f', '--foo=a', '--bar', 'b']; const options = {}; const { flags, values, positionals } = parseArgs(argv, options); @@ -107,6 +108,7 @@ const { flags, values, positionals } = parseArgs(argv, options); ``` ```js +const { parseArgs } = require('@pkgjs/parseargs'); // withValue const argv = ['-f', '--foo=a', '--bar', 'b']; const options = { @@ -119,6 +121,7 @@ const { flags, values, positionals } = parseArgs(argv, options); ``` ```js +const { parseArgs } = require('@pkgjs/parseargs'); // withValue & multiples const argv = ['-f', '--foo=a', '--foo', 'b']; const options = { @@ -132,6 +135,7 @@ const { flags, values, positionals } = parseArgs(argv, options); ``` ```js +const { parseArgs } = require('@pkgjs/parseargs'); // shorts const argv = ['-f', 'b']; const options = { diff --git a/index.js b/index.js index 997bad5..e5e824f 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,18 @@ 'use strict'; +const { + ArrayIsArray, + ArrayPrototypeConcat, + ArrayPrototypeIncludes, + ArrayPrototypeSlice, + ArrayPrototypePush, + StringPrototypeCharAt, + StringPrototypeIncludes, + StringPrototypeSlice, + StringPrototypeSplit, + StringPrototypeStartsWith, +} = require('./primordials'); + function getMainArgs() { // This function is a placeholder for proposed process.mainArgs. // Work out where to slice process.argv for user supplied arguments. @@ -18,18 +31,20 @@ function getMainArgs() { // (Not included in tests as hopefully temporary example.) /* c8 ignore next 3 */ if (process.versions && process.versions.electron && !process.defaultApp) { - return process.argv.slice(1); + return ArrayPrototypeSlice(process.argv, 1); } // Check node options for scenarios where user CLI args follow executable. const execArgv = process.execArgv; - if (execArgv.includes('-e') || execArgv.includes('--eval') || - execArgv.includes('-p') || execArgv.includes('--print')) { - return process.argv.slice(1); + if (StringPrototypeIncludes(execArgv, '-e') || + StringPrototypeIncludes(execArgv, '--eval') || + StringPrototypeIncludes(execArgv, '-p') || + StringPrototypeIncludes(execArgv, '--print')) { + return ArrayPrototypeSlice(process.argv, 1); } // Normally first two arguments are executable and script, then CLI arguments - return process.argv.slice(2); + return ArrayPrototypeSlice(process.argv, 2); } const parseArgs = ( @@ -39,7 +54,7 @@ const parseArgs = ( if (typeof options !== 'object' || options === null) { throw new Error('Whoops!'); } - if (options.withValue !== undefined && !Array.isArray(options.withValue)) { + if (options.withValue !== undefined && !ArrayIsArray(options.withValue)) { throw new Error('Whoops! options.withValue should be an array.'); } @@ -53,37 +68,44 @@ const parseArgs = ( while (pos < argv.length) { let arg = argv[pos]; - if (arg.startsWith('-')) { + if (StringPrototypeStartsWith(arg, '-')) { // Everything after a bare '--' is considered a positional argument // and is returned verbatim if (arg === '--') { - result.positionals.push(...argv.slice(++pos)); + result.positionals = ArrayPrototypeConcat( + result.positionals, + ArrayPrototypeSlice(argv, ++pos) + ); return result; - } else if (arg.charAt(1) !== '-') { // Look for shortcodes: -fXzy + } else if ( + StringPrototypeCharAt(arg, 1) !== '-' + ) { // Look for shortcodes: -fXzy throw new Error('What are we doing with shortcodes!?!'); } - arg = arg.slice(2); // remove leading -- + arg = StringPrototypeSlice(arg, 2); // remove leading -- - if (arg.includes('=')) { + if (StringPrototypeIncludes(arg, '=')) { // withValue equals(=) case - const argParts = arg.split('='); + const argParts = StringPrototypeSplit(arg, '='); result.flags[argParts[0]] = true; // If withValue option is specified, take 2nd part after '=' as value, // else set value as undefined const val = options.withValue && - options.withValue.includes(argParts[0]) ? + ArrayPrototypeIncludes(options.withValue, argParts[0]) ? argParts[1] : undefined; // Append value to previous values array for case of multiples // option, else add to empty array - result.values[argParts[0]] = [].concat( - options.multiples && - options.multiples.includes(argParts[0]) && + result.values[argParts[0]] = ArrayPrototypeConcat([], + options.multiples && + ArrayPrototypeIncludes(options.multiples, argParts[0]) && result.values[argParts[0]] || [], - val, + val, ); - } else if (pos + 1 < argv.length && !argv[pos + 1].startsWith('-')) { + } else if (pos + 1 < argv.length && + !StringPrototypeStartsWith(argv[pos + 1], '-') + ) { // withValue option should also support setting values when '= // isn't used ie. both --foo=b and --foo b should work @@ -92,17 +114,19 @@ const parseArgs = ( // value and then increment pos so that we don't re-evaluate that // arg, else set value as undefined ie. --foo b --bar c, after setting // b as the value for foo, evaluate --bar next and skip 'b' - const val = options.withValue && options.withValue.includes(arg) ? - argv[++pos] : + const val = options.withValue && + ArrayPrototypeIncludes(options.withValue, arg) ? argv[++pos] : undefined; // Append value to previous values array for case of multiples // option, else add to empty array - result.values[arg] = [].concat( - options.multiples && options.multiples.includes(arg) && - result.values[arg] ? - result.values[arg] : - [], - val); + result.values[arg] = ArrayPrototypeConcat( + [], + options.multiples && + ArrayPrototypeIncludes(options.multiples, arg) && + result.values[arg] || + [], + val + ); } else { // Cases when an arg is specified without a value, example // '--foo --bar' <- 'foo' and 'bar' flags should be set to true and @@ -110,18 +134,19 @@ const parseArgs = ( result.flags[arg] = true; // Append undefined to previous values array for case of // multiples option, else add to empty array - result.values[arg] = [].concat( - options.multiples && options.multiples.includes(arg) && - result.values[arg] ? - result.values[arg] : - [], + result.values[arg] = ArrayPrototypeConcat( + [], + options.multiples && + ArrayPrototypeIncludes(options.multiples, arg) && + result.values[arg] || + [], undefined ); } } else { // Arguements without a dash prefix are considered "positional" - result.positionals.push(arg); + ArrayPrototypePush(result.positionals, arg); } pos++; diff --git a/package.json b/package.json index ddfa5b0..86080dc 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "scripts": { "coverage": "c8 --check-coverage node test/index.js", "test": "c8 node test/index.js", - "posttest": "eslint index.js", - "fix": "eslint index.js --fix" + "posttest": "eslint .", + "fix": "npm run posttest -- --fix" }, "repository": { "type": "git", diff --git a/primordials.js b/primordials.js new file mode 100644 index 0000000..e2af4e8 --- /dev/null +++ b/primordials.js @@ -0,0 +1,432 @@ +'use strict'; + +/* eslint-disable node-core/prefer-primordials */ + +// This file subclasses and stores the JS builtins that come from the VM +// so that Node.js's builtin modules do not need to later look these up from +// the global proxy, which can be mutated by users. + +// Use of primordials have sometimes a dramatic impact on performance, please +// benchmark all changes made in performance-sensitive areas of the codebase. +// See: https://github.com/nodejs/node/pull/38248 + +const primordials = {}; + +const { + defineProperty: ReflectDefineProperty, + getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor, + ownKeys: ReflectOwnKeys, +} = Reflect; + +// `uncurryThis` is equivalent to `func => Function.prototype.call.bind(func)`. +// It is using `bind.bind(call)` to avoid using `Function.prototype.bind` +// and `Function.prototype.call` after it may have been mutated by users. +const { apply, bind, call } = Function.prototype; +const uncurryThis = bind.bind(call); +primordials.uncurryThis = uncurryThis; + +// `applyBind` is equivalent to `func => Function.prototype.apply.bind(func)`. +// It is using `bind.bind(apply)` to avoid using `Function.prototype.bind` +// and `Function.prototype.apply` after it may have been mutated by users. +const applyBind = bind.bind(apply); +primordials.applyBind = applyBind; + +// Methods that accept a variable number of arguments, and thus it's useful to +// also create `${prefix}${key}Apply`, which uses `Function.prototype.apply`, +// instead of `Function.prototype.call`, and thus doesn't require iterator +// destructuring. +const varargsMethods = [ + // 'ArrayPrototypeConcat' is omitted, because it performs the spread + // on its own for arrays and array-likes with a truthy + // @@isConcatSpreadable symbol property. + 'ArrayOf', + 'ArrayPrototypePush', + 'ArrayPrototypeUnshift', + // 'FunctionPrototypeCall' is omitted, since there's 'ReflectApply' + // and 'FunctionPrototypeApply'. + 'MathHypot', + 'MathMax', + 'MathMin', + 'StringPrototypeConcat', + 'TypedArrayOf', +]; + +function getNewKey(key) { + return typeof key === 'symbol' ? + `Symbol${key.description[7].toUpperCase()}${key.description.slice(8)}` : + `${key[0].toUpperCase()}${key.slice(1)}`; +} + +function copyAccessor(dest, prefix, key, { enumerable, get, set }) { + ReflectDefineProperty(dest, `${prefix}Get${key}`, { + value: uncurryThis(get), + enumerable + }); + if (set !== undefined) { + ReflectDefineProperty(dest, `${prefix}Set${key}`, { + value: uncurryThis(set), + enumerable + }); + } +} + +function copyPropsRenamed(src, dest, prefix) { + for (const key of ReflectOwnKeys(src)) { + const newKey = getNewKey(key); + const desc = ReflectGetOwnPropertyDescriptor(src, key); + if ('get' in desc) { + copyAccessor(dest, prefix, newKey, desc); + } else { + const name = `${prefix}${newKey}`; + ReflectDefineProperty(dest, name, desc); + if (varargsMethods.includes(name)) { + ReflectDefineProperty(dest, `${name}Apply`, { + // `src` is bound as the `this` so that the static `this` points + // to the object it was defined on, + // e.g.: `ArrayOfApply` gets a `this` of `Array`: + value: applyBind(desc.value, src), + }); + } + } + } +} + +function copyPropsRenamedBound(src, dest, prefix) { + for (const key of ReflectOwnKeys(src)) { + const newKey = getNewKey(key); + const desc = ReflectGetOwnPropertyDescriptor(src, key); + if ('get' in desc) { + copyAccessor(dest, prefix, newKey, desc); + } else { + const { value } = desc; + if (typeof value === 'function') { + desc.value = value.bind(src); + } + + const name = `${prefix}${newKey}`; + ReflectDefineProperty(dest, name, desc); + if (varargsMethods.includes(name)) { + ReflectDefineProperty(dest, `${name}Apply`, { + value: applyBind(value, src), + }); + } + } + } +} + +function copyPrototype(src, dest, prefix) { + for (const key of ReflectOwnKeys(src)) { + const newKey = getNewKey(key); + const desc = ReflectGetOwnPropertyDescriptor(src, key); + if ('get' in desc) { + copyAccessor(dest, prefix, newKey, desc); + } else { + const { value } = desc; + if (typeof value === 'function') { + desc.value = uncurryThis(value); + } + + const name = `${prefix}${newKey}`; + ReflectDefineProperty(dest, name, desc); + if (varargsMethods.includes(name)) { + ReflectDefineProperty(dest, `${name}Apply`, { + value: applyBind(value), + }); + } + } + } +} + +// Create copies of configurable value properties of the global object +[ + 'Proxy', + 'globalThis', +].forEach((name) => { + // eslint-disable-next-line no-restricted-globals + primordials[name] = globalThis[name]; +}); + +// Create copies of URI handling functions +[ + decodeURI, + decodeURIComponent, + encodeURI, + encodeURIComponent, +].forEach((fn) => { + primordials[fn.name] = fn; +}); + +// Create copies of the namespace objects +[ + 'JSON', + 'Math', + 'Proxy', + 'Reflect', +].forEach((name) => { + // eslint-disable-next-line no-restricted-globals + copyPropsRenamed(global[name], primordials, name); +}); + +// Create copies of intrinsic objects +[ + 'AggregateError', + 'Array', + 'ArrayBuffer', + 'BigInt', + 'BigInt64Array', + 'BigUint64Array', + 'Boolean', + 'DataView', + 'Date', + 'Error', + 'EvalError', + 'FinalizationRegistry', + 'Float32Array', + 'Float64Array', + 'Function', + 'Int16Array', + 'Int32Array', + 'Int8Array', + 'Map', + 'Number', + 'Object', + 'RangeError', + 'ReferenceError', + 'RegExp', + 'Set', + 'String', + 'Symbol', + 'SyntaxError', + 'TypeError', + 'URIError', + 'Uint16Array', + 'Uint32Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'WeakMap', + 'WeakRef', + 'WeakSet', +].forEach((name) => { + // eslint-disable-next-line no-restricted-globals + const original = global[name]; + primordials[name] = original; + copyPropsRenamed(original, primordials, name); + copyPrototype(original.prototype, primordials, `${name}Prototype`); +}); + +// Create copies of intrinsic objects that require a valid `this` to call +// static methods. +// Refs: https://www.ecma-international.org/ecma-262/#sec-promise.all +[ + 'Promise', +].forEach((name) => { + // eslint-disable-next-line no-restricted-globals + const original = global[name]; + primordials[name] = original; + copyPropsRenamedBound(original, primordials, name); + copyPrototype(original.prototype, primordials, `${name}Prototype`); +}); + +// Create copies of abstract intrinsic objects that are not directly exposed +// on the global object. +// Refs: https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object +[ + { name: 'TypedArray', original: Reflect.getPrototypeOf(Uint8Array) }, + { + name: 'ArrayIterator', original: { + prototype: Reflect.getPrototypeOf(Array.prototype[Symbol.iterator]()), + } + }, + { + name: 'StringIterator', original: { + prototype: Reflect.getPrototypeOf(String.prototype[Symbol.iterator]()), + } + }, +].forEach(({ name, original }) => { + primordials[name] = original; + // The static %TypedArray% methods require a valid `this`, but can't be bound, + // as they need a subclass constructor as the receiver: + copyPrototype(original, primordials, name); + copyPrototype(original.prototype, primordials, `${name}Prototype`); +}); + +/* eslint-enable node-core/prefer-primordials */ + +const { + ArrayPrototypeForEach, + FinalizationRegistry, + FunctionPrototypeCall, + Map, + ObjectFreeze, + ObjectSetPrototypeOf, + Promise, + PromisePrototypeThen, + Set, + SymbolIterator, + WeakMap, + WeakRef, + WeakSet, +} = primordials; + +// Because these functions are used by `makeSafe`, which is exposed +// on the `primordials` object, it's important to use const references +// to the primordials that they use: +const createSafeIterator = (factory, next) => { + class SafeIterator { + constructor(iterable) { + this._iterator = factory(iterable); + } + next() { + return next(this._iterator); + } + [SymbolIterator]() { + return this; + } + } + ObjectSetPrototypeOf(SafeIterator.prototype, null); + ObjectFreeze(SafeIterator.prototype); + ObjectFreeze(SafeIterator); + return SafeIterator; +}; + +primordials.SafeArrayIterator = createSafeIterator( + primordials.ArrayPrototypeSymbolIterator, + primordials.ArrayIteratorPrototypeNext +); +primordials.SafeStringIterator = createSafeIterator( + primordials.StringPrototypeSymbolIterator, + primordials.StringIteratorPrototypeNext +); + +const copyProps = (src, dest) => { + ArrayPrototypeForEach(ReflectOwnKeys(src), (key) => { + if (!ReflectGetOwnPropertyDescriptor(dest, key)) { + ReflectDefineProperty( + dest, + key, + ReflectGetOwnPropertyDescriptor(src, key)); + } + }); +}; + +/** + * @type {typeof primordials.makeSafe} + */ +const makeSafe = (unsafe, safe) => { + if (SymbolIterator in unsafe.prototype) { + const dummy = new unsafe(); + let next; // We can reuse the same `next` method. + + ArrayPrototypeForEach(ReflectOwnKeys(unsafe.prototype), (key) => { + if (!ReflectGetOwnPropertyDescriptor(safe.prototype, key)) { + const desc = ReflectGetOwnPropertyDescriptor(unsafe.prototype, key); + if ( + typeof desc.value === 'function' && + desc.value.length === 0 && + SymbolIterator in (FunctionPrototypeCall(desc.value, dummy) ?? {}) + ) { + const createIterator = uncurryThis(desc.value); + next ??= uncurryThis(createIterator(dummy).next); + const SafeIterator = createSafeIterator(createIterator, next); + desc.value = function() { + return new SafeIterator(this); + }; + } + ReflectDefineProperty(safe.prototype, key, desc); + } + }); + } else { + copyProps(unsafe.prototype, safe.prototype); + } + copyProps(unsafe, safe); + + ObjectSetPrototypeOf(safe.prototype, null); + ObjectFreeze(safe.prototype); + ObjectFreeze(safe); + return safe; +}; +primordials.makeSafe = makeSafe; + +// Subclass the constructors because we need to use their prototype +// methods later. +// Defining the `constructor` is necessary here to avoid the default +// constructor which uses the user-mutable `%ArrayIteratorPrototype%.next`. +primordials.SafeMap = makeSafe( + Map, + class SafeMap extends Map { + constructor(i) { super(i); } // eslint-disable-line no-useless-constructor + } +); +primordials.SafeWeakMap = makeSafe( + WeakMap, + class SafeWeakMap extends WeakMap { + constructor(i) { super(i); } // eslint-disable-line no-useless-constructor + } +); + +primordials.SafeSet = makeSafe( + Set, + class SafeSet extends Set { + constructor(i) { super(i); } // eslint-disable-line no-useless-constructor + } +); +primordials.SafeWeakSet = makeSafe( + WeakSet, + class SafeWeakSet extends WeakSet { + constructor(i) { super(i); } // eslint-disable-line no-useless-constructor + } +); + +primordials.SafeFinalizationRegistry = makeSafe( + FinalizationRegistry, + class SafeFinalizationRegistry extends FinalizationRegistry { + // eslint-disable-next-line no-useless-constructor + constructor(cleanupCallback) { super(cleanupCallback); } + } +); +primordials.SafeWeakRef = makeSafe( + WeakRef, + class SafeWeakRef extends WeakRef { + // eslint-disable-next-line no-useless-constructor + constructor(target) { super(target); } + } +); + +const SafePromise = makeSafe( + Promise, + class SafePromise extends Promise { + // eslint-disable-next-line no-useless-constructor + constructor(executor) { super(executor); } + } +); + +primordials.PromisePrototypeCatch = (thisPromise, onRejected) => + PromisePrototypeThen(thisPromise, undefined, onRejected); + +/** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or + * rejected). The resolved value cannot be modified from the callback. + * Prefer using async functions when possible. + * @param {Promise} thisPromise + * @param {() => void) | undefined | null} onFinally The callback to execute + * when the Promise is settled (fulfilled or rejected). + * @returns {Promise} A Promise for the completion of the callback. + */ +primordials.SafePromisePrototypeFinally = (thisPromise, onFinally) => + // Wrapping on a new Promise is necessary to not expose the SafePromise + // prototype to user-land. + new Promise((a, b) => + new SafePromise((a, b) => PromisePrototypeThen(thisPromise, a, b)) + .finally(onFinally) + .then(a, b) + ); + +primordials.AsyncIteratorPrototype = + primordials.ReflectGetPrototypeOf( + primordials.ReflectGetPrototypeOf( + async function* () { }).prototype); + +ObjectSetPrototypeOf(primordials, null); +ObjectFreeze(primordials); + +module.exports = primordials; diff --git a/test/index.js b/test/index.js index 36eb4c6..c75802a 100644 --- a/test/index.js +++ b/test/index.js @@ -1,72 +1,73 @@ 'use strict'; +/* eslint max-len: 0 */ -const test = require('tape') -const {parseArgs} = require('../index.js') +const test = require('tape'); +const { parseArgs } = require('../index.js'); -//Test results are as we expect +// Test results are as we expect -test('Everything after a bare `--` is considered a positional argument', function (t) { - const passedArgs = ['--', 'barepositionals', 'mopositionals'] - const expected = { flags: {}, values: {}, positionals: ['barepositionals', 'mopositionals'] } - const args = parseArgs(passedArgs) +test('Everything after a bare `--` is considered a positional argument', function(t) { + const passedArgs = ['--', 'barepositionals', 'mopositionals']; + const expected = { flags: {}, values: {}, positionals: ['barepositionals', 'mopositionals'] }; + const args = parseArgs(passedArgs); - t.deepEqual(args, expected, 'testing bare positionals') + t.deepEqual(args, expected, 'testing bare positionals'); - t.end() -}) + t.end(); +}); -test('args are true', function (t) { - const passedArgs = ['--foo', '--bar'] - const expected = { flags: { foo: true, bar: true}, values: {foo: [undefined], bar: [undefined]}, positionals: [] } - const args = parseArgs(passedArgs) +test('args are true', function(t) { + const passedArgs = ['--foo', '--bar']; + const expected = { flags: { foo: true, bar: true }, values: { foo: [undefined], bar: [undefined] }, positionals: [] }; + const args = parseArgs(passedArgs); - t.deepEqual(args, expected, 'args are true') + t.deepEqual(args, expected, 'args are true'); - t.end() -}) + t.end(); +}); -test('arg is true and positional is identified', function (t) { - const passedArgs = ['--foo=a', '--foo', 'b'] - const expected = { flags: { foo: true}, values: { foo: [undefined]}, positionals: ['b'] } - const args = parseArgs(passedArgs) +test('arg is true and positional is identified', function(t) { + const passedArgs = ['--foo=a', '--foo', 'b']; + const expected = { flags: { foo: true }, values: { foo: [undefined] }, positionals: ['b'] }; + const args = parseArgs(passedArgs); - t.deepEqual(args, expected, 'arg is true and positional is identified') + t.deepEqual(args, expected, 'arg is true and positional is identified'); - t.end() -}) + t.end(); +}); -test('args equals are passed "withValue"', function (t) { - const passedArgs = ['--so=wat'] - const passedOptions = { withValue: ['so'] } - const expected = { flags: { so: true}, values: { so: ["wat"]}, positionals: [] } - const args = parseArgs(passedArgs, passedOptions) +test('args equals are passed "withValue"', function(t) { + const passedArgs = ['--so=wat']; + const passedOptions = { withValue: ['so'] }; + const expected = { flags: { so: true }, values: { so: ['wat'] }, positionals: [] }; + const args = parseArgs(passedArgs, passedOptions); - t.deepEqual(args, expected, 'arg value is passed') + t.deepEqual(args, expected, 'arg value is passed'); - t.end() -}) + t.end(); +}); -test('same arg is passed twice "withValue" and last value is recorded', function (t) { - const passedArgs = ['--foo=a', '--foo', 'b'] - const passedOptions = { withValue: ['foo'] } - const expected = { flags: { foo: true}, values: { foo: ['b']}, positionals: [] } - const args = parseArgs(passedArgs, passedOptions) +test('same arg is passed twice "withValue" and last value is recorded', function(t) { + const passedArgs = ['--foo=a', '--foo', 'b']; + const passedOptions = { withValue: ['foo'] }; + const expected = { flags: { foo: true }, values: { foo: ['b'] }, positionals: [] }; + const args = parseArgs(passedArgs, passedOptions); - t.deepEqual(args, expected, 'last arg value is passed') + t.deepEqual(args, expected, 'last arg value is passed'); - t.end() -}) + t.end(); +}); -test('args are passed "withValue" and "multiples"', function (t) { - const passedArgs = ['--foo=a', '--foo', 'b'] - const passedOptions = { withValue: ['foo'], multiples: ['foo'] } - const expected = { flags: { foo: true}, values: { foo: ['a', 'b']}, positionals: [] } - const args = parseArgs(passedArgs, passedOptions) +test('args are passed "withValue" and "multiples"', function(t) { + const passedArgs = ['--foo=a', '--foo', 'b']; + const passedOptions = { withValue: ['foo'], multiples: ['foo'] }; + const expected = { flags: { foo: true }, values: { foo: ['a', 'b'] }, positionals: [] }; + const args = parseArgs(passedArgs, passedOptions); - t.deepEqual(args, expected, 'both arg values are passed') + t.deepEqual(args, expected, 'both arg values are passed'); - t.end() -}) + t.end(); +}); test('correct default args when use node -p', function(t) { const holdArgv = process.argv; @@ -171,20 +172,20 @@ test('excess leading dashes on options are retained', function(t) { // Test bad inputs -test('boolean passed to "withValue" option', function (t) { - const passedArgs = ['--so=wat'] - const passedOptions = { withValue: true } +test('boolean passed to "withValue" option', function(t) { + const passedArgs = ['--so=wat']; + const passedOptions = { withValue: true }; - t.throws(function() { parseArgs(passedArgs, passedOptions) }); + t.throws(function() { parseArgs(passedArgs, passedOptions); }); - t.end() -}) + t.end(); +}); -test('string passed to "withValue" option', function (t) { - const passedArgs = ['--so=wat'] - const passedOptions = { withValue: 'so' } +test('string passed to "withValue" option', function(t) { + const passedArgs = ['--so=wat']; + const passedOptions = { withValue: 'so' }; - t.throws(function() { parseArgs(passedArgs, passedOptions) }); + t.throws(function() { parseArgs(passedArgs, passedOptions); }); - t.end() -}) + t.end(); +});