diff --git a/index.js b/index.js index cf73e7c..d1fa1ce 100644 --- a/index.js +++ b/index.js @@ -58,8 +58,24 @@ function getMainArgs() { return ArrayPrototypeSlice(process.argv, 2); } -function storeOptionValue(options, longOption, value, result) { - const optionConfig = options[longOption] || {}; +function storeOptionValue(strict, options, longOption, value, result) { + const hasOptionConfig = ObjectHasOwn(options, longOption); + + if (strict) { + if (!hasOptionConfig) { + throw new Error(`Unknown option: --${longOption}`); + } + + if (options[longOption].type === 'string' && value == null) { + throw new Error(`Missing value for 'string' option: --${longOption}`); + } + + if (options[longOption].type === 'boolean' && value != null) { + throw new Error(`Unexpected value for 'boolean' option: --${longOption}`); + } + } + + const optionConfig = hasOptionConfig ? options[longOption] : {}; // Flags result.flags[longOption] = true; @@ -83,9 +99,11 @@ function storeOptionValue(options, longOption, value, result) { const parseArgs = ({ args = getMainArgs(), + strict = false, options = {} } = {}) => { validateArray(args, 'args'); + validateBoolean(strict, 'strict'); validateObject(options, 'options'); ArrayPrototypeForEach( ObjectEntries(options), @@ -166,6 +184,7 @@ const parseArgs = ({ // - preserve information for author to process further const index = StringPrototypeIndexOf(arg, '='); storeOptionValue( + strict, options, StringPrototypeSlice(arg, 0, index), StringPrototypeSlice(arg, index + 1), @@ -183,12 +202,12 @@ const parseArgs = ({ const val = options[arg] && options[arg].type === 'string' ? args[++pos] : undefined; - storeOptionValue(options, arg, val, result); + storeOptionValue(strict, options, arg, val, result); } else { // Cases when an arg is specified without a value, example // '--foo --bar' <- 'foo' and 'bar' flags should be set to true and // save value as undefined - storeOptionValue(options, arg, undefined, result); + storeOptionValue(strict, options, arg, undefined, result); } } else { // Arguments without a dash prefix are considered "positional" diff --git a/test/index.js b/test/index.js index a393bf8..e089575 100644 --- a/test/index.js +++ b/test/index.js @@ -381,6 +381,76 @@ test('invalid union value passed to "type" option', function(t) { t.end(); }); +// Test strict mode + +test('unknown long option --bar', function(t) { + const passedArgs = ['--foo', '--bar']; + const passedOptions = { foo: { type: 'boolean' } }; + const strict = true; + + t.throws(function() { parseArgs({ strict, args: passedArgs, options: passedOptions }); }, { + code: 'ERR_UNKNOWN_OPTION' + }); + + t.end(); +}); + +test('unknown short option -b', function(t) { + const passedArgs = ['--foo', '-b']; + const passedOptions = { foo: { type: 'boolean' } }; + const strict = true; + + t.throws(function() { parseArgs({ strict, args: passedArgs, options: passedOptions }); }, { + code: 'ERR_UNKNOWN_OPTION' + }); + + t.end(); +}); + +test('unknown option -r in short option group -bar', function(t) { + const passedArgs = ['--foo', '-bar']; + const passedOptions = { foo: { type: 'boolean' }, b: { type: 'boolean' }, a: { type: 'boolean' } }; + const strict = true; + + t.throws(function() { parseArgs({ strict, args: passedArgs, options: passedOptions }); }, { + code: 'ERR_UNKNOWN_OPTION' + }); + + t.end(); +}); + +test('unknown option with explicit value', function(t) { + const passedArgs = ['--foo', '--bar=baz']; + const passedOptions = { foo: { type: 'boolean' } }; + const strict = true; + + t.throws(function() { parseArgs({ strict, args: passedArgs, options: passedOptions }); }, { + code: 'ERR_UNKNOWN_OPTION' + }); + + t.end(); +}); + +test('string option used as boolean', function(t) { + const passedArgs = ['--foo']; + const passedOptions = { foo: { type: 'string' } }; + const strict = true; + + t.throws(function() { parseArgs({ strict, args: passedArgs, options: passedOptions }); }); + + t.end(); +}); + +test('boolean option used with value', function(t) { + const passedArgs = ['--foo=bar']; + const passedOptions = { foo: { type: 'boolean' } }; + const strict = true; + + t.throws(function() { parseArgs({ strict, args: passedArgs, options: passedOptions }); }); + + t.end(); +}); + test('invalid short option length', function(t) { const passedArgs = []; const passedOptions = { foo: { short: 'fo' } };