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]; }); export default getGlobalType;