Skip to content

Commit

Permalink
fix: console.warn() rather than throwing errors when api signatures a…
Browse files Browse the repository at this point in the history
…re incorrect (#804)
  • Loading branch information
bcoe committed Feb 26, 2017
1 parent d78a0f5 commit a607061
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 95 deletions.
86 changes: 46 additions & 40 deletions lib/argsert.js
@@ -1,54 +1,60 @@
const command = require('./command')()
const YError = require('./yerror')

const positionName = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth']

module.exports = function (expected, callerArguments, length) {
// preface the argument description with "cmd", so
// that we can run it through yargs' command parser.
var position = 0
var parsed = {demanded: [], optional: []}
if (typeof expected === 'object') {
length = callerArguments
callerArguments = expected
} else {
parsed = command.parseCommand('cmd ' + expected)
}
const args = [].slice.call(callerArguments)
// TODO: should this eventually raise an exception.
try {
// preface the argument description with "cmd", so
// that we can run it through yargs' command parser.
var position = 0
var parsed = {demanded: [], optional: []}
if (typeof expected === 'object') {
length = callerArguments
callerArguments = expected
} else {
parsed = command.parseCommand('cmd ' + expected)
}
const args = [].slice.call(callerArguments)

while (args.length && args[args.length - 1] === undefined) args.pop()
length = length || args.length
while (args.length && args[args.length - 1] === undefined) args.pop()
length = length || args.length

if (length < parsed.demanded.length) {
throw Error('Not enough arguments provided. Expected ' + parsed.demanded.length +
' but received ' + args.length + '.')
}
if (length < parsed.demanded.length) {
throw new YError('Not enough arguments provided. Expected ' + parsed.demanded.length +
' but received ' + args.length + '.')
}

const totalCommands = parsed.demanded.length + parsed.optional.length
if (length > totalCommands) {
throw Error('Too many arguments provided. Expected max ' + totalCommands +
' but received ' + length + '.')
}
const totalCommands = parsed.demanded.length + parsed.optional.length
if (length > totalCommands) {
throw new YError('Too many arguments provided. Expected max ' + totalCommands +
' but received ' + length + '.')
}

parsed.demanded.forEach(function (demanded) {
const arg = args.shift()
const observedType = guessType(arg)
const matchingTypes = demanded.cmd.filter(function (type) {
return type === observedType || type === '*'
parsed.demanded.forEach(function (demanded) {
const arg = args.shift()
const observedType = guessType(arg)
const matchingTypes = demanded.cmd.filter(function (type) {
return type === observedType || type === '*'
})
if (matchingTypes.length === 0) argumentTypeError(observedType, demanded.cmd, position, false)
position += 1
})
if (matchingTypes.length === 0) argumentTypeError(observedType, demanded.cmd, position, false)
position += 1
})

parsed.optional.forEach(function (optional) {
if (args.length === 0) return
const arg = args.shift()
const observedType = guessType(arg)
const matchingTypes = optional.cmd.filter(function (type) {
return type === observedType || type === '*'
parsed.optional.forEach(function (optional) {
if (args.length === 0) return
const arg = args.shift()
const observedType = guessType(arg)
const matchingTypes = optional.cmd.filter(function (type) {
return type === observedType || type === '*'
})
if (matchingTypes.length === 0) argumentTypeError(observedType, optional.cmd, position, true)
position += 1
})
if (matchingTypes.length === 0) argumentTypeError(observedType, optional.cmd, position, true)
position += 1
})
} catch (err) {
console.warn(err.stack)
}
}

function guessType (arg) {
Expand All @@ -61,6 +67,6 @@ function guessType (arg) {
}

function argumentTypeError (observedType, allowedTypes, position, optional) {
throw Error('Invalid ' + (positionName[position] || 'manyith') + ' argument.' +
throw new YError('Invalid ' + (positionName[position] || 'manyith') + ' argument.' +
' Expected ' + allowedTypes.join(' or ') + ' but received ' + observedType + '.')
}
155 changes: 100 additions & 55 deletions test/argsert.js
@@ -1,98 +1,143 @@
/* global describe, it */

const argsert = require('../lib/argsert')
const expect = require('chai').expect
const checkOutput = require('./helpers/utils').checkOutput

require('chai').should()

describe('Argsert', function () {
it('does not throw exception if optional argument is not provided', function () {
argsert('[object]', [].slice.call(arguments))
it('does not warn if optional argument is not provided', function () {
var o = checkOutput(function () {
argsert('[object]', [].slice.call(arguments))
})

o.warnings.length.should.equal(0)
})

it('throws exception if wrong type is provided for optional argument', function () {
function foo (opts) {
argsert('[object|number]', [].slice.call(arguments))
}
expect(function () {
it('warn if wrong type is provided for optional argument', function () {
var o = checkOutput(function () {
function foo (opts) {
argsert('[object|number]', [].slice.call(arguments))
}

foo('hello')
}).to.throw(/Invalid first argument. Expected object or number but received string./)
})

o.warnings[0].should.match(/Invalid first argument. Expected object or number but received string./)
})

it('does not throw exception if optional argument is valid', function () {
function foo (opts) {
argsert('[object]', [].slice.call(arguments))
}
foo({foo: 'bar'})
it('does not warn if optional argument is valid', function () {
var o = checkOutput(function () {
function foo (opts) {
argsert('[object]', [].slice.call(arguments))
}

foo({foo: 'bar'})
})

o.warnings.length.should.equal(0)
})

it('throws exception if required argument is not provided', function () {
expect(function () {
it('warns if required argument is not provided', function () {
var o = checkOutput(function () {
argsert('<object>', [].slice.call(arguments))
}).to.throw(/Not enough arguments provided. Expected 1 but received 0./)
})

o.warnings[0].should.match(/Not enough arguments provided. Expected 1 but received 0./)
})

it('throws exception if required argument is of wrong type', function () {
function foo (opts) {
argsert('<object>', [].slice.call(arguments))
}
expect(function () {
it('warns if required argument is of wrong type', function () {
var o = checkOutput(function () {
function foo (opts) {
argsert('<object>', [].slice.call(arguments))
}

foo('bar')
}).to.throw(/Invalid first argument. Expected object but received string./)
})

o.warnings[0].should.match(/Invalid first argument. Expected object but received string./)
})

it('supports a combination of required and optional arguments', function () {
function foo (opts) {
argsert('<array> <string|object> [string|object]', [].slice.call(arguments))
}
foo([], 'foo', {})
var o = checkOutput(function () {
function foo (opts) {
argsert('<array> <string|object> [string|object]', [].slice.call(arguments))
}

foo([], 'foo', {})
})

o.warnings.length.should.equal(0)
})

it('throws an exception if too many arguments are provided', function () {
function foo (expected) {
argsert('<array> [batman]', [].slice.call(arguments))
}
expect(function () {
it('warns if too many arguments are provided', function () {
var o = checkOutput(function () {
function foo (expected) {
argsert('<array> [batman]', [].slice.call(arguments))
}

foo([], 33, 99)
}).to.throw(/Too many arguments provided. Expected max 2 but received 3./)
})

o.warnings[0].should.match(/Too many arguments provided. Expected max 2 but received 3./)
})

it('configures function to accept 0 parameters, if only arguments object is provided', function () {
function foo (expected) {
argsert([].slice.call(arguments))
}
expect(function () {
var o = checkOutput(function () {
function foo (expected) {
argsert([].slice.call(arguments))
}

foo(99)
}).to.throw(/Too many arguments provided. Expected max 0 but received 1./)
})

o.warnings[0].should.match(/Too many arguments provided. Expected max 0 but received 1./)
})

it('allows for any type if * is provided', function () {
function foo (opts) {
argsert('<*>', [].slice.call(arguments))
}
foo('bar')
var o = checkOutput(function () {
function foo (opts) {
argsert('<*>', [].slice.call(arguments))
}

foo('bar')
})

o.warnings.length.should.equal(0)
})

it('should ignore trailing undefined values', function () {
function foo (opts) {
argsert('<*>', [].slice.call(arguments))
}
foo('bar', undefined, undefined)
var o = checkOutput(function () {
function foo (opts) {
argsert('<*>', [].slice.call(arguments))
}

foo('bar', undefined, undefined)
})

o.warnings.length.should.equal(0)
})

it('should not ignore undefined values that are not trailing', function () {
function foo (opts) {
argsert('<*>', [].slice.call(arguments))
}
expect(function () {
var o = checkOutput(function () {
function foo (opts) {
argsert('<*>', [].slice.call(arguments))
}

foo('bar', undefined, undefined, 33)
}).to.throw(/Too many arguments provided. Expected max 1 but received 4./)
})

o.warnings[0].should.match(/Too many arguments provided. Expected max 1 but received 4./)
})

it('supports null as special type', function () {
function foo (arg) {
argsert('<null>', [].slice.call(arguments))
}
foo(null)
var o = checkOutput(function () {
function foo (arg) {
argsert('<null>', [].slice.call(arguments))
}
foo(null)
})

o.warnings.length.should.equal(0)
})
})
5 changes: 5 additions & 0 deletions test/helpers/utils.js
Expand Up @@ -10,16 +10,19 @@ exports.checkOutput = function (f, argv, cb) {
var _argv = process.argv
var _error = console.error
var _log = console.log
var _warn = console.warn

process.exit = function () { exit = true }
process.env = Hash.merge(process.env, { _: 'node' })
process.argv = argv || [ './usage' ]

var errors = []
var logs = []
var warnings = []

console.error = function (msg) { errors.push(msg) }
console.log = function (msg) { logs.push(msg) }
console.warn = function (msg) { warnings.push(msg) }

var result

Expand Down Expand Up @@ -57,6 +60,7 @@ exports.checkOutput = function (f, argv, cb) {

console.error = _error
console.log = _log
console.warn = _warn
}

function done () {
Expand All @@ -65,6 +69,7 @@ exports.checkOutput = function (f, argv, cb) {
return {
errors: errors,
logs: logs,
warnings: warnings,
exit: exit,
result: result
}
Expand Down

0 comments on commit a607061

Please sign in to comment.