commit 58abf58f3ab4888a766add1a7ccbfcf501d9de62 Author: Sindre Sorhus Date: Fri Sep 22 00:44:27 2017 +0700 Init diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1c6314a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..391f0a4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.js text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..239ecff --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +yarn.lock diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7d69d74 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - '8' + - '6' + - '4' diff --git a/header.gif b/header.gif new file mode 100644 index 0000000..f957944 Binary files /dev/null and b/header.gif differ diff --git a/index.js b/index.js new file mode 100644 index 0000000..132db41 --- /dev/null +++ b/index.js @@ -0,0 +1,151 @@ +'use strict'; +const toString = Object.prototype.toString; +const getObjectType = x => toString.call(x).slice(8, -1); + +const is = value => { + if (value == null) { // eslint-disable-line no-eq-null, eqeqeq + return 'null'; + } + + if (value === true || value === false) { + return 'boolean'; + } + + const type = typeof value; + + if (type === 'undefined') { + return 'undefined'; + } + + if (type === 'string') { + return 'string'; + } + + if (type === 'number') { + return 'number'; + } + + if (type === 'symbol') { + return 'symbol'; + } + + if (type === 'function') { + return 'Function'; + } + + if (Array.isArray(value)) { + return 'Array'; + } + + if (Buffer.isBuffer(value)) { + return 'Buffer'; + } + + const tagType = getObjectType(value); + if (tagType) { + return tagType; + } + + if (value instanceof String || value instanceof Boolean || value instanceof Number) { + throw new TypeError('Please don\'t use object wrappers for primitive types'); + } + + return 'Object'; +}; + +is.undefined = x => typeof x === 'undefined'; +is.null = x => x === null; +is.string = x => typeof x === 'string'; +is.number = x => typeof x === 'number'; +is.boolean = x => typeof x === 'boolean'; +is.symbol = x => typeof x === 'symbol'; + +is.array = Array.isArray; +is.function = x => typeof x === 'function'; +is.buffer = Buffer.isBuffer; + +is.object = x => { + const type = typeof x; + return x !== null && (type === 'object' || type === 'function'); +}; + +is.nativePromise = x => getObjectType(x) === 'Promise'; + +is.promise = x => { + return is.nativePromise(x) || + ( + x !== null && + typeof x === 'object' && + typeof x.then === 'function' && + typeof x.catch === 'function' + ); +}; + +is.regExp = x => getObjectType(x) === 'RegExp'; +is.date = x => getObjectType(x) === 'Date'; +is.error = x => getObjectType(x) === 'Error'; +is.map = x => getObjectType(x) === 'Map'; +is.set = x => getObjectType(x) === 'Set'; +is.weakMap = x => getObjectType(x) === 'WeakMap'; +is.weakSet = x => getObjectType(x) === 'WeakSet'; + +is.int8Array = x => getObjectType(x) === 'Int8Array'; +is.uint8Array = x => getObjectType(x) === 'Uint8Array'; +is.uint8ClampedArray = x => getObjectType(x) === 'Uint8ClampedArray'; +is.int16Array = x => getObjectType(x) === 'Int16Array'; +is.uint16Array = x => getObjectType(x) === 'Uint16Array'; +is.int32Array = x => getObjectType(x) === 'Int32Array'; +is.uint32Array = x => getObjectType(x) === 'Uint32Array'; +is.float32Array = x => getObjectType(x) === 'Float32Array'; +is.float64Array = x => getObjectType(x) === 'Float64Array'; + +is.arrayBuffer = x => getObjectType(x) === 'ArrayBuffer'; + +is.sharedArrayBuffer = x => { + try { + return getObjectType(x) === 'SharedArrayBuffer'; + } catch (err) { + return false; + } +}; + +is.nan = Number.isNaN; +is.nullOrUndefined = x => x === null || typeof x === 'undefined'; + +is.primitive = x => { + const type = typeof x; + return x === null || + type === 'undefined' || + type === 'string' || + type === 'number' || + type === 'boolean' || + type === 'symbol'; +}; + +is.integer = Number.isInteger; + +is.plainObject = x => { + // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js + let prototype; + // eslint-disable-next-line no-return-assign + return getObjectType(x) === 'Object' && + (prototype = Object.getPrototypeOf(x), prototype === null || + prototype === Object.getPrototypeOf({})); +}; + +is.iterable = x => !is.null(x) && !is.undefined(x) && typeof x[Symbol.iterator] === 'function'; + +const typedArrayTypes = new Set([ + 'Int8Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'Int16Array', + 'Uint16Array', + 'Int32Array', + 'Uint32Array', + 'Float32Array', + 'Float64Array' +]); +is.typedArray = x => typedArrayTypes.has(getObjectType(x)); + +module.exports = is; diff --git a/license b/license new file mode 100644 index 0000000..e7af2f7 --- /dev/null +++ b/license @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/package.json b/package.json new file mode 100644 index 0000000..148e58e --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "@sindresorhus/is", + "version": "0.0.0", + "description": "Type check values: `is.string('🦄') //=> true`", + "license": "MIT", + "repository": "sindresorhus/is", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=4" + }, + "scripts": { + "test": "xo && ava" + }, + "files": [ + "index.js" + ], + "keywords": [ + "type", + "types", + "is", + "check", + "checking", + "validate", + "validation", + "utility", + "util", + "typeof", + "instanceof", + "object", + "assert", + "assertion", + "test", + "kind", + "primitive", + "verify", + "compare" + ], + "devDependencies": { + "ava": "*", + "xo": "*" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..ee1761d --- /dev/null +++ b/readme.md @@ -0,0 +1,152 @@ +# is [![Build Status](https://travis-ci.org/sindresorhus/is.svg?branch=master)](https://travis-ci.org/sindresorhus/is) + +> Type check values: `is.string('🦄') //=> true` + + + + +## Install + +``` +$ npm install @sindresorhus/is +``` + + +## Usage + +```js +const is = require('@sindresorhus/is'); + +is('🦄'); +//=> 'string' + +is(new Map()); +//=> 'Map' + +is.number(6); +//=> true +``` + + +## API + +### is(value) + +Returns the type of `value`. + +Primitives are lowercase and object types are camelcase. + +Example: + +- `'undefined'` +- `'null'` +- `'string'` +- `'symbol'` +- `'Array'` +- `'Function'` +- `'Object'` + +Note: It will throw if you try to feed it object-wrapped primitives, as that's a bad practice. For example `new String('foo')`. + +### is.{method} + +All the below methods accept a value and returns a boolean for whether the value is of the desired type. + +#### Primitives + +##### .undefined(value) +##### .null(value) +##### .string(value) +##### .number(value) +##### .boolean(value) +##### .symbol(value) + +#### Built-in types + +##### .array(value) +##### .function(value) +##### .buffer(value) +##### .object(value) + +Keep in mind that [functions are objects too](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions). + +##### .regExp(value) +##### .date(value) +##### .error(value) +##### .nativePromise(value) +##### .promise(value) + +Returns `true` for any object with a `.then()` and `.catch()` method. Prefer this one over `.nativePromise()` as you usually want to allow userland promise implementations too. + +##### .map(value) +##### .set(value) +##### .weakMap(value) +##### .weakSet(value) + +#### Typed arrays + +##### .int8Array(value) +##### .uint8Array(value) +##### .uint8ClampedArray(value) +##### .int16Array(value) +##### .uint16Array(value) +##### .int32Array(value) +##### .uint32Array(value) +##### .float32Array(value) +##### .float64Array(value) + +#### Structured data + +##### .arrayBuffer(value) +##### .sharedArrayBuffer(value) +##### .dataView(value) + +#### Miscellaneous + +##### .nan(value) +##### .nullOrUndefined(value) +##### .primitive(value) + +JavaScript primitives are as follows: `null`, `undefined`, `string`, `number`, `boolean`, `symbol`. + +##### .integer(value) +##### .plainObject(value) + +An object is plain if it's created by either `{}`, `new Object()`, or `Object.create(null)`. + +##### .iterable(value) +##### .typedArray(value) + + +## FAQ + +### Why yet another type checking module? + +There are hundreds of type checking modules on npm, unfortunately, I couldn't find any that fit my needs: + +- Includes both type methods and ability to get the type +- Types of primitives returned as lowercase and object types as camelcase +- Covers all built-ins +- Unsurprising behavior +- Well-maintained +- Comprehensive test suite + +For the ones I found, pick 3 of these. + +The most common mistakes I noticed in these modules was using `instanceof` for type checking, forgetting that functions are objects, and omitting `symbol` as a primitive. + + +## Related + +- [is-stream](https://github.com/sindresorhus/is-stream) - Check if something is a Node.js stream +- [is-observable](https://github.com/sindresorhus/is-observable) - Check if a value is an Observable +- [file-type](https://github.com/sindresorhus/file-type) - Detect the file type of a Buffer/Uint8Array +- [is-ip](https://github.com/sindresorhus/is-ip) - Check if a string is an IP address +- [is-array-sorted](https://github.com/sindresorhus/is-array-sorted) - Check if an Array is sorted +- [is-error-constructor](https://github.com/sindresorhus/is-error-constructor) - Check if a value is an error constructor +- [is-empty-iterable](https://github.com/sindresorhus/is-empty-iterable) - Check if an Iterable is empty + + +## License + +MIT © [Sindre Sorhus](https://sindresorhus.com) diff --git a/test.js b/test.js new file mode 100644 index 0000000..7868637 --- /dev/null +++ b/test.js @@ -0,0 +1,295 @@ +import util from 'util'; +import test from 'ava'; +import m from '.'; + +const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; + +const PromiseSubclassFixture = class extends Promise {}; +const ErrorSubclassFixture = class extends Error {}; + +const types = new Map([ + ['undefined', undefined], + ['null', null], + ['string', '🦄'], + ['number', [ + 6, + 1.4, + 0, + -0, + Infinity, + -Infinity + ]], + ['boolean', [ + true, + false + ]], + ['symbol', Symbol('🦄')], + ['array', [ + [1, 2], + new Array(2) + ]], + ['function', [ + function foo() {}, // eslint-disable-line func-names + function () {}, + () => {}, + async function () {}, + function * () {} + ]], + ['buffer', Buffer.from('🦄')], + ['object', [ + {x: 1}, + Object.create({x: 1}) + ]], + ['regExp', [ + /\w/, + new RegExp('\\w') + ]], + ['date', new Date()], + ['error', [ + new Error('🦄'), + new ErrorSubclassFixture() + ]], + ['nativePromise', [ + Promise.resolve(), + PromiseSubclassFixture.resolve() + ]], + ['promise', {then() {}, catch() {}}], + ['map', new Map()], + ['set', new Set()], + ['weakMap', new WeakMap()], + ['int8Array', new Int8Array()], + ['uint8Array', new Uint8Array()], + ['uint8ClampedArray', new Uint8ClampedArray()], + ['uint16Array', new Uint16Array()], + ['int32Array', new Int32Array()], + ['uint32Array', new Uint32Array()], + ['float32Array', new Float32Array()], + ['float64Array', new Float64Array()], + ['arrayBuffer', new ArrayBuffer(10)], + ['nan', [ + NaN, + Number.NaN + ]], + ['nullOrUndefined', [ + null, + undefined + ]], + ['plainObject', [ + {x: 1}, + Object.create(null), + new Object() // eslint-disable-line no-new-object + ]], + ['integer', 6] +]); + +// This ensure a certain method matches only the types +// it's supposed to and none of the other methods' types +const testType = (t, type, exclude) => { + for (const [key, value] of types) { + // TODO: Automatically exclude value types in other tests that we have in the current one. + // Could reduce the use of `exclude`. + if (exclude && exclude.indexOf(key) !== -1) { + continue; + } + + const assert = key === type ? t.true.bind(t) : t.false.bind(t); + const is = m[type]; + const fixtures = Array.isArray(value) ? value : [value]; + + for (const fixture of fixtures) { + assert(is(fixture), `Value: ${util.inspect(fixture)}`); + } + } +}; + +test('is.undefined', t => { + testType(t, 'undefined', ['nullOrUndefined']); +}); + +test('is.null', t => { + testType(t, 'null', ['nullOrUndefined']); +}); + +test('is.string', t => { + testType(t, 'string'); +}); + +test('is.number', t => { + testType(t, 'number', ['nan', 'integer']); +}); + +test('is.boolean', t => { + testType(t, 'boolean'); +}); + +test('is.symbol', t => { + testType(t, 'symbol'); +}); + +test('is.array', t => { + testType(t, 'array'); +}); + +test('is.function', t => { + testType(t, 'function'); +}); + +test('is.buffer', t => { + testType(t, 'buffer'); +}); + +test('is.object', t => { + for (const el of types.get('object')) { + t.true(m.object(el)); + } +}); + +test('is.regExp', t => { + testType(t, 'regExp'); +}); + +test('is.date', t => { + testType(t, 'date'); +}); + +test('is.error', t => { + testType(t, 'error'); +}); + +if (isNode8orHigher) { + test('is.nativePromise', t => { + testType(t, 'nativePromise'); + }); + + test('is.promise', t => { + testType(t, 'promise', ['nativePromise']); + }); +} + +test('is.map', t => { + testType(t, 'map'); +}); + +test('is.set', t => { + testType(t, 'set'); +}); + +test('is.weakMap', t => { + testType(t, 'weakMap'); +}); + +test('is.weakSet', t => { + testType(t, 'weakSet'); +}); + +test('is.int8Array', t => { + testType(t, 'int8Array'); +}); + +test('is.uint8Array', t => { + testType(t, 'uint8Array', ['buffer']); +}); + +test('is.uint8ClampedArray', t => { + testType(t, 'uint8ClampedArray'); +}); + +test('is.int16Array', t => { + testType(t, 'int16Array'); +}); + +test('is.uint16Array', t => { + testType(t, 'uint16Array'); +}); + +test('is.int32Array', t => { + testType(t, 'int32Array'); +}); + +test('is.uint32Array', t => { + testType(t, 'uint32Array'); +}); + +test('is.float32Array', t => { + testType(t, 'float32Array'); +}); + +test('is.float64Array', t => { + testType(t, 'float64Array'); +}); + +test('is.arrayBuffer', t => { + testType(t, 'arrayBuffer'); +}); + +test('is.dataView', t => { + testType(t, 'arrayBuffer'); +}); + +test('is.nan', t => { + testType(t, 'nan'); +}); + +test('is.nullOrUndefined', t => { + testType(t, 'nullOrUndefined', ['undefined', 'null']); +}); + +test('is.primitive', t => { + const primitives = [ + undefined, + null, + '🦄', + 6, + Infinity, + -Infinity, + true, + false, + Symbol('🦄') + ]; + + for (const el of primitives) { + t.true(m.primitive(el)); + } +}); + +test('is.integer', t => { + testType(t, 'integer', ['number']); + t.false(m.integer(1.4)); +}); + +test('is.plainObject', t => { + testType(t, 'plainObject', ['object', 'promise']); +}); + +test('is.iterable', t => { + t.true(m.iterable('')); + t.true(m.iterable([])); + t.true(m.iterable(new Map())); + t.false(m.iterable(null)); + t.false(m.iterable(undefined)); + t.false(m.iterable(0)); + t.false(m.iterable(NaN)); + t.false(m.iterable(Infinity)); + t.false(m.iterable({})); +}); + +test('is.typedArray', t => { + const typedArrays = [ + new Int8Array(), + new Uint8Array(), + new Uint8ClampedArray(), + new Uint16Array(), + new Int32Array(), + new Uint32Array(), + new Float32Array(), + new Float64Array() + ]; + + for (const el of typedArrays) { + t.true(m.typedArray(el)); + } + + t.false(m.typedArray(new ArrayBuffer(1))); + t.false(m.typedArray([])); + t.false(m.typedArray({})); +});