angular-docs-cn/modules/rtts_assert/src/rtts_assert.es6

379 lines
8.5 KiB
JavaScript

var _global = typeof window === 'object' ? window : global;
// TODO(vojta):
// - extract into multiple files
// - different error types
// - simplify/humanize error messages
// - throw when invalid input (such as odd number of args into assert.argumentTypes)
var POSITION_NAME = ['', '1st', '2nd', '3rd'];
function argPositionName(i) {
var position = (i / 2) + 1;
return POSITION_NAME[position] || (position + 'th');
}
var primitives;
var genericType;
if (typeof $traceurRuntime === 'object') {
primitives = $traceurRuntime.type;
genericType = $traceurRuntime.genericType;
} else {
// Allow to work without traceur runtime as well!
primitives = {
any: {name: 'any'},
boolean: {name: 'boolean'},
number: {name: 'number'},
string: {name: 'string'},
symbol: {name: 'symbol'},
void: {name: 'void'}
};
genericType = function(type, args) {
return {
type: type,
args: args
}
}
}
Object.keys(primitives).forEach(function(name) {
primitives[name].__assertName = name;
});
export function proxy(){
}
function assertArgumentTypes(...params) {
var actual, type;
var currentArgErrors;
var errors = [];
var msg;
for (var i = 0, l = params.length; i < l; i = i + 2) {
actual = params[i];
type = params[i + 1];
currentArgErrors = [];
// currentStack = [];
//
if (!isType(actual, type, currentArgErrors)) {
// console.log(JSON.stringify(errors, null, ' '));
// TODO(vojta): print "an instance of" only if T starts with uppercase.
errors.push(argPositionName(i) + ' argument has to be an instance of ' + prettyPrint(type) + ', got ' + prettyPrint(actual));
if (currentArgErrors.length) {
errors.push(currentArgErrors);
}
}
}
if (errors.length) {
throw new Error('Invalid arguments given!\n' + formatErrors(errors));
}
}
function prettyPrint(value, depth) {
if (typeof(depth) === 'undefined') {
depth = 0;
}
if (depth++ > 3) {
return '[...]';
}
if (typeof value === 'undefined') {
return 'undefined';
}
if (typeof value === 'string') {
return '"' + value + '"';
}
if (typeof value === 'boolean') {
return value.toString();
}
if (value === null) {
return 'null';
}
if (typeof value === 'object') {
if (value.__assertName) {
return value.__assertName;
}
if (value.map && typeof value.map === 'function') {
return '[' + value.map((v) => prettyPrint(v, depth)).join(', ') + ']';
}
var properties = Object.keys(value);
var suffix = '}';
if (properties.length > 20) {
properties.length = 20;
suffix = ', ... }';
}
return '{' + properties.map((p) => p + ': ' + prettyPrint(value[p], depth)).join(', ') + suffix;
}
return value.__assertName || value.name || value.toString();
}
function isType(value, T, errors) {
if (T && T.type) {
// needed for generics.
// TODO(tbosch): read out T.args and do assertions based on them as well!
T = T.type;
}
if (T === primitives.void) {
return typeof value === 'undefined';
}
if (_isProxy(value)) {
return true;
}
if (T === primitives.any || value === null) {
return true;
}
if (T === primitives.string) {
return typeof value === 'string';
}
if (T === primitives.number) {
return typeof value === 'number';
}
if (T === primitives.boolean) {
return typeof value === 'boolean';
}
// var parentStack = currentStack;
// currentStack = [];
// shouldnt this create new stack?
if (typeof T.assert === 'function') {
var parentStack = currentStack;
var isValid;
currentStack = errors;
try {
isValid = T.assert(value) ;
} catch (e) {
fail(e.message);
isValid = false;
}
currentStack = parentStack;
if (typeof isValid === 'undefined') {
isValid = errors.length === 0;
}
return isValid;
// if (!currentStack.length) {
// currentStack = parentStack;
// return [];
// }
// var res = currentStack;
// currentStack = parentStack;
// return ['not instance of ' + prettyPrint(T), res];
}
return value instanceof T;
// if (!(value instanceof T)) {
// fail('not instance of ' + prettyPrint(T));
// }
// var res = currentStack;
// currentStack = parentStack;
// return res;
}
function _isProxy(obj) {
if (!obj || !obj.constructor || !obj.constructor.annotations) return false;
return obj.constructor.annotations.filter((a) => a instanceof proxy).length > 0;
}
function formatErrors(errors, indent = ' ') {
return errors.map((e) => {
if (typeof e === 'string') return indent + '- ' + e;
return formatErrors(e, indent + ' ');
}).join('\n');
}
// assert a type of given value and throw if does not pass
function type(actual, T) {
var errors = [];
// currentStack = [];
if (!isType(actual, T, errors)) {
// console.log(JSON.stringify(errors, null, ' '));
// TODO(vojta): print "an instance of" only if T starts with uppercase.
var msg = 'Expected an instance of ' + prettyPrint(T) + ', got ' + prettyPrint(actual) + '!';
if (errors.length) {
msg += '\n' + formatErrors(errors);
}
throw new Error(msg);
}
return actual;
}
function returnType(actual, T) {
var errors = [];
// currentStack = [];
if (!isType(actual, T, errors)) {
// console.log(JSON.stringify(errors, null, ' '));
// TODO(vojta): print "an instance of" only if T starts with uppercase.
var msg = 'Expected to return an instance of ' + prettyPrint(T) + ', got ' + prettyPrint(actual) + '!';
if (errors.length) {
msg += '\n' + formatErrors(errors);
}
throw new Error(msg);
}
return actual;
}
// TODO(vojta): define these with DSL?
var string = type.string = define('string', function(value) {
return typeof value === 'string';
});
var boolean = type.boolean = define('boolean', function(value) {
return typeof value === 'boolean';
});
var number = type.number = define('number', function(value) {
return typeof value === 'number';
});
function arrayOf(...types) {
return assert.define('array of ' + types.map(prettyPrint).join('/'), function(value) {
if (assert(value).is(Array)) {
for (var i = 0; i < value.length; i++) {
assert(value[i]).is(...types);
}
}
});
}
function structure(definition) {
var properties = Object.keys(definition);
return assert.define('object with properties ' + properties.join(', '), function(value) {
if (assert(value).is(Object)) {
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
assert(value[property]).is(definition[property]);
}
}
})
}
// I'm sorry, bad global state... to make the API nice ;-)
var currentStack = [];
function fail(message) {
currentStack.push(message);
}
function define(classOrName, check) {
var cls = classOrName;
if (typeof classOrName === 'string') {
cls = function() {};
cls.__assertName = classOrName;
}
cls.assert = function(value) {
// var parentStack = currentStack;
// currentStack = [];
return check(value);
// if (currentStack.length) {
// parentStack.push(currentStack)
// }
// currentStack = parentStack;
};
return cls;
}
function assert(value) {
return {
is: function is(...types) {
// var errors = []
var allErrors = [];
var errors;
for (var i = 0; i < types.length; i++) {
var type = types[i];
errors = [];
if (isType(value, type, errors)) {
return true;
}
// if no errors, merge multiple "is not instance of " into x/y/z ?
allErrors.push(prettyPrint(value) + ' is not instance of ' + prettyPrint(type))
if (errors.length) {
allErrors.push(errors);
}
}
// if (types.length > 1) {
// currentStack.push(['has to be ' + types.map(prettyPrint).join(' or '), ...allErrors]);
// } else {
currentStack.push(...allErrors);
// }
return false;
}
};
}
// PUBLIC API
// asserting API
// throw if no type provided
assert.type = type;
for (var prop in primitives) {
assert.type[prop] = primitives[prop];
}
assert.genericType = genericType;
// throw if odd number of args
assert.argumentTypes = assertArgumentTypes;
assert.returnType = returnType;
// define AP;
assert.define = define;
assert.fail = fail;
// primitive value type;
assert.string = string;
assert.number = number;
assert.boolean = boolean;
// custom types
assert.arrayOf = arrayOf;
assert.structure = structure;
export {assert}