Last active
June 20, 2016 11:38
-
-
Save fakedrake/3aa756cbff346255b0a7942f4a89d057 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* ---------------------------------------------------------------------------- | |
* "THE BEER-WARE LICENSE" (Revision 80085): | |
* <cperivol@csail.mit.edu> wrote this file. As long as you retain this notice you | |
* can do whatever you want with this stuff. If we meet some day, and you think | |
* this stuff is worth it, you can buy me a beer in return | |
* | |
* Chris Perivolaropoulos. | |
* ---------------------------------------------------------------------------- | |
*/ | |
function mkChecker(expected, check) { | |
return function (obj) { | |
if (!check(obj)) { | |
return {error: "Expected " + expected + " got " + obj}; | |
} | |
return {ok: true}; | |
}; | |
} | |
function isSerializable (obj, dontCheckRecursive) { | |
// Is a class/function | |
if (obj === null) return true; | |
if (typeof obj === 'function') return false; | |
if (typeof obj !== 'object') return true; | |
if (obj instanceof Array) return !obj.some(function (x) { | |
return !isSerializable(x); | |
}); | |
// Is a class instance | |
if (obj.__proto__ && obj.__proto__.__proto__) return false; | |
// Is recursive | |
if (!dontCheckRecursive) | |
try {JSON.stringify(obj);} catch(c) {return false;} | |
// Check the children | |
return !Object.getOwnPropertyNames(obj).some(function (k) { | |
return !isSerializable(obj[k], true);}); | |
} | |
var _callback = mkChecker('function', function (o) { | |
return typeof o === 'function';}); | |
var checks = { | |
callback: _callback, | |
function: _callback, | |
object: mkChecker('object', function (o) {return o instanceof Object;}), | |
arraybuffer: mkChecker('arraybuffer', function (o) { | |
return o instanceof ArrayBuffer;}), | |
array: mkChecker('array', function (o) {return o instanceof Array;}), | |
number: mkChecker('number', function (o) {return typeof o === 'number';}), | |
string: mkChecker('string', function (o) {return typeof o === 'string';}), | |
bool: mkChecker('string', function (o) {return typeof o === 'boolean';}), | |
boolean: mkChecker('string', function (o) {return typeof o === 'boolean';}), | |
json: mkChecker('json', isSerializable), | |
any: function () {return {ok: true};} | |
}; | |
function hasKey(obj, key) { | |
return !Object.getOwnPropertyNames(obj).some(function (k) { | |
return key === k; | |
}); | |
} | |
function getCheck(checker) { | |
// Hardcoded checkers | |
var chk = checks[checker]; | |
if (typeof chk === 'function') return chk; | |
// Instance of a class | |
if (typeof checker === 'function' && checker.prototype) | |
return mkChecker(checker.name || 'class', function (obj) { | |
return obj instanceof checker; | |
}); | |
// Array with checked arguments | |
if (checker instanceof Array) | |
return function(arr) {return match (arr, checker);}; | |
// Object with checked values | |
if (typeof checker === 'object') return function (obj) { | |
var ret; | |
Object.getOwnPropertyNames(checker).some(function (k) { | |
if (typeof obj !== 'object') { | |
ret = {error: "Expected object, got " + obj}; | |
return true; | |
} | |
ret = getCheck(checker[k])(obj[k]); | |
if (ret.error) ret.error = "{" + k + ": " + ret.error + "}"; | |
return !!ret.error; | |
}); | |
return ret; | |
}; | |
throw Error('Unknown type checker:' + checker); | |
} | |
function match(args, checkers, index, cb) { | |
// In haskell-like | |
// match [] [] = True | |
// match [] [VarAny] = True | |
// match [] x = False | |
// match x [] = False | |
// match as [VarAny] = True | |
// match a:as VarAny:f:fs = (match a:as f:fs) || (match as VarAny:b:bs) | |
// match a:as b:bs = f a && match as fs | |
if (args.length === 0 && checkers.length === 0) { | |
return {ok: true}; | |
} | |
if (args.length === 0 && checkers.length === 1 && checkers[0] === 'varany') | |
return {ok: true}; | |
if (args.length === 0 || checkers.length === 0) { | |
if (checkers[0] === 'varany') return { | |
error: "Last args should check with " + checkers.slice(1) + | |
" but couldn't." | |
}; | |
cb && cb(args); | |
return { | |
error: "Wrong num of arguments: " + (index + args.length) + | |
" (expected " + (index + checkers.length) + ")" | |
}; | |
} | |
var checker = checkers[0], m; | |
if (checker === 'varany') { | |
if (checkers.length === 1) return {ok: true}; | |
m = match(args, checkers.slice(1), index, cb); | |
if (m.ok) return m; | |
return match(args.slice(1), checkers, index + 1, cb); | |
} | |
m = getCheck(checker)(args[0]); | |
if (!m.ok) { | |
cb && cb(checker, args[0]); | |
return {error: "Argument #" + index + ": " + m.error}; | |
}; | |
return match(args.slice(1), checkers.slice(1), index + 1, cb); | |
} | |
var PRODUCTION = | |
typeof global.it !== 'function' && | |
typeof global.describe !== 'function' && | |
typeof global.process === 'undefined'; | |
var settings = require('./settings.js').settings; | |
function typecheck (args, checkers, callback) { | |
if (PRODUCTION && !settings.get('typecheck')) return; | |
var m = match([].slice.call(args), checkers, 0, callback); | |
if (m.ok) return; | |
throw Error(m.error); | |
} | |
function typechecked(fn, argtypes) { | |
return function () { | |
typecheck(arguments, argtypes, console.log.bind(console, "Typechecked:")); | |
return fn.apply(null, arguments); | |
}; | |
} | |
module.exports.typechecked = typechecked; | |
module.exports.typecheck = typecheck; | |
module.exports.isSerializable = isSerializable; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment