Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Sep 8, 2020
1 parent 0dc3475 commit ec0c3ee
Show file tree
Hide file tree
Showing 6 changed files with 6,712 additions and 21 deletions.
79 changes: 78 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,84 @@

## Usage

This module is under development and not ready for use yet.
### Standard values

```js
const typeOf = require('native-type-of');

// Primitives
typeOf('abc') === 'string';
typeOf(123) === 'number';
typeOf(true) === 'boolean';
typeOf(Symbol()) === 'symbol';
typeOf(100n) === 'bigint';
typeOf(null) === 'null';
typeOf(undefined) === 'undefined';

// Functions
typeOf(function() {}) === 'Function';
typeOf(() => {}) === 'Function';
typeOf(async function() {}) === 'Function';
typeOf(function*() {}) === 'Function';
typeOf(async function*() {}) === 'Function';

// Objects
typeOf({}) === 'Object';
class C {}
typeOf(new C()) === 'Object';

// Built-ins
typeOf([]) === 'Array';
typeOf(/^abc$/) === 'RegExp';
typeOf(new Date()) === 'Date';
typeOf(Promise.resolve()) === 'Promise';
typeOf(new Set()) === 'Set';
typeOf(new Map()) === 'Map';
typeOf(new WeakSet()) === 'WeakSet';
typeOf(new WeakMap()) === 'WeakMap';
typeOf(new WeakRef({})) === 'WeakRef';
typeOf(new FinalizationRegistry(() => {})) === 'FinalizationRegistry';

// Boxed primitives
typeOf(new String('')) === 'String';
typeOf(new Number(123)) === 'Number';
typeOf(new Boolean(true)) === 'Boolean';

// Errors
typeOf(new Error()) === 'Error';
typeOf(new TypeError()) === 'TypeError';
typeOf(new ReferenceError()) === 'ReferenceError';
// ...etc

// Buffers
typeOf(new ArrayBuffer(8)) === 'ArrayBuffer';
typeOf(new SharedArrayBuffer(8)) === 'SharedArrayBuffer';
typeOf(new Uint8Array([1, 2, 3])) === 'Uint8Array';
typeOf(new Int32Array([1, 2, 3])) === 'Int32Array';
// ...etc
```

### Values with altered prototype

If the prototype chain is altered, this package aims to return the *native* type of the underlying object.

This is where this package differs from others like [kind-of](https://www.npmjs.com/package/kind-of).

```js
const map = new Map();
Object.setPrototypeOf(map, Array.prototype);
typeOf(map) === 'Map';
```

### Known issues

There are a few rare cases where native type cannot be correctly determined:

* `typeOf( Object.setPrototypeOf( Promise.resolve(), Array.prototype ) ) === 'Object'` ([issue](https://github.com/overlookmotel/native-type-of/issues/1))
* `typeOf( Object.setPrototypeOf( new Error(), Map.prototype ) ) === 'Object'` ([issue](https://github.com/overlookmotel/native-type-of/issues/2))
* `typeOf( Object.setPrototypeOf( {}, TypeError.prototype ) ) === 'TypeError'` ([issue](https://github.com/overlookmotel/native-type-of/issues/3))

These are the only known cases where the result is inaccurate.

## Versioning

Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@

// Exports

module.exports = {};
module.exports = require('./lib/index.js');
102 changes: 102 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* --------------------
* native-type-of module
* `typeOf()` function
* ------------------*/

'use strict';

// Exports

module.exports = typeOf;

const {toStringTag} = Symbol,
objectToString = Object.prototype.toString,
errorToString = Error.prototype.toString,
typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype),
typedArrayEntries = typedArrayPrototype.entries,
typedArrayStringTagGetter = Object.getOwnPropertyDescriptor(typedArrayPrototype, Symbol.toStringTag)
.get;

const detectionMethods = [
['RegExp', 'test'],
['Date', 'toString'],
['Set', 'has'],
['Map', 'has'],
['WeakSet', 'has'],
['WeakMap', 'has'],
['WeakRef', 'deref'],
['FinalizationRegistry', 'unregister', [{}]],
['BigInt', 'toString'],
['Promise', 'then'],
['String', 'valueOf'],
['Number', 'toString'],
['Boolean', 'toString'],
['DataView', 'getUint8']
].map(
([type, methodName, args]) => {
const ctor = global[type];
if (!ctor) return false;
return {type, method: ctor.prototype[methodName], args: args || []};
}
).filter(Boolean);

detectionMethods.push({
type: 'ArrayBuffer',
method: Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength').get,
args: []
});
detectionMethods.push({
type: 'SharedArrayBuffer',
method: Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, 'byteLength').get,
args: []
});

function typeOf(val) {
// Use `typeof`
const typeofType = typeof val;
if (typeofType === 'function') return 'Function';
if (typeofType !== 'object') return typeofType;

// Identify `null`
if (val === null) return 'null';

// Identify Arrays
if (Array.isArray(val)) return 'Array';

// Try detection methods and return type for first that doesn't throw
for (const {type, method, args} of detectionMethods) {
if (methodDoesNotThrow(val, method, args)) return type;
}

// Identify TypedArrays
if (methodDoesNotThrow(val, typedArrayEntries, [])) return typedArrayStringTagGetter.call(val);

// Identify Errors and Arguments objects
const errorMatch = errorToString.call(val).match(/^(.+Error)(?::|$)/);
if (errorMatch) return errorMatch[1];

if (hasNoToStringTagProp(val)) return objectToString.call(val).slice(8, -1);

// Must be a plain object, null-prototype object, or user-defined class instance
return 'Object';
}

function methodDoesNotThrow(val, method, args) {
try {
method.call(val, ...args);
return true;
} catch (err) {
return false;
}
}

function hasNoToStringTagProp(val) {
let current = val;
do {
const descriptor = Object.getOwnPropertyDescriptor(current, toStringTag);
if (descriptor && (typeof descriptor.value === 'string' || descriptor.get)) return false;
current = Object.getPrototypeOf(current);
} while (current);

return true;
}
Loading

0 comments on commit ec0c3ee

Please sign in to comment.