should-type
Version:
Simple module to get instance type. Like a bit more advanced version of typeof
263 lines (232 loc) • 6.93 kB
JavaScript
;
var types = {
NUMBER: 'number',
UNDEFINED: 'undefined',
STRING: 'string',
BOOLEAN: 'boolean',
OBJECT: 'object',
FUNCTION: 'function',
NULL: 'null',
ARRAY: 'array',
REGEXP: 'regexp',
DATE: 'date',
ERROR: 'error',
ARGUMENTS: 'arguments',
SYMBOL: 'symbol',
ARRAY_BUFFER: 'array-buffer',
TYPED_ARRAY: 'typed-array',
DATA_VIEW: 'data-view',
MAP: 'map',
SET: 'set',
WEAK_SET: 'weak-set',
WEAK_MAP: 'weak-map',
PROMISE: 'promise',
// node buffer
BUFFER: 'buffer',
// dom html element
HTML_ELEMENT: 'html-element',
HTML_ELEMENT_TEXT: 'html-element-text',
DOCUMENT: 'document',
WINDOW: 'window',
FILE: 'file',
FILE_LIST: 'file-list',
BLOB: 'blob',
HOST: 'host',
XHR: 'xhr',
// simd
SIMD: 'simd'
};
/*
* Simple data function to store type information
* @param {string} type Usually what is returned from typeof
* @param {string} cls Sanitized @Class via Object.prototype.toString
* @param {string} sub If type and cls the same, and need to specify somehow
* @private
* @example
*
* //for null
* new Type('null');
*
* //for Date
* new Type('object', 'date');
*
* //for Uint8Array
*
* new Type('object', 'typed-array', 'uint8');
*/
function Type(type, cls, sub) {
if (!type) {
throw new Error('Type class must be initialized at least with `type` information');
}
this.type = type;
this.cls = cls;
this.sub = sub;
}
Type.prototype = {
toString: function(sep) {
sep = sep || ';';
var str = [this.type];
if (this.cls) {
str.push(this.cls);
}
if (this.sub) {
str.push(this.sub);
}
return str.join(sep);
},
toTryTypes: function() {
var _types = [];
if (this.sub) {
_types.push(new Type(this.type, this.cls, this.sub));
}
if (this.cls) {
_types.push(new Type(this.type, this.cls));
}
_types.push(new Type(this.type));
return _types;
}
};
var toString = Object.prototype.toString;
/**
* Function to store type checks
* @private
*/
function TypeChecker() {
this.checks = [];
}
TypeChecker.prototype = {
add: function(func) {
this.checks.push(func);
return this;
},
addBeforeFirstMatch: function(obj, func) {
var match = this.getFirstMatch(obj);
if (match) {
this.checks.splice(match.index, 0, func);
} else {
this.add(func);
}
},
addTypeOf: function(type, res) {
return this.add(function(obj, tpeOf) {
if (tpeOf === type) {
return new Type(res);
}
});
},
addClass: function(cls, res, sub) {
return this.add(function(obj, tpeOf, objCls) {
if (objCls === cls) {
return new Type(types.OBJECT, res, sub);
}
});
},
getFirstMatch: function(obj) {
var typeOf = typeof obj;
var cls = toString.call(obj);
for (var i = 0, l = this.checks.length; i < l; i++) {
var res = this.checks[i].call(this, obj, typeOf, cls);
if (typeof res !== 'undefined') {
return { result: res, func: this.checks[i], index: i };
}
}
},
getType: function(obj) {
var match = this.getFirstMatch(obj);
return match && match.result;
}
};
var main = new TypeChecker();
//TODO add iterators
main
.addTypeOf(types.NUMBER, types.NUMBER)
.addTypeOf(types.UNDEFINED, types.UNDEFINED)
.addTypeOf(types.STRING, types.STRING)
.addTypeOf(types.BOOLEAN, types.BOOLEAN)
.addTypeOf(types.FUNCTION, types.FUNCTION)
.addTypeOf(types.SYMBOL, types.SYMBOL)
.add(function(obj) {
if (obj === null) {
return new Type(types.NULL);
}
})
.addClass('[object String]', types.STRING)
.addClass('[object Boolean]', types.BOOLEAN)
.addClass('[object Number]', types.NUMBER)
.addClass('[object Array]', types.ARRAY)
.addClass('[object RegExp]', types.REGEXP)
.addClass('[object Error]', types.ERROR)
.addClass('[object Date]', types.DATE)
.addClass('[object Arguments]', types.ARGUMENTS)
.addClass('[object ArrayBuffer]', types.ARRAY_BUFFER)
.addClass('[object Int8Array]', types.TYPED_ARRAY, 'int8')
.addClass('[object Uint8Array]', types.TYPED_ARRAY, 'uint8')
.addClass('[object Uint8ClampedArray]', types.TYPED_ARRAY, 'uint8clamped')
.addClass('[object Int16Array]', types.TYPED_ARRAY, 'int16')
.addClass('[object Uint16Array]', types.TYPED_ARRAY, 'uint16')
.addClass('[object Int32Array]', types.TYPED_ARRAY, 'int32')
.addClass('[object Uint32Array]', types.TYPED_ARRAY, 'uint32')
.addClass('[object Float32Array]', types.TYPED_ARRAY, 'float32')
.addClass('[object Float64Array]', types.TYPED_ARRAY, 'float64')
.addClass('[object Bool16x8]', types.SIMD, 'bool16x8')
.addClass('[object Bool32x4]', types.SIMD, 'bool32x4')
.addClass('[object Bool8x16]', types.SIMD, 'bool8x16')
.addClass('[object Float32x4]', types.SIMD, 'float32x4')
.addClass('[object Int16x8]', types.SIMD, 'int16x8')
.addClass('[object Int32x4]', types.SIMD, 'int32x4')
.addClass('[object Int8x16]', types.SIMD, 'int8x16')
.addClass('[object Uint16x8]', types.SIMD, 'uint16x8')
.addClass('[object Uint32x4]', types.SIMD, 'uint32x4')
.addClass('[object Uint8x16]', types.SIMD, 'uint8x16')
.addClass('[object DataView]', types.DATA_VIEW)
.addClass('[object Map]', types.MAP)
.addClass('[object WeakMap]', types.WEAK_MAP)
.addClass('[object Set]', types.SET)
.addClass('[object WeakSet]', types.WEAK_SET)
.addClass('[object Promise]', types.PROMISE)
.addClass('[object Blob]', types.BLOB)
.addClass('[object File]', types.FILE)
.addClass('[object FileList]', types.FILE_LIST)
.addClass('[object XMLHttpRequest]', types.XHR)
.add(function(obj) {
if ((typeof Promise === types.FUNCTION && obj instanceof Promise) ||
(typeof obj.then === types.FUNCTION)) {
return new Type(types.OBJECT, types.PROMISE);
}
})
.add(function(obj) {
if (typeof Buffer !== 'undefined' && obj instanceof Buffer) {// eslint-disable-line no-undef
return new Type(types.OBJECT, types.BUFFER);
}
})
.add(function(obj) {
if (typeof Node !== 'undefined' && obj instanceof Node) {
return new Type(types.OBJECT, types.HTML_ELEMENT, obj.nodeName);
}
})
.add(function(obj) {
// probably at the begginging should be enough these checks
if (obj.Boolean === Boolean && obj.Number === Number && obj.String === String && obj.Date === Date) {
return new Type(types.OBJECT, types.HOST);
}
})
.add(function() {
return new Type(types.OBJECT);
});
/**
* Get type information of anything
*
* @param {any} obj Anything that could require type information
* @return {Type} type info
* @private
*/
function getGlobalType(obj) {
return main.getType(obj);
}
getGlobalType.checker = main;
getGlobalType.TypeChecker = TypeChecker;
getGlobalType.Type = Type;
Object.keys(types).forEach(function(typeName) {
getGlobalType[typeName] = types[typeName];
});
module.exports = getGlobalType;