Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: console.warn() rather than throwing errors when api signatures are incorrect #804

Merged
merged 6 commits into from Feb 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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