Skip to content

Commit

Permalink
feat: manage defaultValue array
Browse files Browse the repository at this point in the history
  • Loading branch information
Eomm committed Aug 16, 2022
1 parent d144645 commit 6c91d6d
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 17 deletions.
60 changes: 43 additions & 17 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ const {
const {
validateArray,
validateBoolean,
validateBooleanArray,
validateObject,
validateString,
validateStringArray,
validateUnion,
} = require('./internal/validators');

const {
kEmptyObject,
isDefaultValueOptionUsed,
kEmptyObject
} = require('./internal/util');

const {
Expand All @@ -40,6 +41,7 @@ const {
isOptionLikeValue,
isShortOptionAndValue,
isShortOptionGroup,
useDefaultValueOption,
objectGetOwn,
optionsGetOwn,
} = require('./utils');
Expand Down Expand Up @@ -144,6 +146,25 @@ function storeOption(longOption, optionValue, options, values) {
}
}

/**
* Store the default option value in `values`.
*
* @param {string} longOption - long option name e.g. 'foo'
* @param {string
* | boolean
* | string[]
* | boolean[]} optionValue - default value from option config
* @param {object} options - option configs, from parseArgs({ options })
* @param {object} values - option values returned in `values` by parseArgs
*/
function storeDefaultOption(longOption, optionValue, options, values) {
if (longOption === '__proto__') {
return; // No. Just no.
}

values[longOption] = optionValue;
}

/**
* Process args and turn into identified tokens:
* - option (along with value, if any)
Expand Down Expand Up @@ -295,15 +316,6 @@ const parseArgs = (config = kEmptyObject) => {
const optionType = objectGetOwn(optionConfig, 'type');
validateUnion(optionType, `options.${longOption}.type`, ['string', 'boolean']);

if (ObjectHasOwn(optionConfig, 'defaultValue')) {
const defaultValue = objectGetOwn(optionConfig, 'defaultValue');
if (optionType === 'string') {
validateString(defaultValue, `options.${longOption}.defaultValue`);
} else if (optionType === 'boolean') {
validateBoolean(defaultValue, `options.${longOption}.defaultValue`);
}
}

if (ObjectHasOwn(optionConfig, 'short')) {
const shortOption = optionConfig.short;
validateString(shortOption, `options.${longOption}.short`);
Expand All @@ -316,9 +328,23 @@ const parseArgs = (config = kEmptyObject) => {
}
}

if (ObjectHasOwn(optionConfig, 'multiple')) {
const hasMultipleFlag = ObjectHasOwn(optionConfig, 'multiple');
if (hasMultipleFlag) {
validateBoolean(optionConfig.multiple, `options.${longOption}.multiple`);
}

if (ObjectHasOwn(optionConfig, 'defaultValue')) {
const defaultValue = objectGetOwn(optionConfig, 'defaultValue');
if (optionType === 'string' && !hasMultipleFlag) {
validateString(defaultValue, `options.${longOption}.defaultValue`);
} else if (optionType === 'string' && hasMultipleFlag) {
validateStringArray(defaultValue, `options.${longOption}.defaultValue`);
} else if (optionType === 'boolean' && !hasMultipleFlag) {
validateBoolean(defaultValue, `options.${longOption}.defaultValue`);
} else if (optionType === 'boolean' && hasMultipleFlag) {
validateBooleanArray(defaultValue, `options.${longOption}.defaultValue`);
}
}
}
);

Expand Down Expand Up @@ -351,16 +377,16 @@ const parseArgs = (config = kEmptyObject) => {
// Phase 3: fill in default values for missing args
const defaultValueOptions = ArrayPrototypeFilter(
ObjectEntries(options), (option) => {
return isDefaultValueOptionUsed(option, result.values);
return useDefaultValueOption(option, result.values);
});

if (defaultValueOptions.length > 0) {
ArrayPrototypeForEach(defaultValueOptions, ({ 0: longOption,
1: optionConfig }) => {
storeOption(longOption,
optionConfig.defaultValue,
options,
result.values);
storeDefaultOption(longOption,
optionConfig.defaultValue,
options,
result.values);
});

}
Expand Down
16 changes: 16 additions & 0 deletions internal/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ function validateArray(value, name) {
}
}

function validateStringArray(value, name) {
validateArray(value, name);
for (let i = 0; i < value.length; i++) {
validateString(value[i], `${name}[${i}]`);
}
}

function validateBooleanArray(value, name) {
validateArray(value, name);
for (let i = 0; i < value.length; i++) {
validateBoolean(value[i], `${name}[${i}]`);
}
}

/**
* @param {unknown} value
* @param {string} name
Expand Down Expand Up @@ -63,6 +77,8 @@ module.exports = {
validateArray,
validateObject,
validateString,
validateStringArray,
validateUnion,
validateBoolean,
validateBooleanArray,
};
76 changes: 76 additions & 0 deletions test/default-values.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@ test('defaultValue must be a boolean when option type is boolean', (t) => {
t.end();
});

test('defaultValue must be a boolean array when option type is boolean and multiple', (t) => {
const args = [];
const options = { alpha: { type: 'boolean', multiple: true, defaultValue: 'not an array' } };
t.throws(() => {
parseArgs({ args, options });
}, /alpha\.defaultValue must be Array/
);
t.end();
});

test('defaultValue must be a boolean array when option type is string and multiple is true', (t) => {
const args = [];
const options = { alpha: { type: 'boolean', multiple: true, defaultValue: [true, true, 42] } };
t.throws(() => {
parseArgs({ args, options });
}, /alpha\.defaultValue\[2\] must be Boolean/
);
t.end();
});

test('defaultValue must be a string when option type is string', (t) => {
const args = [];
const options = { alpha: { type: 'string', defaultValue: true } };
Expand All @@ -25,6 +45,49 @@ test('defaultValue must be a string when option type is string', (t) => {
t.end();
});

test('defaultValue must be an array when option type is string and multiple is true', (t) => {
const args = [];
const options = { alpha: { type: 'string', multiple: true, defaultValue: 'not an array' } };
t.throws(() => {
parseArgs({ args, options });
}, /alpha\.defaultValue must be Array/
);
t.end();
});

test('defaultValue must be a string array when option type is string and multiple is true', (t) => {
const args = [];
const options = { alpha: { type: 'string', multiple: true, defaultValue: ['str', 42] } };
t.throws(() => {
parseArgs({ args, options });
}, /alpha\.defaultValue\[1\] must be String/
);
t.end();
});

test('defaultValue accepted input when multiple is true', (t) => {
const args = ['--inputStringArr', 'c', '--inputStringArr', 'd', '--inputBoolArr', '--inputBoolArr'];
const options = {
inputStringArr: { type: 'string', multiple: true, defaultValue: ['a', 'b'] },
emptyStringArr: { type: 'string', multiple: true, defaultValue: [] },
fullStringArr: { type: 'string', multiple: true, defaultValue: ['a', 'b'] },
inputBoolArr: { type: 'boolean', multiple: true, defaultValue: [false, true, false] },
emptyBoolArr: { type: 'boolean', multiple: true, defaultValue: [] },
fullBoolArr: { type: 'boolean', multiple: true, defaultValue: [false, true, false] },
};
const expected = { values: { __proto__: null,
inputStringArr: ['c', 'd'],
inputBoolArr: [true, true],
emptyStringArr: [],
fullStringArr: ['a', 'b'],
emptyBoolArr: [],
fullBoolArr: [false, true, false] },
positionals: [] };
const result = parseArgs({ args, options });
t.deepEqual(result, expected);
t.end();
});

test('when defaultValue is set, the option must be added as result', (t) => {
const args = [];
const options = {
Expand Down Expand Up @@ -88,3 +151,16 @@ test('tokens:true should not include the defaultValue options after the args inp
t.deepEqual(tokens, expectedTokens);
t.end();
});

test('proto as default value must be ignored', (t) => {
const args = [];
const options = Object.create(null);

// eslint-disable-next-line no-proto
options.__proto__ = { type: 'string', defaultValue: 'HELLO' };

const result = parseArgs({ args, options, allowPositionals: true });
const expected = { values: { __proto__: null }, positionals: [] };
t.deepEqual(result, expected);
t.end();
});

0 comments on commit 6c91d6d

Please sign in to comment.