From 119c41d39bfdf598a3b235bf5f88f6f7adc5d5d7 Mon Sep 17 00:00:00 2001 From: Or Schneider Date: Sun, 24 Sep 2017 22:26:15 +0300 Subject: [PATCH 001/254] Add `is.class()` (#2) --- index.js | 4 +++- readme.md | 4 ++++ test.js | 12 ++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 132db41..78b4368 100644 --- a/index.js +++ b/index.js @@ -130,11 +130,13 @@ is.plainObject = x => { // eslint-disable-next-line no-return-assign return getObjectType(x) === 'Object' && (prototype = Object.getPrototypeOf(x), prototype === null || - prototype === Object.getPrototypeOf({})); + prototype === Object.getPrototypeOf({})); }; is.iterable = x => !is.null(x) && !is.undefined(x) && typeof x[Symbol.iterator] === 'function'; +is.class = x => typeof x === 'function' && x.toString().startsWith('class '); + const typedArrayTypes = new Set([ 'Int8Array', 'Uint8Array', diff --git a/readme.md b/readme.md index ee1761d..45ade28 100644 --- a/readme.md +++ b/readme.md @@ -115,6 +115,10 @@ JavaScript primitives are as follows: `null`, `undefined`, `string`, `number`, ` An object is plain if it's created by either `{}`, `new Object()`, or `Object.create(null)`. ##### .iterable(value) +##### .class(value) + +Returns `true` for instances created by a ES2015 class. + ##### .typedArray(value) diff --git a/test.js b/test.js index 7868637..357aa55 100644 --- a/test.js +++ b/test.js @@ -273,6 +273,18 @@ test('is.iterable', t => { t.false(m.iterable({})); }); +test('is.class', t => { + class Foo {} + const classDeclarations = [ + Foo, + class Bar extends Foo {} + ]; + + for (const x of classDeclarations) { + t.true(m.class(x)); + } +}); + test('is.typedArray', t => { const typedArrays = [ new Int8Array(), From 3cbef48b6c95698696c5d6a2d4ba800523774c98 Mon Sep 17 00:00:00 2001 From: gui Date: Mon, 25 Sep 2017 20:20:06 +0100 Subject: [PATCH 002/254] Add `is.inRange()` (#3) --- index.js | 8 ++++++++ readme.md | 7 +++++++ test.js | 13 +++++++++++++ 3 files changed, 28 insertions(+) diff --git a/index.js b/index.js index 78b4368..99caa9a 100644 --- a/index.js +++ b/index.js @@ -150,4 +150,12 @@ const typedArrayTypes = new Set([ ]); is.typedArray = x => typedArrayTypes.has(getObjectType(x)); +is.inRange = (x, range) => { + if (is.number(range)) { + return (x >= Math.min(0, range)) && (x <= Math.max(range, 0)); + } + + return (is.array(range)) && (x >= Math.min.apply(Math, range)) && (x <= Math.max.apply(Math, range)); +}; + module.exports = is; diff --git a/readme.md b/readme.md index 45ade28..40e5d1b 100644 --- a/readme.md +++ b/readme.md @@ -121,6 +121,13 @@ Returns `true` for instances created by a ES2015 class. ##### .typedArray(value) +##### .inRange(value, range) + +Check to see if the value is within a given range, for example `is.inRange(3, [0, 5])`. +The range supplied can be an array of any size, though this will only check for +the minimum and maximum values in it. + +If `range` is a number (e.g., `is.inRange(3, 10)`), it will be treated as a range of 0 to `range`. ## FAQ diff --git a/test.js b/test.js index 357aa55..6f5d6a6 100644 --- a/test.js +++ b/test.js @@ -305,3 +305,16 @@ test('is.typedArray', t => { t.false(m.typedArray([])); t.false(m.typedArray({})); }); + +test('is.inRange', t => { + const x = 3; + + t.true(m.inRange(x, [0, 5])); + t.true(m.inRange(x, [5, 0])); + t.true(m.inRange(x, [-5, 5])); + t.true(m.inRange(x, [5, -5])); + t.false(m.inRange(x, [4, 8])); + + t.true(m.inRange(x, 10)); + t.false(m.inRange(x, 2)); +}); From 26ca1953028c6eb0b73286a5beef69fe87cf7a64 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 26 Sep 2017 02:32:58 +0700 Subject: [PATCH 003/254] Minor tweaks to `is.inRange()` --- index.js | 9 +++++++-- readme.md | 19 +++++++++++++++---- test.js | 12 ++++++++++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 99caa9a..2ba0ab3 100644 --- a/index.js +++ b/index.js @@ -152,10 +152,15 @@ is.typedArray = x => typedArrayTypes.has(getObjectType(x)); is.inRange = (x, range) => { if (is.number(range)) { - return (x >= Math.min(0, range)) && (x <= Math.max(range, 0)); + return x >= Math.min(0, range) && x <= Math.max(range, 0); } - return (is.array(range)) && (x >= Math.min.apply(Math, range)) && (x <= Math.max.apply(Math, range)); + if (is.array(range) && range.length === 2) { + // TODO: Use spread operator here when targeting Node.js 6 or higher + return x >= Math.min.apply(null, range) && x <= Math.max.apply(null, range); + } + + throw new TypeError('Invalid range'); }; module.exports = is; diff --git a/readme.md b/readme.md index 40e5d1b..6038e65 100644 --- a/readme.md +++ b/readme.md @@ -123,11 +123,22 @@ Returns `true` for instances created by a ES2015 class. ##### .inRange(value, range) -Check to see if the value is within a given range, for example `is.inRange(3, [0, 5])`. -The range supplied can be an array of any size, though this will only check for -the minimum and maximum values in it. +Check if `value` (number) is in the given `range`. The range is an array of two values, lower bound and upper bound, in no specific order. + +```js +is.inRange(3, [0, 5]); +is.inRange(3, [5, 0]); +is.inRange(0, [-2, 2]); +``` + +##### .inRange(value, upperBound) + +Check if `value` (number) is in the range of `0` to `upperBound`. + +```js +is.inRange(3, 10); +``` -If `range` is a number (e.g., `is.inRange(3, 10)`), it will be treated as a range of 0 to `range`. ## FAQ diff --git a/test.js b/test.js index 6f5d6a6..18f6158 100644 --- a/test.js +++ b/test.js @@ -314,7 +314,19 @@ test('is.inRange', t => { t.true(m.inRange(x, [-5, 5])); t.true(m.inRange(x, [5, -5])); t.false(m.inRange(x, [4, 8])); + t.true(m.inRange(-7, [-5, -10])); + t.true(m.inRange(-5, [-5, -10])); + t.true(m.inRange(-10, [-5, -10])); t.true(m.inRange(x, 10)); + t.true(m.inRange(0, 0)); t.false(m.inRange(x, 2)); + + t.throws(() => { + t.true(m.inRange(0)); + }); + + t.throws(() => { + t.true(m.inRange(0, [5])); + }); }); From f918d8a7d4388b59dc7509861c09a38e83950c5c Mon Sep 17 00:00:00 2001 From: Giora Guttsait Date: Thu, 28 Sep 2017 06:41:16 +0300 Subject: [PATCH 004/254] Refactor to reduce code repetition (#6) --- index.js | 109 ++++++++++++++++++++++++++----------------------------- test.js | 20 ++++++++-- 2 files changed, 68 insertions(+), 61 deletions(-) diff --git a/index.js b/index.js index 2ba0ab3..5734148 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,10 @@ 'use strict'; +const util = require('util'); + const toString = Object.prototype.toString; const getObjectType = x => toString.call(x).slice(8, -1); +const isOfType = type => x => typeof x === type; // eslint-disable-line valid-typeof +const isObjectOfType = type => x => getObjectType(x) === type; const is = value => { if (value == null) { // eslint-disable-line no-eq-null, eqeqeq @@ -29,7 +33,7 @@ const is = value => { return 'symbol'; } - if (type === 'function') { + if (is.function(value)) { return 'Function'; } @@ -53,74 +57,63 @@ const is = value => { return 'Object'; }; -is.undefined = x => typeof x === 'undefined'; +is.undefined = isOfType('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.string = isOfType('string'); +is.number = isOfType('number'); +is.boolean = isOfType('boolean'); +is.symbol = isOfType('symbol'); is.array = Array.isArray; -is.function = x => typeof x === 'function'; +is.function = isOfType('function'); is.buffer = Buffer.isBuffer; -is.object = x => { - const type = typeof x; - return x !== null && (type === 'object' || type === 'function'); -}; +const isObject = x => typeof x === 'object'; -is.nativePromise = x => getObjectType(x) === 'Promise'; +is.object = x => !is.nullOrUndefined(x) && (is.function(x) || isObject(x)); -is.promise = x => { - return is.nativePromise(x) || - ( - x !== null && - typeof x === 'object' && - typeof x.then === 'function' && - typeof x.catch === 'function' - ); -}; +is.nativePromise = isObjectOfType('Promise'); -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'; +const hasPromiseAPI = x => + !is.null(x) && + isObject(x) && + is.function(x.then) && + is.function(x.catch); -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.promise = x => is.nativePromise(x) || hasPromiseAPI(x); -is.arrayBuffer = x => getObjectType(x) === 'ArrayBuffer'; +is.regExp = isObjectOfType('RegExp'); +is.date = isObjectOfType('Date'); +is.error = isObjectOfType('Error'); +is.map = isObjectOfType('Map'); +is.set = isObjectOfType('Set'); +is.weakMap = isObjectOfType('WeakMap'); +is.weakSet = isObjectOfType('WeakSet'); -is.sharedArrayBuffer = x => { - try { - return getObjectType(x) === 'SharedArrayBuffer'; - } catch (err) { - return false; - } -}; +is.int8Array = isObjectOfType('Int8Array'); +is.uint8Array = isObjectOfType('Uint8Array'); +is.uint8ClampedArray = isObjectOfType('Uint8ClampedArray'); +is.int16Array = isObjectOfType('Int16Array'); +is.uint16Array = isObjectOfType('Uint16Array'); +is.int32Array = isObjectOfType('Int32Array'); +is.uint32Array = isObjectOfType('Uint32Array'); +is.float32Array = isObjectOfType('Float32Array'); +is.float64Array = isObjectOfType('Float64Array'); + +is.arrayBuffer = isObjectOfType('ArrayBuffer'); +is.sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); is.nan = Number.isNaN; -is.nullOrUndefined = x => x === null || typeof x === 'undefined'; +is.nullOrUndefined = x => is.null(x) || is.undefined(x); -is.primitive = x => { - const type = typeof x; - return x === null || - type === 'undefined' || - type === 'string' || - type === 'number' || - type === 'boolean' || - type === 'symbol'; -}; +const primitiveTypes = new Set([ + 'undefined', + 'string', + 'number', + 'boolean', + 'symbol' +]); +is.primitive = x => is.null(x) || primitiveTypes.has(typeof x); is.integer = Number.isInteger; @@ -133,9 +126,9 @@ is.plainObject = x => { prototype === Object.getPrototypeOf({})); }; -is.iterable = x => !is.null(x) && !is.undefined(x) && typeof x[Symbol.iterator] === 'function'; +is.iterable = x => !is.nullOrUndefined(x) && is.function(x[Symbol.iterator]); -is.class = x => typeof x === 'function' && x.toString().startsWith('class '); +is.class = x => is.function(x) && x.toString().startsWith('class '); const typedArrayTypes = new Set([ 'Int8Array', @@ -160,7 +153,7 @@ is.inRange = (x, range) => { return x >= Math.min.apply(null, range) && x <= Math.max.apply(null, range); } - throw new TypeError('Invalid range'); + throw new TypeError(`Invalid range: ${util.inspect}`); }; module.exports = is; diff --git a/test.js b/test.js index 18f6158..316bb41 100644 --- a/test.js +++ b/test.js @@ -10,7 +10,11 @@ const ErrorSubclassFixture = class extends Error {}; const types = new Map([ ['undefined', undefined], ['null', null], - ['string', '🦄'], + ['string', [ + '🦄', + 'hello world', + '' + ]], ['number', [ 6, 1.4, @@ -320,13 +324,23 @@ test('is.inRange', t => { t.true(m.inRange(x, 10)); t.true(m.inRange(0, 0)); + t.true(m.inRange(-2, -3)); t.false(m.inRange(x, 2)); + t.false(m.inRange(-3, -2)); t.throws(() => { - t.true(m.inRange(0)); + m.inRange(0); }); t.throws(() => { - t.true(m.inRange(0, [5])); + m.inRange(0, []); + }); + + t.throws(() => { + m.inRange(0, [5]); + }); + + t.throws(() => { + m.inRange(0, [1, 2, 3]); }); }); From e97e68cd0193f718e9c58b504d675087b75292b2 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 28 Sep 2017 13:26:23 +0700 Subject: [PATCH 005/254] Welcome @gioragutt as a maintainer --- readme.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 6038e65..0962385 100644 --- a/readme.md +++ b/readme.md @@ -169,6 +169,12 @@ The most common mistakes I noticed in these modules was using `instanceof` for t - [is-empty-iterable](https://github.com/sindresorhus/is-empty-iterable) - Check if an Iterable is empty +## Created by + +- [Sindre Sorhus](https://github.com/sindresorhus) +- [Giora Guttsait](https://github.com/gioragutt) + + ## License -MIT © [Sindre Sorhus](https://sindresorhus.com) +MIT From 000f66bbdcbab8d1c9e62ed774851392984bfed6 Mon Sep 17 00:00:00 2001 From: Sam Gluck Date: Thu, 28 Sep 2017 14:57:32 +0100 Subject: [PATCH 006/254] Fix call to util.inspect --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 5734148..befda91 100644 --- a/index.js +++ b/index.js @@ -153,7 +153,7 @@ is.inRange = (x, range) => { return x >= Math.min.apply(null, range) && x <= Math.max.apply(null, range); } - throw new TypeError(`Invalid range: ${util.inspect}`); + throw new TypeError(`Invalid range: ${util.inspect(range)}`); }; module.exports = is; From dbe5cc8d82795469151bf643a8355a921141d3af Mon Sep 17 00:00:00 2001 From: Kunall Banerjee Date: Thu, 28 Sep 2017 23:01:48 -0400 Subject: [PATCH 007/254] Add `is.generator()` and `is.generatorFunction()` (#4) --- index.js | 5 +++++ readme.md | 6 ++++++ test.js | 16 +++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index befda91..02036be 100644 --- a/index.js +++ b/index.js @@ -82,6 +82,11 @@ const hasPromiseAPI = x => is.promise = x => is.nativePromise(x) || hasPromiseAPI(x); +is.generator = x => is.iterable(x) && is.function(x.next) && is.function(x.throw); + +// Change to use `isObjectOfType` once Node 4.x.x LTS is dropped +is.generatorFunction = x => x.constructor.name === 'GeneratorFunction'; + is.regExp = isObjectOfType('RegExp'); is.date = isObjectOfType('Date'); is.error = isObjectOfType('Error'); diff --git a/readme.md b/readme.md index 0962385..be284c4 100644 --- a/readme.md +++ b/readme.md @@ -78,6 +78,12 @@ Keep in mind that [functions are objects too](https://developer.mozilla.org/en-U 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. +##### .generator(value) + +Returns `true` for any object that implements its own `.next()` and `.throw()` methods and has a function definition for `Symbol.iterator`. + +##### .generatorFunction(value) + ##### .map(value) ##### .set(value) ##### .weakMap(value) diff --git a/test.js b/test.js index 316bb41..89b4a41 100644 --- a/test.js +++ b/test.js @@ -58,6 +58,9 @@ const types = new Map([ PromiseSubclassFixture.resolve() ]], ['promise', {then() {}, catch() {}}], + ['generator', (function * () { + yield 42; + })()], ['map', new Map()], ['set', new Set()], ['weakMap', new WeakMap()], @@ -86,7 +89,7 @@ const types = new Map([ ['integer', 6] ]); -// This ensure a certain method matches only the types +// This ensures 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) { @@ -170,6 +173,17 @@ if (isNode8orHigher) { }); } +test('is.generator', t => { + testType(t, 'generator', ['function']); +}); + +test('is.generatorFunction', t => { + const gen = function * () { + yield 42; + }; + t.true(m.generatorFunction(gen)); +}); + test('is.map', t => { testType(t, 'map'); }); From 226e4d90dacdac0019866f805cf93179cb00d2f9 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 29 Sep 2017 10:10:29 +0700 Subject: [PATCH 008/254] Clean up #4 --- index.js | 4 ++-- test.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 02036be..045577f 100644 --- a/index.js +++ b/index.js @@ -84,8 +84,8 @@ is.promise = x => is.nativePromise(x) || hasPromiseAPI(x); is.generator = x => is.iterable(x) && is.function(x.next) && is.function(x.throw); -// Change to use `isObjectOfType` once Node 4.x.x LTS is dropped -is.generatorFunction = x => x.constructor.name === 'GeneratorFunction'; +// TODO: Change to use `isObjectOfType` once Node.js 6 or higher is targeted +is.generatorFunction = x => is.function(x) && is.function(x.constructor) && x.constructor.name === 'GeneratorFunction'; is.regExp = isObjectOfType('RegExp'); is.date = isObjectOfType('Date'); diff --git a/test.js b/test.js index 89b4a41..b4b50e6 100644 --- a/test.js +++ b/test.js @@ -59,8 +59,11 @@ const types = new Map([ ]], ['promise', {then() {}, catch() {}}], ['generator', (function * () { - yield 42; + yield 4; })()], + ['generatorFunction', function * () { + yield 4; + }], ['map', new Map()], ['set', new Set()], ['weakMap', new WeakMap()], @@ -138,7 +141,7 @@ test('is.array', t => { }); test('is.function', t => { - testType(t, 'function'); + testType(t, 'function', ['generatorFunction']); }); test('is.buffer', t => { @@ -174,14 +177,11 @@ if (isNode8orHigher) { } test('is.generator', t => { - testType(t, 'generator', ['function']); + testType(t, 'generator'); }); test('is.generatorFunction', t => { - const gen = function * () { - yield 42; - }; - t.true(m.generatorFunction(gen)); + testType(t, 'generatorFunction', ['function']); }); test('is.map', t => { From 27d15f40bda9e390d1dcac5722e37a2675425483 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 29 Sep 2017 10:22:56 +0700 Subject: [PATCH 009/254] 0.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0085cc1..9174f2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.1.0", + "version": "0.2.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From 47982af117f1c4929860b27ba5c7af6de981ec58 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 5 Oct 2017 11:20:39 +0700 Subject: [PATCH 010/254] Fix silly `null` check mistake Fixes #13 --- index.js | 2 +- test.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 045577f..6ee2179 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ const isOfType = type => x => typeof x === type; // eslint-disable-line valid-ty const isObjectOfType = type => x => getObjectType(x) === type; const is = value => { - if (value == null) { // eslint-disable-line no-eq-null, eqeqeq + if (value === null) { return 'null'; } diff --git a/test.js b/test.js index b4b50e6..7fac42f 100644 --- a/test.js +++ b/test.js @@ -112,6 +112,13 @@ const testType = (t, type, exclude) => { } }; +test('is', t => { + t.is(m(null), 'null'); + t.is(m(undefined), 'undefined'); + + // TODO: Expand this to all the supported types. Maybe reuse `testType()` somehow. +}); + test('is.undefined', t => { testType(t, 'undefined', ['nullOrUndefined']); }); From 11b98171c82991416c3ff82f076d963b0c775184 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 5 Oct 2017 11:21:46 +0700 Subject: [PATCH 011/254] 0.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9174f2e..40412cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.2.0", + "version": "0.2.1", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From 24c964a7c7f0bf2c73a56e7b023afcb46eefd359 Mon Sep 17 00:00:00 2001 From: Melvin Philips Date: Thu, 5 Oct 2017 00:18:25 -0700 Subject: [PATCH 012/254] Add is.infinite() --- index.js | 2 ++ readme.md | 4 ++++ test.js | 12 ++++++++++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 6ee2179..b3d52b9 100644 --- a/index.js +++ b/index.js @@ -161,4 +161,6 @@ is.inRange = (x, range) => { throw new TypeError(`Invalid range: ${util.inspect(range)}`); }; +is.infinite = x => x === Infinity || x === -Infinity; + module.exports = is; diff --git a/readme.md b/readme.md index be284c4..fab01c1 100644 --- a/readme.md +++ b/readme.md @@ -145,6 +145,10 @@ Check if `value` (number) is in the range of `0` to `upperBound`. is.inRange(3, 10); ``` +##### .infinite(value) + +Check if `value` is `Infinity` or `-Infinity`. + ## FAQ diff --git a/test.js b/test.js index 7fac42f..a2c4337 100644 --- a/test.js +++ b/test.js @@ -89,7 +89,11 @@ const types = new Map([ Object.create(null), new Object() // eslint-disable-line no-new-object ]], - ['integer', 6] + ['integer', 6], + ['infinite', [ + Infinity, + -Infinity + ]] ]); // This ensures a certain method matches only the types @@ -132,7 +136,7 @@ test('is.string', t => { }); test('is.number', t => { - testType(t, 'number', ['nan', 'integer']); + testType(t, 'number', ['nan', 'integer', 'infinite']); }); test('is.boolean', t => { @@ -365,3 +369,7 @@ test('is.inRange', t => { m.inRange(0, [1, 2, 3]); }); }); + +test('is.infinite', t => { + testType(t, 'infinite', ['number']); +}); From ee8f5d16f849e8e45f160c3ea5f0fa1ed265d1b8 Mon Sep 17 00:00:00 2001 From: Khaled Al-Ansari Date: Thu, 5 Oct 2017 16:50:15 +0300 Subject: [PATCH 013/254] Update `is.boolean()` method (#15) --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index b3d52b9..f7a16fd 100644 --- a/index.js +++ b/index.js @@ -61,7 +61,7 @@ is.undefined = isOfType('undefined'); is.null = x => x === null; is.string = isOfType('string'); is.number = isOfType('number'); -is.boolean = isOfType('boolean'); +is.boolean = x => x === true || x === false; is.symbol = isOfType('symbol'); is.array = Array.isArray; From 46e886d10b3f4fdc4746c58f13e3c68285718651 Mon Sep 17 00:00:00 2001 From: Kodie Grantham Date: Fri, 6 Oct 2017 02:39:36 -0500 Subject: [PATCH 014/254] Add is.empty() (#16) --- index.js | 6 ++++++ readme.md | 5 +++++ test.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/index.js b/index.js index f7a16fd..37f27f6 100644 --- a/index.js +++ b/index.js @@ -163,4 +163,10 @@ is.inRange = (x, range) => { is.infinite = x => x === Infinity || x === -Infinity; +const isEmptyStringOrArray = x => (is.string(x) || is.array(x)) && x.length === 0; +const isEmptyObject = x => !is.map(x) && !is.set(x) && is.object(x) && Object.keys(x).length === 0; +const isEmptyMapOrSet = x => (is.map(x) || is.set(x)) && x.size === 0; + +is.empty = x => !x || isEmptyStringOrArray(x) || isEmptyObject(x) || isEmptyMapOrSet(x); + module.exports = is; diff --git a/readme.md b/readme.md index fab01c1..8c9a29e 100644 --- a/readme.md +++ b/readme.md @@ -150,6 +150,11 @@ is.inRange(3, 10); Check if `value` is `Infinity` or `-Infinity`. +##### .empty(value) + +Returns `true` if `value` is falsy or an empty string, array, object, map, or set. + + ## FAQ ### Why yet another type checking module? diff --git a/test.js b/test.js index a2c4337..336bc45 100644 --- a/test.js +++ b/test.js @@ -373,3 +373,32 @@ test('is.inRange', t => { test('is.infinite', t => { testType(t, 'infinite', ['number']); }); + +test('is.empty', t => { + t.true(m.empty(null)); + t.true(m.empty(undefined)); + + t.true(m.empty(false)); + t.false(m.empty(true)); + + t.true(m.empty('')); + t.false(m.empty('🦄')); + + t.true(m.empty([])); + t.false(m.empty(['🦄'])); + + t.true(m.empty({})); + t.false(m.empty({unicorn: '🦄'})); + + const tempMap = new Map(); + t.true(m.empty(tempMap)); + + tempMap.set('unicorn', '🦄'); + t.false(m.empty(tempMap)); + + const tempSet = new Set(); + t.true(m.empty(tempSet)); + + tempSet.add(1); + t.false(m.empty(tempSet)); +}); From 6268253ec6e6722e2bf4d55e05834b8e3d1e5e12 Mon Sep 17 00:00:00 2001 From: Melvin Date: Fri, 6 Oct 2017 00:48:04 -0700 Subject: [PATCH 015/254] Add link to is-blob (#17) --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 8c9a29e..d3dc021 100644 --- a/readme.md +++ b/readme.md @@ -182,6 +182,7 @@ The most common mistakes I noticed in these modules was using `instanceof` for t - [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 +- [is-blob](https://github.com/sindresorhus/is-blob) - Check if a value is a Blob - File-like object of immutable, raw data ## Created by From 75ac3cd574f9c2223844276e51f56afbdb044389 Mon Sep 17 00:00:00 2001 From: Melvin Date: Sat, 7 Oct 2017 09:19:11 -0700 Subject: [PATCH 016/254] Add is.domElement() (#11) --- index.js | 12 ++++++++++++ package.json | 1 + readme.md | 5 ++++- test.js | 25 +++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 37f27f6..a24021c 100644 --- a/index.js +++ b/index.js @@ -161,6 +161,18 @@ is.inRange = (x, range) => { throw new TypeError(`Invalid range: ${util.inspect(range)}`); }; +const NODE_TYPE_ELEMENT = 1; +const DOM_PROPERTIES_TO_CHECK = [ + 'innerHTML', + 'ownerDocument', + 'style', + 'attributes', + 'nodeValue' +]; + +is.domElement = x => is.object(x) && x.nodeType === NODE_TYPE_ELEMENT && is.string(x.nodeName) && + !is.plainObject(x) && DOM_PROPERTIES_TO_CHECK.every(property => property in x); + is.infinite = x => x === Infinity || x === -Infinity; const isEmptyStringOrArray = x => (is.string(x) || is.array(x)) && x.length === 0; diff --git a/package.json b/package.json index 40412cd..8ef725c 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ ], "devDependencies": { "ava": "*", + "jsdom": "^9.12.0", "xo": "*" } } diff --git a/readme.md b/readme.md index d3dc021..0508540 100644 --- a/readme.md +++ b/readme.md @@ -145,11 +145,14 @@ Check if `value` (number) is in the range of `0` to `upperBound`. is.inRange(3, 10); ``` +##### .domElement(value) + +Returns `true` if `value` is a DOM Element. + ##### .infinite(value) Check if `value` is `Infinity` or `-Infinity`. - ##### .empty(value) Returns `true` if `value` is falsy or an empty string, array, object, map, or set. diff --git a/test.js b/test.js index 336bc45..c0f3cbf 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,6 @@ import util from 'util'; import test from 'ava'; +import {jsdom} from 'jsdom'; import m from '.'; const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; @@ -7,6 +8,9 @@ const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; const PromiseSubclassFixture = class extends Promise {}; const ErrorSubclassFixture = class extends Error {}; +const document = jsdom(); +const createDomElement = el => document.createElement(el); + const types = new Map([ ['undefined', undefined], ['null', null], @@ -90,6 +94,22 @@ const types = new Map([ new Object() // eslint-disable-line no-new-object ]], ['integer', 6], + ['domElement', [ + 'div', + 'input', + 'span', + 'img', + 'canvas', + 'script' + ].map(createDomElement)], + ['non-domElements', [ + document.createTextNode('data'), + document.createProcessingInstruction('xml-stylesheet', 'href="mycss.css" type="text/css"'), + document.createComment('This is a comment'), + document, + document.implementation.createDocumentType('svg:svg', '-//W3C//DTD SVG 1.1//EN', 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'), + document.createDocumentFragment() + ]], ['infinite', [ Infinity, -Infinity @@ -370,6 +390,11 @@ test('is.inRange', t => { }); }); +test('is.domElement', t => { + testType(t, 'domElement'); + t.false(m.domElement({nodeType: 1, nodeName: 'div'})); +}); + test('is.infinite', t => { testType(t, 'infinite', ['number']); }); From 651f434eab5ef135bc17a2ae2f8f1898a5609a3a Mon Sep 17 00:00:00 2001 From: Kodie Grantham Date: Wed, 11 Oct 2017 04:26:45 -0500 Subject: [PATCH 017/254] Add is.any() and is.all() methods (#19) --- index.js | 27 +++++++++++++++++++++++++++ readme.md | 24 ++++++++++++++++++++++++ test.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/index.js b/index.js index a24021c..6f57809 100644 --- a/index.js +++ b/index.js @@ -181,4 +181,31 @@ const isEmptyMapOrSet = x => (is.map(x) || is.set(x)) && x.size === 0; is.empty = x => !x || isEmptyStringOrArray(x) || isEmptyObject(x) || isEmptyMapOrSet(x); +const predicateOnArray = (method, predicate, values) => { + // `values` is the calling function's "arguments object". + // We have to do it this way to keep node v4 support. + // So here we convert it to an array and slice off the first item. + values = Array.prototype.slice.call(values, 1); + + if (is.function(predicate) === false) { + throw new TypeError(`Invalid predicate: ${util.inspect(predicate)}`); + } + + if (values.length === 0) { + throw new TypeError(`Invalid number of values`); + } + + return method.call(values, predicate); +}; + +// We have to use anonymous functions for the any() and all() methods +// to get the arguments since we can't use rest parameters in node v4. +is.any = function (predicate) { + return predicateOnArray(Array.prototype.some, predicate, arguments); +}; + +is.all = function (predicate) { + return predicateOnArray(Array.prototype.every, predicate, arguments); +}; + module.exports = is; diff --git a/readme.md b/readme.md index 0508540..d2e572b 100644 --- a/readme.md +++ b/readme.md @@ -158,6 +158,30 @@ Check if `value` is `Infinity` or `-Infinity`. Returns `true` if `value` is falsy or an empty string, array, object, map, or set. +##### .any(predicate, ...values) + +Returns `true` if **any** of the input `values` returns true in the `predicate`: + +```js +is.any(is.string, {}, true, '🦄'); +//=> true + +is.any(is.boolean, 'unicorns', [], new Map()); +//=> false +``` + +##### .all(predicate, ...values) + +Returns `true` if **all** of the input `values` returns true in the `predicate`: + +```js +is.all(is.object, {}, new Map(), new Set()); +//=> true + +is.all(is.string, '🦄', [], 'unicorns'); +//=> false +``` + ## FAQ ### Why yet another type checking module? diff --git a/test.js b/test.js index c0f3cbf..43566f5 100644 --- a/test.js +++ b/test.js @@ -427,3 +427,33 @@ test('is.empty', t => { tempSet.add(1); t.false(m.empty(tempSet)); }); + +test('is.any', t => { + t.true(m.any(m.string, {}, true, '🦄')); + t.true(m.any(m.object, false, {}, 'unicorns')); + t.false(m.any(m.boolean, '🦄', [], 3)); + t.false(m.any(m.integer, true, 'lol', {})); + + t.throws(() => { + m.any(null, true); + }); + + t.throws(() => { + m.any(m.string); + }); +}); + +test('is.all', t => { + t.true(m.all(m.object, {}, new Set(), new Map())); + t.true(m.all(m.boolean, true, false)); + t.false(m.all(m.string, '🦄', [])); + t.false(m.all(m.set, new Map(), {})); + + t.throws(() => { + m.all(null, true); + }); + + t.throws(() => { + m.all(m.string); + }); +}); From 04cb80dfb170d9c2e004f9ba0825fd0bab9411fe Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 11 Oct 2017 16:51:12 +0700 Subject: [PATCH 018/254] 0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ef725c..5988758 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.2.1", + "version": "0.3.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From dc3b6ff86be0234ced8f0a03c6e256be4454c547 Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Sun, 15 Oct 2017 23:29:10 -0400 Subject: [PATCH 019/254] Add is.asyncFunction (#20) --- index.js | 5 ++++- readme.md | 12 ++++++++++++ test.js | 12 +++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 6f57809..0da91b3 100644 --- a/index.js +++ b/index.js @@ -85,7 +85,10 @@ is.promise = x => is.nativePromise(x) || hasPromiseAPI(x); is.generator = x => is.iterable(x) && is.function(x.next) && is.function(x.throw); // TODO: Change to use `isObjectOfType` once Node.js 6 or higher is targeted -is.generatorFunction = x => is.function(x) && is.function(x.constructor) && x.constructor.name === 'GeneratorFunction'; +const isFunctionOfType = type => x => is.function(x) && is.function(x.constructor) && x.constructor.name === type; + +is.generatorFunction = isFunctionOfType('GeneratorFunction'); +is.asyncFunction = isFunctionOfType('AsyncFunction'); is.regExp = isObjectOfType('RegExp'); is.date = isObjectOfType('Date'); diff --git a/readme.md b/readme.md index d2e572b..9e565fb 100644 --- a/readme.md +++ b/readme.md @@ -84,6 +84,18 @@ Returns `true` for any object that implements its own `.next()` and `.throw()` m ##### .generatorFunction(value) +##### .asyncFunction(value) + +Returns `true` for any `async` function that can be called with the `await` operator. + +```js +is.asyncFunction(async () => {}); +// => true + +is.asyncFunction(() => {}); +// => false +``` + ##### .map(value) ##### .set(value) ##### .weakMap(value) diff --git a/test.js b/test.js index 43566f5..0d62dce 100644 --- a/test.js +++ b/test.js @@ -68,6 +68,10 @@ const types = new Map([ ['generatorFunction', function * () { yield 4; }], + ['asyncFunction', [ + async function () {}, + async () => {} + ]], ['map', new Map()], ['set', new Set()], ['weakMap', new WeakMap()], @@ -172,7 +176,7 @@ test('is.array', t => { }); test('is.function', t => { - testType(t, 'function', ['generatorFunction']); + testType(t, 'function', ['generatorFunction', 'asyncFunction']); }); test('is.buffer', t => { @@ -215,6 +219,12 @@ test('is.generatorFunction', t => { testType(t, 'generatorFunction', ['function']); }); +if (isNode8orHigher) { + test('is.asyncFunction', t => { + testType(t, 'asyncFunction', ['function']); + }); +} + test('is.map', t => { testType(t, 'map'); }); From 00974a2fe9e1513e8d48ec934be72d69872bd328 Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Tue, 17 Oct 2017 11:19:17 -0400 Subject: [PATCH 020/254] Add is.safeInteger() (#22) --- index.js | 1 + readme.md | 5 +++++ test.js | 14 ++++++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 0da91b3..e98bd94 100644 --- a/index.js +++ b/index.js @@ -124,6 +124,7 @@ const primitiveTypes = new Set([ is.primitive = x => is.null(x) || primitiveTypes.has(typeof x); is.integer = Number.isInteger; +is.safeInteger = Number.isSafeInteger; is.plainObject = x => { // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js diff --git a/readme.md b/readme.md index 9e565fb..3f3e20d 100644 --- a/readme.md +++ b/readme.md @@ -128,6 +128,11 @@ is.asyncFunction(() => {}); JavaScript primitives are as follows: `null`, `undefined`, `string`, `number`, `boolean`, `symbol`. ##### .integer(value) + +##### .safeInteger(value) + +Returns `true` if `value` is a [safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger). + ##### .plainObject(value) An object is plain if it's created by either `{}`, `new Object()`, or `Object.create(null)`. diff --git a/test.js b/test.js index 0d62dce..d65a7a0 100644 --- a/test.js +++ b/test.js @@ -98,6 +98,10 @@ const types = new Map([ new Object() // eslint-disable-line no-new-object ]], ['integer', 6], + ['safeInteger', [ + Math.pow(2, 53) - 1, + -Math.pow(2, 53) + 1 + ]], ['domElement', [ 'div', 'input', @@ -160,7 +164,7 @@ test('is.string', t => { }); test('is.number', t => { - testType(t, 'number', ['nan', 'integer', 'infinite']); + testType(t, 'number', ['nan', 'integer', 'safeInteger', 'infinite']); }); test('is.boolean', t => { @@ -312,10 +316,16 @@ test('is.primitive', t => { }); test('is.integer', t => { - testType(t, 'integer', ['number']); + testType(t, 'integer', ['number', 'safeInteger']); t.false(m.integer(1.4)); }); +test('is.safeInteger', t => { + testType(t, 'safeInteger', ['number', 'integer']); + t.false(m.safeInteger(Math.pow(2, 53))); + t.false(m.safeInteger(-Math.pow(2, 53))); +}); + test('is.plainObject', t => { testType(t, 'plainObject', ['object', 'promise']); }); From 615932d6c29bda4f40283590a67ec5c0e1cc4fa2 Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Tue, 17 Oct 2017 11:36:10 -0400 Subject: [PATCH 021/254] Add is.even() and is.odd() (#23) --- index.js | 4 ++++ readme.md | 8 ++++++++ test.js | 20 ++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/index.js b/index.js index e98bd94..d5abf64 100644 --- a/index.js +++ b/index.js @@ -179,6 +179,10 @@ is.domElement = x => is.object(x) && x.nodeType === NODE_TYPE_ELEMENT && is.stri is.infinite = x => x === Infinity || x === -Infinity; +const isAbsoluteMod2 = value => x => is.integer(x) && Math.abs(x % 2) === value; +is.even = isAbsoluteMod2(0); +is.odd = isAbsoluteMod2(1); + const isEmptyStringOrArray = x => (is.string(x) || is.array(x)) && x.length === 0; const isEmptyObject = x => !is.map(x) && !is.set(x) && is.object(x) && Object.keys(x).length === 0; const isEmptyMapOrSet = x => (is.map(x) || is.set(x)) && x.size === 0; diff --git a/readme.md b/readme.md index 3f3e20d..1760692 100644 --- a/readme.md +++ b/readme.md @@ -170,6 +170,14 @@ Returns `true` if `value` is a DOM Element. Check if `value` is `Infinity` or `-Infinity`. +##### .even(value) + +Returns `true` if `value` is an even integer. + +##### .odd(value) + +Returns `true` if `value` is an odd integer. + ##### .empty(value) Returns `true` if `value` is falsy or an empty string, array, object, map, or set. diff --git a/test.js b/test.js index d65a7a0..910f37a 100644 --- a/test.js +++ b/test.js @@ -419,6 +419,26 @@ test('is.infinite', t => { testType(t, 'infinite', ['number']); }); +test('is.even', t => { + for (const el of [-6, 2, 4]) { + t.true(m.even(el)); + } + + for (const el of [-3, 1, 5]) { + t.false(m.even(el)); + } +}); + +test('is.odd', t => { + for (const el of [-5, 7, 13]) { + t.true(m.odd(el)); + } + + for (const el of [-8, 8, 10]) { + t.false(m.odd(el)); + } +}); + test('is.empty', t => { t.true(m.empty(null)); t.true(m.empty(undefined)); From cdd4829edfec08131dade41ab353b7f1d004f8ff Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Tue, 17 Oct 2017 16:46:58 -0400 Subject: [PATCH 022/254] Add is.emptyOrWhitespace() (#21) --- index.js | 2 ++ readme.md | 4 ++++ test.js | 7 +++++++ 3 files changed, 13 insertions(+) diff --git a/index.js b/index.js index d5abf64..f5d76ce 100644 --- a/index.js +++ b/index.js @@ -183,11 +183,13 @@ const isAbsoluteMod2 = value => x => is.integer(x) && Math.abs(x % 2) === value; is.even = isAbsoluteMod2(0); is.odd = isAbsoluteMod2(1); +const isWhiteSpaceString = x => is.string(x) && /\S/.test(x) === false; const isEmptyStringOrArray = x => (is.string(x) || is.array(x)) && x.length === 0; const isEmptyObject = x => !is.map(x) && !is.set(x) && is.object(x) && Object.keys(x).length === 0; const isEmptyMapOrSet = x => (is.map(x) || is.set(x)) && x.size === 0; is.empty = x => !x || isEmptyStringOrArray(x) || isEmptyObject(x) || isEmptyMapOrSet(x); +is.emptyOrWhitespace = x => is.empty(x) || isWhiteSpaceString(x); const predicateOnArray = (method, predicate, values) => { // `values` is the calling function's "arguments object". diff --git a/readme.md b/readme.md index 1760692..c1eae3f 100644 --- a/readme.md +++ b/readme.md @@ -182,6 +182,10 @@ Returns `true` if `value` is an odd integer. Returns `true` if `value` is falsy or an empty string, array, object, map, or set. +##### .emptyOrWhitespace(value) + +Returns `true` if `is.empty(value)` or a string that is all whitespace. + ##### .any(predicate, ...values) diff --git a/test.js b/test.js index 910f37a..31e6055 100644 --- a/test.js +++ b/test.js @@ -468,6 +468,13 @@ test('is.empty', t => { t.false(m.empty(tempSet)); }); +test('is.emptyOrWhitespace', t => { + t.true(m.emptyOrWhitespace('')); + t.true(m.emptyOrWhitespace(' ')); + t.false(m.emptyOrWhitespace('🦄')); + t.false(m.emptyOrWhitespace('unicorn')); +}); + test('is.any', t => { t.true(m.any(m.string, {}, true, '🦄')); t.true(m.any(m.object, false, {}, 'unicorns')); From dfcdfc3a83110422d959aa48f8a8c39fba6cf2fb Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Fri, 20 Oct 2017 14:49:52 +0000 Subject: [PATCH 023/254] Add is.truthy() and is.falsy() (#25) --- index.js | 5 ++++- readme.md | 16 ++++++++++++++++ test.js | 17 +++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index f5d76ce..5c5e656 100644 --- a/index.js +++ b/index.js @@ -111,6 +111,9 @@ is.float64Array = isObjectOfType('Float64Array'); is.arrayBuffer = isObjectOfType('ArrayBuffer'); is.sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); +is.truthy = x => !!x; // eslint-disable-line no-implicit-coercion +is.falsy = x => !x; + is.nan = Number.isNaN; is.nullOrUndefined = x => is.null(x) || is.undefined(x); @@ -188,7 +191,7 @@ const isEmptyStringOrArray = x => (is.string(x) || is.array(x)) && x.length === const isEmptyObject = x => !is.map(x) && !is.set(x) && is.object(x) && Object.keys(x).length === 0; const isEmptyMapOrSet = x => (is.map(x) || is.set(x)) && x.size === 0; -is.empty = x => !x || isEmptyStringOrArray(x) || isEmptyObject(x) || isEmptyMapOrSet(x); +is.empty = x => is.falsy(x) || isEmptyStringOrArray(x) || isEmptyObject(x) || isEmptyMapOrSet(x); is.emptyOrWhitespace = x => is.empty(x) || isWhiteSpaceString(x); const predicateOnArray = (method, predicate, values) => { diff --git a/readme.md b/readme.md index c1eae3f..51b18bf 100644 --- a/readme.md +++ b/readme.md @@ -121,6 +121,22 @@ is.asyncFunction(() => {}); #### Miscellaneous +##### .truthy(value) + +Returns `true` for all values that evaluate to true in a boolean context: + +```js +is.truthy('🦄'); +//=> true + +is.truthy(undefined); +//=> false +``` + +##### .falsy(value) + +Returns `true` if `value` is one of: `false`, `0`, `''`, `null`, `undefined`, `NaN`. + ##### .nan(value) ##### .nullOrUndefined(value) ##### .primitive(value) diff --git a/test.js b/test.js index 31e6055..f35eb08 100644 --- a/test.js +++ b/test.js @@ -289,6 +289,23 @@ test('is.dataView', t => { testType(t, 'arrayBuffer'); }); +test('is.truthy', t => { + t.true(m.truthy('unicorn')); + t.true(m.truthy('🦄')); + t.true(m.truthy(new Set())); + t.true(m.truthy(Symbol('🦄'))); + t.true(m.truthy(true)); +}); + +test('is.falsy', t => { + t.true(m.falsy(false)); + t.true(m.falsy(0)); + t.true(m.falsy('')); + t.true(m.falsy(null)); + t.true(m.falsy(undefined)); + t.true(m.falsy(NaN)); +}); + test('is.nan', t => { testType(t, 'nan'); }); From a4eebe4b4a95eeb7c6a476d89e92d2af0daf2f2b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 22 Oct 2017 02:28:27 +0700 Subject: [PATCH 024/254] 0.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5988758..9cc88cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.3.0", + "version": "0.4.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From bd428997a2923ee852b80fbd6d6a102fe529b20d Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 23 Oct 2017 19:03:35 +0700 Subject: [PATCH 025/254] Remove trailing whitespace --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 51b18bf..0a8fa36 100644 --- a/readme.md +++ b/readme.md @@ -86,7 +86,7 @@ Returns `true` for any object that implements its own `.next()` and `.throw()` m ##### .asyncFunction(value) -Returns `true` for any `async` function that can be called with the `await` operator. +Returns `true` for any `async` function that can be called with the `await` operator. ```js is.asyncFunction(async () => {}); @@ -135,7 +135,7 @@ is.truthy(undefined); ##### .falsy(value) -Returns `true` if `value` is one of: `false`, `0`, `''`, `null`, `undefined`, `NaN`. +Returns `true` if `value` is one of: `false`, `0`, `''`, `null`, `undefined`, `NaN`. ##### .nan(value) ##### .nullOrUndefined(value) From 0bafa04e77df883ef37c43ea701d2a8e7c6a9a56 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 23 Oct 2017 19:04:11 +0700 Subject: [PATCH 026/254] =?UTF-8?q?Welcome=20@brandon93s=20to=20the=20proj?= =?UTF-8?q?ect=20=F0=9F=8E=89=F0=9F=8C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 0a8fa36..a99dd93 100644 --- a/readme.md +++ b/readme.md @@ -261,6 +261,7 @@ The most common mistakes I noticed in these modules was using `instanceof` for t - [Sindre Sorhus](https://github.com/sindresorhus) - [Giora Guttsait](https://github.com/gioragutt) +- [Brandon Smith](https://github.com/brandon93s) ## License From 83adc096ef2f49bc60727df8dba7638a788f14d6 Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Thu, 26 Oct 2017 09:30:17 -0400 Subject: [PATCH 027/254] Add `is.arrayLike()` (#26) --- index.js | 3 +++ readme.md | 14 ++++++++++++++ test.js | 12 ++++++++++++ 3 files changed, 29 insertions(+) diff --git a/index.js b/index.js index 5c5e656..ed9fed7 100644 --- a/index.js +++ b/index.js @@ -155,6 +155,9 @@ const typedArrayTypes = new Set([ ]); is.typedArray = x => typedArrayTypes.has(getObjectType(x)); +const isValidLength = x => is.safeInteger(x) && x > -1; +is.arrayLike = x => !is.nullOrUndefined(x) && !is.function(x) && isValidLength(x.length); + is.inRange = (x, range) => { if (is.number(range)) { return x >= Math.min(0, range) && x <= Math.max(range, 0); diff --git a/readme.md b/readme.md index a99dd93..20e9441 100644 --- a/readme.md +++ b/readme.md @@ -160,6 +160,20 @@ Returns `true` for instances created by a ES2015 class. ##### .typedArray(value) +##### .arrayLike(value) + +A `value` is array-like if it is not a function and has a `value.length` that is a safe integer greater than or equal to 0. + +```js +is.arrayLike(document.forms); +//=> true + +function () { + is.arrayLike(arguments); + //=> true +} +``` + ##### .inRange(value, range) Check if `value` (number) is in the given `range`. The range is an array of two values, lower bound and upper bound, in no specific order. diff --git a/test.js b/test.js index f35eb08..8b7967c 100644 --- a/test.js +++ b/test.js @@ -392,6 +392,18 @@ test('is.typedArray', t => { t.false(m.typedArray({})); }); +test('is.arrayLike', t => { + (() => { + t.true(m.arrayLike(arguments)); + })(); + t.true(m.arrayLike([])); + t.true(m.arrayLike('unicorn')); + + t.false(m.arrayLike({})); + t.false(m.arrayLike(() => {})); + t.false(m.arrayLike(new Map())); +}); + test('is.inRange', t => { const x = 3; From 8d8fd2b7e012f85e1a95859de6c47ebfdcbbc586 Mon Sep 17 00:00:00 2001 From: Lukas Tetzlaff Date: Mon, 6 Nov 2017 16:26:59 +0100 Subject: [PATCH 028/254] Refactor module in TypeScript (#28) --- .gitignore | 1 + index.js | 227 --------------- package.json | 16 +- source/index.ts | 243 +++++++++++++++++ test.js => source/tests/test.ts | 470 ++++++++++++++++++++++---------- tsconfig.json | 17 ++ tslint.json | 3 + 7 files changed, 602 insertions(+), 375 deletions(-) delete mode 100644 index.js create mode 100644 source/index.ts rename test.js => source/tests/test.ts (54%) create mode 100644 tsconfig.json create mode 100644 tslint.json diff --git a/.gitignore b/.gitignore index 239ecff..c406da7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules yarn.lock +dist diff --git a/index.js b/index.js deleted file mode 100644 index ed9fed7..0000000 --- a/index.js +++ /dev/null @@ -1,227 +0,0 @@ -'use strict'; -const util = require('util'); - -const toString = Object.prototype.toString; -const getObjectType = x => toString.call(x).slice(8, -1); -const isOfType = type => x => typeof x === type; // eslint-disable-line valid-typeof -const isObjectOfType = type => x => getObjectType(x) === type; - -const is = value => { - if (value === null) { - 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 (is.function(value)) { - 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 = isOfType('undefined'); -is.null = x => x === null; -is.string = isOfType('string'); -is.number = isOfType('number'); -is.boolean = x => x === true || x === false; -is.symbol = isOfType('symbol'); - -is.array = Array.isArray; -is.function = isOfType('function'); -is.buffer = Buffer.isBuffer; - -const isObject = x => typeof x === 'object'; - -is.object = x => !is.nullOrUndefined(x) && (is.function(x) || isObject(x)); - -is.nativePromise = isObjectOfType('Promise'); - -const hasPromiseAPI = x => - !is.null(x) && - isObject(x) && - is.function(x.then) && - is.function(x.catch); - -is.promise = x => is.nativePromise(x) || hasPromiseAPI(x); - -is.generator = x => is.iterable(x) && is.function(x.next) && is.function(x.throw); - -// TODO: Change to use `isObjectOfType` once Node.js 6 or higher is targeted -const isFunctionOfType = type => x => is.function(x) && is.function(x.constructor) && x.constructor.name === type; - -is.generatorFunction = isFunctionOfType('GeneratorFunction'); -is.asyncFunction = isFunctionOfType('AsyncFunction'); - -is.regExp = isObjectOfType('RegExp'); -is.date = isObjectOfType('Date'); -is.error = isObjectOfType('Error'); -is.map = isObjectOfType('Map'); -is.set = isObjectOfType('Set'); -is.weakMap = isObjectOfType('WeakMap'); -is.weakSet = isObjectOfType('WeakSet'); - -is.int8Array = isObjectOfType('Int8Array'); -is.uint8Array = isObjectOfType('Uint8Array'); -is.uint8ClampedArray = isObjectOfType('Uint8ClampedArray'); -is.int16Array = isObjectOfType('Int16Array'); -is.uint16Array = isObjectOfType('Uint16Array'); -is.int32Array = isObjectOfType('Int32Array'); -is.uint32Array = isObjectOfType('Uint32Array'); -is.float32Array = isObjectOfType('Float32Array'); -is.float64Array = isObjectOfType('Float64Array'); - -is.arrayBuffer = isObjectOfType('ArrayBuffer'); -is.sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); - -is.truthy = x => !!x; // eslint-disable-line no-implicit-coercion -is.falsy = x => !x; - -is.nan = Number.isNaN; -is.nullOrUndefined = x => is.null(x) || is.undefined(x); - -const primitiveTypes = new Set([ - 'undefined', - 'string', - 'number', - 'boolean', - 'symbol' -]); -is.primitive = x => is.null(x) || primitiveTypes.has(typeof x); - -is.integer = Number.isInteger; -is.safeInteger = Number.isSafeInteger; - -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.nullOrUndefined(x) && is.function(x[Symbol.iterator]); - -is.class = x => is.function(x) && x.toString().startsWith('class '); - -const typedArrayTypes = new Set([ - 'Int8Array', - 'Uint8Array', - 'Uint8ClampedArray', - 'Int16Array', - 'Uint16Array', - 'Int32Array', - 'Uint32Array', - 'Float32Array', - 'Float64Array' -]); -is.typedArray = x => typedArrayTypes.has(getObjectType(x)); - -const isValidLength = x => is.safeInteger(x) && x > -1; -is.arrayLike = x => !is.nullOrUndefined(x) && !is.function(x) && isValidLength(x.length); - -is.inRange = (x, range) => { - if (is.number(range)) { - return x >= Math.min(0, range) && x <= Math.max(range, 0); - } - - if (is.array(range) && range.length === 2) { - // TODO: Use spread operator here when targeting Node.js 6 or higher - return x >= Math.min.apply(null, range) && x <= Math.max.apply(null, range); - } - - throw new TypeError(`Invalid range: ${util.inspect(range)}`); -}; - -const NODE_TYPE_ELEMENT = 1; -const DOM_PROPERTIES_TO_CHECK = [ - 'innerHTML', - 'ownerDocument', - 'style', - 'attributes', - 'nodeValue' -]; - -is.domElement = x => is.object(x) && x.nodeType === NODE_TYPE_ELEMENT && is.string(x.nodeName) && - !is.plainObject(x) && DOM_PROPERTIES_TO_CHECK.every(property => property in x); - -is.infinite = x => x === Infinity || x === -Infinity; - -const isAbsoluteMod2 = value => x => is.integer(x) && Math.abs(x % 2) === value; -is.even = isAbsoluteMod2(0); -is.odd = isAbsoluteMod2(1); - -const isWhiteSpaceString = x => is.string(x) && /\S/.test(x) === false; -const isEmptyStringOrArray = x => (is.string(x) || is.array(x)) && x.length === 0; -const isEmptyObject = x => !is.map(x) && !is.set(x) && is.object(x) && Object.keys(x).length === 0; -const isEmptyMapOrSet = x => (is.map(x) || is.set(x)) && x.size === 0; - -is.empty = x => is.falsy(x) || isEmptyStringOrArray(x) || isEmptyObject(x) || isEmptyMapOrSet(x); -is.emptyOrWhitespace = x => is.empty(x) || isWhiteSpaceString(x); - -const predicateOnArray = (method, predicate, values) => { - // `values` is the calling function's "arguments object". - // We have to do it this way to keep node v4 support. - // So here we convert it to an array and slice off the first item. - values = Array.prototype.slice.call(values, 1); - - if (is.function(predicate) === false) { - throw new TypeError(`Invalid predicate: ${util.inspect(predicate)}`); - } - - if (values.length === 0) { - throw new TypeError(`Invalid number of values`); - } - - return method.call(values, predicate); -}; - -// We have to use anonymous functions for the any() and all() methods -// to get the arguments since we can't use rest parameters in node v4. -is.any = function (predicate) { - return predicateOnArray(Array.prototype.some, predicate, arguments); -}; - -is.all = function (predicate) { - return predicateOnArray(Array.prototype.every, predicate, arguments); -}; - -module.exports = is; diff --git a/package.json b/package.json index 9cc88cc..459679d 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,15 @@ "node": ">=4" }, "scripts": { - "test": "xo && ava" + "lint": "tslint --project .", + "build": "tsc", + "test": "npm run lint && npm run build && ava dist/tests", + "prepublish": "npm run build && del dist/tests" }, + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ - "index.js" + "dist" ], "keywords": [ "type", @@ -43,8 +48,13 @@ "compare" ], "devDependencies": { + "@types/jsdom": "^2.0.31", + "@types/node": "^8.0.47", "ava": "*", + "del-cli": "^1.1.0", "jsdom": "^9.12.0", - "xo": "*" + "tslint": "^5.8.0", + "tslint-xo": "^0.2.1", + "typescript": "^2.6.1" } } diff --git a/source/index.ts b/source/index.ts new file mode 100644 index 0000000..ce877eb --- /dev/null +++ b/source/index.ts @@ -0,0 +1,243 @@ +import * as util from 'util'; + +const toString = Object.prototype.toString; +const getObjectType = (value: any) => toString.call(value).slice(8, -1) as string; +const isOfType = (type: string) => (value: any) => typeof value === type; // tslint:disable-line +const isObjectOfType = (type: string) => (value: any) => getObjectType(value) === type; + +function is(value: any) { // tslint:disable-line:only-arrow-functions + if (value === null) { + 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 (is.function_(value)) { + 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'; +} + +namespace is { // tslint:disable-line:no-namespace + const isObject = (value: any) => typeof value === 'object'; + + // tslint:disable:variable-name + export const undefined = isOfType('undefined'); + export const string = isOfType('string'); + export const number = isOfType('number'); + export const function_ = isOfType('function'); + export const null_ = (value: any) => value === null; + + export const class_ = (value: any) => function_(value) && value.toString().startsWith('class '); + export const boolean = (value: any) => value === true || value === false; + // tslint:enable:variable-name + + export const symbol = isOfType('symbol'); + + export const array = Array.isArray; + export const buffer = Buffer.isBuffer; + + export const nullOrUndefined = (value: any) => null_(value) || undefined(value); + export const object = (value: any) => !nullOrUndefined(value) && (function_(value) || isObject(value)); + export const iterable = (value: any) => !nullOrUndefined(value) && function_(value[Symbol.iterator]); + export const generator = (value: any) => iterable(value) && function_(value.next) && function_(value.throw); + + export const nativePromise = isObjectOfType('Promise'); + + const hasPromiseAPI = (value: any) => + !null_(value) && + isObject(value) && + function_(value.then) && + function_(value.catch); + + export const promise = (value: any) => nativePromise(value) || hasPromiseAPI(value); + + // TODO: Change to use `isObjectOfType` once Node.js 6 or higher is targeted + const isFunctionOfType = (type: string) => (value: any) => function_(value) && function_(value.constructor) && value.constructor.name === type; + + export const generatorFunction = isFunctionOfType('GeneratorFunction'); + export const asyncFunction = isFunctionOfType('AsyncFunction'); + + export const regExp = isObjectOfType('RegExp'); + export const date = isObjectOfType('Date'); + export const error = isObjectOfType('Error'); + export const map = isObjectOfType('Map'); + export const set = isObjectOfType('Set'); + export const weakMap = isObjectOfType('WeakMap'); + export const weakSet = isObjectOfType('WeakSet'); + + export const int8Array = isObjectOfType('Int8Array'); + export const uint8Array = isObjectOfType('Uint8Array'); + export const uint8ClampedArray = isObjectOfType('Uint8ClampedArray'); + export const int16Array = isObjectOfType('Int16Array'); + export const uint16Array = isObjectOfType('Uint16Array'); + export const int32Array = isObjectOfType('Int32Array'); + export const uint32Array = isObjectOfType('Uint32Array'); + export const float32Array = isObjectOfType('Float32Array'); + export const float64Array = isObjectOfType('Float64Array'); + + export const arrayBuffer = isObjectOfType('ArrayBuffer'); + export const sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); + + export const truthy = (value: any) => Boolean(value); + export const falsy = (value: any) => !value; + + export const nan = (value: any) => Number.isNaN(value); + + const primitiveTypes = new Set([ + 'undefined', + 'string', + 'number', + 'boolean', + 'symbol' + ]); + + export const primitive = (value: any) => null_(value) || primitiveTypes.has(typeof value); + + export const integer = (value: any) => Number.isInteger(value); + export const safeInteger = (value: any) => Number.isSafeInteger(value); + + export const plainObject = (value: any) => { + // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js + let prototype; + + return getObjectType(value) === 'Object' && + (prototype = Object.getPrototypeOf(value), prototype === null || // tslint:disable-line:ban-comma-operator + prototype === Object.getPrototypeOf({})); + }; + + const typedArrayTypes = new Set([ + 'Int8Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'Int16Array', + 'Uint16Array', + 'Int32Array', + 'Uint32Array', + 'Float32Array', + 'Float64Array' + ]); + export const typedArray = (value: any) => typedArrayTypes.has(getObjectType(value)); + + const isValidLength = (value: any) => safeInteger(value) && value > -1; + export const arrayLike = (value: any) => !nullOrUndefined(value) && !function_(value) && isValidLength(value.length); + + export const inRange = (value: number, range: number | number[]) => { + if (number(range)) { + return value >= Math.min(0, range as number) && value <= Math.max(range as number, 0); + } + + if (array(range) && range.length === 2) { + // TODO: Use spread operator here when targeting Node.js 6 or higher + return value >= Math.min.apply(null, range) && value <= Math.max.apply(null, range); + } + + throw new TypeError(`Invalid range: ${util.inspect(range)}`); + }; + + const NODE_TYPE_ELEMENT = 1; + const DOM_PROPERTIES_TO_CHECK = [ + 'innerHTML', + 'ownerDocument', + 'style', + 'attributes', + 'nodeValue' + ]; + + export const domElement = (value: any) => object(value) && value.nodeType === NODE_TYPE_ELEMENT && string(value.nodeName) && + !plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in value); + + export const infinite = (value: any) => value === Infinity || value === -Infinity; + + const isAbsoluteMod2 = (value: number) => (rem: number) => integer(rem) && Math.abs(rem % 2) === value; + export const even = isAbsoluteMod2(0); + export const odd = isAbsoluteMod2(1); + + const isWhiteSpaceString = (value: any) => string(value) && /\S/.test(value) === false; + const isEmptyStringOrArray = (value: any) => (string(value) || array(value)) && value.length === 0; + const isEmptyObject = (value: any) => !map(value) && !set(value) && object(value) && Object.keys(value).length === 0; + const isEmptyMapOrSet = (value: any) => (map(value) || set(value)) && value.size === 0; + + export const empty = (value: any) => falsy(value) || isEmptyStringOrArray(value) || isEmptyObject(value) || isEmptyMapOrSet(value); + export const emptyOrWhitespace = (value: any) => empty(value) || isWhiteSpaceString(value); + + type ArrayMethod = (fn: (value: any, index: number, arr: any[]) => boolean, thisArg?: any) => boolean; + const predicateOnArray = (method: ArrayMethod, predicate: any, args: IArguments) => { + // `args` is the calling function's "arguments object". + // We have to do it this way to keep node v4 support. + // So here we convert it to an array and slice off the first item. + const values = Array.prototype.slice.call(args, 1); + + if (function_(predicate) === false) { + throw new TypeError(`Invalid predicate: ${util.inspect(predicate)}`); + } + + if (values.length === 0) { + throw new TypeError('Invalid number of values'); + } + + return method.call(values, predicate); + }; + + // We can't use rest parameters in node v4 due to the lack of the spread operator. + // Therefore We have to use anonymous functions for the any() and all() methods + // tslint:disable:only-arrow-functions no-function-expression + export function any(...predicate: any[]): any; // tslint:disable-line:variable-name + export function any(predicate: any) { + return predicateOnArray(Array.prototype.some, predicate, arguments); + } + + export function all(...predicate: any[]): any; + export function all(predicate: any) { + return predicateOnArray(Array.prototype.every, predicate, arguments); + } + // tslint:enable:only-arrow-functions no-function-expression +} + +// Some few keywords are reserved, but we'll populate them for the node-folks +// See https://github.com/Microsoft/TypeScript/issues/2536 +Object.defineProperties(is, { + class: {value: is.class_}, + function: {value: is.function_}, + null: {value: is.null_} +}); + +export default is; // tslint:disable-line:no-default-export diff --git a/test.js b/source/tests/test.ts similarity index 54% rename from test.js rename to source/tests/test.ts index 8b7967c..54f9041 100644 --- a/test.js +++ b/source/tests/test.ts @@ -1,133 +1,311 @@ -import util from 'util'; -import test from 'ava'; +import * as util from 'util'; +import test, {TestContext, Context} from 'ava'; import {jsdom} from 'jsdom'; -import m from '.'; +import m from '..'; const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; -const PromiseSubclassFixture = class extends Promise {}; -const ErrorSubclassFixture = class extends Error {}; +// Currently out of order, see https://github.com/Microsoft/TypeScript/issues/15202 class PromiseSubclassFixture extends Promise {} +class ErrorSubclassFixture extends Error {} const document = jsdom(); -const createDomElement = el => document.createElement(el); +const createDomElement = (el: string) => document.createElement(el); -const types = new Map([ - ['undefined', undefined], - ['null', null], - ['string', [ - '🦄', - 'hello world', - '' - ]], - ['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() {}}], - ['generator', (function * () { - yield 4; - })()], - ['generatorFunction', function * () { - yield 4; +interface Test { + is(value: any): boolean; + fixtures: any[]; +} + +const types = new Map([ + ['undefined', { + is: m.undefined, + fixtures: [ + undefined + ] }], - ['asyncFunction', [ - async function () {}, - async () => {} - ]], - ['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], - ['safeInteger', [ - Math.pow(2, 53) - 1, - -Math.pow(2, 53) + 1 - ]], - ['domElement', [ - 'div', - 'input', - 'span', - 'img', - 'canvas', - 'script' - ].map(createDomElement)], - ['non-domElements', [ - document.createTextNode('data'), - document.createProcessingInstruction('xml-stylesheet', 'href="mycss.css" type="text/css"'), - document.createComment('This is a comment'), - document, - document.implementation.createDocumentType('svg:svg', '-//W3C//DTD SVG 1.1//EN', 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'), - document.createDocumentFragment() - ]], - ['infinite', [ - Infinity, - -Infinity - ]] + ['null', { + is: m.null_, + fixtures: [ + null + ] + }], + ['string', { + is: m.string, + fixtures: [ + '🦄', + 'hello world', + '' + ] + }], + ['number', { + is: m.number, + fixtures: [ + 6, + 1.4, + 0, + -0, + Infinity, + -Infinity + ] + }], + ['boolean', { + is: m.boolean, + fixtures: [ + true, false + ] + }], + ['symbol', { + is: m.symbol, + fixtures: [ + Symbol('🦄') + ] + }], + ['array', { + is: m.array, + fixtures: [ + [1, 2], + new Array(2) // tslint:disable-line:prefer-array-literal + ] + }], + ['function', { + is: m.function_, + fixtures: [ + // tslint:disable:no-empty no-unused-variable only-arrow-functions no-function-expression + function foo() {}, // tslint:disable-line:no-unused + function() {}, + () => {}, + async function() {}, + function *(): any {} + // tslint:enable:no-empty no-unused-variable only-arrow-functions no-function-expression + ] + }], + ['buffer', { + is: m.buffer, + fixtures: [ + Buffer.from('🦄') + ] + }], + ['object', { + is: m.object, + fixtures: [ + {x: 1}, + Object.create({x: 1}) + ] + }], + ['regExp', { + is: m.regExp, + fixtures: [ + /\w/, + new RegExp('\\w') + ] + }], + ['date', { + is: m.date, + fixtures: [ + new Date() + ] + }], + ['error', { + is: m.error, + fixtures: [ + new Error('🦄'), + new ErrorSubclassFixture() + ] + }], + ['nativePromise', { + is: m.nativePromise, + fixtures: [ + Promise.resolve(), + // PromiseSubclassFixture.resolve() + ] + }], + ['promise', { + is: m.promise, + fixtures: [ + {then() {}, catch() {}} // tslint:disable-line:no-empty + ] + }], + ['generator', { + is: m.generator, + fixtures: [ + (function *() { yield 4; })() // tslint:disable-line + ] + }], + ['generatorFunction', { + is: m.generatorFunction, + fixtures: [ + function *() { yield 4; } // tslint:disable-line + ] + }], + ['asyncFunction', { + is: m.asyncFunction, + fixtures: [ + async function() {}, // tslint:disable-line:no-empty only-arrow-functions no-function-expression + async () => {} // tslint:disable-line:no-empty + ] + }], + ['map', { + is: m.map, + fixtures: [ + new Map() + ] + }], + ['set', { + is: m.set, + fixtures: [ + new Set() + ] + }], + ['weakSet', { + is: m.weakSet, + fixtures: [ + new WeakSet() + ] + }], + ['weakMap', { + is: m.weakMap, + fixtures: [ + new WeakMap() + ] + }], + ['int8Array', { + is: m.int8Array, + fixtures: [ + new Int8Array(0) + ] + }], + ['uint8Array', { + is: m.uint8Array, + fixtures: [ + new Uint8Array(0) + ] + }], + ['uint8ClampedArray', { + is: m.uint8ClampedArray, + fixtures: [ + new Uint8ClampedArray(0) + ] + }], + ['int16Array', { + is: m.int16Array, + fixtures: [ + new Int16Array(0) + ] + }], + ['uint16Array', { + is: m.uint16Array, + fixtures: [ + new Uint16Array(0) + ] + }], + ['int32Array', { + is: m.int32Array, + fixtures: [ + new Int32Array(0) + ] + }], + ['uint32Array', { + is: m.uint32Array, + fixtures: [ + new Uint32Array(0) + ] + }], + ['float32Array', { + is: m.float32Array, + fixtures: [ + new Float32Array(0) + ] + }], + ['float64Array', { + is: m.float64Array, + fixtures: [ + new Float64Array(0) + ] + }], + ['arrayBuffer', { + is: m.arrayBuffer, + fixtures: [ + new ArrayBuffer(10) + ] + }], + ['nan', { + is: m.nan, + fixtures: [ + NaN, + Number.NaN + ] + }], + ['nullOrUndefined', { + is: m.nullOrUndefined, + fixtures: [ + null, + undefined + ] + }], + ['plainObject', { + is: m.plainObject, + fixtures: [ + {x: 1}, + Object.create(null), + new Object() + ] + }], + ['integer', { + is: m.integer, + fixtures: [ + 6 + ] + }], + ['safeInteger', { + is: m.safeInteger, + fixtures: [ + Math.pow(2, 53) - 1, + -Math.pow(2, 53) + 1 + ] + }], + ['domElement', { + is: m.domElement, + fixtures: [ + 'div', + 'input', + 'span', + 'img', + 'canvas', + 'script' + ].map(createDomElement) } + ], ['non-domElements', { + is: value => !m.domElement(value), + fixtures: [ + document.createTextNode('data'), + document.createProcessingInstruction('xml-stylesheet', 'href="mycss.css" type="text/css"'), + document.createComment('This is a comment'), + document, + document.implementation.createDocumentType('svg:svg', '-//W3C//DTD SVG 1.1//EN', 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'), // tslint:disable-line + document.createDocumentFragment() + ] + }], + ['infinite', { + is: m.infinite, + fixtures: [ + Infinity, + -Infinity + ] + }] ]); -// This ensures 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) { +// This ensures a certain method matches only the types it's supposed to and none of the other methods' types +const testType = (t: TestContext & Context, type: string, exclude?: string[]) => { + const testData = types.get(type); + + if (testData === undefined) { + t.fail(`is.${type} not defined`); + + return; + } + + const {is} = testData; + + for (const [key, {fixtures}] 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) { @@ -135,8 +313,6 @@ const testType = (t, type, exclude) => { } 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)}`); @@ -188,7 +364,15 @@ test('is.buffer', t => { }); test('is.object', t => { - for (const el of types.get('object')) { + const testData = types.get('object'); + + if (testData === undefined) { + t.fail('is.object not defined'); + + return; + } + + for (const el of testData.fixtures) { t.true(m.object(el)); } }); @@ -213,6 +397,10 @@ if (isNode8orHigher) { test('is.promise', t => { testType(t, 'promise', ['nativePromise']); }); + + /*test('is.asyncFunction', t => { + testType(t, 'asyncFunction', ['function']); + });*/ } test('is.generator', t => { @@ -223,12 +411,6 @@ test('is.generatorFunction', t => { testType(t, 'generatorFunction', ['function']); }); -if (isNode8orHigher) { - test('is.asyncFunction', t => { - testType(t, 'asyncFunction', ['function']); - }); -} - test('is.map', t => { testType(t, 'map'); }); @@ -360,27 +542,29 @@ test('is.iterable', t => { }); test('is.class', t => { - class Foo {} + class Foo {} // tslint:disable-line const classDeclarations = [ Foo, - class Bar extends Foo {} + class Bar extends Foo {} // tslint:disable-line ]; for (const x of classDeclarations) { - t.true(m.class(x)); + t.true(m.class_(x)); } }); test('is.typedArray', t => { + // Typescript currently does not support empty constructors for these + // See https://github.com/Microsoft/TypeScript/issues/19680 const typedArrays = [ - new Int8Array(), - new Uint8Array(), - new Uint8ClampedArray(), - new Uint16Array(), - new Int32Array(), - new Uint32Array(), - new Float32Array(), - new Float64Array() + new Int8Array(0), + new Uint8Array(0), + new Uint8ClampedArray(0), + new Uint16Array(0), + new Int32Array(0), + new Uint32Array(0), + new Float32Array(0), + new Float64Array(0) ]; for (const el of typedArrays) { @@ -400,7 +584,7 @@ test('is.arrayLike', t => { t.true(m.arrayLike('unicorn')); t.false(m.arrayLike({})); - t.false(m.arrayLike(() => {})); + t.false(m.arrayLike(() => {})); // tslint:disable-line:no-empty t.false(m.arrayLike(new Map())); }); @@ -422,10 +606,6 @@ test('is.inRange', t => { t.false(m.inRange(x, 2)); t.false(m.inRange(-3, -2)); - t.throws(() => { - m.inRange(0); - }); - t.throws(() => { m.inRange(0, []); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b3d8332 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compileOnSave": true, + "compilerOptions": { + /* es2015 requires moduleResolution and module to be set, + see https://github.com/Microsoft/TypeScript/issues/8189 */ + "target": "es2015", + "moduleResolution": "node", + "module": "none", + "strict": true, + "declaration": true, + "sourceMap": true, + "outDir": "dist", + "lib": [ + "es2015", "dom", "scripthost" + ] + } +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..55e9f36 --- /dev/null +++ b/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "tslint-xo" +} From 9770f6689946dffd72b3ba0b212c609418827c3f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 6 Nov 2017 22:36:51 +0700 Subject: [PATCH 029/254] Cleanup --- package.json | 116 +++++++++++++++++++++---------------------- source/index.ts | 16 ++++-- source/tests/test.ts | 4 +- tsconfig.json | 33 ++++++------ 4 files changed, 90 insertions(+), 79 deletions(-) diff --git a/package.json b/package.json index 459679d..22e3e1d 100644 --- a/package.json +++ b/package.json @@ -1,60 +1,60 @@ { - "name": "@sindresorhus/is", - "version": "0.4.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": { - "lint": "tslint --project .", - "build": "tsc", - "test": "npm run lint && npm run build && ava dist/tests", - "prepublish": "npm run build && del dist/tests" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "files": [ - "dist" - ], - "keywords": [ - "type", - "types", - "is", - "check", - "checking", - "validate", - "validation", - "utility", - "util", - "typeof", - "instanceof", - "object", - "assert", - "assertion", - "test", - "kind", - "primitive", - "verify", - "compare" - ], - "devDependencies": { - "@types/jsdom": "^2.0.31", - "@types/node": "^8.0.47", - "ava": "*", - "del-cli": "^1.1.0", - "jsdom": "^9.12.0", - "tslint": "^5.8.0", - "tslint-xo": "^0.2.1", - "typescript": "^2.6.1" - } + "name": "@sindresorhus/is", + "version": "0.4.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" + }, + "main": "dist/index.js", + "engines": { + "node": ">=4" + }, + "scripts": { + "lint": "tslint --project .", + "build": "tsc", + "test": "npm run lint && npm run build && ava dist/tests", + "prepublish": "npm run build && del dist/tests" + }, + "files": [ + "dist" + ], + "keywords": [ + "type", + "types", + "is", + "check", + "checking", + "validate", + "validation", + "utility", + "util", + "typeof", + "instanceof", + "object", + "assert", + "assertion", + "test", + "kind", + "primitive", + "verify", + "compare" + ], + "devDependencies": { + "@types/jsdom": "^2.0.31", + "@types/node": "^8.0.47", + "ava": "*", + "del-cli": "^1.1.0", + "jsdom": "^9.12.0", + "tslint": "^5.8.0", + "tslint-xo": "^0.2.1", + "typescript": "^2.6.1" + }, + "types": "dist/index.d.ts" } diff --git a/source/index.ts b/source/index.ts index ce877eb..3bf75ff 100644 --- a/source/index.ts +++ b/source/index.ts @@ -2,7 +2,7 @@ import * as util from 'util'; const toString = Object.prototype.toString; const getObjectType = (value: any) => toString.call(value).slice(8, -1) as string; -const isOfType = (type: string) => (value: any) => typeof value === type; // tslint:disable-line +const isOfType = (type: string) => (value: any) => typeof value === type; // tslint:disable-line:strict-type-predicates const isObjectOfType = (type: string) => (value: any) => getObjectType(value) === type; function is(value: any) { // tslint:disable-line:only-arrow-functions @@ -232,12 +232,18 @@ namespace is { // tslint:disable-line:no-namespace // tslint:enable:only-arrow-functions no-function-expression } -// Some few keywords are reserved, but we'll populate them for the node-folks +// Some few keywords are reserved, but we'll populate them for Node.js users // See https://github.com/Microsoft/TypeScript/issues/2536 Object.defineProperties(is, { - class: {value: is.class_}, - function: {value: is.function_}, - null: {value: is.null_} + class: { + value: is.class_ + }, + function: { + value: is.function_ + }, + null: { + value: is.null_ + } }); export default is; // tslint:disable-line:no-default-export diff --git a/source/tests/test.ts b/source/tests/test.ts index 54f9041..6e9246a 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -5,7 +5,9 @@ import m from '..'; const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; -// Currently out of order, see https://github.com/Microsoft/TypeScript/issues/15202 class PromiseSubclassFixture extends Promise {} +// Currently not working. See https://github.com/Microsoft/TypeScript/issues/15202 +// `class PromiseSubclassFixture extends Promise {}` + class ErrorSubclassFixture extends Error {} const document = jsdom(); diff --git a/tsconfig.json b/tsconfig.json index b3d8332..7448590 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,20 @@ { - "compileOnSave": true, - "compilerOptions": { - /* es2015 requires moduleResolution and module to be set, - see https://github.com/Microsoft/TypeScript/issues/8189 */ - "target": "es2015", - "moduleResolution": "node", - "module": "none", - "strict": true, - "declaration": true, - "sourceMap": true, - "outDir": "dist", - "lib": [ - "es2015", "dom", "scripthost" - ] - } + "compileOnSave": true, + "compilerOptions": { + /* es2015 requires moduleResolution and module to be set, + see https://github.com/Microsoft/TypeScript/issues/8189 */ + "target": "es2015", + "moduleResolution": "node", + "module": "none", + "strict": true, + "declaration": true, + "sourceMap": true, + "outDir": "dist", + "removeComments": true, + "lib": [ + "es2015", + "dom", + "scripthost" + ] + } } From d075b547fc8430ff3f6b870c2e1f566a5956f215 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 6 Nov 2017 23:00:28 +0700 Subject: [PATCH 030/254] Update `tslint-xo` --- package.json | 2 +- source/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 22e3e1d..1fcc978 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "del-cli": "^1.1.0", "jsdom": "^9.12.0", "tslint": "^5.8.0", - "tslint-xo": "^0.2.1", + "tslint-xo": "^0.2.2", "typescript": "^2.6.1" }, "types": "dist/index.d.ts" diff --git a/source/index.ts b/source/index.ts index 3bf75ff..86e7ca4 100644 --- a/source/index.ts +++ b/source/index.ts @@ -246,4 +246,4 @@ Object.defineProperties(is, { } }); -export default is; // tslint:disable-line:no-default-export +export default is; From 59a638b216305f3613e63c2f38cbaa98d60f4213 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 7 Nov 2017 09:58:49 +0700 Subject: [PATCH 031/254] Restore ability to use default export in CommonJS (#29) So you can use `require('is')` instead of `require('is').default`. --- source/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/index.ts b/source/index.ts index 86e7ca4..1b7cf38 100644 --- a/source/index.ts +++ b/source/index.ts @@ -247,3 +247,7 @@ Object.defineProperties(is, { }); export default is; + +// For CommonJS default export support +module.exports = is; +module.exports.default = is; From 89867fbc169adc06162fc8936b95e35ce4b0554c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 7 Nov 2017 10:23:00 +0700 Subject: [PATCH 032/254] Various tweaks --- package.json | 4 ++-- source/tests/test.ts | 26 +++++++++++++++----------- tsconfig.json | 37 +++++++++++++++++++++---------------- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 1fcc978..ae72df0 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "node": ">=4" }, "scripts": { - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "build": "tsc", "test": "npm run lint && npm run build && ava dist/tests", "prepublish": "npm run build && del dist/tests" @@ -53,7 +53,7 @@ "del-cli": "^1.1.0", "jsdom": "^9.12.0", "tslint": "^5.8.0", - "tslint-xo": "^0.2.2", + "tslint-xo": "^0.3.0", "typescript": "^2.6.1" }, "types": "dist/index.d.ts" diff --git a/source/tests/test.ts b/source/tests/test.ts index 6e9246a..9b350c5 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -72,13 +72,13 @@ const types = new Map([ ['function', { is: m.function_, fixtures: [ - // tslint:disable:no-empty no-unused-variable only-arrow-functions no-function-expression - function foo() {}, // tslint:disable-line:no-unused - function() {}, + // tslint:disable:no-unused no-empty no-unused-variable only-arrow-functions no-function-expression + function foo() {}, + function () {}, () => {}, - async function() {}, - function *(): any {} - // tslint:enable:no-empty no-unused-variable only-arrow-functions no-function-expression + async function () {}, + function * (): any {} + // tslint:enable:no-unused no-empty no-unused-variable only-arrow-functions no-function-expression ] }], ['buffer', { @@ -130,19 +130,23 @@ const types = new Map([ ['generator', { is: m.generator, fixtures: [ - (function *() { yield 4; })() // tslint:disable-line + (function * () { + yield 4; + })() ] }], ['generatorFunction', { is: m.generatorFunction, fixtures: [ - function *() { yield 4; } // tslint:disable-line + function * () { + yield 4; + } ] }], ['asyncFunction', { is: m.asyncFunction, fixtures: [ - async function() {}, // tslint:disable-line:no-empty only-arrow-functions no-function-expression + async function () {}, // tslint:disable-line:no-empty only-arrow-functions no-function-expression async () => {} // tslint:disable-line:no-empty ] }], @@ -282,7 +286,7 @@ const types = new Map([ document.createProcessingInstruction('xml-stylesheet', 'href="mycss.css" type="text/css"'), document.createComment('This is a comment'), document, - document.implementation.createDocumentType('svg:svg', '-//W3C//DTD SVG 1.1//EN', 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'), // tslint:disable-line + document.implementation.createDocumentType('svg:svg', '-//W3C//DTD SVG 1.1//EN', 'https://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'), document.createDocumentFragment() ] }], @@ -579,7 +583,7 @@ test('is.typedArray', t => { }); test('is.arrayLike', t => { - (() => { + (function () { // tslint:disable-line:only-arrow-functions t.true(m.arrayLike(arguments)); })(); t.true(m.arrayLike([])); diff --git a/tsconfig.json b/tsconfig.json index 7448590..05773b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,20 +1,25 @@ { - "compileOnSave": true, "compilerOptions": { - /* es2015 requires moduleResolution and module to be set, - see https://github.com/Microsoft/TypeScript/issues/8189 */ - "target": "es2015", - "moduleResolution": "node", - "module": "none", - "strict": true, - "declaration": true, - "sourceMap": true, "outDir": "dist", - "removeComments": true, - "lib": [ - "es2015", - "dom", - "scripthost" - ] - } + "target": "es2015", + "module": "commonjs", + "moduleResolution": "node", + "declaration": true, + "pretty": true, + "newLine": "lf", + "stripInternal": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "alwaysStrict": true + }, + "exclude": [ + "node_modules", + "dist" + ] } From e8ac7dc4aaf8d1f9a9b07ae0b0e50e45fb24fbba Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 7 Nov 2017 10:23:46 +0700 Subject: [PATCH 033/254] 0.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae72df0..516866c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.4.0", + "version": "0.5.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From 89fc424975ef9434347a7e28979e47f4df117e6b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 8 Nov 2017 17:22:23 +0700 Subject: [PATCH 034/254] Add `has-emoji` to the Related section in the readme Closes #24 --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 20e9441..effcd76 100644 --- a/readme.md +++ b/readme.md @@ -269,6 +269,7 @@ The most common mistakes I noticed in these modules was using `instanceof` for t - [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 - [is-blob](https://github.com/sindresorhus/is-blob) - Check if a value is a Blob - File-like object of immutable, raw data +- [has-emoji](https://github.com/sindresorhus/has-emoji) - Check whether a string has any emoji ## Created by From 319982a09c351c572fd671f6304f959b6aff8d72 Mon Sep 17 00:00:00 2001 From: Darren Scerri Date: Fri, 10 Nov 2017 19:11:35 +0100 Subject: [PATCH 035/254] Add is.boundFunction() (#31) --- readme.md | 15 +++++++++++++++ source/index.ts | 1 + source/tests/test.ts | 13 ++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index effcd76..0e52ee0 100644 --- a/readme.md +++ b/readme.md @@ -96,6 +96,21 @@ is.asyncFunction(() => {}); // => false ``` +##### .boundFunction(value) + +Returns `true` for any `bound` function. + +```js +is.boundFunction(() => {}); +// => true + +is.boundFunction(function () {}.bind(null)); +// => true + +is.boundFunction(function () {}); +// => false +``` + ##### .map(value) ##### .set(value) ##### .weakMap(value) diff --git a/source/index.ts b/source/index.ts index 1b7cf38..4da25b1 100644 --- a/source/index.ts +++ b/source/index.ts @@ -95,6 +95,7 @@ namespace is { // tslint:disable-line:no-namespace export const generatorFunction = isFunctionOfType('GeneratorFunction'); export const asyncFunction = isFunctionOfType('AsyncFunction'); + export const boundFunction = (value: any) => function_(value) && !value.hasOwnProperty('prototype'); export const regExp = isObjectOfType('RegExp'); export const date = isObjectOfType('Date'); diff --git a/source/tests/test.ts b/source/tests/test.ts index 9b350c5..a24f535 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -150,6 +150,13 @@ const types = new Map([ async () => {} // tslint:disable-line:no-empty ] }], + ['boundFunction', { + is: m.boundFunction, + fixtures: [ + () => {}, // tslint:disable-line:no-empty + function () {}.bind(null), // tslint:disable-line:no-empty only-arrow-functions + ] + }], ['map', { is: m.map, fixtures: [ @@ -362,7 +369,11 @@ test('is.array', t => { }); test('is.function', t => { - testType(t, 'function', ['generatorFunction', 'asyncFunction']); + testType(t, 'function', ['generatorFunction', 'asyncFunction', 'boundFunction']); +}); + +test('is.boundFunction', t => { + t.false(m.boundFunction(function () {})); // tslint:disable-line:no-empty only-arrow-functions }); test('is.buffer', t => { From ae3adc9818c8db4d3ce55e081be6d56607359975 Mon Sep 17 00:00:00 2001 From: Mario Nebl Date: Sun, 19 Nov 2017 21:07:24 +0100 Subject: [PATCH 036/254] Enumerate type names (#32) --- source/index.ts | 142 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 97 insertions(+), 45 deletions(-) diff --git a/source/index.ts b/source/index.ts index 4da25b1..b990464 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,47 +1,91 @@ import * as util from 'util'; -const toString = Object.prototype.toString; -const getObjectType = (value: any) => toString.call(value).slice(8, -1) as string; -const isOfType = (type: string) => (value: any) => typeof value === type; // tslint:disable-line:strict-type-predicates -const isObjectOfType = (type: string) => (value: any) => getObjectType(value) === type; +export const enum TypeName { + null = 'null', + boolean = 'boolean', + undefined = 'undefined', + string = 'string', + number = 'number', + symbol = 'symbol', + Function = 'Function', + Array = 'Array', + Buffer = 'Buffer', + Object = 'Object', + RegExp = 'RegExp', + Date = 'Date', + Error = 'Error', + Map = 'Map', + Set = 'Set', + WeakMap = 'WeakMap', + WeakSet = 'WeakSet', + Int8Array = 'Int8Array', + Uint8Array = 'Uint8Array', + Uint8ClampedArray = 'Uint8ClampedArray', + Int16Array = 'Int16Array', + Uint16Array = 'Uint16Array', + Int32Array = 'Int32Array', + Uint32Array = 'Uint32Array', + Float32Array = 'Float32Array', + Float64Array = 'Float64Array', + ArrayBuffer = 'ArrayBuffer', + SharedArrayBuffer = 'SharedArrayBuffer', + Promise = 'Promise' +} -function is(value: any) { // tslint:disable-line:only-arrow-functions +const toString = Object.prototype.toString; +const isOfType = (type: string) => (value: any) => typeof value === type; // tslint:disable-line:strict-type-predicates + +const getObjectType = (value: any): TypeName | null => { + const objectName = toString.call(value).slice(8, -1) as string; + + if (objectName) { + return objectName as TypeName; + } + + return null; +}; + +const isObjectOfType = (typeName: TypeName) => (value: any) => { + return getObjectType(value) === typeName; +}; + +function is(value: any): TypeName { // tslint:disable-line:only-arrow-functions if (value === null) { - return 'null'; + return TypeName.null; } if (value === true || value === false) { - return 'boolean'; + return TypeName.boolean; } const type = typeof value; if (type === 'undefined') { - return 'undefined'; + return TypeName.undefined; } if (type === 'string') { - return 'string'; + return TypeName.string; } if (type === 'number') { - return 'number'; + return TypeName.number; } if (type === 'symbol') { - return 'symbol'; + return TypeName.symbol; } if (is.function_(value)) { - return 'Function'; + return TypeName.Function; } if (Array.isArray(value)) { - return 'Array'; + return TypeName.Array; } if (Buffer.isBuffer(value)) { - return 'Buffer'; + return TypeName.Buffer; } const tagType = getObjectType(value); @@ -53,7 +97,7 @@ function is(value: any) { // tslint:disable-line:only-arrow-functions throw new TypeError('Please don\'t use object wrappers for primitive types'); } - return 'Object'; + return TypeName.Object; } namespace is { // tslint:disable-line:no-namespace @@ -80,7 +124,7 @@ namespace is { // tslint:disable-line:no-namespace export const iterable = (value: any) => !nullOrUndefined(value) && function_(value[Symbol.iterator]); export const generator = (value: any) => iterable(value) && function_(value.next) && function_(value.throw); - export const nativePromise = isObjectOfType('Promise'); + export const nativePromise = isObjectOfType(TypeName.Promise); const hasPromiseAPI = (value: any) => !null_(value) && @@ -97,26 +141,26 @@ namespace is { // tslint:disable-line:no-namespace export const asyncFunction = isFunctionOfType('AsyncFunction'); export const boundFunction = (value: any) => function_(value) && !value.hasOwnProperty('prototype'); - export const regExp = isObjectOfType('RegExp'); - export const date = isObjectOfType('Date'); - export const error = isObjectOfType('Error'); - export const map = isObjectOfType('Map'); - export const set = isObjectOfType('Set'); - export const weakMap = isObjectOfType('WeakMap'); - export const weakSet = isObjectOfType('WeakSet'); + export const regExp = isObjectOfType(TypeName.RegExp); + export const date = isObjectOfType(TypeName.Date); + export const error = isObjectOfType(TypeName.Error); + export const map = isObjectOfType(TypeName.Map); + export const set = isObjectOfType(TypeName.Set); + export const weakMap = isObjectOfType(TypeName.WeakMap); + export const weakSet = isObjectOfType(TypeName.WeakSet); - export const int8Array = isObjectOfType('Int8Array'); - export const uint8Array = isObjectOfType('Uint8Array'); - export const uint8ClampedArray = isObjectOfType('Uint8ClampedArray'); - export const int16Array = isObjectOfType('Int16Array'); - export const uint16Array = isObjectOfType('Uint16Array'); - export const int32Array = isObjectOfType('Int32Array'); - export const uint32Array = isObjectOfType('Uint32Array'); - export const float32Array = isObjectOfType('Float32Array'); - export const float64Array = isObjectOfType('Float64Array'); + export const int8Array = isObjectOfType(TypeName.Int8Array); + export const uint8Array = isObjectOfType(TypeName.Uint8Array); + export const uint8ClampedArray = isObjectOfType(TypeName.Uint8ClampedArray); + export const int16Array = isObjectOfType(TypeName.Int16Array); + export const uint16Array = isObjectOfType(TypeName.Uint16Array); + export const int32Array = isObjectOfType(TypeName.Int32Array); + export const uint32Array = isObjectOfType(TypeName.Uint32Array); + export const float32Array = isObjectOfType(TypeName.Float32Array); + export const float64Array = isObjectOfType(TypeName.Float64Array); - export const arrayBuffer = isObjectOfType('ArrayBuffer'); - export const sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); + export const arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); + export const sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); export const truthy = (value: any) => Boolean(value); export const falsy = (value: any) => !value; @@ -140,23 +184,31 @@ namespace is { // tslint:disable-line:no-namespace // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js let prototype; - return getObjectType(value) === 'Object' && + return getObjectType(value) === TypeName.Object && (prototype = Object.getPrototypeOf(value), prototype === null || // tslint:disable-line:ban-comma-operator prototype === Object.getPrototypeOf({})); }; const typedArrayTypes = new Set([ - 'Int8Array', - 'Uint8Array', - 'Uint8ClampedArray', - 'Int16Array', - 'Uint16Array', - 'Int32Array', - 'Uint32Array', - 'Float32Array', - 'Float64Array' + TypeName.Int8Array, + TypeName.Uint8Array, + TypeName.Uint8ClampedArray, + TypeName.Int16Array, + TypeName.Uint16Array, + TypeName.Int32Array, + TypeName.Uint32Array, + TypeName.Float32Array, + TypeName.Float64Array ]); - export const typedArray = (value: any) => typedArrayTypes.has(getObjectType(value)); + export const typedArray = (value: any) => { + const objectType = getObjectType(value); + + if (objectType === null) { + return false; + } + + return typedArrayTypes.has(objectType); + }; const isValidLength = (value: any) => safeInteger(value) && value > -1; export const arrayLike = (value: any) => !nullOrUndefined(value) && !function_(value) && isValidLength(value.length); From a04a04c131763c72a552cdc42d1647381d3c49f7 Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Sun, 19 Nov 2017 15:16:06 -0500 Subject: [PATCH 037/254] Add `is.nodeStream()` (#34) --- package.json | 2 ++ readme.md | 10 ++++++++++ source/index.ts | 2 ++ source/tests/test.ts | 25 ++++++++++++++++++++++++- 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 516866c..0e87f9b 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,11 @@ "devDependencies": { "@types/jsdom": "^2.0.31", "@types/node": "^8.0.47", + "@types/tempy": "^0.1.0", "ava": "*", "del-cli": "^1.1.0", "jsdom": "^9.12.0", + "tempy": "^0.2.1", "tslint": "^5.8.0", "tslint-xo": "^0.3.0", "typescript": "^2.6.1" diff --git a/readme.md b/readme.md index 0e52ee0..a0d135d 100644 --- a/readme.md +++ b/readme.md @@ -211,6 +211,16 @@ is.inRange(3, 10); Returns `true` if `value` is a DOM Element. +##### .nodeStream(value) + +Returns `true` if `value` is a Node.js [stream](https://nodejs.org/api/stream.html). + +```js +const fs = require('fs'); +is.nodeStream(fs.createReadStream('unicorn.png')); +//=> true +``` + ##### .infinite(value) Check if `value` is `Infinity` or `-Infinity`. diff --git a/source/index.ts b/source/index.ts index b990464..196a49c 100644 --- a/source/index.ts +++ b/source/index.ts @@ -238,6 +238,8 @@ namespace is { // tslint:disable-line:no-namespace export const domElement = (value: any) => object(value) && value.nodeType === NODE_TYPE_ELEMENT && string(value.nodeName) && !plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in value); + export const nodeStream = (value: any) => !nullOrUndefined(value) && isObject(value) && function_(value.pipe); + export const infinite = (value: any) => value === Infinity || value === -Infinity; const isAbsoluteMod2 = (value: number) => (rem: number) => integer(rem) && Math.abs(rem % 2) === value; diff --git a/source/tests/test.ts b/source/tests/test.ts index a24f535..340defd 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -1,4 +1,8 @@ +import * as fs from 'fs'; +import * as net from 'net'; +import * as Stream from 'stream'; import * as util from 'util'; +import * as tempy from 'tempy'; import test, {TestContext, Context} from 'ava'; import {jsdom} from 'jsdom'; import m from '..'; @@ -286,7 +290,8 @@ const types = new Map([ 'canvas', 'script' ].map(createDomElement) } - ], ['non-domElements', { + ], + ['non-domElements', { is: value => !m.domElement(value), fixtures: [ document.createTextNode('data'), @@ -297,6 +302,20 @@ const types = new Map([ document.createDocumentFragment() ] }], + ['nodeStream', { + is: m.nodeStream, + fixtures: [ + fs.createReadStream('readme.md'), + fs.createWriteStream(tempy.file()), + new net.Socket(), + new Stream.Duplex(), + new Stream.PassThrough(), + new Stream.Readable(), + new Stream.Transform(), + new Stream.Stream(), + new Stream.Writable() + ] + }], ['infinite', { is: m.infinite, fixtures: [ @@ -641,6 +660,10 @@ test('is.domElement', t => { t.false(m.domElement({nodeType: 1, nodeName: 'div'})); }); +test('is.nodeStream', t => { + testType(t, 'nodeStream'); +}); + test('is.infinite', t => { testType(t, 'infinite', ['number']); }); From 50e3fe88c722133953118053bb31c6a6f7a39c9a Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 20 Nov 2017 03:18:11 +0700 Subject: [PATCH 038/254] 0.6.0 --- package.json | 120 +++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index 0e87f9b..b900f14 100644 --- a/package.json +++ b/package.json @@ -1,62 +1,62 @@ { - "name": "@sindresorhus/is", - "version": "0.5.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" - }, - "main": "dist/index.js", - "engines": { - "node": ">=4" - }, - "scripts": { - "lint": "tslint --format stylish --project .", - "build": "tsc", - "test": "npm run lint && npm run build && ava dist/tests", - "prepublish": "npm run build && del dist/tests" - }, - "files": [ - "dist" - ], - "keywords": [ - "type", - "types", - "is", - "check", - "checking", - "validate", - "validation", - "utility", - "util", - "typeof", - "instanceof", - "object", - "assert", - "assertion", - "test", - "kind", - "primitive", - "verify", - "compare" - ], - "devDependencies": { - "@types/jsdom": "^2.0.31", - "@types/node": "^8.0.47", - "@types/tempy": "^0.1.0", - "ava": "*", - "del-cli": "^1.1.0", - "jsdom": "^9.12.0", - "tempy": "^0.2.1", - "tslint": "^5.8.0", - "tslint-xo": "^0.3.0", - "typescript": "^2.6.1" - }, - "types": "dist/index.d.ts" + "name": "@sindresorhus/is", + "version": "0.6.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" + }, + "main": "dist/index.js", + "engines": { + "node": ">=4" + }, + "scripts": { + "lint": "tslint --format stylish --project .", + "build": "tsc", + "test": "npm run lint && npm run build && ava dist/tests", + "prepublish": "npm run build && del dist/tests" + }, + "files": [ + "dist" + ], + "keywords": [ + "type", + "types", + "is", + "check", + "checking", + "validate", + "validation", + "utility", + "util", + "typeof", + "instanceof", + "object", + "assert", + "assertion", + "test", + "kind", + "primitive", + "verify", + "compare" + ], + "devDependencies": { + "@types/jsdom": "^2.0.31", + "@types/node": "^8.0.47", + "@types/tempy": "^0.1.0", + "ava": "*", + "del-cli": "^1.1.0", + "jsdom": "^9.12.0", + "tempy": "^0.2.1", + "tslint": "^5.8.0", + "tslint-xo": "^0.3.0", + "typescript": "^2.6.1" + }, + "types": "dist/index.d.ts" } From 4ce2ee9d39724e703ef49fbf0076f180f4ea56a6 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 2 Dec 2017 12:10:04 +0100 Subject: [PATCH 039/254] package.json indentation --- package.json | 120 +++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index b900f14..c8ee782 100644 --- a/package.json +++ b/package.json @@ -1,62 +1,62 @@ { - "name": "@sindresorhus/is", - "version": "0.6.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" - }, - "main": "dist/index.js", - "engines": { - "node": ">=4" - }, - "scripts": { - "lint": "tslint --format stylish --project .", - "build": "tsc", - "test": "npm run lint && npm run build && ava dist/tests", - "prepublish": "npm run build && del dist/tests" - }, - "files": [ - "dist" - ], - "keywords": [ - "type", - "types", - "is", - "check", - "checking", - "validate", - "validation", - "utility", - "util", - "typeof", - "instanceof", - "object", - "assert", - "assertion", - "test", - "kind", - "primitive", - "verify", - "compare" - ], - "devDependencies": { - "@types/jsdom": "^2.0.31", - "@types/node": "^8.0.47", - "@types/tempy": "^0.1.0", - "ava": "*", - "del-cli": "^1.1.0", - "jsdom": "^9.12.0", - "tempy": "^0.2.1", - "tslint": "^5.8.0", - "tslint-xo": "^0.3.0", - "typescript": "^2.6.1" - }, - "types": "dist/index.d.ts" + "name": "@sindresorhus/is", + "version": "0.6.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" + }, + "main": "dist/index.js", + "engines": { + "node": ">=4" + }, + "scripts": { + "lint": "tslint --format stylish --project .", + "build": "tsc", + "test": "npm run lint && npm run build && ava dist/tests", + "prepublish": "npm run build && del dist/tests" + }, + "files": [ + "dist" + ], + "keywords": [ + "type", + "types", + "is", + "check", + "checking", + "validate", + "validation", + "utility", + "util", + "typeof", + "instanceof", + "object", + "assert", + "assertion", + "test", + "kind", + "primitive", + "verify", + "compare" + ], + "devDependencies": { + "@types/jsdom": "^2.0.31", + "@types/node": "^8.0.47", + "@types/tempy": "^0.1.0", + "ava": "*", + "del-cli": "^1.1.0", + "jsdom": "^9.12.0", + "tempy": "^0.2.1", + "tslint": "^5.8.0", + "tslint-xo": "^0.3.0", + "typescript": "^2.6.1" + }, + "types": "dist/index.d.ts" } From 70b08940be4ef426c1baa89302978b8b6ace160e Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Sat, 9 Dec 2017 11:55:08 -0500 Subject: [PATCH 040/254] Add `is.directInstanceOf()` (#38) --- readme.md | 14 ++++++++++++++ source/index.ts | 4 ++++ source/tests/test.ts | 11 +++++++++++ 3 files changed, 29 insertions(+) diff --git a/readme.md b/readme.md index a0d135d..67fad06 100644 --- a/readme.md +++ b/readme.md @@ -136,6 +136,20 @@ is.boundFunction(function () {}); #### Miscellaneous +##### .directInstanceOf(value, class) + +Returns `true` if `value` is a direct instance of `class`. + +```js +is.directInstanceOf(new Error(), Error); +//=> true + +class UnicornError extends Error {}; + +is.directInstanceOf(new UnicornError(), Error); +//=> false +``` + ##### .truthy(value) Returns `true` for all values that evaluate to true in a boolean context: diff --git a/source/index.ts b/source/index.ts index 196a49c..56d7ba2 100644 --- a/source/index.ts +++ b/source/index.ts @@ -162,6 +162,10 @@ namespace is { // tslint:disable-line:no-namespace export const arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); export const sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); + // TODO: Remove `object` checks when targeting ES2015 or higher + // See `Notes`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf + export const directInstanceOf = (instance: any, klass: any) => object(instance) && object(klass) && Object.getPrototypeOf(instance) === klass.prototype; + export const truthy = (value: any) => Boolean(value); export const falsy = (value: any) => !value; diff --git a/source/tests/test.ts b/source/tests/test.ts index 340defd..130594b 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -507,6 +507,17 @@ test('is.dataView', t => { testType(t, 'arrayBuffer'); }); +test('is.directInstanceOf', t => { + const error = new Error(); + const errorSubclass = new ErrorSubclassFixture(); + + t.true(m.directInstanceOf(error, Error)); + t.true(m.directInstanceOf(errorSubclass, ErrorSubclassFixture)); + + t.false(m.directInstanceOf(error, ErrorSubclassFixture)); + t.false(m.directInstanceOf(errorSubclass, Error)); +}); + test('is.truthy', t => { t.true(m.truthy('unicorn')); t.true(m.truthy('🦄')); From 28702421bfa27bebd12416be1c2c609e65054854 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 11 Dec 2017 21:38:53 +0100 Subject: [PATCH 041/254] Add missing `dataView` method Fixes #37 --- source/index.ts | 2 ++ source/tests/test.ts | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/source/index.ts b/source/index.ts index 56d7ba2..8d82479 100644 --- a/source/index.ts +++ b/source/index.ts @@ -29,6 +29,7 @@ export const enum TypeName { Float64Array = 'Float64Array', ArrayBuffer = 'ArrayBuffer', SharedArrayBuffer = 'SharedArrayBuffer', + DataView = 'DataView', Promise = 'Promise' } @@ -161,6 +162,7 @@ namespace is { // tslint:disable-line:no-namespace export const arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); export const sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); + export const dataView = isObjectOfType(TypeName.DataView); // TODO: Remove `object` checks when targeting ES2015 or higher // See `Notes`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf diff --git a/source/tests/test.ts b/source/tests/test.ts index 130594b..cb1008c 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -245,6 +245,12 @@ const types = new Map([ new ArrayBuffer(10) ] }], + ['dataView', { + is: m.dataView, + fixtures: [ + new DataView(new ArrayBuffer(10)) + ] + }], ['nan', { is: m.nan, fixtures: [ @@ -504,7 +510,7 @@ test('is.arrayBuffer', t => { }); test('is.dataView', t => { - testType(t, 'arrayBuffer'); + testType(t, 'dataView'); }); test('is.directInstanceOf', t => { From 828a5b3088804f32fec824a2fbff0396d4cd13b3 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 11 Dec 2017 21:40:15 +0100 Subject: [PATCH 042/254] 0.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c8ee782..c4a3a24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.6.0", + "version": "0.7.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From 40fc2fd790f8d9f22f40915602c9563c9139542c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 29 Jan 2018 21:55:09 +0700 Subject: [PATCH 043/254] Remove `publishConfig` from package.json Apparently, it's only needed for the initial publish. --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index c4a3a24..94b22a9 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,6 @@ "email": "sindresorhus@gmail.com", "url": "sindresorhus.com" }, - "publishConfig": { - "access": "public" - }, "main": "dist/index.js", "engines": { "node": ">=4" From fd32b11b6d1ce6adab866464992ecb5bd126c603 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 1 Feb 2018 08:41:13 +0700 Subject: [PATCH 044/254] Bump TypeScript --- package.json | 2 +- tsconfig.json | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 94b22a9..0ae7c8b 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "tempy": "^0.2.1", "tslint": "^5.8.0", "tslint-xo": "^0.3.0", - "typescript": "^2.6.1" + "typescript": "^2.7.0" }, "types": "dist/index.d.ts" } diff --git a/tsconfig.json b/tsconfig.json index 05773b4..5621df9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,9 @@ "noFallthroughCasesInSwitch": true, "strictNullChecks": true, "strictFunctionTypes": true, - "alwaysStrict": true + "strictPropertyInitialization": true, + "alwaysStrict": true, + "esModuleInterop": true }, "exclude": [ "node_modules", From ef85a5173deec78af38608a569e0a5ce7dd5c5c9 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 28 Mar 2018 01:24:25 +0700 Subject: [PATCH 045/254] Drop support for Node.js 4 Fixes #41 Fixes #40 --- .travis.yml | 1 - package.json | 14 ++++++------ source/index.ts | 51 +++++++++++++------------------------------- source/tests/test.ts | 11 +++++----- 4 files changed, 27 insertions(+), 50 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7d69d74..e0cc348 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,3 @@ language: node_js node_js: - '8' - '6' - - '4' diff --git a/package.json b/package.json index 0ae7c8b..5cc8aba 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "main": "dist/index.js", "engines": { - "node": ">=4" + "node": ">=6" }, "scripts": { "lint": "tslint --format stylish --project .", @@ -44,16 +44,16 @@ "compare" ], "devDependencies": { - "@types/jsdom": "^2.0.31", - "@types/node": "^8.0.47", + "@types/jsdom": "^11.0.4", + "@types/node": "^9.6.0", "@types/tempy": "^0.1.0", "ava": "*", "del-cli": "^1.1.0", - "jsdom": "^9.12.0", + "jsdom": "^11.6.2", "tempy": "^0.2.1", - "tslint": "^5.8.0", - "tslint-xo": "^0.3.0", - "typescript": "^2.7.0" + "tslint": "^5.9.1", + "tslint-xo": "^0.7.0", + "typescript": "^2.8.1" }, "types": "dist/index.d.ts" } diff --git a/source/index.ts b/source/index.ts index 8d82479..540dd06 100644 --- a/source/index.ts +++ b/source/index.ts @@ -8,6 +8,8 @@ export const enum TypeName { number = 'number', symbol = 'symbol', Function = 'Function', + GeneratorFunction = 'GeneratorFunction', + AsyncFunction = 'AsyncFunction', Array = 'Array', Buffer = 'Buffer', Object = 'Object', @@ -46,9 +48,7 @@ const getObjectType = (value: any): TypeName | null => { return null; }; -const isObjectOfType = (typeName: TypeName) => (value: any) => { - return getObjectType(value) === typeName; -}; +const isObjectOfType = (type: TypeName) => (value: any) => getObjectType(value) === type; function is(value: any): TypeName { // tslint:disable-line:only-arrow-functions if (value === null) { @@ -110,12 +110,10 @@ namespace is { // tslint:disable-line:no-namespace export const number = isOfType('number'); export const function_ = isOfType('function'); export const null_ = (value: any) => value === null; - export const class_ = (value: any) => function_(value) && value.toString().startsWith('class '); export const boolean = (value: any) => value === true || value === false; - // tslint:enable:variable-name - export const symbol = isOfType('symbol'); + // tslint:enable:variable-name export const array = Array.isArray; export const buffer = Buffer.isBuffer; @@ -135,11 +133,9 @@ namespace is { // tslint:disable-line:no-namespace export const promise = (value: any) => nativePromise(value) || hasPromiseAPI(value); - // TODO: Change to use `isObjectOfType` once Node.js 6 or higher is targeted - const isFunctionOfType = (type: string) => (value: any) => function_(value) && function_(value.constructor) && value.constructor.name === type; - - export const generatorFunction = isFunctionOfType('GeneratorFunction'); - export const asyncFunction = isFunctionOfType('AsyncFunction'); + const isFunctionOfType = (type: TypeName) => (value: any) => isObjectOfType(type)(value); + export const generatorFunction = isFunctionOfType(TypeName.GeneratorFunction); + export const asyncFunction = isFunctionOfType(TypeName.AsyncFunction); export const boundFunction = (value: any) => function_(value) && !value.hasOwnProperty('prototype'); export const regExp = isObjectOfType(TypeName.RegExp); @@ -164,9 +160,7 @@ namespace is { // tslint:disable-line:no-namespace export const sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); export const dataView = isObjectOfType(TypeName.DataView); - // TODO: Remove `object` checks when targeting ES2015 or higher - // See `Notes`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf - export const directInstanceOf = (instance: any, klass: any) => object(instance) && object(klass) && Object.getPrototypeOf(instance) === klass.prototype; + export const directInstanceOf = (instance: any, klass: any) => Object.getPrototypeOf(instance) === klass.prototype; export const truthy = (value: any) => Boolean(value); export const falsy = (value: any) => !value; @@ -225,8 +219,7 @@ namespace is { // tslint:disable-line:no-namespace } if (array(range) && range.length === 2) { - // TODO: Use spread operator here when targeting Node.js 6 or higher - return value >= Math.min.apply(null, range) && value <= Math.max.apply(null, range); + return value >= Math.min(...range) && value <= Math.max(...range); } throw new TypeError(`Invalid range: ${util.inspect(range)}`); @@ -260,13 +253,8 @@ namespace is { // tslint:disable-line:no-namespace export const empty = (value: any) => falsy(value) || isEmptyStringOrArray(value) || isEmptyObject(value) || isEmptyMapOrSet(value); export const emptyOrWhitespace = (value: any) => empty(value) || isWhiteSpaceString(value); - type ArrayMethod = (fn: (value: any, index: number, arr: any[]) => boolean, thisArg?: any) => boolean; - const predicateOnArray = (method: ArrayMethod, predicate: any, args: IArguments) => { - // `args` is the calling function's "arguments object". - // We have to do it this way to keep node v4 support. - // So here we convert it to an array and slice off the first item. - const values = Array.prototype.slice.call(args, 1); - + type ArrayMethod = (fn: (value: any, index: number, array: any[]) => boolean, thisArg?: any) => boolean; + const predicateOnArray = (method: ArrayMethod, predicate: any, values: any[]) => { if (function_(predicate) === false) { throw new TypeError(`Invalid predicate: ${util.inspect(predicate)}`); } @@ -278,19 +266,10 @@ namespace is { // tslint:disable-line:no-namespace return method.call(values, predicate); }; - // We can't use rest parameters in node v4 due to the lack of the spread operator. - // Therefore We have to use anonymous functions for the any() and all() methods - // tslint:disable:only-arrow-functions no-function-expression - export function any(...predicate: any[]): any; // tslint:disable-line:variable-name - export function any(predicate: any) { - return predicateOnArray(Array.prototype.some, predicate, arguments); - } - - export function all(...predicate: any[]): any; - export function all(predicate: any) { - return predicateOnArray(Array.prototype.every, predicate, arguments); - } - // tslint:enable:only-arrow-functions no-function-expression + // tslint:disable variable-name + export const any = (predicate: any, ...values: any[]) => predicateOnArray(Array.prototype.some, predicate, values); + export const all = (predicate: any, ...values: any[]) => predicateOnArray(Array.prototype.every, predicate, values); + // tslint:enable variable-name } // Some few keywords are reserved, but we'll populate them for Node.js users diff --git a/source/tests/test.ts b/source/tests/test.ts index cb1008c..64edaed 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -4,17 +4,16 @@ import * as Stream from 'stream'; import * as util from 'util'; import * as tempy from 'tempy'; import test, {TestContext, Context} from 'ava'; -import {jsdom} from 'jsdom'; +import {JSDOM} from 'jsdom'; import m from '..'; const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; -// Currently not working. See https://github.com/Microsoft/TypeScript/issues/15202 -// `class PromiseSubclassFixture extends Promise {}` - +class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} -const document = jsdom(); +const {window} = new JSDOM(); +const {document} = window; const createDomElement = (el: string) => document.createElement(el); interface Test { @@ -122,7 +121,7 @@ const types = new Map([ is: m.nativePromise, fixtures: [ Promise.resolve(), - // PromiseSubclassFixture.resolve() + PromiseSubclassFixture.resolve() ] }], ['promise', { From 64ced4d33c18fcf26ed9a9f9c73950737fc180d3 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 29 Mar 2018 11:56:13 +0700 Subject: [PATCH 046/254] Simplify `isFunctionOfType` https://github.com/sindresorhus/is/commit/ef85a5173deec78af38608a569e0a5ce7dd5c5c9#r28310156 --- source/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.ts b/source/index.ts index 540dd06..39bb591 100644 --- a/source/index.ts +++ b/source/index.ts @@ -133,7 +133,7 @@ namespace is { // tslint:disable-line:no-namespace export const promise = (value: any) => nativePromise(value) || hasPromiseAPI(value); - const isFunctionOfType = (type: TypeName) => (value: any) => isObjectOfType(type)(value); + const isFunctionOfType = (type: TypeName) => isObjectOfType(type); export const generatorFunction = isFunctionOfType(TypeName.GeneratorFunction); export const asyncFunction = isFunctionOfType(TypeName.AsyncFunction); export const boundFunction = (value: any) => function_(value) && !value.hasOwnProperty('prototype'); From 5670ed7a97f9fd649c3721c9c6254eb4cdae7edd Mon Sep 17 00:00:00 2001 From: Amit Merchant Date: Fri, 30 Mar 2018 09:25:09 +0530 Subject: [PATCH 047/254] Minor readme improvement (#44) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 67fad06..d761485 100644 --- a/readme.md +++ b/readme.md @@ -46,7 +46,7 @@ Example: - `'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')`. +Note: It will throw an error if you try to feed it object-wrapped primitives, as that's a bad practice. For example `new String('foo')`. ### is.{method} From 8894dbec17c0eb235753a0a4f42cf3df9c7fcc88 Mon Sep 17 00:00:00 2001 From: Sam Verschueren Date: Tue, 3 Apr 2018 19:05:59 +0200 Subject: [PATCH 048/254] Add type guards (#43) --- readme.md | 26 ++++++++++++++ source/index.ts | 95 ++++++++++++++++++++++++++----------------------- tsconfig.json | 5 +++ 3 files changed, 82 insertions(+), 44 deletions(-) diff --git a/readme.md b/readme.md index d761485..3e6e33f 100644 --- a/readme.md +++ b/readme.md @@ -27,6 +27,32 @@ is.number(6); //=> true ``` +When using `is` together with TypeScript, [type guards](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types) are being used to infer the correct type inside if-else statements. + +```ts +import is from '@sindresorhus/is'; + +const padLeft = (value: string, padding: string | number) => { + if (is.number(padding)) { + // `padding` is typed as `number` + return Array(padding + 1).join(' ') + value; + } + + if (is.string(padding)) { + // `padding` is typed as `string` + return padding + value; + } + + throw new TypeError(`Expected 'padding' to be of type 'string' or 'number', got '${is(padding)}'.`); +} + +padLeft('🦄', 3); +//=> ' 🦄' + +padLeft('🦄', '🌈'); +//=> '🌈🦄' +``` + ## API diff --git a/source/index.ts b/source/index.ts index 39bb591..e86d89f 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,5 +1,12 @@ import * as util from 'util'; +type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; +type Primitive = null | undefined | string | number | boolean | Symbol; + +export interface ArrayLike { + length: number; +} + export const enum TypeName { null = 'null', boolean = 'boolean', @@ -36,7 +43,7 @@ export const enum TypeName { } const toString = Object.prototype.toString; -const isOfType = (type: string) => (value: any) => typeof value === type; // tslint:disable-line:strict-type-predicates +const isOfType = (type: string) => (value: any): value is T => typeof value === type; // tslint:disable-line:strict-type-predicates const getObjectType = (value: any): TypeName | null => { const objectName = toString.call(value).slice(8, -1) as string; @@ -48,7 +55,7 @@ const getObjectType = (value: any): TypeName | null => { return null; }; -const isObjectOfType = (type: TypeName) => (value: any) => getObjectType(value) === type; +const isObjectOfType = (type: TypeName) => (value: any): value is T => getObjectType(value) === type; function is(value: any): TypeName { // tslint:disable-line:only-arrow-functions if (value === null) { @@ -105,60 +112,60 @@ namespace is { // tslint:disable-line:no-namespace const isObject = (value: any) => typeof value === 'object'; // tslint:disable:variable-name - export const undefined = isOfType('undefined'); - export const string = isOfType('string'); - export const number = isOfType('number'); - export const function_ = isOfType('function'); - export const null_ = (value: any) => value === null; + export const undefined = isOfType('undefined'); + export const string = isOfType('string'); + export const number = isOfType('number'); + export const function_ = isOfType('function'); + export const null_ = (value: any): value is null => value === null; export const class_ = (value: any) => function_(value) && value.toString().startsWith('class '); - export const boolean = (value: any) => value === true || value === false; - export const symbol = isOfType('symbol'); + export const boolean = (value: any): value is boolean => value === true || value === false; + export const symbol = isOfType('symbol'); // tslint:enable:variable-name export const array = Array.isArray; export const buffer = Buffer.isBuffer; - export const nullOrUndefined = (value: any) => null_(value) || undefined(value); + export const nullOrUndefined = (value: any): value is null | undefined => null_(value) || undefined(value); export const object = (value: any) => !nullOrUndefined(value) && (function_(value) || isObject(value)); - export const iterable = (value: any) => !nullOrUndefined(value) && function_(value[Symbol.iterator]); - export const generator = (value: any) => iterable(value) && function_(value.next) && function_(value.throw); + export const iterable = (value: any): value is Iterator => !nullOrUndefined(value) && function_(value[Symbol.iterator]); + export const generator = (value: any): value is Generator => iterable(value) && function_(value.next) && function_(value.throw); - export const nativePromise = isObjectOfType(TypeName.Promise); + export const nativePromise = isObjectOfType>(TypeName.Promise); - const hasPromiseAPI = (value: any) => + const hasPromiseAPI = (value: any): value is Promise => !null_(value) && isObject(value) && function_(value.then) && function_(value.catch); - export const promise = (value: any) => nativePromise(value) || hasPromiseAPI(value); + export const promise = (value: any): value is Promise => nativePromise(value) || hasPromiseAPI(value); - const isFunctionOfType = (type: TypeName) => isObjectOfType(type); - export const generatorFunction = isFunctionOfType(TypeName.GeneratorFunction); - export const asyncFunction = isFunctionOfType(TypeName.AsyncFunction); - export const boundFunction = (value: any) => function_(value) && !value.hasOwnProperty('prototype'); + const isFunctionOfType = (type: TypeName) => isObjectOfType(type); + export const generatorFunction = isFunctionOfType(TypeName.GeneratorFunction); + export const asyncFunction = isFunctionOfType(TypeName.AsyncFunction); + export const boundFunction = (value: any): value is Function => function_(value) && !value.hasOwnProperty('prototype'); - export const regExp = isObjectOfType(TypeName.RegExp); - export const date = isObjectOfType(TypeName.Date); - export const error = isObjectOfType(TypeName.Error); - export const map = isObjectOfType(TypeName.Map); - export const set = isObjectOfType(TypeName.Set); - export const weakMap = isObjectOfType(TypeName.WeakMap); - export const weakSet = isObjectOfType(TypeName.WeakSet); + export const regExp = isObjectOfType(TypeName.RegExp); + export const date = isObjectOfType(TypeName.Date); + export const error = isObjectOfType(TypeName.Error); + export const map = isObjectOfType>(TypeName.Map); + export const set = isObjectOfType>(TypeName.Set); + export const weakMap = isObjectOfType>(TypeName.WeakMap); + export const weakSet = isObjectOfType>(TypeName.WeakSet); - export const int8Array = isObjectOfType(TypeName.Int8Array); - export const uint8Array = isObjectOfType(TypeName.Uint8Array); - export const uint8ClampedArray = isObjectOfType(TypeName.Uint8ClampedArray); - export const int16Array = isObjectOfType(TypeName.Int16Array); - export const uint16Array = isObjectOfType(TypeName.Uint16Array); - export const int32Array = isObjectOfType(TypeName.Int32Array); - export const uint32Array = isObjectOfType(TypeName.Uint32Array); - export const float32Array = isObjectOfType(TypeName.Float32Array); - export const float64Array = isObjectOfType(TypeName.Float64Array); + export const int8Array = isObjectOfType(TypeName.Int8Array); + export const uint8Array = isObjectOfType(TypeName.Uint8Array); + export const uint8ClampedArray = isObjectOfType(TypeName.Uint8ClampedArray); + export const int16Array = isObjectOfType(TypeName.Int16Array); + export const uint16Array = isObjectOfType(TypeName.Uint16Array); + export const int32Array = isObjectOfType(TypeName.Int32Array); + export const uint32Array = isObjectOfType(TypeName.Uint32Array); + export const float32Array = isObjectOfType(TypeName.Float32Array); + export const float64Array = isObjectOfType(TypeName.Float64Array); - export const arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); - export const sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); - export const dataView = isObjectOfType(TypeName.DataView); + export const arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); + export const sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); + export const dataView = isObjectOfType(TypeName.DataView); export const directInstanceOf = (instance: any, klass: any) => Object.getPrototypeOf(instance) === klass.prototype; @@ -175,10 +182,10 @@ namespace is { // tslint:disable-line:no-namespace 'symbol' ]); - export const primitive = (value: any) => null_(value) || primitiveTypes.has(typeof value); + export const primitive = (value: any): value is Primitive => null_(value) || primitiveTypes.has(typeof value); - export const integer = (value: any) => Number.isInteger(value); - export const safeInteger = (value: any) => Number.isSafeInteger(value); + export const integer = (value: any): value is number => Number.isInteger(value); + export const safeInteger = (value: any): value is number => Number.isSafeInteger(value); export const plainObject = (value: any) => { // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js @@ -200,7 +207,7 @@ namespace is { // tslint:disable-line:no-namespace TypeName.Float32Array, TypeName.Float64Array ]); - export const typedArray = (value: any) => { + export const typedArray = (value: any): value is TypedArray => { const objectType = getObjectType(value); if (objectType === null) { @@ -211,11 +218,11 @@ namespace is { // tslint:disable-line:no-namespace }; const isValidLength = (value: any) => safeInteger(value) && value > -1; - export const arrayLike = (value: any) => !nullOrUndefined(value) && !function_(value) && isValidLength(value.length); + export const arrayLike = (value: any): value is ArrayLike => !nullOrUndefined(value) && !function_(value) && isValidLength(value.length); export const inRange = (value: number, range: number | number[]) => { if (number(range)) { - return value >= Math.min(0, range as number) && value <= Math.max(range as number, 0); + return value >= Math.min(0, range) && value <= Math.max(range, 0); } if (array(range) && range.length === 2) { diff --git a/tsconfig.json b/tsconfig.json index 5621df9..fd0ae77 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,11 @@ "declaration": true, "pretty": true, "newLine": "lf", + "lib": [ + "dom", + "es2015", + "es2017.sharedmemory" + ], "stripInternal": true, "noImplicitAny": true, "noImplicitReturns": true, From 7ae4b44ca281da26703793c52be20c4982a352f9 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 4 Apr 2018 00:10:07 +0700 Subject: [PATCH 049/254] 0.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5cc8aba..00b01ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.7.0", + "version": "0.8.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From 38df0cfb24d1383a86b9f3d6c37c0d0abc3e5108 Mon Sep 17 00:00:00 2001 From: Sam Verschueren Date: Wed, 4 Apr 2018 08:08:29 +0200 Subject: [PATCH 050/254] Remove moot tslint disable comment (#46) --- source/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.ts b/source/index.ts index e86d89f..64f5600 100644 --- a/source/index.ts +++ b/source/index.ts @@ -43,7 +43,7 @@ export const enum TypeName { } const toString = Object.prototype.toString; -const isOfType = (type: string) => (value: any): value is T => typeof value === type; // tslint:disable-line:strict-type-predicates +const isOfType = (type: string) => (value: any): value is T => typeof value === type; const getObjectType = (value: any): TypeName | null => { const objectName = toString.call(value).slice(8, -1) as string; From 55c00956f935244724d3cb3d243691a1cfc32e17 Mon Sep 17 00:00:00 2001 From: Sam Verschueren Date: Thu, 3 May 2018 05:22:32 +0200 Subject: [PATCH 051/254] Add `is.observable` (#50) --- package.json | 8 +++++++- readme.md | 12 +++++++++++- source/index.ts | 9 ++++++++- source/tests/test.ts | 10 ++++++++++ tsconfig.json | 1 + 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 00b01ba..2e112ef 100644 --- a/package.json +++ b/package.json @@ -43,17 +43,23 @@ "verify", "compare" ], + "dependencies": { + "symbol-observable": "^1.2.0" + }, "devDependencies": { "@types/jsdom": "^11.0.4", "@types/node": "^9.6.0", "@types/tempy": "^0.1.0", + "@types/zen-observable": "^0.5.3", "ava": "*", "del-cli": "^1.1.0", "jsdom": "^11.6.2", + "rxjs": "^6.0.0", "tempy": "^0.2.1", "tslint": "^5.9.1", "tslint-xo": "^0.7.0", - "typescript": "^2.8.1" + "typescript": "^2.8.1", + "zen-observable": "^0.8.8" }, "types": "dist/index.d.ts" } diff --git a/readme.md b/readme.md index 3e6e33f..ac7876e 100644 --- a/readme.md +++ b/readme.md @@ -261,6 +261,16 @@ is.nodeStream(fs.createReadStream('unicorn.png')); //=> true ``` +##### .observable(value) + +Returns `true` if `value` is an `Observable`. + +```js +const {Observable} = require('rxjs'); +is.observable(new Observable()); +//=> true +``` + ##### .infinite(value) Check if `value` is `Infinity` or `-Infinity`. @@ -281,7 +291,6 @@ Returns `true` if `value` is falsy or an empty string, array, object, map, or se Returns `true` if `is.empty(value)` or a string that is all whitespace. - ##### .any(predicate, ...values) Returns `true` if **any** of the input `values` returns true in the `predicate`: @@ -306,6 +315,7 @@ is.all(is.string, '🦄', [], 'unicorns'); //=> false ``` + ## FAQ ### Why yet another type checking module? diff --git a/source/index.ts b/source/index.ts index 64f5600..18ce4e8 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,4 +1,5 @@ import * as util from 'util'; +import symbolObservable from 'symbol-observable'; type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; type Primitive = null | undefined | string | number | boolean | Symbol; @@ -17,6 +18,7 @@ export const enum TypeName { Function = 'Function', GeneratorFunction = 'GeneratorFunction', AsyncFunction = 'AsyncFunction', + Observable = 'Observable', Array = 'Array', Buffer = 'Buffer', Object = 'Object', @@ -88,6 +90,10 @@ function is(value: any): TypeName { // tslint:disable-line:only-arrow-functions return TypeName.Function; } + if (is.observable(value)) { + return TypeName.Observable; + } + if (Array.isArray(value)) { return TypeName.Array; } @@ -244,7 +250,8 @@ namespace is { // tslint:disable-line:no-namespace export const domElement = (value: any) => object(value) && value.nodeType === NODE_TYPE_ELEMENT && string(value.nodeName) && !plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in value); - export const nodeStream = (value: any) => !nullOrUndefined(value) && isObject(value) && function_(value.pipe); + export const observable = (value: any) => Boolean(value && value[symbolObservable] && value === value[symbolObservable]()); + export const nodeStream = (value: any) => !nullOrUndefined(value) && isObject(value) && function_(value.pipe) && !observable(value); export const infinite = (value: any) => value === Infinity || value === -Infinity; diff --git a/source/tests/test.ts b/source/tests/test.ts index 64edaed..737fd2e 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -5,6 +5,8 @@ import * as util from 'util'; import * as tempy from 'tempy'; import test, {TestContext, Context} from 'ava'; import {JSDOM} from 'jsdom'; +import {Subject, Observable} from 'rxjs'; +import ZenObservable from 'zen-observable'; import m from '..'; const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; @@ -321,6 +323,14 @@ const types = new Map([ new Stream.Writable() ] }], + ['observable', { + is: m.observable, + fixtures: [ + new Observable(), + new Subject(), + new ZenObservable(() => {}) // tslint:disable-line:no-empty + ] + }], ['infinite', { is: m.infinite, fixtures: [ diff --git a/tsconfig.json b/tsconfig.json index fd0ae77..161f88e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "module": "commonjs", "moduleResolution": "node", "declaration": true, + "skipLibCheck": true, "pretty": true, "newLine": "lf", "lib": [ From 1df2ff09ce8f6f91a7ad7a7c94e379bb92ba5954 Mon Sep 17 00:00:00 2001 From: Lukas Tetzlaff Date: Fri, 4 May 2018 07:29:21 +0200 Subject: [PATCH 052/254] Improve TS code by adding generics and removing `any` where applicable (#49) --- source/index.ts | 78 +++++++++++++++++++++++--------------------- source/tests/test.ts | 2 +- tsconfig.json | 7 +--- 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/source/index.ts b/source/index.ts index 18ce4e8..ddb4c6f 100644 --- a/source/index.ts +++ b/source/index.ts @@ -8,6 +8,13 @@ export interface ArrayLike { length: number; } +export interface Class { + new(...args: any[]): T; +} + +type DomElement = object & { nodeType: 1; nodeName: string }; +type NodeStream = object & { pipe: Function }; + export const enum TypeName { null = 'null', boolean = 'boolean', @@ -60,30 +67,25 @@ const getObjectType = (value: any): TypeName | null => { const isObjectOfType = (type: TypeName) => (value: any): value is T => getObjectType(value) === type; function is(value: any): TypeName { // tslint:disable-line:only-arrow-functions - if (value === null) { - return TypeName.null; + switch (value) { + case null: + return TypeName.null; + case true: + case false: + return TypeName.boolean; + default: } - if (value === true || value === false) { - return TypeName.boolean; - } - - const type = typeof value; - - if (type === 'undefined') { - return TypeName.undefined; - } - - if (type === 'string') { - return TypeName.string; - } - - if (type === 'number') { - return TypeName.number; - } - - if (type === 'symbol') { - return TypeName.symbol; + switch (typeof value) { + case 'undefined': + return TypeName.undefined; + case 'string': + return TypeName.string; + case 'number': + return TypeName.number; + case 'symbol': + return TypeName.symbol; + default: } if (is.function_(value)) { @@ -115,7 +117,7 @@ function is(value: any): TypeName { // tslint:disable-line:only-arrow-functions } namespace is { // tslint:disable-line:no-namespace - const isObject = (value: any) => typeof value === 'object'; + const isObject = (value: any): value is object => typeof value === 'object'; // tslint:disable:variable-name export const undefined = isOfType('undefined'); @@ -123,7 +125,7 @@ namespace is { // tslint:disable-line:no-namespace export const number = isOfType('number'); export const function_ = isOfType('function'); export const null_ = (value: any): value is null => value === null; - export const class_ = (value: any) => function_(value) && value.toString().startsWith('class '); + export const class_ = (value: any): value is Class => function_(value) && value.toString().startsWith('class '); export const boolean = (value: any): value is boolean => value === true || value === false; export const symbol = isOfType('symbol'); // tslint:enable:variable-name @@ -132,32 +134,32 @@ namespace is { // tslint:disable-line:no-namespace export const buffer = Buffer.isBuffer; export const nullOrUndefined = (value: any): value is null | undefined => null_(value) || undefined(value); - export const object = (value: any) => !nullOrUndefined(value) && (function_(value) || isObject(value)); - export const iterable = (value: any): value is Iterator => !nullOrUndefined(value) && function_(value[Symbol.iterator]); + export const object = (value: any): value is object => !nullOrUndefined(value) && (function_(value) || isObject(value)); + export const iterable = (value: any): value is IterableIterator => !nullOrUndefined(value) && function_(value[Symbol.iterator]); export const generator = (value: any): value is Generator => iterable(value) && function_(value.next) && function_(value.throw); - export const nativePromise = isObjectOfType>(TypeName.Promise); + export const nativePromise = (value: any): value is Promise => + isObjectOfType>(TypeName.Promise)(value); const hasPromiseAPI = (value: any): value is Promise => !null_(value) && - isObject(value) && + isObject(value) as any && function_(value.then) && function_(value.catch); export const promise = (value: any): value is Promise => nativePromise(value) || hasPromiseAPI(value); - const isFunctionOfType = (type: TypeName) => isObjectOfType(type); - export const generatorFunction = isFunctionOfType(TypeName.GeneratorFunction); - export const asyncFunction = isFunctionOfType(TypeName.AsyncFunction); + export const generatorFunction = isObjectOfType(TypeName.GeneratorFunction); + export const asyncFunction = isObjectOfType(TypeName.AsyncFunction); export const boundFunction = (value: any): value is Function => function_(value) && !value.hasOwnProperty('prototype'); export const regExp = isObjectOfType(TypeName.RegExp); export const date = isObjectOfType(TypeName.Date); export const error = isObjectOfType(TypeName.Error); - export const map = isObjectOfType>(TypeName.Map); - export const set = isObjectOfType>(TypeName.Set); - export const weakMap = isObjectOfType>(TypeName.WeakMap); - export const weakSet = isObjectOfType>(TypeName.WeakSet); + export const map = (value: any): value is Map => isObjectOfType>(TypeName.Map)(value); + export const set = (value: any): value is Set => isObjectOfType>(TypeName.Set)(value); + export const weakMap = (value: any): value is WeakMap => isObjectOfType>(TypeName.WeakMap)(value); + export const weakSet = (value: any): value is WeakSet => isObjectOfType>(TypeName.WeakSet)(value); export const int8Array = isObjectOfType(TypeName.Int8Array); export const uint8Array = isObjectOfType(TypeName.Uint8Array); @@ -173,7 +175,7 @@ namespace is { // tslint:disable-line:no-namespace export const sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); export const dataView = isObjectOfType(TypeName.DataView); - export const directInstanceOf = (instance: any, klass: any) => Object.getPrototypeOf(instance) === klass.prototype; + export const directInstanceOf = (instance: any, klass: Class): instance is T => Object.getPrototypeOf(instance) === klass.prototype; export const truthy = (value: any) => Boolean(value); export const falsy = (value: any) => !value; @@ -247,11 +249,11 @@ namespace is { // tslint:disable-line:no-namespace 'nodeValue' ]; - export const domElement = (value: any) => object(value) && value.nodeType === NODE_TYPE_ELEMENT && string(value.nodeName) && + export const domElement = (value: any): value is DomElement => object(value) as any && value.nodeType === NODE_TYPE_ELEMENT && string(value.nodeName) && !plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in value); export const observable = (value: any) => Boolean(value && value[symbolObservable] && value === value[symbolObservable]()); - export const nodeStream = (value: any) => !nullOrUndefined(value) && isObject(value) && function_(value.pipe) && !observable(value); + export const nodeStream = (value: any): value is NodeStream => !nullOrUndefined(value) && isObject(value) as any && function_(value.pipe) && !observable(value); export const infinite = (value: any) => value === Infinity || value === -Infinity; diff --git a/source/tests/test.ts b/source/tests/test.ts index 737fd2e..c86ed75 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -332,7 +332,7 @@ const types = new Map([ ] }], ['infinite', { - is: m.infinite, + is: m.infinite, fixtures: [ Infinity, -Infinity diff --git a/tsconfig.json b/tsconfig.json index 161f88e..fe251cc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,16 +14,11 @@ "es2017.sharedmemory" ], "stripInternal": true, - "noImplicitAny": true, + "strict": true, "noImplicitReturns": true, - "noImplicitThis": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictPropertyInitialization": true, - "alwaysStrict": true, "esModuleInterop": true }, "exclude": [ From 37f6cc3fe3df89e3b77aa9bb2cb87957fa9759ce Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 4 May 2018 12:30:58 +0700 Subject: [PATCH 053/254] 0.9.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2e112ef..93c044a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.8.0", + "version": "0.9.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From db25d554dc8dcf5565b0c6a32c4df660d17948fd Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 19 May 2018 15:42:12 +0700 Subject: [PATCH 054/254] TS config tweaks --- source/index.ts | 2 +- source/tests/test.ts | 10 +++++----- tsconfig.json | 21 +++++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/source/index.ts b/source/index.ts index ddb4c6f..ba5c0f7 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,4 +1,4 @@ -import * as util from 'util'; +import util from 'util'; import symbolObservable from 'symbol-observable'; type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; diff --git a/source/tests/test.ts b/source/tests/test.ts index c86ed75..ae99c2c 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -1,8 +1,8 @@ -import * as fs from 'fs'; -import * as net from 'net'; -import * as Stream from 'stream'; -import * as util from 'util'; -import * as tempy from 'tempy'; +import fs from 'fs'; +import net from 'net'; +import Stream from 'stream'; +import util from 'util'; +import tempy from 'tempy'; import test, {TestContext, Context} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; diff --git a/tsconfig.json b/tsconfig.json index fe251cc..2a1baca 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,25 +1,30 @@ { "compilerOptions": { "outDir": "dist", - "target": "es2015", - "module": "commonjs", - "moduleResolution": "node", - "declaration": true, - "skipLibCheck": true, - "pretty": true, - "newLine": "lf", + "target": "es2016", "lib": [ "dom", "es2015", + "es2016", "es2017.sharedmemory" ], + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "sourceMap": true, + "declaration": true, + "pretty": true, + "newLine": "lf", "stripInternal": true, "strict": true, "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "esModuleInterop": true + "noEmitOnError": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true }, "exclude": [ "node_modules", From 0988832ef7b8cd8bc5a91b64e00e2f6f8eddc5f8 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 25 May 2018 10:40:55 +0700 Subject: [PATCH 055/254] Meta tweaks --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 93c044a..b632a2a 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,9 @@ }, "scripts": { "lint": "tslint --format stylish --project .", - "build": "tsc", + "build": "del dist && tsc", "test": "npm run lint && npm run build && ava dist/tests", - "prepublish": "npm run build && del dist/tests" + "prepublish": "npm run build" }, "files": [ "dist" @@ -48,7 +48,7 @@ }, "devDependencies": { "@types/jsdom": "^11.0.4", - "@types/node": "^9.6.0", + "@types/node": "^10.1.2", "@types/tempy": "^0.1.0", "@types/zen-observable": "^0.5.3", "ava": "*", @@ -57,7 +57,7 @@ "rxjs": "^6.0.0", "tempy": "^0.2.1", "tslint": "^5.9.1", - "tslint-xo": "^0.7.0", + "tslint-xo": "^0.8.0", "typescript": "^2.8.1", "zen-observable": "^0.8.8" }, From fe754b55280b3564efa2cfc89f1cdb69321ba3a7 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 1 Jun 2018 21:28:26 +0700 Subject: [PATCH 056/254] Update dev dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b632a2a..87fa3f7 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "tempy": "^0.2.1", "tslint": "^5.9.1", "tslint-xo": "^0.8.0", - "typescript": "^2.8.1", + "typescript": "^2.9.0", "zen-observable": "^0.8.8" }, "types": "dist/index.d.ts" From ff268de3d0d001ba2dac402143961fba147d6aa2 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 30 Jun 2018 02:07:02 +0700 Subject: [PATCH 057/254] Add related module to the readme --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index ac7876e..66f83b1 100644 --- a/readme.md +++ b/readme.md @@ -336,6 +336,7 @@ The most common mistakes I noticed in these modules was using `instanceof` for t ## Related +- [ow](https://github.com/sindresorhus/ow) - Function argument validation for humans - [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 From d11b7eaaa1ca6a8565196a8bf07284b6435f4e50 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 5 Jul 2018 23:33:34 +0700 Subject: [PATCH 058/254] 0.10.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 87fa3f7..37e73e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.9.0", + "version": "0.10.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From c84c2cbecae918b08a863955e03633c3008248df Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 5 Jul 2018 23:37:24 +0700 Subject: [PATCH 059/254] Don't publish the compiled test directory --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 37e73e9..3a9c07d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "lint": "tslint --format stylish --project .", "build": "del dist && tsc", "test": "npm run lint && npm run build && ava dist/tests", - "prepublish": "npm run build" + "prepublish": "npm run build && del dist/tests" }, "files": [ "dist" From c8736c2972033c1f244d25e0b436b7656edc3843 Mon Sep 17 00:00:00 2001 From: Artur Androsovych Date: Mon, 9 Jul 2018 20:35:16 +0300 Subject: [PATCH 060/254] Add `is.asyncIterable` (#59) --- readme.md | 1 + source/index.ts | 1 + source/tests/test.ts | 16 ++++++++++++++++ tsconfig.json | 3 ++- 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 66f83b1..26031e8 100644 --- a/readme.md +++ b/readme.md @@ -209,6 +209,7 @@ Returns `true` if `value` is a [safe integer](https://developer.mozilla.org/en-U An object is plain if it's created by either `{}`, `new Object()`, or `Object.create(null)`. ##### .iterable(value) +##### .asyncIterable(value) ##### .class(value) Returns `true` for instances created by a ES2015 class. diff --git a/source/index.ts b/source/index.ts index ba5c0f7..868ec18 100644 --- a/source/index.ts +++ b/source/index.ts @@ -136,6 +136,7 @@ namespace is { // tslint:disable-line:no-namespace export const nullOrUndefined = (value: any): value is null | undefined => null_(value) || undefined(value); export const object = (value: any): value is object => !nullOrUndefined(value) && (function_(value) || isObject(value)); export const iterable = (value: any): value is IterableIterator => !nullOrUndefined(value) && function_(value[Symbol.iterator]); + export const asyncIterable = (value: any): value is AsyncIterableIterator => !nullOrUndefined(value) && function_(value[Symbol.asyncIterator]); export const generator = (value: any): value is Generator => iterable(value) && function_(value.next) && function_(value.throw); export const nativePromise = (value: any): value is Promise => diff --git a/source/tests/test.ts b/source/tests/test.ts index ae99c2c..a2d56ad 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -10,6 +10,7 @@ import ZenObservable from 'zen-observable'; import m from '..'; const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; +const isNode10orHigher = Number(process.versions.node.split('.')[0]) >= 10; class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} @@ -603,6 +604,21 @@ test('is.iterable', t => { t.false(m.iterable({})); }); +if (isNode10orHigher) { + test('is.asyncIterable', t => { + t.true(m.asyncIterable({ + [Symbol.asyncIterator]: () => {} // tslint:disable-line:no-empty + })); + + t.false(m.asyncIterable(null)); + t.false(m.asyncIterable(undefined)); + t.false(m.asyncIterable(0)); + t.false(m.asyncIterable(NaN)); + t.false(m.asyncIterable(Infinity)); + t.false(m.asyncIterable({})); + }); +} + test('is.class', t => { class Foo {} // tslint:disable-line const classDeclarations = [ diff --git a/tsconfig.json b/tsconfig.json index 2a1baca..f7d6fd1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "dom", "es2015", "es2016", - "es2017.sharedmemory" + "es2017.sharedmemory", + "esnext.asynciterable" ], "module": "commonjs", "moduleResolution": "node", From 3bdaf21475a6046c7b0aa68ac6ef5cf19022a2d2 Mon Sep 17 00:00:00 2001 From: Artur Androsovych Date: Tue, 10 Jul 2018 12:04:20 +0300 Subject: [PATCH 061/254] Add `is.urlInstance` (#60) --- readme.md | 11 +++++++++++ source/index.ts | 4 +++- source/tests/test.ts | 9 +++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 26031e8..51a8b0d 100644 --- a/readme.md +++ b/readme.md @@ -176,6 +176,17 @@ is.directInstanceOf(new UnicornError(), Error); //=> false ``` +##### .urlInstance(value) + +Returns `true` if `value` is an instance of the [`URL` class](https://developer.mozilla.org/en-US/docs/Web/API/URL). + +```js +const url = new URL('https://example.com'); + +is.urlInstance(url); +//=> true +``` + ##### .truthy(value) Returns `true` for all values that evaluate to true in a boolean context: diff --git a/source/index.ts b/source/index.ts index 868ec18..c6aeea2 100644 --- a/source/index.ts +++ b/source/index.ts @@ -48,7 +48,8 @@ export const enum TypeName { ArrayBuffer = 'ArrayBuffer', SharedArrayBuffer = 'SharedArrayBuffer', DataView = 'DataView', - Promise = 'Promise' + Promise = 'Promise', + URL = 'URL' } const toString = Object.prototype.toString; @@ -177,6 +178,7 @@ namespace is { // tslint:disable-line:no-namespace export const dataView = isObjectOfType(TypeName.DataView); export const directInstanceOf = (instance: any, klass: Class): instance is T => Object.getPrototypeOf(instance) === klass.prototype; + export const urlInstance = (value: any): value is URL => isObjectOfType(TypeName.URL)(value); export const truthy = (value: any) => Boolean(value); export const falsy = (value: any) => !value; diff --git a/source/tests/test.ts b/source/tests/test.ts index a2d56ad..810b42f 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -3,6 +3,7 @@ import net from 'net'; import Stream from 'stream'; import util from 'util'; import tempy from 'tempy'; +import {URL} from 'url'; import test, {TestContext, Context} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; @@ -534,6 +535,14 @@ test('is.directInstanceOf', t => { t.false(m.directInstanceOf(errorSubclass, Error)); }); +test('is.urlInstance', t => { + const url = new URL('https://www.example.com'); + t.true(m.urlInstance(url)); + t.false(m.urlInstance({})); + t.false(m.urlInstance(undefined)); + t.false(m.urlInstance(null)); +}); + test('is.truthy', t => { t.true(m.truthy('unicorn')); t.true(m.truthy('🦄')); From b2bb3e7d3717de9734a3881156b1f8ab00236fe9 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 10 Jul 2018 16:56:38 +0700 Subject: [PATCH 062/254] 0.11.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a9c07d..c1938fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.10.0", + "version": "0.11.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From 36b46f1889858f29ccafaa7970ee3bb45dfed251 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 31 Jul 2018 02:17:39 +0700 Subject: [PATCH 063/254] Update dev dependencies --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c1938fd..d1b4adf 100644 --- a/package.json +++ b/package.json @@ -50,15 +50,15 @@ "@types/jsdom": "^11.0.4", "@types/node": "^10.1.2", "@types/tempy": "^0.1.0", - "@types/zen-observable": "^0.5.3", + "@types/zen-observable": "^0.8.0", "ava": "*", "del-cli": "^1.1.0", "jsdom": "^11.6.2", "rxjs": "^6.0.0", "tempy": "^0.2.1", "tslint": "^5.9.1", - "tslint-xo": "^0.8.0", - "typescript": "^2.9.0", + "tslint-xo": "^0.9.0", + "typescript": "^3.0.1", "zen-observable": "^0.8.8" }, "types": "dist/index.d.ts" From ad6f372c47ca3b70950c041f2a24fde912db9288 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Tue, 18 Sep 2018 17:22:55 +0100 Subject: [PATCH 064/254] Fix parameter naming of `isAbsoluteMod2` function. (#64) --- source/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.ts b/source/index.ts index c6aeea2..bd1034a 100644 --- a/source/index.ts +++ b/source/index.ts @@ -260,7 +260,7 @@ namespace is { // tslint:disable-line:no-namespace export const infinite = (value: any) => value === Infinity || value === -Infinity; - const isAbsoluteMod2 = (value: number) => (rem: number) => integer(rem) && Math.abs(rem % 2) === value; + const isAbsoluteMod2 = (rem: number) => (value: number) => integer(value) && Math.abs(value % 2) === rem; export const even = isAbsoluteMod2(0); export const odd = isAbsoluteMod2(1); From 442f7b709f39ed2ac6bf073dd427f7f12659011d Mon Sep 17 00:00:00 2001 From: Sam Verschueren Date: Wed, 26 Sep 2018 21:02:41 +0200 Subject: [PATCH 065/254] Use is-buffer ponyfill for better browser support (#66) --- source/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/index.ts b/source/index.ts index bd1034a..d53a3da 100644 --- a/source/index.ts +++ b/source/index.ts @@ -54,6 +54,7 @@ export const enum TypeName { const toString = Object.prototype.toString; const isOfType = (type: string) => (value: any): value is T => typeof value === type; +const isBuffer = (input: any): input is Buffer => !is.nullOrUndefined(input) && !is.nullOrUndefined(input.constructor) && is.function_(input.constructor.isBuffer) && input.constructor.isBuffer(input); const getObjectType = (value: any): TypeName | null => { const objectName = toString.call(value).slice(8, -1) as string; @@ -101,7 +102,7 @@ function is(value: any): TypeName { // tslint:disable-line:only-arrow-functions return TypeName.Array; } - if (Buffer.isBuffer(value)) { + if (isBuffer(value)) { return TypeName.Buffer; } @@ -132,7 +133,7 @@ namespace is { // tslint:disable-line:no-namespace // tslint:enable:variable-name export const array = Array.isArray; - export const buffer = Buffer.isBuffer; + export const buffer = isBuffer; export const nullOrUndefined = (value: any): value is null | undefined => null_(value) || undefined(value); export const object = (value: any): value is object => !nullOrUndefined(value) && (function_(value) || isObject(value)); From 65c94f1a02c631376a0cf131a2dcb43da5e65824 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 28 Sep 2018 13:08:17 +0700 Subject: [PATCH 066/254] Include TS type lib references directly in the file Fixes #51 --- package.json | 8 ++++---- source/index.ts | 4 ++++ tsconfig.json | 5 ++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d1b4adf..5cf9f86 100644 --- a/package.json +++ b/package.json @@ -47,18 +47,18 @@ "symbol-observable": "^1.2.0" }, "devDependencies": { - "@types/jsdom": "^11.0.4", - "@types/node": "^10.1.2", + "@types/jsdom": "^11.12.0", + "@types/node": "^10.11.2", "@types/tempy": "^0.1.0", "@types/zen-observable": "^0.8.0", "ava": "*", "del-cli": "^1.1.0", "jsdom": "^11.6.2", - "rxjs": "^6.0.0", + "rxjs": "^6.3.3", "tempy": "^0.2.1", "tslint": "^5.9.1", "tslint-xo": "^0.9.0", - "typescript": "^3.0.1", + "typescript": "^3.1.1", "zen-observable": "^0.8.8" }, "types": "dist/index.d.ts" diff --git a/source/index.ts b/source/index.ts index d53a3da..8f93bba 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,3 +1,7 @@ +/// +/// +/// +/// import util from 'util'; import symbolObservable from 'symbol-observable'; diff --git a/tsconfig.json b/tsconfig.json index f7d6fd1..e1bc1fa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,11 +3,10 @@ "outDir": "dist", "target": "es2016", "lib": [ - "dom", - "es2015", "es2016", "es2017.sharedmemory", - "esnext.asynciterable" + "esnext.asynciterable", + "dom" ], "module": "commonjs", "moduleResolution": "node", From 6e07df5896f017aae28e6ad9c18f7b32598759c7 Mon Sep 17 00:00:00 2001 From: Arfat Salman Date: Fri, 28 Sep 2018 11:54:35 +0530 Subject: [PATCH 067/254] Add emptiness methods (#61) Fix #53 --- readme.md | 70 ++++++++++++++++++++--- source/index.ts | 20 +++++-- source/tests/test.ts | 130 ++++++++++++++++++++++++++++++------------- 3 files changed, 169 insertions(+), 51 deletions(-) diff --git a/readme.md b/readme.md index 51a8b0d..46fa906 100644 --- a/readme.md +++ b/readme.md @@ -160,6 +160,68 @@ is.boundFunction(function () {}); ##### .sharedArrayBuffer(value) ##### .dataView(value) +#### Emptiness + +##### .emptyString(value) + +Returns `true` if the value is a `string` and the `.length` is 0. + +##### .nonEmptyString(value) + +Returns `true` if the value is a `string` and the `.length` is more than 0. + +##### .emptyStringOrWhitespace(value) + +Returns `true` if `is.emptyString(value)` or if it's a `string` that is all whitespace. + +##### .emptyArray(value) + +Returns `true` if the value is an `Array` and the `.length` is 0. + +##### .nonEmptyArray(value) + +Returns `true` if the value is an `Array` and the `.length` is more than 0. + +##### .emptyObject(value) + +Returns `true` if the value is an `Object` and `Object.keys(value).length` is 0. + +Please note that `Object.keys` returns only own enumerable properties. Hence something like this can happen: + +```js +const object1 = {}; + +Object.defineProperty(object1, 'property1', { + value: 42, + writable: true, + enumerable: false, + configurable: true +}); + +is.emptyObject(object1); +// => true +``` + +##### .nonEmptyObject(value) + +Returns `true` if the value is an `Object` and `Object.keys(value).length` is more than 0. + +##### .emptySet(value) + +Returns `true` if the value is a `Set` and the `.size` is 0. + +##### .nonEmptySet(Value) + +Returns `true` if the value is a `Set` and the `.size` is more than 0. + +##### .emptyMap(value) + +Returns `true` if the value is a `Map` and the `.size` is 0. + +##### .nonEmptyMap(value) + +Returns `true` if the value is a `Map` and the `.size` is more than 0. + #### Miscellaneous ##### .directInstanceOf(value, class) @@ -295,14 +357,6 @@ Returns `true` if `value` is an even integer. Returns `true` if `value` is an odd integer. -##### .empty(value) - -Returns `true` if `value` is falsy or an empty string, array, object, map, or set. - -##### .emptyOrWhitespace(value) - -Returns `true` if `is.empty(value)` or a string that is all whitespace. - ##### .any(predicate, ...values) Returns `true` if **any** of the input `values` returns true in the `predicate`: diff --git a/source/index.ts b/source/index.ts index 8f93bba..534700b 100644 --- a/source/index.ts +++ b/source/index.ts @@ -270,12 +270,22 @@ namespace is { // tslint:disable-line:no-namespace export const odd = isAbsoluteMod2(1); const isWhiteSpaceString = (value: any) => string(value) && /\S/.test(value) === false; - const isEmptyStringOrArray = (value: any) => (string(value) || array(value)) && value.length === 0; - const isEmptyObject = (value: any) => !map(value) && !set(value) && object(value) && Object.keys(value).length === 0; - const isEmptyMapOrSet = (value: any) => (map(value) || set(value)) && value.size === 0; - export const empty = (value: any) => falsy(value) || isEmptyStringOrArray(value) || isEmptyObject(value) || isEmptyMapOrSet(value); - export const emptyOrWhitespace = (value: any) => empty(value) || isWhiteSpaceString(value); + export const emptyArray = (value: any) => array(value) && value.length === 0; + export const nonEmptyArray = (value: any) => array(value) && value.length > 0; + + export const emptyString = (value: any) => string(value) && value.length === 0; + export const nonEmptyString = (value: any) => string(value) && value.length > 0; + export const emptyStringOrWhitespace = (value: any) => emptyString(value) || isWhiteSpaceString(value); + + export const emptyObject = (value: any) => object(value) && !map(value) && !set(value) && Object.keys(value).length === 0; + export const nonEmptyObject = (value: any) => object(value) && !map(value) && !set(value) && Object.keys(value).length > 0; + + export const emptySet = (value: any) => set(value) && value.size === 0; + export const nonEmptySet = (value: any) => set(value) && value.size > 0; + + export const emptyMap = (value: any) => map(value) && value.size === 0; + export const nonEmptyMap = (value: any) => map(value) && value.size > 0; type ArrayMethod = (fn: (value: any, index: number, array: any[]) => boolean, thisArg?: any) => boolean; const predicateOnArray = (method: ArrayMethod, predicate: any, values: any[]) => { diff --git a/source/tests/test.ts b/source/tests/test.ts index 810b42f..66c3c9e 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -46,6 +46,13 @@ const types = new Map([ '' ] }], + ['emptyString', { + is: m.emptyString, + fixtures: [ + '', + String() + ] + }], ['number', { is: m.number, fixtures: [ @@ -76,6 +83,13 @@ const types = new Map([ new Array(2) // tslint:disable-line:prefer-array-literal ] }], + ['emptyArray', { + is: m.emptyArray, + fixtures: [ + [], + new Array() + ] + }], ['function', { is: m.function_, fixtures: [ @@ -167,13 +181,25 @@ const types = new Map([ ['map', { is: m.map, fixtures: [ - new Map() + new Map([['one', '1']]), + ] + }], + ['emptyMap', { + is: m.emptyMap, + fixtures: [ + new Map(), ] }], ['set', { is: m.set, fixtures: [ - new Set() + new Set(['one']) + ] + }], + ['emptySet', { + is: m.emptySet, + fixtures: [ + new Set(), ] }], ['weakSet', { @@ -385,7 +411,7 @@ test('is.null', t => { }); test('is.string', t => { - testType(t, 'string'); + testType(t, 'string', ['emptyString']); }); test('is.number', t => { @@ -401,7 +427,7 @@ test('is.symbol', t => { }); test('is.array', t => { - testType(t, 'array'); + testType(t, 'array', ['emptyArray']); }); test('is.function', t => { @@ -465,11 +491,11 @@ test('is.generatorFunction', t => { }); test('is.map', t => { - testType(t, 'map'); + testType(t, 'map', ['emptyMap']); }); test('is.set', t => { - testType(t, 'set'); + testType(t, 'set', ['emptySet']); }); test('is.weakMap', t => { @@ -739,40 +765,68 @@ test('is.odd', t => { } }); -test('is.empty', t => { - t.true(m.empty(null)); - t.true(m.empty(undefined)); - - t.true(m.empty(false)); - t.false(m.empty(true)); - - t.true(m.empty('')); - t.false(m.empty('🦄')); - - t.true(m.empty([])); - t.false(m.empty(['🦄'])); - - t.true(m.empty({})); - t.false(m.empty({unicorn: '🦄'})); - - const tempMap = new Map(); - t.true(m.empty(tempMap)); - - tempMap.set('unicorn', '🦄'); - t.false(m.empty(tempMap)); - - const tempSet = new Set(); - t.true(m.empty(tempSet)); - - tempSet.add(1); - t.false(m.empty(tempSet)); +test('is.emptyArray', t => { + testType(t, 'emptyArray'); }); -test('is.emptyOrWhitespace', t => { - t.true(m.emptyOrWhitespace('')); - t.true(m.emptyOrWhitespace(' ')); - t.false(m.emptyOrWhitespace('🦄')); - t.false(m.emptyOrWhitespace('unicorn')); +test('is.nonEmptyArray', t => { + t.true(m.nonEmptyArray([1, 2, 3])); + t.false(m.nonEmptyArray([])); + t.false(m.nonEmptyArray(new Array())); +}); + +test('is.emptyString', t => { + testType(t, 'emptyString', ['string']); + t.false(m.emptyString('🦄')); +}); + +test('is.nonEmptyString', t => { + t.false(m.nonEmptyString('')); + t.false(m.nonEmptyString(String())); + t.true(m.nonEmptyString('🦄')); +}); + +test('is.emptyStringOrWhitespace', t => { + testType(t, 'emptyString', ['string']); + t.true(m.emptyStringOrWhitespace(' ')); + t.false(m.emptyStringOrWhitespace('🦄')); + t.false(m.emptyStringOrWhitespace('unicorn')); +}); + +test('is.emptyObject', t => { + t.true(m.emptyObject({})); + t.true(m.emptyObject(new Object())); + t.false(m.emptyObject({unicorn: '🦄'})); +}); + +test('is.nonEmptyObject', t => { + t.false(m.nonEmptyObject({})); + t.false(m.nonEmptyObject(new Object())); + t.true(m.nonEmptyObject({unicorn: '🦄'})); +}); + +test('is.emptySet', t => { + testType(t, 'emptySet'); +}); + +test('is.nonEmptySet', t => { + const tempSet = new Set(); + t.false(m.nonEmptySet(tempSet)); + + tempSet.add(1); + t.true(m.nonEmptySet(tempSet)); +}); + +test('is.emptyMap', t => { + testType(t, 'emptyMap'); +}); + +test('is.nonEmptyMap', t => { + const tempMap = new Map(); + t.false(m.nonEmptyMap(tempMap)); + + tempMap.set('unicorn', '🦄'); + t.true(m.nonEmptyMap(tempMap)); }); test('is.any', t => { From 45c976071ac16e9f1ce9b2308b19785030b89d25 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 28 Sep 2018 13:30:19 +0700 Subject: [PATCH 068/254] Don't import `util` We don't really need its full power. Not worth the bloat. --- .gitattributes | 3 +-- .travis.yml | 1 + readme.md | 4 ++-- source/index.ts | 5 ++--- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.gitattributes b/.gitattributes index 391f0a4..6313b56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -* text=auto -*.js text eol=lf +* text=auto eol=lf diff --git a/.travis.yml b/.travis.yml index e0cc348..2ae9d62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: node_js node_js: + - '10' - '8' - '6' diff --git a/readme.md b/readme.md index 46fa906..e5adf94 100644 --- a/readme.md +++ b/readme.md @@ -184,7 +184,7 @@ Returns `true` if the value is an `Array` and the `.length` is more than 0. ##### .emptyObject(value) -Returns `true` if the value is an `Object` and `Object.keys(value).length` is 0. +Returns `true` if the value is an `Object` and `Object.keys(value).length` is 0. Please note that `Object.keys` returns only own enumerable properties. Hence something like this can happen: @@ -202,7 +202,7 @@ is.emptyObject(object1); // => true ``` -##### .nonEmptyObject(value) +##### .nonEmptyObject(value) Returns `true` if the value is an `Object` and `Object.keys(value).length` is more than 0. diff --git a/source/index.ts b/source/index.ts index 534700b..3ae4dd1 100644 --- a/source/index.ts +++ b/source/index.ts @@ -2,7 +2,6 @@ /// /// /// -import util from 'util'; import symbolObservable from 'symbol-observable'; type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; @@ -245,7 +244,7 @@ namespace is { // tslint:disable-line:no-namespace return value >= Math.min(...range) && value <= Math.max(...range); } - throw new TypeError(`Invalid range: ${util.inspect(range)}`); + throw new TypeError(`Invalid range: ${JSON.stringify(range)}`); }; const NODE_TYPE_ELEMENT = 1; @@ -290,7 +289,7 @@ namespace is { // tslint:disable-line:no-namespace type ArrayMethod = (fn: (value: any, index: number, array: any[]) => boolean, thisArg?: any) => boolean; const predicateOnArray = (method: ArrayMethod, predicate: any, values: any[]) => { if (function_(predicate) === false) { - throw new TypeError(`Invalid predicate: ${util.inspect(predicate)}`); + throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); } if (values.length === 0) { From c983ffa4cd83c114b18d021c180e6ba9265070cd Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 28 Sep 2018 13:35:34 +0700 Subject: [PATCH 069/254] 0.12.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5cf9f86..746bfa5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.11.0", + "version": "0.12.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From 9ac56f1be77d707bae88f36d9676200e2c30db12 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 30 Oct 2018 23:19:19 +0700 Subject: [PATCH 070/254] Minor code style tweaks in the tests --- source/tests/test.ts | 323 ++++++++++++++++++++++--------------------- 1 file changed, 163 insertions(+), 160 deletions(-) diff --git a/source/tests/test.ts b/source/tests/test.ts index 66c3c9e..af7eda9 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -8,7 +8,7 @@ import test, {TestContext, Context} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; import ZenObservable from 'zen-observable'; -import m from '..'; +import is from '..'; const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; const isNode10orHigher = Number(process.versions.node.split('.')[0]) >= 10; @@ -18,7 +18,7 @@ class ErrorSubclassFixture extends Error {} const {window} = new JSDOM(); const {document} = window; -const createDomElement = (el: string) => document.createElement(el); +const createDomElement = (element: string) => document.createElement(element); interface Test { is(value: any): boolean; @@ -27,19 +27,19 @@ interface Test { const types = new Map([ ['undefined', { - is: m.undefined, + is: is.undefined, fixtures: [ undefined ] }], ['null', { - is: m.null_, + is: is.null_, fixtures: [ null ] }], ['string', { - is: m.string, + is: is.string, fixtures: [ '🦄', 'hello world', @@ -47,14 +47,14 @@ const types = new Map([ ] }], ['emptyString', { - is: m.emptyString, + is: is.emptyString, fixtures: [ '', String() ] }], ['number', { - is: m.number, + is: is.number, fixtures: [ 6, 1.4, @@ -65,33 +65,33 @@ const types = new Map([ ] }], ['boolean', { - is: m.boolean, + is: is.boolean, fixtures: [ true, false ] }], ['symbol', { - is: m.symbol, + is: is.symbol, fixtures: [ Symbol('🦄') ] }], ['array', { - is: m.array, + is: is.array, fixtures: [ [1, 2], new Array(2) // tslint:disable-line:prefer-array-literal ] }], ['emptyArray', { - is: m.emptyArray, + is: is.emptyArray, fixtures: [ [], new Array() ] }], ['function', { - is: m.function_, + is: is.function_, fixtures: [ // tslint:disable:no-unused no-empty no-unused-variable only-arrow-functions no-function-expression function foo() {}, @@ -103,53 +103,53 @@ const types = new Map([ ] }], ['buffer', { - is: m.buffer, + is: is.buffer, fixtures: [ Buffer.from('🦄') ] }], ['object', { - is: m.object, + is: is.object, fixtures: [ {x: 1}, Object.create({x: 1}) ] }], ['regExp', { - is: m.regExp, + is: is.regExp, fixtures: [ /\w/, new RegExp('\\w') ] }], ['date', { - is: m.date, + is: is.date, fixtures: [ new Date() ] }], ['error', { - is: m.error, + is: is.error, fixtures: [ new Error('🦄'), new ErrorSubclassFixture() ] }], ['nativePromise', { - is: m.nativePromise, + is: is.nativePromise, fixtures: [ Promise.resolve(), PromiseSubclassFixture.resolve() ] }], ['promise', { - is: m.promise, + is: is.promise, fixtures: [ {then() {}, catch() {}} // tslint:disable-line:no-empty ] }], ['generator', { - is: m.generator, + is: is.generator, fixtures: [ (function * () { yield 4; @@ -157,7 +157,7 @@ const types = new Map([ ] }], ['generatorFunction', { - is: m.generatorFunction, + is: is.generatorFunction, fixtures: [ function * () { yield 4; @@ -165,137 +165,137 @@ const types = new Map([ ] }], ['asyncFunction', { - is: m.asyncFunction, + is: is.asyncFunction, fixtures: [ async function () {}, // tslint:disable-line:no-empty only-arrow-functions no-function-expression async () => {} // tslint:disable-line:no-empty ] }], ['boundFunction', { - is: m.boundFunction, + is: is.boundFunction, fixtures: [ () => {}, // tslint:disable-line:no-empty function () {}.bind(null), // tslint:disable-line:no-empty only-arrow-functions ] }], ['map', { - is: m.map, + is: is.map, fixtures: [ new Map([['one', '1']]), ] }], ['emptyMap', { - is: m.emptyMap, + is: is.emptyMap, fixtures: [ new Map(), ] }], ['set', { - is: m.set, + is: is.set, fixtures: [ new Set(['one']) ] }], ['emptySet', { - is: m.emptySet, + is: is.emptySet, fixtures: [ new Set(), ] }], ['weakSet', { - is: m.weakSet, + is: is.weakSet, fixtures: [ new WeakSet() ] }], ['weakMap', { - is: m.weakMap, + is: is.weakMap, fixtures: [ new WeakMap() ] }], ['int8Array', { - is: m.int8Array, + is: is.int8Array, fixtures: [ new Int8Array(0) ] }], ['uint8Array', { - is: m.uint8Array, + is: is.uint8Array, fixtures: [ new Uint8Array(0) ] }], ['uint8ClampedArray', { - is: m.uint8ClampedArray, + is: is.uint8ClampedArray, fixtures: [ new Uint8ClampedArray(0) ] }], ['int16Array', { - is: m.int16Array, + is: is.int16Array, fixtures: [ new Int16Array(0) ] }], ['uint16Array', { - is: m.uint16Array, + is: is.uint16Array, fixtures: [ new Uint16Array(0) ] }], ['int32Array', { - is: m.int32Array, + is: is.int32Array, fixtures: [ new Int32Array(0) ] }], ['uint32Array', { - is: m.uint32Array, + is: is.uint32Array, fixtures: [ new Uint32Array(0) ] }], ['float32Array', { - is: m.float32Array, + is: is.float32Array, fixtures: [ new Float32Array(0) ] }], ['float64Array', { - is: m.float64Array, + is: is.float64Array, fixtures: [ new Float64Array(0) ] }], ['arrayBuffer', { - is: m.arrayBuffer, + is: is.arrayBuffer, fixtures: [ new ArrayBuffer(10) ] }], ['dataView', { - is: m.dataView, + is: is.dataView, fixtures: [ new DataView(new ArrayBuffer(10)) ] }], ['nan', { - is: m.nan, + is: is.nan, fixtures: [ NaN, Number.NaN ] }], ['nullOrUndefined', { - is: m.nullOrUndefined, + is: is.nullOrUndefined, fixtures: [ null, undefined ] }], ['plainObject', { - is: m.plainObject, + is: is.plainObject, fixtures: [ {x: 1}, Object.create(null), @@ -303,20 +303,20 @@ const types = new Map([ ] }], ['integer', { - is: m.integer, + is: is.integer, fixtures: [ 6 ] }], ['safeInteger', { - is: m.safeInteger, + is: is.safeInteger, fixtures: [ Math.pow(2, 53) - 1, -Math.pow(2, 53) + 1 ] }], ['domElement', { - is: m.domElement, + is: is.domElement, fixtures: [ 'div', 'input', @@ -327,7 +327,7 @@ const types = new Map([ ].map(createDomElement) } ], ['non-domElements', { - is: value => !m.domElement(value), + is: value => !is.domElement(value), fixtures: [ document.createTextNode('data'), document.createProcessingInstruction('xml-stylesheet', 'href="mycss.css" type="text/css"'), @@ -338,7 +338,7 @@ const types = new Map([ ] }], ['nodeStream', { - is: m.nodeStream, + is: is.nodeStream, fixtures: [ fs.createReadStream('readme.md'), fs.createWriteStream(tempy.file()), @@ -352,7 +352,7 @@ const types = new Map([ ] }], ['observable', { - is: m.observable, + is: is.observable, fixtures: [ new Observable(), new Subject(), @@ -360,7 +360,7 @@ const types = new Map([ ] }], ['infinite', { - is: m.infinite, + is: is.infinite, fixtures: [ Infinity, -Infinity @@ -378,7 +378,7 @@ const testType = (t: TestContext & Context, type: string, exclude?: string[ return; } - const {is} = testData; + const {is: testIs} = testData; for (const [key, {fixtures}] of types) { // TODO: Automatically exclude value types in other tests that we have in the current one. @@ -390,14 +390,14 @@ const testType = (t: TestContext & Context, type: string, exclude?: string[ const assert = key === type ? t.true.bind(t) : t.false.bind(t); for (const fixture of fixtures) { - assert(is(fixture), `Value: ${util.inspect(fixture)}`); + assert(testIs(fixture), `Value: ${util.inspect(fixture)}`); } } }; test('is', t => { - t.is(m(null), 'null'); - t.is(m(undefined), 'undefined'); + t.is(is(null), 'null'); + t.is(is(undefined), 'undefined'); // TODO: Expand this to all the supported types. Maybe reuse `testType()` somehow. }); @@ -435,7 +435,7 @@ test('is.function', t => { }); test('is.boundFunction', t => { - t.false(m.boundFunction(function () {})); // tslint:disable-line:no-empty only-arrow-functions + t.false(is.boundFunction(function () {})); // tslint:disable-line:no-empty only-arrow-functions }); test('is.buffer', t => { @@ -452,7 +452,7 @@ test('is.object', t => { } for (const el of testData.fixtures) { - t.true(m.object(el)); + t.true(is.object(el)); } }); @@ -554,36 +554,36 @@ test('is.directInstanceOf', t => { const error = new Error(); const errorSubclass = new ErrorSubclassFixture(); - t.true(m.directInstanceOf(error, Error)); - t.true(m.directInstanceOf(errorSubclass, ErrorSubclassFixture)); + t.true(is.directInstanceOf(error, Error)); + t.true(is.directInstanceOf(errorSubclass, ErrorSubclassFixture)); - t.false(m.directInstanceOf(error, ErrorSubclassFixture)); - t.false(m.directInstanceOf(errorSubclass, Error)); + t.false(is.directInstanceOf(error, ErrorSubclassFixture)); + t.false(is.directInstanceOf(errorSubclass, Error)); }); test('is.urlInstance', t => { const url = new URL('https://www.example.com'); - t.true(m.urlInstance(url)); - t.false(m.urlInstance({})); - t.false(m.urlInstance(undefined)); - t.false(m.urlInstance(null)); + t.true(is.urlInstance(url)); + t.false(is.urlInstance({})); + t.false(is.urlInstance(undefined)); + t.false(is.urlInstance(null)); }); test('is.truthy', t => { - t.true(m.truthy('unicorn')); - t.true(m.truthy('🦄')); - t.true(m.truthy(new Set())); - t.true(m.truthy(Symbol('🦄'))); - t.true(m.truthy(true)); + t.true(is.truthy('unicorn')); + t.true(is.truthy('🦄')); + t.true(is.truthy(new Set())); + t.true(is.truthy(Symbol('🦄'))); + t.true(is.truthy(true)); }); test('is.falsy', t => { - t.true(m.falsy(false)); - t.true(m.falsy(0)); - t.true(m.falsy('')); - t.true(m.falsy(null)); - t.true(m.falsy(undefined)); - t.true(m.falsy(NaN)); + t.true(is.falsy(false)); + t.true(is.falsy(0)); + t.true(is.falsy('')); + t.true(is.falsy(null)); + t.true(is.falsy(undefined)); + t.true(is.falsy(NaN)); }); test('is.nan', t => { @@ -608,19 +608,19 @@ test('is.primitive', t => { ]; for (const el of primitives) { - t.true(m.primitive(el)); + t.true(is.primitive(el)); } }); test('is.integer', t => { testType(t, 'integer', ['number', 'safeInteger']); - t.false(m.integer(1.4)); + t.false(is.integer(1.4)); }); test('is.safeInteger', t => { testType(t, 'safeInteger', ['number', 'integer']); - t.false(m.safeInteger(Math.pow(2, 53))); - t.false(m.safeInteger(-Math.pow(2, 53))); + t.false(is.safeInteger(Math.pow(2, 53))); + t.false(is.safeInteger(-Math.pow(2, 53))); }); test('is.plainObject', t => { @@ -628,29 +628,29 @@ test('is.plainObject', t => { }); 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({})); + t.true(is.iterable('')); + t.true(is.iterable([])); + t.true(is.iterable(new Map())); + t.false(is.iterable(null)); + t.false(is.iterable(undefined)); + t.false(is.iterable(0)); + t.false(is.iterable(NaN)); + t.false(is.iterable(Infinity)); + t.false(is.iterable({})); }); if (isNode10orHigher) { test('is.asyncIterable', t => { - t.true(m.asyncIterable({ + t.true(is.asyncIterable({ [Symbol.asyncIterator]: () => {} // tslint:disable-line:no-empty })); - t.false(m.asyncIterable(null)); - t.false(m.asyncIterable(undefined)); - t.false(m.asyncIterable(0)); - t.false(m.asyncIterable(NaN)); - t.false(m.asyncIterable(Infinity)); - t.false(m.asyncIterable({})); + t.false(is.asyncIterable(null)); + t.false(is.asyncIterable(undefined)); + t.false(is.asyncIterable(0)); + t.false(is.asyncIterable(NaN)); + t.false(is.asyncIterable(Infinity)); + t.false(is.asyncIterable({})); }); } @@ -662,7 +662,7 @@ test('is.class', t => { ]; for (const x of classDeclarations) { - t.true(m.class_(x)); + t.true(is.class_(x)); } }); @@ -680,61 +680,61 @@ test('is.typedArray', t => { new Float64Array(0) ]; - for (const el of typedArrays) { - t.true(m.typedArray(el)); + for (const item of typedArrays) { + t.true(is.typedArray(item)); } - t.false(m.typedArray(new ArrayBuffer(1))); - t.false(m.typedArray([])); - t.false(m.typedArray({})); + t.false(is.typedArray(new ArrayBuffer(1))); + t.false(is.typedArray([])); + t.false(is.typedArray({})); }); test('is.arrayLike', t => { (function () { // tslint:disable-line:only-arrow-functions - t.true(m.arrayLike(arguments)); + t.true(is.arrayLike(arguments)); })(); - t.true(m.arrayLike([])); - t.true(m.arrayLike('unicorn')); + t.true(is.arrayLike([])); + t.true(is.arrayLike('unicorn')); - t.false(m.arrayLike({})); - t.false(m.arrayLike(() => {})); // tslint:disable-line:no-empty - t.false(m.arrayLike(new Map())); + t.false(is.arrayLike({})); + t.false(is.arrayLike(() => {})); // tslint:disable-line:no-empty + t.false(is.arrayLike(new Map())); }); test('is.inRange', t => { const x = 3; - t.true(m.inRange(x, [0, 5])); - t.true(m.inRange(x, [5, 0])); - t.true(m.inRange(x, [-5, 5])); - t.true(m.inRange(x, [5, -5])); - t.false(m.inRange(x, [4, 8])); - t.true(m.inRange(-7, [-5, -10])); - t.true(m.inRange(-5, [-5, -10])); - t.true(m.inRange(-10, [-5, -10])); + t.true(is.inRange(x, [0, 5])); + t.true(is.inRange(x, [5, 0])); + t.true(is.inRange(x, [-5, 5])); + t.true(is.inRange(x, [5, -5])); + t.false(is.inRange(x, [4, 8])); + t.true(is.inRange(-7, [-5, -10])); + t.true(is.inRange(-5, [-5, -10])); + t.true(is.inRange(-10, [-5, -10])); - t.true(m.inRange(x, 10)); - t.true(m.inRange(0, 0)); - t.true(m.inRange(-2, -3)); - t.false(m.inRange(x, 2)); - t.false(m.inRange(-3, -2)); + t.true(is.inRange(x, 10)); + t.true(is.inRange(0, 0)); + t.true(is.inRange(-2, -3)); + t.false(is.inRange(x, 2)); + t.false(is.inRange(-3, -2)); t.throws(() => { - m.inRange(0, []); + is.inRange(0, []); }); t.throws(() => { - m.inRange(0, [5]); + is.inRange(0, [5]); }); t.throws(() => { - m.inRange(0, [1, 2, 3]); + is.inRange(0, [1, 2, 3]); }); }); test('is.domElement', t => { testType(t, 'domElement'); - t.false(m.domElement({nodeType: 1, nodeName: 'div'})); + t.false(is.domElement({nodeType: 1, nodeName: 'div'})); }); test('is.nodeStream', t => { @@ -747,21 +747,21 @@ test('is.infinite', t => { test('is.even', t => { for (const el of [-6, 2, 4]) { - t.true(m.even(el)); + t.true(is.even(el)); } for (const el of [-3, 1, 5]) { - t.false(m.even(el)); + t.false(is.even(el)); } }); test('is.odd', t => { for (const el of [-5, 7, 13]) { - t.true(m.odd(el)); + t.true(is.odd(el)); } for (const el of [-8, 8, 10]) { - t.false(m.odd(el)); + t.false(is.odd(el)); } }); @@ -770,39 +770,42 @@ test('is.emptyArray', t => { }); test('is.nonEmptyArray', t => { - t.true(m.nonEmptyArray([1, 2, 3])); - t.false(m.nonEmptyArray([])); - t.false(m.nonEmptyArray(new Array())); + t.true(is.nonEmptyArray([1, 2, 3])); + t.false(is.nonEmptyArray([])); + t.false(is.nonEmptyArray(new Array())); }); test('is.emptyString', t => { testType(t, 'emptyString', ['string']); - t.false(m.emptyString('🦄')); + t.false(is.emptyString('🦄')); }); test('is.nonEmptyString', t => { - t.false(m.nonEmptyString('')); - t.false(m.nonEmptyString(String())); - t.true(m.nonEmptyString('🦄')); + t.false(is.nonEmptyString('')); + t.false(is.nonEmptyString(String())); + t.true(is.nonEmptyString('🦄')); }); test('is.emptyStringOrWhitespace', t => { testType(t, 'emptyString', ['string']); - t.true(m.emptyStringOrWhitespace(' ')); - t.false(m.emptyStringOrWhitespace('🦄')); - t.false(m.emptyStringOrWhitespace('unicorn')); + t.true(is.emptyStringOrWhitespace(' ')); + t.false(is.emptyStringOrWhitespace('🦄')); + t.false(is.emptyStringOrWhitespace('unicorn')); }); test('is.emptyObject', t => { - t.true(m.emptyObject({})); - t.true(m.emptyObject(new Object())); - t.false(m.emptyObject({unicorn: '🦄'})); + t.true(is.emptyObject({})); + t.true(is.emptyObject(new Object())); + t.false(is.emptyObject({unicorn: '🦄'})); }); test('is.nonEmptyObject', t => { - t.false(m.nonEmptyObject({})); - t.false(m.nonEmptyObject(new Object())); - t.true(m.nonEmptyObject({unicorn: '🦄'})); + const foo = {}; + is.nonEmptyObject(foo); + + t.false(is.nonEmptyObject({})); + t.false(is.nonEmptyObject(new Object())); + t.true(is.nonEmptyObject({unicorn: '🦄'})); }); test('is.emptySet', t => { @@ -811,10 +814,10 @@ test('is.emptySet', t => { test('is.nonEmptySet', t => { const tempSet = new Set(); - t.false(m.nonEmptySet(tempSet)); + t.false(is.nonEmptySet(tempSet)); tempSet.add(1); - t.true(m.nonEmptySet(tempSet)); + t.true(is.nonEmptySet(tempSet)); }); test('is.emptyMap', t => { @@ -823,38 +826,38 @@ test('is.emptyMap', t => { test('is.nonEmptyMap', t => { const tempMap = new Map(); - t.false(m.nonEmptyMap(tempMap)); + t.false(is.nonEmptyMap(tempMap)); tempMap.set('unicorn', '🦄'); - t.true(m.nonEmptyMap(tempMap)); + t.true(is.nonEmptyMap(tempMap)); }); test('is.any', t => { - t.true(m.any(m.string, {}, true, '🦄')); - t.true(m.any(m.object, false, {}, 'unicorns')); - t.false(m.any(m.boolean, '🦄', [], 3)); - t.false(m.any(m.integer, true, 'lol', {})); + t.true(is.any(is.string, {}, true, '🦄')); + t.true(is.any(is.object, false, {}, 'unicorns')); + t.false(is.any(is.boolean, '🦄', [], 3)); + t.false(is.any(is.integer, true, 'lol', {})); t.throws(() => { - m.any(null, true); + is.any(null, true); }); t.throws(() => { - m.any(m.string); + is.any(is.string); }); }); test('is.all', t => { - t.true(m.all(m.object, {}, new Set(), new Map())); - t.true(m.all(m.boolean, true, false)); - t.false(m.all(m.string, '🦄', [])); - t.false(m.all(m.set, new Map(), {})); + t.true(is.all(is.object, {}, new Set(), new Map())); + t.true(is.all(is.boolean, true, false)); + t.false(is.all(is.string, '🦄', [])); + t.false(is.all(is.set, new Map(), {})); t.throws(() => { - m.all(null, true); + is.all(null, true); }); t.throws(() => { - m.all(m.string); + is.all(is.string); }); }); From 7317226c801d6c20f7aa03eacb8f444ebfe32186 Mon Sep 17 00:00:00 2001 From: Itai Steinherz Date: Thu, 1 Nov 2018 13:21:49 +0200 Subject: [PATCH 071/254] Add `is.numericString()` (#67) --- readme.md | 4 ++++ source/index.ts | 5 ++++- source/tests/test.ts | 16 +++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index e5adf94..b934d05 100644 --- a/readme.md +++ b/readme.md @@ -96,6 +96,10 @@ All the below methods accept a value and returns a boolean for whether the value Keep in mind that [functions are objects too](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions). +##### .numericString(value) + +Returns `true` for a string that represents a number. For example, `'42'` and `'-8'`. + ##### .regExp(value) ##### .date(value) ##### .error(value) diff --git a/source/index.ts b/source/index.ts index 3ae4dd1..b8f397c 100644 --- a/source/index.ts +++ b/source/index.ts @@ -135,10 +135,13 @@ namespace is { // tslint:disable-line:no-namespace export const symbol = isOfType('symbol'); // tslint:enable:variable-name + export const nullOrUndefined = (value: any): value is null | undefined => null_(value) || undefined(value); + export const numericString = (value: any): boolean => + string(value) && value.length > 0 && !Number.isNaN(Number(value)); + export const array = Array.isArray; export const buffer = isBuffer; - export const nullOrUndefined = (value: any): value is null | undefined => null_(value) || undefined(value); export const object = (value: any): value is object => !nullOrUndefined(value) && (function_(value) || isObject(value)); export const iterable = (value: any): value is IterableIterator => !nullOrUndefined(value) && function_(value[Symbol.iterator]); export const asyncIterable = (value: any): value is AsyncIterableIterator => !nullOrUndefined(value) && function_(value[Symbol.asyncIterator]); diff --git a/source/tests/test.ts b/source/tests/test.ts index af7eda9..a210807 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -76,6 +76,14 @@ const types = new Map([ Symbol('🦄') ] }], + ['numericString', { + is: m.numericString, + fixtures: [ + '5', + '-3.2', + 'Infinity' + ] + }], ['array', { is: is.array, fixtures: [ @@ -411,7 +419,7 @@ test('is.null', t => { }); test('is.string', t => { - testType(t, 'string', ['emptyString']); + testType(t, 'string', ['emptyString', 'numericString']); }); test('is.number', t => { @@ -426,6 +434,12 @@ test('is.symbol', t => { testType(t, 'symbol'); }); +test('is.numericString', t => { + testType(t, 'numericString'); + t.false(m.numericString('')); + t.false(m.numericString(1)); +}); + test('is.array', t => { testType(t, 'array', ['emptyArray']); }); From d4869045c23e2d3d79d745f9854179323b2d68bd Mon Sep 17 00:00:00 2001 From: Lukas Tetzlaff Date: Fri, 2 Nov 2018 10:43:51 +0000 Subject: [PATCH 072/254] Refactor `any` to `unknown` where possible (#68) Resolves #62 --- package.json | 2 +- source/index.ts | 124 ++++++++++++++++++++++--------------------- source/tests/test.ts | 14 ++--- 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/package.json b/package.json index 746bfa5..92ee644 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "tempy": "^0.2.1", "tslint": "^5.9.1", "tslint-xo": "^0.9.0", - "typescript": "^3.1.1", + "typescript": "^3.1.4", "zen-observable": "^0.8.8" }, "types": "dist/index.d.ts" diff --git a/source/index.ts b/source/index.ts index b8f397c..8b52bc1 100644 --- a/source/index.ts +++ b/source/index.ts @@ -11,7 +11,7 @@ export interface ArrayLike { length: number; } -export interface Class { +export interface Class { new(...args: any[]): T; } @@ -56,10 +56,10 @@ export const enum TypeName { } const toString = Object.prototype.toString; -const isOfType = (type: string) => (value: any): value is T => typeof value === type; -const isBuffer = (input: any): input is Buffer => !is.nullOrUndefined(input) && !is.nullOrUndefined(input.constructor) && is.function_(input.constructor.isBuffer) && input.constructor.isBuffer(input); +const isOfType = (type: string) => (value: unknown): value is T => typeof value === type; +const isBuffer = (input: unknown): input is Buffer => !is.nullOrUndefined(input) && !is.nullOrUndefined((input as Buffer).constructor) && is.function_((input as Buffer).constructor.isBuffer) && (input as Buffer).constructor.isBuffer(input); -const getObjectType = (value: any): TypeName | null => { +const getObjectType = (value: unknown): TypeName | null => { const objectName = toString.call(value).slice(8, -1) as string; if (objectName) { @@ -69,9 +69,9 @@ const getObjectType = (value: any): TypeName | null => { return null; }; -const isObjectOfType = (type: TypeName) => (value: any): value is T => getObjectType(value) === type; +const isObjectOfType = (type: TypeName) => (value: unknown): value is T => getObjectType(value) === type; -function is(value: any): TypeName { // tslint:disable-line:only-arrow-functions +function is(value: unknown): TypeName { // tslint:disable-line:only-arrow-functions switch (value) { case null: return TypeName.null; @@ -122,53 +122,55 @@ function is(value: any): TypeName { // tslint:disable-line:only-arrow-functions } namespace is { // tslint:disable-line:no-namespace - const isObject = (value: any): value is object => typeof value === 'object'; + // tslint:disable-next-line:strict-type-predicates + const isObject = (value: unknown): value is object => typeof value === 'object'; // tslint:disable:variable-name export const undefined = isOfType('undefined'); export const string = isOfType('string'); export const number = isOfType('number'); export const function_ = isOfType('function'); - export const null_ = (value: any): value is null => value === null; - export const class_ = (value: any): value is Class => function_(value) && value.toString().startsWith('class '); - export const boolean = (value: any): value is boolean => value === true || value === false; + // tslint:disable-next-line:strict-type-predicates + export const null_ = (value: unknown): value is null => value === null; + export const class_ = (value: unknown): value is Class => function_(value) && value.toString().startsWith('class '); + export const boolean = (value: unknown): value is boolean => value === true || value === false; export const symbol = isOfType('symbol'); // tslint:enable:variable-name - export const nullOrUndefined = (value: any): value is null | undefined => null_(value) || undefined(value); - export const numericString = (value: any): boolean => + export const numericString = (value: unknown): boolean => string(value) && value.length > 0 && !Number.isNaN(Number(value)); export const array = Array.isArray; export const buffer = isBuffer; - export const object = (value: any): value is object => !nullOrUndefined(value) && (function_(value) || isObject(value)); - export const iterable = (value: any): value is IterableIterator => !nullOrUndefined(value) && function_(value[Symbol.iterator]); - export const asyncIterable = (value: any): value is AsyncIterableIterator => !nullOrUndefined(value) && function_(value[Symbol.asyncIterator]); - export const generator = (value: any): value is Generator => iterable(value) && function_(value.next) && function_(value.throw); + export const nullOrUndefined = (value: unknown): value is null | undefined => null_(value) || undefined(value); + export const object = (value: unknown): value is object => !nullOrUndefined(value) && (function_(value) || isObject(value)); + export const iterable = (value: unknown): value is IterableIterator => !nullOrUndefined(value) && function_((value as IterableIterator)[Symbol.iterator]); + export const asyncIterable = (value: unknown): value is AsyncIterableIterator => !nullOrUndefined(value) && function_((value as AsyncIterableIterator)[Symbol.asyncIterator]); + export const generator = (value: unknown): value is Generator => iterable(value) && function_(value.next) && function_(value.throw); - export const nativePromise = (value: any): value is Promise => - isObjectOfType>(TypeName.Promise)(value); + export const nativePromise = (value: unknown): value is Promise => + isObjectOfType>(TypeName.Promise)(value); - const hasPromiseAPI = (value: any): value is Promise => + const hasPromiseAPI = (value: unknown): value is Promise => !null_(value) && - isObject(value) as any && - function_(value.then) && - function_(value.catch); + isObject(value) as unknown && + function_((value as Promise).then) && + function_((value as Promise).catch); - export const promise = (value: any): value is Promise => nativePromise(value) || hasPromiseAPI(value); + export const promise = (value: unknown): value is Promise => nativePromise(value) || hasPromiseAPI(value); export const generatorFunction = isObjectOfType(TypeName.GeneratorFunction); export const asyncFunction = isObjectOfType(TypeName.AsyncFunction); - export const boundFunction = (value: any): value is Function => function_(value) && !value.hasOwnProperty('prototype'); + export const boundFunction = (value: unknown): value is Function => function_(value) && !value.hasOwnProperty('prototype'); export const regExp = isObjectOfType(TypeName.RegExp); export const date = isObjectOfType(TypeName.Date); export const error = isObjectOfType(TypeName.Error); - export const map = (value: any): value is Map => isObjectOfType>(TypeName.Map)(value); - export const set = (value: any): value is Set => isObjectOfType>(TypeName.Set)(value); - export const weakMap = (value: any): value is WeakMap => isObjectOfType>(TypeName.WeakMap)(value); - export const weakSet = (value: any): value is WeakSet => isObjectOfType>(TypeName.WeakSet)(value); + export const map = (value: unknown): value is Map => isObjectOfType>(TypeName.Map)(value); + export const set = (value: unknown): value is Set => isObjectOfType>(TypeName.Set)(value); + export const weakMap = (value: unknown): value is WeakMap => isObjectOfType>(TypeName.WeakMap)(value); + export const weakSet = (value: unknown): value is WeakSet => isObjectOfType>(TypeName.WeakSet)(value); export const int8Array = isObjectOfType(TypeName.Int8Array); export const uint8Array = isObjectOfType(TypeName.Uint8Array); @@ -184,13 +186,13 @@ namespace is { // tslint:disable-line:no-namespace export const sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); export const dataView = isObjectOfType(TypeName.DataView); - export const directInstanceOf = (instance: any, klass: Class): instance is T => Object.getPrototypeOf(instance) === klass.prototype; - export const urlInstance = (value: any): value is URL => isObjectOfType(TypeName.URL)(value); + export const directInstanceOf = (instance: unknown, klass: Class): instance is T => Object.getPrototypeOf(instance) === klass.prototype; + export const urlInstance = (value: unknown): value is URL => isObjectOfType(TypeName.URL)(value); - export const truthy = (value: any) => Boolean(value); - export const falsy = (value: any) => !value; + export const truthy = (value: unknown) => Boolean(value); + export const falsy = (value: unknown) => !value; - export const nan = (value: any) => Number.isNaN(value); + export const nan = (value: unknown) => Number.isNaN(value as number); const primitiveTypes = new Set([ 'undefined', @@ -200,12 +202,12 @@ namespace is { // tslint:disable-line:no-namespace 'symbol' ]); - export const primitive = (value: any): value is Primitive => null_(value) || primitiveTypes.has(typeof value); + export const primitive = (value: unknown): value is Primitive => null_(value) || primitiveTypes.has(typeof value); - export const integer = (value: any): value is number => Number.isInteger(value); - export const safeInteger = (value: any): value is number => Number.isSafeInteger(value); + export const integer = (value: unknown): value is number => Number.isInteger(value as number); + export const safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); - export const plainObject = (value: any) => { + export const plainObject = (value: unknown) => { // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js let prototype; @@ -225,7 +227,7 @@ namespace is { // tslint:disable-line:no-namespace TypeName.Float32Array, TypeName.Float64Array ]); - export const typedArray = (value: any): value is TypedArray => { + export const typedArray = (value: unknown): value is TypedArray => { const objectType = getObjectType(value); if (objectType === null) { @@ -235,8 +237,8 @@ namespace is { // tslint:disable-line:no-namespace return typedArrayTypes.has(objectType); }; - const isValidLength = (value: any) => safeInteger(value) && value > -1; - export const arrayLike = (value: any): value is ArrayLike => !nullOrUndefined(value) && !function_(value) && isValidLength(value.length); + const isValidLength = (value: unknown) => safeInteger(value) && value > -1; + export const arrayLike = (value: unknown): value is ArrayLike => !nullOrUndefined(value) && !function_(value) && isValidLength((value as ArrayLike).length); export const inRange = (value: number, range: number | number[]) => { if (number(range)) { @@ -259,38 +261,38 @@ namespace is { // tslint:disable-line:no-namespace 'nodeValue' ]; - export const domElement = (value: any): value is DomElement => object(value) as any && value.nodeType === NODE_TYPE_ELEMENT && string(value.nodeName) && - !plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in value); + export const domElement = (value: unknown): value is DomElement => object(value) && (value as DomElement).nodeType === NODE_TYPE_ELEMENT && string((value as DomElement).nodeName) && + !plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in (value as DomElement)); - export const observable = (value: any) => Boolean(value && value[symbolObservable] && value === value[symbolObservable]()); - export const nodeStream = (value: any): value is NodeStream => !nullOrUndefined(value) && isObject(value) as any && function_(value.pipe) && !observable(value); + export const observable = (value: unknown) => Boolean(value && (value as any)[symbolObservable] && value === (value as any)[symbolObservable]()); + export const nodeStream = (value: unknown): value is NodeStream => !nullOrUndefined(value) && isObject(value) as unknown && function_((value as NodeStream).pipe) && !observable(value); - export const infinite = (value: any) => value === Infinity || value === -Infinity; + export const infinite = (value: unknown) => value === Infinity || value === -Infinity; const isAbsoluteMod2 = (rem: number) => (value: number) => integer(value) && Math.abs(value % 2) === rem; export const even = isAbsoluteMod2(0); export const odd = isAbsoluteMod2(1); - const isWhiteSpaceString = (value: any) => string(value) && /\S/.test(value) === false; + const isWhiteSpaceString = (value: unknown) => string(value) && /\S/.test(value) === false; - export const emptyArray = (value: any) => array(value) && value.length === 0; - export const nonEmptyArray = (value: any) => array(value) && value.length > 0; + export const emptyArray = (value: unknown) => array(value) && value.length === 0; + export const nonEmptyArray = (value: unknown) => array(value) && value.length > 0; - export const emptyString = (value: any) => string(value) && value.length === 0; - export const nonEmptyString = (value: any) => string(value) && value.length > 0; - export const emptyStringOrWhitespace = (value: any) => emptyString(value) || isWhiteSpaceString(value); + export const emptyString = (value: unknown) => string(value) && value.length === 0; + export const nonEmptyString = (value: unknown) => string(value) && value.length > 0; + export const emptyStringOrWhitespace = (value: unknown) => emptyString(value) || isWhiteSpaceString(value); - export const emptyObject = (value: any) => object(value) && !map(value) && !set(value) && Object.keys(value).length === 0; - export const nonEmptyObject = (value: any) => object(value) && !map(value) && !set(value) && Object.keys(value).length > 0; + export const emptyObject = (value: unknown) => object(value) && !map(value) && !set(value) && Object.keys(value).length === 0; + export const nonEmptyObject = (value: unknown) => object(value) && !map(value) && !set(value) && Object.keys(value).length > 0; - export const emptySet = (value: any) => set(value) && value.size === 0; - export const nonEmptySet = (value: any) => set(value) && value.size > 0; + export const emptySet = (value: unknown) => set(value) && value.size === 0; + export const nonEmptySet = (value: unknown) => set(value) && value.size > 0; - export const emptyMap = (value: any) => map(value) && value.size === 0; - export const nonEmptyMap = (value: any) => map(value) && value.size > 0; + export const emptyMap = (value: unknown) => map(value) && value.size === 0; + export const nonEmptyMap = (value: unknown) => map(value) && value.size > 0; - type ArrayMethod = (fn: (value: any, index: number, array: any[]) => boolean, thisArg?: any) => boolean; - const predicateOnArray = (method: ArrayMethod, predicate: any, values: any[]) => { + type ArrayMethod = (fn: (value: unknown, index: number, array: unknown[]) => boolean, thisArg?: unknown) => boolean; + const predicateOnArray = (method: ArrayMethod, predicate: unknown, values: unknown[]) => { if (function_(predicate) === false) { throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); } @@ -303,8 +305,8 @@ namespace is { // tslint:disable-line:no-namespace }; // tslint:disable variable-name - export const any = (predicate: any, ...values: any[]) => predicateOnArray(Array.prototype.some, predicate, values); - export const all = (predicate: any, ...values: any[]) => predicateOnArray(Array.prototype.every, predicate, values); + export const any = (predicate: unknown, ...values: unknown[]) => predicateOnArray(Array.prototype.some, predicate, values); + export const all = (predicate: unknown, ...values: unknown[]) => predicateOnArray(Array.prototype.every, predicate, values); // tslint:enable variable-name } diff --git a/source/tests/test.ts b/source/tests/test.ts index a210807..4fcac81 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -21,8 +21,8 @@ const {document} = window; const createDomElement = (element: string) => document.createElement(element); interface Test { - is(value: any): boolean; - fixtures: any[]; + is(value: unknown): boolean; + fixtures: unknown[]; } const types = new Map([ @@ -77,7 +77,7 @@ const types = new Map([ ] }], ['numericString', { - is: m.numericString, + is: is.numericString, fixtures: [ '5', '-3.2', @@ -106,7 +106,7 @@ const types = new Map([ function () {}, () => {}, async function () {}, - function * (): any {} + function * (): unknown {} // tslint:enable:no-unused no-empty no-unused-variable only-arrow-functions no-function-expression ] }], @@ -377,7 +377,7 @@ const types = new Map([ ]); // This ensures a certain method matches only the types it's supposed to and none of the other methods' types -const testType = (t: TestContext & Context, type: string, exclude?: string[]) => { +const testType = (t: TestContext & Context, type: string, exclude?: string[]) => { const testData = types.get(type); if (testData === undefined) { @@ -436,8 +436,8 @@ test('is.symbol', t => { test('is.numericString', t => { testType(t, 'numericString'); - t.false(m.numericString('')); - t.false(m.numericString(1)); + t.false(is.numericString('')); + t.false(is.numericString(1)); }); test('is.array', t => { From 69bbf2d4bed61c0ceea3b38f93fb01fda94f52ee Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 2 Nov 2018 17:53:03 +0700 Subject: [PATCH 073/254] Readme improvements --- readme.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index b934d05..f82781b 100644 --- a/readme.md +++ b/readme.md @@ -100,6 +100,8 @@ Keep in mind that [functions are objects too](https://developer.mozilla.org/en-U Returns `true` for a string that represents a number. For example, `'42'` and `'-8'`. +Note: `'NaN'` return `false`, but `'Infinity'` and `'-Infinity'` return `true`. + ##### .regExp(value) ##### .date(value) ##### .error(value) @@ -236,7 +238,7 @@ Returns `true` if `value` is a direct instance of `class`. is.directInstanceOf(new Error(), Error); //=> true -class UnicornError extends Error {}; +class UnicornError extends Error {} is.directInstanceOf(new UnicornError(), Error); //=> false @@ -289,7 +291,7 @@ An object is plain if it's created by either `{}`, `new Object()`, or `Object.cr ##### .asyncIterable(value) ##### .class(value) -Returns `true` for instances created by a ES2015 class. +Returns `true` for instances created by a class. ##### .typedArray(value) @@ -301,10 +303,11 @@ A `value` is array-like if it is not a function and has a `value.length` that is is.arrayLike(document.forms); //=> true -function () { - is.arrayLike(arguments); - //=> true +function foo() { + is.arrayLike(arguments); + //=> true } +foo(); ``` ##### .inRange(value, range) @@ -335,6 +338,7 @@ Returns `true` if `value` is a Node.js [stream](https://nodejs.org/api/stream.ht ```js const fs = require('fs'); + is.nodeStream(fs.createReadStream('unicorn.png')); //=> true ``` @@ -345,6 +349,7 @@ Returns `true` if `value` is an `Observable`. ```js const {Observable} = require('rxjs'); + is.observable(new Observable()); //=> true ``` From 2232eeea79b3d2a26fb5b0af60e042ff2171cdc3 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 2 Nov 2018 19:08:53 +0700 Subject: [PATCH 074/254] 0.13.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 92ee644..6479b12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.12.0", + "version": "0.13.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From b8a9fb457c7d13cb5bf02ce5a7a7546efb40cb13 Mon Sep 17 00:00:00 2001 From: jokebookservice1 Date: Fri, 2 Nov 2018 16:04:44 +0000 Subject: [PATCH 075/254] Minor documentational correction (#69) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index f82781b..1814497 100644 --- a/readme.md +++ b/readme.md @@ -100,7 +100,7 @@ Keep in mind that [functions are objects too](https://developer.mozilla.org/en-U Returns `true` for a string that represents a number. For example, `'42'` and `'-8'`. -Note: `'NaN'` return `false`, but `'Infinity'` and `'-Infinity'` return `true`. +Note: `'NaN'` returns `false`, but `'Infinity'` and `'-Infinity'` return `true`. ##### .regExp(value) ##### .date(value) From 9df6f4ebe9147fde1ea294a8b6df1e6f37b6cf94 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 30 Nov 2018 14:28:54 +0700 Subject: [PATCH 076/254] Bump TypeScript version --- package.json | 9 +++++---- source/index.ts | 4 ++-- source/tests/test.ts | 2 +- tsconfig.json | 26 +++----------------------- 4 files changed, 11 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 6479b12..5ef42c7 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,10 @@ "symbol-observable": "^1.2.0" }, "devDependencies": { + "@sindresorhus/tsconfig": "^0.1.0", "@types/jsdom": "^11.12.0", - "@types/node": "^10.11.2", - "@types/tempy": "^0.1.0", + "@types/node": "^10.12.10", + "@types/tempy": "^0.2.0", "@types/zen-observable": "^0.8.0", "ava": "*", "del-cli": "^1.1.0", @@ -57,8 +58,8 @@ "rxjs": "^6.3.3", "tempy": "^0.2.1", "tslint": "^5.9.1", - "tslint-xo": "^0.9.0", - "typescript": "^3.1.4", + "tslint-xo": "^0.10.0", + "typescript": "^3.2.1", "zen-observable": "^0.8.8" }, "types": "dist/index.d.ts" diff --git a/source/index.ts b/source/index.ts index 8b52bc1..37e775b 100644 --- a/source/index.ts +++ b/source/index.ts @@ -60,7 +60,7 @@ const isOfType = (type: string) => (value: unknown): value is T => typeof val const isBuffer = (input: unknown): input is Buffer => !is.nullOrUndefined(input) && !is.nullOrUndefined((input as Buffer).constructor) && is.function_((input as Buffer).constructor.isBuffer) && (input as Buffer).constructor.isBuffer(input); const getObjectType = (value: unknown): TypeName | null => { - const objectName = toString.call(value).slice(8, -1) as string; + const objectName = toString.call(value).slice(8, -1); if (objectName) { return objectName as TypeName; @@ -301,7 +301,7 @@ namespace is { // tslint:disable-line:no-namespace throw new TypeError('Invalid number of values'); } - return method.call(values, predicate); + return method.call(values, predicate as any); }; // tslint:disable variable-name diff --git a/source/tests/test.ts b/source/tests/test.ts index 4fcac81..98cb287 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -21,8 +21,8 @@ const {document} = window; const createDomElement = (element: string) => document.createElement(element); interface Test { - is(value: unknown): boolean; fixtures: unknown[]; + is(value: unknown): boolean; } const types = new Map([ diff --git a/tsconfig.json b/tsconfig.json index e1bc1fa..5f5c71e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,5 @@ { + "extends": "@sindresorhus/tsconfig", "compilerOptions": { "outDir": "dist", "target": "es2016", @@ -7,27 +8,6 @@ "es2017.sharedmemory", "esnext.asynciterable", "dom" - ], - "module": "commonjs", - "moduleResolution": "node", - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "sourceMap": true, - "declaration": true, - "pretty": true, - "newLine": "lf", - "stripInternal": true, - "strict": true, - "noImplicitReturns": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "noEmitOnError": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true - }, - "exclude": [ - "node_modules", - "dist" - ] + ] + } } From 2ee148f5a137c6af5e0aa1c9c4ce0b03fe09263e Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 13 Dec 2018 00:58:02 +0100 Subject: [PATCH 077/254] Meta tweaks --- package.json | 2 +- source/tests/test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5ef42c7..40dd260 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@types/node": "^10.12.10", "@types/tempy": "^0.2.0", "@types/zen-observable": "^0.8.0", - "ava": "*", + "ava": "^0.25.0", "del-cli": "^1.1.0", "jsdom": "^11.6.2", "rxjs": "^6.3.3", diff --git a/source/tests/test.ts b/source/tests/test.ts index 98cb287..3dd2f59 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -681,8 +681,9 @@ test('is.class', t => { }); test('is.typedArray', t => { - // Typescript currently does not support empty constructors for these + // TypeScript currently does not support empty constructors for these // See https://github.com/Microsoft/TypeScript/issues/19680 + // TODO: Remove the `0` when targeting `es2017` (Node.js 8), in other places of this file too. const typedArrays = [ new Int8Array(0), new Uint8Array(0), From 566f363632ee1f83fc99bf8593b08d1aad12b9ef Mon Sep 17 00:00:00 2001 From: Itai Steinherz Date: Thu, 13 Dec 2018 17:52:21 +0200 Subject: [PATCH 078/254] Add `is.urlString` (#73) --- readme.md | 16 ++++++++++++++++ source/index.ts | 14 ++++++++++++++ source/tests/test.ts | 11 ++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 1814497..97c023b 100644 --- a/readme.md +++ b/readme.md @@ -255,6 +255,22 @@ is.urlInstance(url); //=> true ``` +### .url(value) + +Returns `true` if `value` is a URL string. + +Note: this only does basic checking using the [`URL` class](https://developer.mozilla.org/en-US/docs/Web/API/URL) constructor. + +```js +const url = 'https://example.com'; + +is.url(url); +//=> true + +is.url(new URL(url)); +//=> false +``` + ##### .truthy(value) Returns `true` for all values that evaluate to true in a boolean context: diff --git a/source/index.ts b/source/index.ts index 37e775b..31fdc6d 100644 --- a/source/index.ts +++ b/source/index.ts @@ -3,6 +3,7 @@ /// /// import symbolObservable from 'symbol-observable'; +import {URL} from 'url'; type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; type Primitive = null | undefined | string | number | boolean | Symbol; @@ -189,6 +190,19 @@ namespace is { // tslint:disable-line:no-namespace export const directInstanceOf = (instance: unknown, klass: Class): instance is T => Object.getPrototypeOf(instance) === klass.prototype; export const urlInstance = (value: unknown): value is URL => isObjectOfType(TypeName.URL)(value); + export const urlString = (value: unknown) => { + if (!string(value)) { + return false; + } + + try { + new URL(value); // tslint:disable-line no-unused-expression + return true; + } catch { + return false; + } + }; + export const truthy = (value: unknown) => Boolean(value); export const falsy = (value: unknown) => !value; diff --git a/source/tests/test.ts b/source/tests/test.ts index 3dd2f59..15bc9bf 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -576,13 +576,22 @@ test('is.directInstanceOf', t => { }); test('is.urlInstance', t => { - const url = new URL('https://www.example.com'); + const url = new URL('https://example.com'); t.true(is.urlInstance(url)); t.false(is.urlInstance({})); t.false(is.urlInstance(undefined)); t.false(is.urlInstance(null)); }); +test('is.urlString', t => { + const url = 'https://example.com'; + t.true(is.urlString(url)); + t.false(is.urlString(new URL(url))); + t.false(is.urlString({})); + t.false(is.urlString(undefined)); + t.false(is.urlString(null)); +}); + test('is.truthy', t => { t.true(is.truthy('unicorn')); t.true(is.truthy('🦄')); From 844b43c9dfcc3caca36001e35466918d57136bcf Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 13 Dec 2018 16:52:55 +0100 Subject: [PATCH 079/254] Improve the `is.observable` check (#74) Fixes #72 --- package.json | 3 --- source/index.ts | 18 ++++++++++++++++-- source/tests/test.ts | 20 ++++++++++++-------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 40dd260..c7b2983 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,6 @@ "verify", "compare" ], - "dependencies": { - "symbol-observable": "^1.2.0" - }, "devDependencies": { "@sindresorhus/tsconfig": "^0.1.0", "@types/jsdom": "^11.12.0", diff --git a/source/index.ts b/source/index.ts index 31fdc6d..babe375 100644 --- a/source/index.ts +++ b/source/index.ts @@ -2,7 +2,6 @@ /// /// /// -import symbolObservable from 'symbol-observable'; import {URL} from 'url'; type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; @@ -278,7 +277,22 @@ namespace is { // tslint:disable-line:no-namespace export const domElement = (value: unknown): value is DomElement => object(value) && (value as DomElement).nodeType === NODE_TYPE_ELEMENT && string((value as DomElement).nodeName) && !plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in (value as DomElement)); - export const observable = (value: unknown) => Boolean(value && (value as any)[symbolObservable] && value === (value as any)[symbolObservable]()); + export const observable = (value: unknown) => { + if (!value) { + return false; + } + + if ((value as any)[Symbol.observable] && value === (value as any)[Symbol.observable]()) { + return true; + } + + if ((value as any)['@@observable'] && value === (value as any)['@@observable']()) { + return true; + } + + return false; + }; + export const nodeStream = (value: unknown): value is NodeStream => !nullOrUndefined(value) && isObject(value) as unknown && function_((value as NodeStream).pipe) && !observable(value); export const infinite = (value: unknown) => value === Infinity || value === -Infinity; diff --git a/source/tests/test.ts b/source/tests/test.ts index 15bc9bf..cc4353e 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -345,6 +345,14 @@ const types = new Map([ document.createDocumentFragment() ] }], + ['observable', { + is: is.observable, + fixtures: [ + new Observable(), + new Subject(), + new ZenObservable(() => {}) // tslint:disable-line:no-empty + ] + }], ['nodeStream', { is: is.nodeStream, fixtures: [ @@ -359,14 +367,6 @@ const types = new Map([ new Stream.Writable() ] }], - ['observable', { - is: is.observable, - fixtures: [ - new Observable(), - new Subject(), - new ZenObservable(() => {}) // tslint:disable-line:no-empty - ] - }], ['infinite', { is: is.infinite, fixtures: [ @@ -761,6 +761,10 @@ test('is.domElement', t => { t.false(is.domElement({nodeType: 1, nodeName: 'div'})); }); +test('is.observable', t => { + testType(t, 'observable'); +}); + test('is.nodeStream', t => { testType(t, 'nodeStream'); }); From 42fd8d357463ff353ef1cc92f31208914cbbcb01 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 13 Dec 2018 16:57:06 +0100 Subject: [PATCH 080/254] Use the URL global when available --- source/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/index.ts b/source/index.ts index babe375..ba1f089 100644 --- a/source/index.ts +++ b/source/index.ts @@ -2,7 +2,10 @@ /// /// /// -import {URL} from 'url'; + +// TODO: Use the `URL` global when targeting Node.js 10 +// tslint:disable-next-line +const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; type Primitive = null | undefined | string | number | boolean | Symbol; @@ -195,7 +198,7 @@ namespace is { // tslint:disable-line:no-namespace } try { - new URL(value); // tslint:disable-line no-unused-expression + new URLGlobal(value); // tslint:disable-line no-unused-expression return true; } catch { return false; From 6cb1d1e91073ada3d0e298e2fef361f0d418b668 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 13 Dec 2018 16:58:24 +0100 Subject: [PATCH 081/254] 0.14.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7b2983..bf0d9fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.13.0", + "version": "0.14.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From 4d0120adb713535cfa27531bab81a40e97a775e0 Mon Sep 17 00:00:00 2001 From: Itai Steinherz Date: Thu, 13 Dec 2018 23:38:35 +0200 Subject: [PATCH 082/254] Fix `is.urlString` documentation (#76) --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 97c023b..ee20bdb 100644 --- a/readme.md +++ b/readme.md @@ -255,7 +255,7 @@ is.urlInstance(url); //=> true ``` -### .url(value) +### .urlString(value) Returns `true` if `value` is a URL string. @@ -264,10 +264,10 @@ Note: this only does basic checking using the [`URL` class](https://developer.mo ```js const url = 'https://example.com'; -is.url(url); +is.urlString(url); //=> true -is.url(new URL(url)); +is.urlString(new URL(url)); //=> false ``` From 0e1cce5d452ce6d84424850f952be4d1473ad6a3 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 11 Jan 2019 16:34:29 +0700 Subject: [PATCH 083/254] Fix readme typo --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index ee20bdb..3621d4e 100644 --- a/readme.md +++ b/readme.md @@ -255,7 +255,7 @@ is.urlInstance(url); //=> true ``` -### .urlString(value) +##### .urlString(value) Returns `true` if `value` is a URL string. From ab586df0f9ff91e4c0e0d692a978cba110df1d7c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 12 Jan 2019 12:14:45 +0700 Subject: [PATCH 084/254] Fix lint issue --- source/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/index.ts b/source/index.ts index ba1f089..45cd17d 100644 --- a/source/index.ts +++ b/source/index.ts @@ -14,9 +14,7 @@ export interface ArrayLike { length: number; } -export interface Class { - new(...args: any[]): T; -} +export type Class = new(...args: any[]) => T; type DomElement = object & { nodeType: 1; nodeName: string }; type NodeStream = object & { pipe: Function }; From 641d856b36deb101f70633775d628265448fa526 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 2 Feb 2019 01:14:08 +0700 Subject: [PATCH 085/254] Stop using TypeScript `namespace` (#78) --- source/index.ts | 375 ++++++++++++++++++++++++------------------------ 1 file changed, 186 insertions(+), 189 deletions(-) diff --git a/source/index.ts b/source/index.ts index 45cd17d..fce18c9 100644 --- a/source/index.ts +++ b/source/index.ts @@ -122,222 +122,219 @@ function is(value: unknown): TypeName { // tslint:disable-line:only-arrow-functi return TypeName.Object; } -namespace is { // tslint:disable-line:no-namespace - // tslint:disable-next-line:strict-type-predicates - const isObject = (value: unknown): value is object => typeof value === 'object'; +// tslint:disable-next-line:strict-type-predicates +const isObject = (value: unknown): value is object => typeof value === 'object'; - // tslint:disable:variable-name - export const undefined = isOfType('undefined'); - export const string = isOfType('string'); - export const number = isOfType('number'); - export const function_ = isOfType('function'); - // tslint:disable-next-line:strict-type-predicates - export const null_ = (value: unknown): value is null => value === null; - export const class_ = (value: unknown): value is Class => function_(value) && value.toString().startsWith('class '); - export const boolean = (value: unknown): value is boolean => value === true || value === false; - export const symbol = isOfType('symbol'); - // tslint:enable:variable-name +is.undefined = isOfType('undefined'); +is.string = isOfType('string'); +is.number = isOfType('number'); +is.function_ = isOfType('function'); +// tslint:disable-next-line:strict-type-predicates +is.null_ = (value: unknown): value is null => value === null; +is.class_ = (value: unknown): value is Class => is.function_(value) && value.toString().startsWith('class '); +is.boolean = (value: unknown): value is boolean => value === true || value === false; +is.symbol = isOfType('symbol'); +// tslint:enable:variable-name - export const numericString = (value: unknown): boolean => - string(value) && value.length > 0 && !Number.isNaN(Number(value)); +is.numericString = (value: unknown): boolean => + is.string(value) && value.length > 0 && !Number.isNaN(Number(value)); - export const array = Array.isArray; - export const buffer = isBuffer; +is.array = Array.isArray; +is.buffer = isBuffer; - export const nullOrUndefined = (value: unknown): value is null | undefined => null_(value) || undefined(value); - export const object = (value: unknown): value is object => !nullOrUndefined(value) && (function_(value) || isObject(value)); - export const iterable = (value: unknown): value is IterableIterator => !nullOrUndefined(value) && function_((value as IterableIterator)[Symbol.iterator]); - export const asyncIterable = (value: unknown): value is AsyncIterableIterator => !nullOrUndefined(value) && function_((value as AsyncIterableIterator)[Symbol.asyncIterator]); - export const generator = (value: unknown): value is Generator => iterable(value) && function_(value.next) && function_(value.throw); +is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); +is.object = (value: unknown): value is object => !is.nullOrUndefined(value) && (is.function_(value) || isObject(value)); +is.iterable = (value: unknown): value is IterableIterator => !is.nullOrUndefined(value) && is.function_((value as IterableIterator)[Symbol.iterator]); +is.asyncIterable = (value: unknown): value is AsyncIterableIterator => !is.nullOrUndefined(value) && is.function_((value as AsyncIterableIterator)[Symbol.asyncIterator]); +is.generator = (value: unknown): value is Generator => is.iterable(value) && is.function_(value.next) && is.function_(value.throw); - export const nativePromise = (value: unknown): value is Promise => - isObjectOfType>(TypeName.Promise)(value); +is.nativePromise = (value: unknown): value is Promise => + isObjectOfType>(TypeName.Promise)(value); - const hasPromiseAPI = (value: unknown): value is Promise => - !null_(value) && - isObject(value) as unknown && - function_((value as Promise).then) && - function_((value as Promise).catch); +const hasPromiseAPI = (value: unknown): value is Promise => + !is.null_(value) && + isObject(value) as unknown && + is.function_((value as Promise).then) && + is.function_((value as Promise).catch); - export const promise = (value: unknown): value is Promise => nativePromise(value) || hasPromiseAPI(value); +is.promise = (value: unknown): value is Promise => is.nativePromise(value) || hasPromiseAPI(value); - export const generatorFunction = isObjectOfType(TypeName.GeneratorFunction); - export const asyncFunction = isObjectOfType(TypeName.AsyncFunction); - export const boundFunction = (value: unknown): value is Function => function_(value) && !value.hasOwnProperty('prototype'); +is.generatorFunction = isObjectOfType(TypeName.GeneratorFunction); +is.asyncFunction = isObjectOfType(TypeName.AsyncFunction); +is.boundFunction = (value: unknown): value is Function => is.function_(value) && !value.hasOwnProperty('prototype'); - export const regExp = isObjectOfType(TypeName.RegExp); - export const date = isObjectOfType(TypeName.Date); - export const error = isObjectOfType(TypeName.Error); - export const map = (value: unknown): value is Map => isObjectOfType>(TypeName.Map)(value); - export const set = (value: unknown): value is Set => isObjectOfType>(TypeName.Set)(value); - export const weakMap = (value: unknown): value is WeakMap => isObjectOfType>(TypeName.WeakMap)(value); - export const weakSet = (value: unknown): value is WeakSet => isObjectOfType>(TypeName.WeakSet)(value); +is.regExp = isObjectOfType(TypeName.RegExp); +is.date = isObjectOfType(TypeName.Date); +is.error = isObjectOfType(TypeName.Error); +is.map = (value: unknown): value is Map => isObjectOfType>(TypeName.Map)(value); +is.set = (value: unknown): value is Set => isObjectOfType>(TypeName.Set)(value); +is.weakMap = (value: unknown): value is WeakMap => isObjectOfType>(TypeName.WeakMap)(value); +is.weakSet = (value: unknown): value is WeakSet => isObjectOfType>(TypeName.WeakSet)(value); - export const int8Array = isObjectOfType(TypeName.Int8Array); - export const uint8Array = isObjectOfType(TypeName.Uint8Array); - export const uint8ClampedArray = isObjectOfType(TypeName.Uint8ClampedArray); - export const int16Array = isObjectOfType(TypeName.Int16Array); - export const uint16Array = isObjectOfType(TypeName.Uint16Array); - export const int32Array = isObjectOfType(TypeName.Int32Array); - export const uint32Array = isObjectOfType(TypeName.Uint32Array); - export const float32Array = isObjectOfType(TypeName.Float32Array); - export const float64Array = isObjectOfType(TypeName.Float64Array); +is.int8Array = isObjectOfType(TypeName.Int8Array); +is.uint8Array = isObjectOfType(TypeName.Uint8Array); +is.uint8ClampedArray = isObjectOfType(TypeName.Uint8ClampedArray); +is.int16Array = isObjectOfType(TypeName.Int16Array); +is.uint16Array = isObjectOfType(TypeName.Uint16Array); +is.int32Array = isObjectOfType(TypeName.Int32Array); +is.uint32Array = isObjectOfType(TypeName.Uint32Array); +is.float32Array = isObjectOfType(TypeName.Float32Array); +is.float64Array = isObjectOfType(TypeName.Float64Array); - export const arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); - export const sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); - export const dataView = isObjectOfType(TypeName.DataView); +is.arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); +is.sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); +is.dataView = isObjectOfType(TypeName.DataView); - export const directInstanceOf = (instance: unknown, klass: Class): instance is T => Object.getPrototypeOf(instance) === klass.prototype; - export const urlInstance = (value: unknown): value is URL => isObjectOfType(TypeName.URL)(value); - - export const urlString = (value: unknown) => { - if (!string(value)) { - return false; - } - - try { - new URLGlobal(value); // tslint:disable-line no-unused-expression - return true; - } catch { - return false; - } - }; - - export const truthy = (value: unknown) => Boolean(value); - export const falsy = (value: unknown) => !value; - - export const nan = (value: unknown) => Number.isNaN(value as number); - - const primitiveTypes = new Set([ - 'undefined', - 'string', - 'number', - 'boolean', - 'symbol' - ]); - - export const primitive = (value: unknown): value is Primitive => null_(value) || primitiveTypes.has(typeof value); - - export const integer = (value: unknown): value is number => Number.isInteger(value as number); - export const safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); - - export const plainObject = (value: unknown) => { - // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js - let prototype; - - return getObjectType(value) === TypeName.Object && - (prototype = Object.getPrototypeOf(value), prototype === null || // tslint:disable-line:ban-comma-operator - prototype === Object.getPrototypeOf({})); - }; - - const typedArrayTypes = new Set([ - TypeName.Int8Array, - TypeName.Uint8Array, - TypeName.Uint8ClampedArray, - TypeName.Int16Array, - TypeName.Uint16Array, - TypeName.Int32Array, - TypeName.Uint32Array, - TypeName.Float32Array, - TypeName.Float64Array - ]); - export const typedArray = (value: unknown): value is TypedArray => { - const objectType = getObjectType(value); - - if (objectType === null) { - return false; - } - - return typedArrayTypes.has(objectType); - }; - - const isValidLength = (value: unknown) => safeInteger(value) && value > -1; - export const arrayLike = (value: unknown): value is ArrayLike => !nullOrUndefined(value) && !function_(value) && isValidLength((value as ArrayLike).length); - - export const inRange = (value: number, range: number | number[]) => { - if (number(range)) { - return value >= Math.min(0, range) && value <= Math.max(range, 0); - } - - if (array(range) && range.length === 2) { - return value >= Math.min(...range) && value <= Math.max(...range); - } - - throw new TypeError(`Invalid range: ${JSON.stringify(range)}`); - }; - - const NODE_TYPE_ELEMENT = 1; - const DOM_PROPERTIES_TO_CHECK = [ - 'innerHTML', - 'ownerDocument', - 'style', - 'attributes', - 'nodeValue' - ]; - - export const domElement = (value: unknown): value is DomElement => object(value) && (value as DomElement).nodeType === NODE_TYPE_ELEMENT && string((value as DomElement).nodeName) && - !plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in (value as DomElement)); - - export const observable = (value: unknown) => { - if (!value) { - return false; - } - - if ((value as any)[Symbol.observable] && value === (value as any)[Symbol.observable]()) { - return true; - } - - if ((value as any)['@@observable'] && value === (value as any)['@@observable']()) { - return true; - } +is.directInstanceOf = (instance: unknown, klass: Class): instance is T => Object.getPrototypeOf(instance) === klass.prototype; +is.urlInstance = (value: unknown): value is URL => isObjectOfType(TypeName.URL)(value); +is.urlString = (value: unknown) => { + if (!is.string(value)) { return false; - }; + } - export const nodeStream = (value: unknown): value is NodeStream => !nullOrUndefined(value) && isObject(value) as unknown && function_((value as NodeStream).pipe) && !observable(value); + try { + new URLGlobal(value); // tslint:disable-line no-unused-expression + return true; + } catch { + return false; + } +}; - export const infinite = (value: unknown) => value === Infinity || value === -Infinity; +is.truthy = (value: unknown) => Boolean(value); +is.falsy = (value: unknown) => !value; - const isAbsoluteMod2 = (rem: number) => (value: number) => integer(value) && Math.abs(value % 2) === rem; - export const even = isAbsoluteMod2(0); - export const odd = isAbsoluteMod2(1); +is.nan = (value: unknown) => Number.isNaN(value as number); - const isWhiteSpaceString = (value: unknown) => string(value) && /\S/.test(value) === false; +const primitiveTypes = new Set([ + 'undefined', + 'string', + 'number', + 'boolean', + 'symbol' +]); - export const emptyArray = (value: unknown) => array(value) && value.length === 0; - export const nonEmptyArray = (value: unknown) => array(value) && value.length > 0; +is.primitive = (value: unknown): value is Primitive => is.null_(value) || primitiveTypes.has(typeof value); - export const emptyString = (value: unknown) => string(value) && value.length === 0; - export const nonEmptyString = (value: unknown) => string(value) && value.length > 0; - export const emptyStringOrWhitespace = (value: unknown) => emptyString(value) || isWhiteSpaceString(value); +is.integer = (value: unknown): value is number => Number.isInteger(value as number); +is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); - export const emptyObject = (value: unknown) => object(value) && !map(value) && !set(value) && Object.keys(value).length === 0; - export const nonEmptyObject = (value: unknown) => object(value) && !map(value) && !set(value) && Object.keys(value).length > 0; +is.plainObject = (value: unknown) => { + // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js + let prototype; - export const emptySet = (value: unknown) => set(value) && value.size === 0; - export const nonEmptySet = (value: unknown) => set(value) && value.size > 0; + return getObjectType(value) === TypeName.Object && + (prototype = Object.getPrototypeOf(value), prototype === null || // tslint:disable-line:ban-comma-operator + prototype === Object.getPrototypeOf({})); +}; - export const emptyMap = (value: unknown) => map(value) && value.size === 0; - export const nonEmptyMap = (value: unknown) => map(value) && value.size > 0; +const typedArrayTypes = new Set([ + TypeName.Int8Array, + TypeName.Uint8Array, + TypeName.Uint8ClampedArray, + TypeName.Int16Array, + TypeName.Uint16Array, + TypeName.Int32Array, + TypeName.Uint32Array, + TypeName.Float32Array, + TypeName.Float64Array +]); +is.typedArray = (value: unknown): value is TypedArray => { + const objectType = getObjectType(value); - type ArrayMethod = (fn: (value: unknown, index: number, array: unknown[]) => boolean, thisArg?: unknown) => boolean; - const predicateOnArray = (method: ArrayMethod, predicate: unknown, values: unknown[]) => { - if (function_(predicate) === false) { - throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); - } + if (objectType === null) { + return false; + } - if (values.length === 0) { - throw new TypeError('Invalid number of values'); - } + return typedArrayTypes.has(objectType); +}; - return method.call(values, predicate as any); - }; +const isValidLength = (value: unknown) => is.safeInteger(value) && value > -1; +is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); - // tslint:disable variable-name - export const any = (predicate: unknown, ...values: unknown[]) => predicateOnArray(Array.prototype.some, predicate, values); - export const all = (predicate: unknown, ...values: unknown[]) => predicateOnArray(Array.prototype.every, predicate, values); - // tslint:enable variable-name -} +is.inRange = (value: number, range: number | number[]) => { + if (is.number(range)) { + return value >= Math.min(0, range) && value <= Math.max(range, 0); + } + + if (is.array(range) && range.length === 2) { + return value >= Math.min(...range) && value <= Math.max(...range); + } + + throw new TypeError(`Invalid range: ${JSON.stringify(range)}`); +}; + +const NODE_TYPE_ELEMENT = 1; +const DOM_PROPERTIES_TO_CHECK = [ + 'innerHTML', + 'ownerDocument', + 'style', + 'attributes', + 'nodeValue' +]; + +is.domElement = (value: unknown): value is DomElement => is.object(value) && (value as DomElement).nodeType === NODE_TYPE_ELEMENT && is.string((value as DomElement).nodeName) && + !is.plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in (value as DomElement)); + +is.observable = (value: unknown) => { + if (!value) { + return false; + } + + if ((value as any)[Symbol.observable] && value === (value as any)[Symbol.observable]()) { + return true; + } + + if ((value as any)['@@observable'] && value === (value as any)['@@observable']()) { + return true; + } + + return false; +}; + +is.nodeStream = (value: unknown): value is NodeStream => !is.nullOrUndefined(value) && isObject(value) as unknown && is.function_((value as NodeStream).pipe) && !is.observable(value); + +is.infinite = (value: unknown) => value === Infinity || value === -Infinity; + +const isAbsoluteMod2 = (rem: number) => (value: number) => is.integer(value) && Math.abs(value % 2) === rem; +is.even = isAbsoluteMod2(0); +is.odd = isAbsoluteMod2(1); + +const isWhiteSpaceString = (value: unknown) => is.string(value) && /\S/.test(value) === false; + +is.emptyArray = (value: unknown) => is.array(value) && value.length === 0; +is.nonEmptyArray = (value: unknown) => is.array(value) && value.length > 0; + +is.emptyString = (value: unknown) => is.string(value) && value.length === 0; +is.nonEmptyString = (value: unknown) => is.string(value) && value.length > 0; +is.emptyStringOrWhitespace = (value: unknown) => is.emptyString(value) || isWhiteSpaceString(value); + +is.emptyObject = (value: unknown) => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0; +is.nonEmptyObject = (value: unknown) => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length > 0; + +is.emptySet = (value: unknown) => is.set(value) && value.size === 0; +is.nonEmptySet = (value: unknown) => is.set(value) && value.size > 0; + +is.emptyMap = (value: unknown) => is.map(value) && value.size === 0; +is.nonEmptyMap = (value: unknown) => is.map(value) && value.size > 0; + +type ArrayMethod = (fn: (value: unknown, index: number, array: unknown[]) => boolean, thisArg?: unknown) => boolean; +const predicateOnArray = (method: ArrayMethod, predicate: unknown, values: unknown[]) => { + if (is.function_(predicate) === false) { + throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); + } + + if (values.length === 0) { + throw new TypeError('Invalid number of values'); + } + + return method.call(values, predicate as any); +}; + +// tslint:disable variable-name +is.any = (predicate: unknown, ...values: unknown[]) => predicateOnArray(Array.prototype.some, predicate, values); +is.all = (predicate: unknown, ...values: unknown[]) => predicateOnArray(Array.prototype.every, predicate, values); +// tslint:enable variable-name // Some few keywords are reserved, but we'll populate them for Node.js users // See https://github.com/Microsoft/TypeScript/issues/2536 From 250244240441d4141ae865b3ab4f5fe919318e8f Mon Sep 17 00:00:00 2001 From: Scottie Enriquez Date: Sat, 2 Feb 2019 00:22:23 -0600 Subject: [PATCH 086/254] Rename `.odd()` to `.oddInteger()` and `.even()` to `.evenInteger()` for clarity (#77) --- readme.md | 4 ++-- source/index.ts | 5 +++-- source/tests/test.ts | 12 ++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index 3621d4e..ed66af6 100644 --- a/readme.md +++ b/readme.md @@ -374,11 +374,11 @@ is.observable(new Observable()); Check if `value` is `Infinity` or `-Infinity`. -##### .even(value) +##### .evenInteger(value) Returns `true` if `value` is an even integer. -##### .odd(value) +##### .oddInteger(value) Returns `true` if `value` is an odd integer. diff --git a/source/index.ts b/source/index.ts index fce18c9..e6d1cff 100644 --- a/source/index.ts +++ b/source/index.ts @@ -239,6 +239,7 @@ const typedArrayTypes = new Set([ TypeName.Float32Array, TypeName.Float64Array ]); + is.typedArray = (value: unknown): value is TypedArray => { const objectType = getObjectType(value); @@ -297,8 +298,8 @@ is.nodeStream = (value: unknown): value is NodeStream => !is.nullOrUndefined(val is.infinite = (value: unknown) => value === Infinity || value === -Infinity; const isAbsoluteMod2 = (rem: number) => (value: number) => is.integer(value) && Math.abs(value % 2) === rem; -is.even = isAbsoluteMod2(0); -is.odd = isAbsoluteMod2(1); +is.evenInteger = isAbsoluteMod2(0); +is.oddInteger = isAbsoluteMod2(1); const isWhiteSpaceString = (value: unknown) => is.string(value) && /\S/.test(value) === false; diff --git a/source/tests/test.ts b/source/tests/test.ts index cc4353e..1fcd976 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -773,23 +773,23 @@ test('is.infinite', t => { testType(t, 'infinite', ['number']); }); -test('is.even', t => { +test('is.evenInteger', t => { for (const el of [-6, 2, 4]) { - t.true(is.even(el)); + t.true(is.evenInteger(el)); } for (const el of [-3, 1, 5]) { - t.false(is.even(el)); + t.false(is.evenInteger(el)); } }); -test('is.odd', t => { +test('is.oddInteger', t => { for (const el of [-5, 7, 13]) { - t.true(is.odd(el)); + t.true(is.oddInteger(el)); } for (const el of [-8, 8, 10]) { - t.false(is.odd(el)); + t.false(is.oddInteger(el)); } }); From 3c847be5a042334e994d1e18158e9b9118cf1fba Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 4 Feb 2019 02:13:23 +0700 Subject: [PATCH 087/254] Improve the TypeScript types (#80) --- source/index.ts | 113 ++++++++++++++++++++++++------------------- source/tests/test.ts | 4 +- tslint.json | 5 +- 3 files changed, 70 insertions(+), 52 deletions(-) diff --git a/source/index.ts b/source/index.ts index e6d1cff..60a29a2 100644 --- a/source/index.ts +++ b/source/index.ts @@ -7,18 +7,8 @@ // tslint:disable-next-line const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; -type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; -type Primitive = null | undefined | string | number | boolean | Symbol; - -export interface ArrayLike { - length: number; -} - export type Class = new(...args: any[]) => T; -type DomElement = object & { nodeType: 1; nodeName: string }; -type NodeStream = object & { pipe: Function }; - export const enum TypeName { null = 'null', boolean = 'boolean', @@ -58,11 +48,9 @@ export const enum TypeName { const toString = Object.prototype.toString; const isOfType = (type: string) => (value: unknown): value is T => typeof value === type; -const isBuffer = (input: unknown): input is Buffer => !is.nullOrUndefined(input) && !is.nullOrUndefined((input as Buffer).constructor) && is.function_((input as Buffer).constructor.isBuffer) && (input as Buffer).constructor.isBuffer(input); const getObjectType = (value: unknown): TypeName | null => { const objectName = toString.call(value).slice(8, -1); - if (objectName) { return objectName as TypeName; } @@ -72,7 +60,8 @@ const getObjectType = (value: unknown): TypeName | null => { const isObjectOfType = (type: TypeName) => (value: unknown): value is T => getObjectType(value) === type; -function is(value: unknown): TypeName { // tslint:disable-line:only-arrow-functions +// tslint:disable-next-line: no-use-before-declare +function is(value: unknown): TypeName { switch (value) { case null: return TypeName.null; @@ -102,11 +91,11 @@ function is(value: unknown): TypeName { // tslint:disable-line:only-arrow-functi return TypeName.Observable; } - if (Array.isArray(value)) { + if (is.array(value)) { return TypeName.Array; } - if (isBuffer(value)) { + if (is.buffer(value)) { return TypeName.Buffer; } @@ -122,25 +111,24 @@ function is(value: unknown): TypeName { // tslint:disable-line:only-arrow-functi return TypeName.Object; } -// tslint:disable-next-line:strict-type-predicates +// tslint:disable-next-line: strict-type-predicates const isObject = (value: unknown): value is object => typeof value === 'object'; is.undefined = isOfType('undefined'); is.string = isOfType('string'); is.number = isOfType('number'); is.function_ = isOfType('function'); -// tslint:disable-next-line:strict-type-predicates +// tslint:disable-next-line: strict-type-predicates is.null_ = (value: unknown): value is null => value === null; is.class_ = (value: unknown): value is Class => is.function_(value) && value.toString().startsWith('class '); is.boolean = (value: unknown): value is boolean => value === true || value === false; -is.symbol = isOfType('symbol'); -// tslint:enable:variable-name +is.symbol = isOfType('symbol'); -is.numericString = (value: unknown): boolean => +is.numericString = (value: unknown): value is string => is.string(value) && value.length > 0 && !Number.isNaN(Number(value)); is.array = Array.isArray; -is.buffer = isBuffer; +is.buffer = (value: unknown): value is Buffer => !is.nullOrUndefined(value) && !is.nullOrUndefined((value as Buffer).constructor) && is.function_((value as Buffer).constructor.isBuffer) && (value as Buffer).constructor.isBuffer(value); is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); is.object = (value: unknown): value is object => !is.nullOrUndefined(value) && (is.function_(value) || isObject(value)); @@ -188,7 +176,7 @@ is.dataView = isObjectOfType(TypeName.DataView); is.directInstanceOf = (instance: unknown, klass: Class): instance is T => Object.getPrototypeOf(instance) === klass.prototype; is.urlInstance = (value: unknown): value is URL => isObjectOfType(TypeName.URL)(value); -is.urlString = (value: unknown) => { +is.urlString = (value: unknown): value is string => { if (!is.string(value)) { return false; } @@ -201,12 +189,15 @@ is.urlString = (value: unknown) => { } }; +// TODO: Use the `not` operator with a type guard here when it's available. +// Example: `is.truthy = (value: unknown): value is (not false | not 0 | not '' | not undefined | not null) => Boolean(value);` is.truthy = (value: unknown) => Boolean(value); +// Example: `is.falsy = (value: unknown): value is (not true | 0 | '' | undefined | null) => Boolean(value);` is.falsy = (value: unknown) => !value; is.nan = (value: unknown) => Number.isNaN(value as number); -const primitiveTypes = new Set([ +const primitiveTypeOfTypes = new Set([ 'undefined', 'string', 'number', @@ -214,12 +205,15 @@ const primitiveTypes = new Set([ 'symbol' ]); -is.primitive = (value: unknown): value is Primitive => is.null_(value) || primitiveTypes.has(typeof value); +// TODO: This should be able to be `not object` when the `not` operator is out +export type Primitive = null | undefined | string | number | boolean | symbol; + +is.primitive = (value: unknown): value is Primitive => is.null_(value) || primitiveTypeOfTypes.has(typeof value); is.integer = (value: unknown): value is number => Number.isInteger(value as number); is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); -is.plainObject = (value: unknown) => { +is.plainObject = (value: unknown): value is {[key: string]: unknown} => { // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js let prototype; @@ -240,9 +234,10 @@ const typedArrayTypes = new Set([ TypeName.Float64Array ]); +export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; + is.typedArray = (value: unknown): value is TypedArray => { const objectType = getObjectType(value); - if (objectType === null) { return false; } @@ -250,10 +245,15 @@ is.typedArray = (value: unknown): value is TypedArray => { return typedArrayTypes.has(objectType); }; -const isValidLength = (value: unknown) => is.safeInteger(value) && value > -1; -is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); +export interface ArrayLike { + readonly length: number; + readonly [index: number]: T; +} -is.inRange = (value: number, range: number | number[]) => { +const isValidLength = (value: unknown) => is.safeInteger(value) && value >= 0; +is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); + +is.inRange = (value: number, range: number | number[]): value is number => { if (is.number(range)) { return value >= Math.min(0, range) && value <= Math.max(range, 0); } @@ -274,10 +274,15 @@ const DOM_PROPERTIES_TO_CHECK = [ 'nodeValue' ]; -is.domElement = (value: unknown): value is DomElement => is.object(value) && (value as DomElement).nodeType === NODE_TYPE_ELEMENT && is.string((value as DomElement).nodeName) && - !is.plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in (value as DomElement)); +is.domElement = (value: unknown): value is Element => is.object(value) && (value as Element).nodeType === NODE_TYPE_ELEMENT && is.string((value as Element).nodeName) && + !is.plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in (value as Element)); -is.observable = (value: unknown) => { +export interface ObservableLike { + subscribe(observer: (value: unknown) => void): void; + [Symbol.observable](): ObservableLike; +} + +is.observable = (value: unknown): value is ObservableLike => { if (!value) { return false; } @@ -293,34 +298,44 @@ is.observable = (value: unknown) => { return false; }; +export type NodeStream = object & {readonly pipe: Function}; + is.nodeStream = (value: unknown): value is NodeStream => !is.nullOrUndefined(value) && isObject(value) as unknown && is.function_((value as NodeStream).pipe) && !is.observable(value); -is.infinite = (value: unknown) => value === Infinity || value === -Infinity; +is.infinite = (value: unknown): value is number => value === Infinity || value === -Infinity; -const isAbsoluteMod2 = (rem: number) => (value: number) => is.integer(value) && Math.abs(value % 2) === rem; +const isAbsoluteMod2 = (rem: number) => (value: number): value is number => is.integer(value) && Math.abs(value % 2) === rem; is.evenInteger = isAbsoluteMod2(0); is.oddInteger = isAbsoluteMod2(1); +is.emptyArray = (value: unknown): value is never[] => is.array(value) && value.length === 0; +is.nonEmptyArray = (value: unknown): value is unknown[] => is.array(value) && value.length > 0; + +is.emptyString = (value: unknown): value is '' => is.string(value) && value.length === 0; + +// TODO: Use `not ''` when the `not` operator is available. +is.nonEmptyString = (value: unknown): value is string => is.string(value) && value.length > 0; + const isWhiteSpaceString = (value: unknown) => is.string(value) && /\S/.test(value) === false; +is.emptyStringOrWhitespace = (value: unknown): value is string => is.emptyString(value) || isWhiteSpaceString(value); -is.emptyArray = (value: unknown) => is.array(value) && value.length === 0; -is.nonEmptyArray = (value: unknown) => is.array(value) && value.length > 0; +is.emptyObject = (value: unknown): value is {[key: string]: never} => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0; -is.emptyString = (value: unknown) => is.string(value) && value.length === 0; -is.nonEmptyString = (value: unknown) => is.string(value) && value.length > 0; -is.emptyStringOrWhitespace = (value: unknown) => is.emptyString(value) || isWhiteSpaceString(value); +// TODO: Use `not` operator here to remove `Map` and `Set` from type guard: +// - https://github.com/Microsoft/TypeScript/pull/29317 +is.nonEmptyObject = (value: unknown): value is {[key: string]: unknown} => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length > 0; -is.emptyObject = (value: unknown) => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0; -is.nonEmptyObject = (value: unknown) => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length > 0; +is.emptySet = (value: unknown): value is Set => is.set(value) && value.size === 0; +is.nonEmptySet = (value: unknown): value is Set => is.set(value) && value.size > 0; -is.emptySet = (value: unknown) => is.set(value) && value.size === 0; -is.nonEmptySet = (value: unknown) => is.set(value) && value.size > 0; +is.emptyMap = (value: unknown): value is Map => is.map(value) && value.size === 0; +is.nonEmptyMap = (value: unknown): value is Map => is.map(value) && value.size > 0; -is.emptyMap = (value: unknown) => is.map(value) && value.size === 0; -is.nonEmptyMap = (value: unknown) => is.map(value) && value.size > 0; +export type Predicate = (value: unknown) => boolean; type ArrayMethod = (fn: (value: unknown, index: number, array: unknown[]) => boolean, thisArg?: unknown) => boolean; -const predicateOnArray = (method: ArrayMethod, predicate: unknown, values: unknown[]) => { + +const predicateOnArray = (method: ArrayMethod, predicate: Predicate, values: unknown[]) => { if (is.function_(predicate) === false) { throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); } @@ -329,12 +344,12 @@ const predicateOnArray = (method: ArrayMethod, predicate: unknown, values: unkno throw new TypeError('Invalid number of values'); } - return method.call(values, predicate as any); + return method.call(values, predicate); }; // tslint:disable variable-name -is.any = (predicate: unknown, ...values: unknown[]) => predicateOnArray(Array.prototype.some, predicate, values); -is.all = (predicate: unknown, ...values: unknown[]) => predicateOnArray(Array.prototype.every, predicate, values); +is.any = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.some, predicate, values); +is.all = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.every, predicate, values); // tslint:enable variable-name // Some few keywords are reserved, but we'll populate them for Node.js users diff --git a/source/tests/test.ts b/source/tests/test.ts index 1fcd976..8342b54 100644 --- a/source/tests/test.ts +++ b/source/tests/test.ts @@ -867,7 +867,7 @@ test('is.any', t => { t.false(is.any(is.integer, true, 'lol', {})); t.throws(() => { - is.any(null, true); + is.any(null as any, true); }); t.throws(() => { @@ -882,7 +882,7 @@ test('is.all', t => { t.false(is.all(is.set, new Map(), {})); t.throws(() => { - is.all(null, true); + is.all(null as any, true); }); t.throws(() => { diff --git a/tslint.json b/tslint.json index 55e9f36..6f6ad0e 100644 --- a/tslint.json +++ b/tslint.json @@ -1,3 +1,6 @@ { - "extends": "tslint-xo" + "extends": "tslint-xo", + "rules": { + "interface-over-type-literal": false + } } From 9683cd7fd95bb471ec6f7f8076f981fdbc79509f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 4 Feb 2019 02:37:14 +0700 Subject: [PATCH 088/254] Run TS as a require hook for tests instead of precompile --- package.json | 27 +++++++++++++++++++-------- {source/tests => test}/test.ts | 4 ++-- tsconfig.json | 5 ++++- 3 files changed, 25 insertions(+), 11 deletions(-) rename {source/tests => test}/test.ts (99%) diff --git a/package.json b/package.json index bf0d9fe..ce688bb 100644 --- a/package.json +++ b/package.json @@ -9,15 +9,15 @@ "email": "sindresorhus@gmail.com", "url": "sindresorhus.com" }, - "main": "dist/index.js", + "main": "dist", "engines": { "node": ">=6" }, "scripts": { "lint": "tslint --format stylish --project .", "build": "del dist && tsc", - "test": "npm run lint && npm run build && ava dist/tests", - "prepublish": "npm run build && del dist/tests" + "test": "npm run lint && ava", + "prepublishOnly": "npm run build" }, "files": [ "dist" @@ -46,18 +46,29 @@ "devDependencies": { "@sindresorhus/tsconfig": "^0.1.0", "@types/jsdom": "^11.12.0", - "@types/node": "^10.12.10", + "@types/node": "^10.12.21", "@types/tempy": "^0.2.0", "@types/zen-observable": "^0.8.0", - "ava": "^0.25.0", + "ava": "^1.2.0", "del-cli": "^1.1.0", "jsdom": "^11.6.2", - "rxjs": "^6.3.3", + "rxjs": "^6.4.0", "tempy": "^0.2.1", + "ts-node": "^8.0.2", "tslint": "^5.9.1", "tslint-xo": "^0.10.0", - "typescript": "^3.2.1", + "typescript": "^3.3.1", "zen-observable": "^0.8.8" }, - "types": "dist/index.d.ts" + "types": "dist", + "ava": { + "babel": false, + "compileEnhancements": false, + "extensions": [ + "ts" + ], + "require": [ + "ts-node/register" + ] + } } diff --git a/source/tests/test.ts b/test/test.ts similarity index 99% rename from source/tests/test.ts rename to test/test.ts index 8342b54..4dbf999 100644 --- a/source/tests/test.ts +++ b/test/test.ts @@ -4,7 +4,7 @@ import Stream from 'stream'; import util from 'util'; import tempy from 'tempy'; import {URL} from 'url'; -import test, {TestContext, Context} from 'ava'; +import test, {ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; import ZenObservable from 'zen-observable'; @@ -377,7 +377,7 @@ const types = new Map([ ]); // This ensures a certain method matches only the types it's supposed to and none of the other methods' types -const testType = (t: TestContext & Context, type: string, exclude?: string[]) => { +const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { const testData = types.get(type); if (testData === undefined) { diff --git a/tsconfig.json b/tsconfig.json index 5f5c71e..058d3a0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,5 +9,8 @@ "esnext.asynciterable", "dom" ] - } + }, + "include": [ + "source" + ] } From 2f5e03bed2fbf83ac4079ee825b7d8c2c5caa8ba Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 4 Feb 2019 02:44:17 +0700 Subject: [PATCH 089/254] Minor tweaks --- source/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/index.ts b/source/index.ts index 60a29a2..fdd3edc 100644 --- a/source/index.ts +++ b/source/index.ts @@ -49,13 +49,13 @@ export const enum TypeName { const toString = Object.prototype.toString; const isOfType = (type: string) => (value: unknown): value is T => typeof value === type; -const getObjectType = (value: unknown): TypeName | null => { +const getObjectType = (value: unknown): TypeName | undefined => { const objectName = toString.call(value).slice(8, -1); if (objectName) { return objectName as TypeName; } - return null; + return; }; const isObjectOfType = (type: TypeName) => (value: unknown): value is T => getObjectType(value) === type; @@ -238,7 +238,7 @@ export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array is.typedArray = (value: unknown): value is TypedArray => { const objectType = getObjectType(value); - if (objectType === null) { + if (objectType === undefined) { return false; } From 3ec41686f710fe1e5291b5ca172b5518f488168c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 4 Feb 2019 02:55:36 +0700 Subject: [PATCH 090/254] 0.15.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce688bb..c588708 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.14.0", + "version": "0.15.0", "description": "Type check values: `is.string('🦄') //=> true`", "license": "MIT", "repository": "sindresorhus/is", From 0fff8265e6c5ab2f28bb6f7d3774610093dfc515 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 4 Feb 2019 03:00:25 +0700 Subject: [PATCH 091/254] Fix tests --- test/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.ts b/test/test.ts index 4dbf999..4097b4a 100644 --- a/test/test.ts +++ b/test/test.ts @@ -8,7 +8,7 @@ import test, {ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; import ZenObservable from 'zen-observable'; -import is from '..'; +import is from '../source'; const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; const isNode10orHigher = Number(process.versions.node.split('.')[0]) >= 10; From c66885b781862d578a8662995d01614ffcdf3497 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 4 Feb 2019 12:49:52 +0700 Subject: [PATCH 092/254] Add `sideEffects` key to package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c588708..619844e 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "compare" ], "devDependencies": { - "@sindresorhus/tsconfig": "^0.1.0", + "@sindresorhus/tsconfig": "^0.2.0", "@types/jsdom": "^11.12.0", "@types/node": "^10.12.21", "@types/tempy": "^0.2.0", @@ -61,6 +61,7 @@ "zen-observable": "^0.8.8" }, "types": "dist", + "sideEffects": false, "ava": { "babel": false, "compileEnhancements": false, From 79144f95425e75a601e208d83cc0eb6c8ad7565d Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 12 Feb 2019 14:40:44 +0700 Subject: [PATCH 093/254] Readme tweaks --- package.json | 7 ++++-- readme.md | 67 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 619844e..cd35b89 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@sindresorhus/is", "version": "0.15.0", - "description": "Type check values: `is.string('🦄') //=> true`", + "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", "author": { @@ -41,7 +41,10 @@ "kind", "primitive", "verify", - "compare" + "compare", + "typescript", + "typeguards", + "types" ], "devDependencies": { "@sindresorhus/tsconfig": "^0.2.0", diff --git a/readme.md b/readme.md index ed66af6..848a172 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,20 @@ # is [![Build Status](https://travis-ci.org/sindresorhus/is.svg?branch=master)](https://travis-ci.org/sindresorhus/is) -> Type check values: `is.string('🦄') //=> true` +> Type check values + +For example, `is.string('🦄') //=> true` +## Highlights + +- Written in TypeScript +- [Extensive use of type guards](#type-guards) +- Actively maintained +- 2 million weekly downloads + + ## Install ``` @@ -27,32 +37,6 @@ is.number(6); //=> true ``` -When using `is` together with TypeScript, [type guards](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types) are being used to infer the correct type inside if-else statements. - -```ts -import is from '@sindresorhus/is'; - -const padLeft = (value: string, padding: string | number) => { - if (is.number(padding)) { - // `padding` is typed as `number` - return Array(padding + 1).join(' ') + value; - } - - if (is.string(padding)) { - // `padding` is typed as `string` - return padding + value; - } - - throw new TypeError(`Expected 'padding' to be of type 'string' or 'number', got '${is(padding)}'.`); -} - -padLeft('🦄', 3); -//=> ' 🦄' - -padLeft('🦄', '🌈'); -//=> '🌈🦄' -``` - ## API @@ -407,6 +391,35 @@ is.all(is.string, '🦄', [], 'unicorns'); ``` +## Type guards + +When using `is` together with TypeScript, [type guards](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types) are being used extensively to infer the correct type inside if-else statements. + +```ts +import is from '@sindresorhus/is'; + +const padLeft = (value: string, padding: string | number) => { + if (is.number(padding)) { + // `padding` is typed as `number` + return Array(padding + 1).join(' ') + value; + } + + if (is.string(padding)) { + // `padding` is typed as `string` + return padding + value; + } + + throw new TypeError(`Expected 'padding' to be of type 'string' or 'number', got '${is(padding)}'.`); +} + +padLeft('🦄', 3); +//=> ' 🦄' + +padLeft('🦄', '🌈'); +//=> '🌈🦄' +``` + + ## FAQ ### Why yet another type checking module? From 28913cae88026e785658256c0d89eaf3c8101fd6 Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Thu, 14 Mar 2019 17:32:19 +0800 Subject: [PATCH 094/254] Drop duplicate export (#84) --- source/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/source/index.ts b/source/index.ts index fdd3edc..cfd16ed 100644 --- a/source/index.ts +++ b/source/index.ts @@ -366,8 +366,5 @@ Object.defineProperties(is, { } }); +module.exports = is; // For CommonJS default export support export default is; - -// For CommonJS default export support -module.exports = is; -module.exports.default = is; From ea4204f0b46964600a9f6e6ca3c9fc078b1e97a5 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 31 Mar 2019 20:41:19 +0700 Subject: [PATCH 095/254] Switch to XO for linting --- package.json | 27 +++++++++++------- source/index.ts | 43 ++++++++++++++++------------ test/test.ts | 74 +++++++++++++++++++++++++------------------------ tslint.json | 6 ---- 4 files changed, 81 insertions(+), 69 deletions(-) delete mode 100644 tslint.json diff --git a/package.json b/package.json index cd35b89..f45f2f3 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,8 @@ "node": ">=6" }, "scripts": { - "lint": "tslint --format stylish --project .", "build": "del dist && tsc", - "test": "npm run lint && ava", + "test": "xo && ava", "prepublishOnly": "npm run build" }, "files": [ @@ -47,20 +46,19 @@ "types" ], "devDependencies": { - "@sindresorhus/tsconfig": "^0.2.0", - "@types/jsdom": "^11.12.0", - "@types/node": "^10.12.21", + "@sindresorhus/tsconfig": "^0.3.0", + "@types/jsdom": "^12.2.3", + "@types/node": "^11.12.2", "@types/tempy": "^0.2.0", "@types/zen-observable": "^0.8.0", - "ava": "^1.2.0", + "@typescript-eslint/eslint-plugin": "^1.5.0", + "ava": "^1.4.1", "del-cli": "^1.1.0", "jsdom": "^11.6.2", "rxjs": "^6.4.0", "tempy": "^0.2.1", - "ts-node": "^8.0.2", - "tslint": "^5.9.1", - "tslint-xo": "^0.10.0", - "typescript": "^3.3.1", + "ts-node": "^8.0.3", + "typescript": "^3.4.1", "zen-observable": "^0.8.8" }, "types": "dist", @@ -74,5 +72,14 @@ "require": [ "ts-node/register" ] + }, + "xo": { + "extends": "xo-typescript", + "extensions": [ + "ts" + ], + "rules": { + "@typescript-eslint/explicit-function-return-type": "off" + } } } diff --git a/source/index.ts b/source/index.ts index cfd16ed..64943ab 100644 --- a/source/index.ts +++ b/source/index.ts @@ -4,10 +4,10 @@ /// // TODO: Use the `URL` global when targeting Node.js 10 -// tslint:disable-next-line +// eslint-disable-next-line @typescript-eslint/no-require-imports const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; -export type Class = new(...args: any[]) => T; +export type Class = new (...args: any[]) => T; export const enum TypeName { null = 'null', @@ -46,7 +46,7 @@ export const enum TypeName { URL = 'URL' } -const toString = Object.prototype.toString; +const {toString} = Object.prototype; const isOfType = (type: string) => (value: unknown): value is T => typeof value === type; const getObjectType = (value: unknown): TypeName | undefined => { @@ -55,12 +55,11 @@ const getObjectType = (value: unknown): TypeName | undefined => { return objectName as TypeName; } - return; + return undefined; }; const isObjectOfType = (type: TypeName) => (value: unknown): value is T => getObjectType(value) === type; -// tslint:disable-next-line: no-use-before-declare function is(value: unknown): TypeName { switch (value) { case null: @@ -111,14 +110,15 @@ function is(value: unknown): TypeName { return TypeName.Object; } -// tslint:disable-next-line: strict-type-predicates const isObject = (value: unknown): value is object => typeof value === 'object'; is.undefined = isOfType('undefined'); is.string = isOfType('string'); is.number = isOfType('number'); + +// eslint-disable-next-line @typescript-eslint/ban-types is.function_ = isOfType('function'); -// tslint:disable-next-line: strict-type-predicates + is.null_ = (value: unknown): value is null => value === null; is.class_ = (value: unknown): value is Class => is.function_(value) && value.toString().startsWith('class '); is.boolean = (value: unknown): value is boolean => value === true || value === false; @@ -133,7 +133,10 @@ is.buffer = (value: unknown): value is Buffer => !is.nullOrUndefined(value) && ! is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); is.object = (value: unknown): value is object => !is.nullOrUndefined(value) && (is.function_(value) || isObject(value)); is.iterable = (value: unknown): value is IterableIterator => !is.nullOrUndefined(value) && is.function_((value as IterableIterator)[Symbol.iterator]); + +// eslint-disable-next-line no-use-extend-native/no-use-extend-native is.asyncIterable = (value: unknown): value is AsyncIterableIterator => !is.nullOrUndefined(value) && is.function_((value as AsyncIterableIterator)[Symbol.asyncIterator]); + is.generator = (value: unknown): value is Generator => is.iterable(value) && is.function_(value.next) && is.function_(value.throw); is.nativePromise = (value: unknown): value is Promise => @@ -148,7 +151,11 @@ const hasPromiseAPI = (value: unknown): value is Promise => is.promise = (value: unknown): value is Promise => is.nativePromise(value) || hasPromiseAPI(value); is.generatorFunction = isObjectOfType(TypeName.GeneratorFunction); + +// eslint-disable-next-line @typescript-eslint/ban-types is.asyncFunction = isObjectOfType(TypeName.AsyncFunction); + +// eslint-disable-next-line no-prototype-builtins, @typescript-eslint/ban-types is.boundFunction = (value: unknown): value is Function => is.function_(value) && !value.hasOwnProperty('prototype'); is.regExp = isObjectOfType(TypeName.RegExp); @@ -182,7 +189,7 @@ is.urlString = (value: unknown): value is string => { } try { - new URLGlobal(value); // tslint:disable-line no-unused-expression + new URLGlobal(value); // eslint-disable-line no-new return true; } catch { return false; @@ -215,11 +222,13 @@ is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value is.plainObject = (value: unknown): value is {[key: string]: unknown} => { // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js - let prototype; + if (getObjectType(value) !== TypeName.Object) { + return false; + } - return getObjectType(value) === TypeName.Object && - (prototype = Object.getPrototypeOf(value), prototype === null || // tslint:disable-line:ban-comma-operator - prototype === Object.getPrototypeOf({})); + const prototype = Object.getPrototypeOf(value); + + return prototype === null || prototype === Object.getPrototypeOf({}); }; const typedArrayTypes = new Set([ @@ -250,7 +259,7 @@ export interface ArrayLike { readonly [index: number]: T; } -const isValidLength = (value: unknown) => is.safeInteger(value) && value >= 0; +const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0; is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); is.inRange = (value: number, range: number | number[]): value is number => { @@ -287,6 +296,7 @@ is.observable = (value: unknown): value is ObservableLike => { return false; } + // eslint-disable-next-line no-use-extend-native/no-use-extend-native if ((value as any)[Symbol.observable] && value === (value as any)[Symbol.observable]()) { return true; } @@ -298,13 +308,14 @@ is.observable = (value: unknown): value is ObservableLike => { return false; }; +// eslint-disable-next-line @typescript-eslint/ban-types export type NodeStream = object & {readonly pipe: Function}; is.nodeStream = (value: unknown): value is NodeStream => !is.nullOrUndefined(value) && isObject(value) as unknown && is.function_((value as NodeStream).pipe) && !is.observable(value); is.infinite = (value: unknown): value is number => value === Infinity || value === -Infinity; -const isAbsoluteMod2 = (rem: number) => (value: number): value is number => is.integer(value) && Math.abs(value % 2) === rem; +const isAbsoluteMod2 = (remainder: number) => (value: number): value is number => is.integer(value) && Math.abs(value % 2) === remainder; is.evenInteger = isAbsoluteMod2(0); is.oddInteger = isAbsoluteMod2(1); @@ -316,7 +327,7 @@ is.emptyString = (value: unknown): value is '' => is.string(value) && value.leng // TODO: Use `not ''` when the `not` operator is available. is.nonEmptyString = (value: unknown): value is string => is.string(value) && value.length > 0; -const isWhiteSpaceString = (value: unknown) => is.string(value) && /\S/.test(value) === false; +const isWhiteSpaceString = (value: unknown): value is string => is.string(value) && /\S/.test(value) === false; is.emptyStringOrWhitespace = (value: unknown): value is string => is.emptyString(value) || isWhiteSpaceString(value); is.emptyObject = (value: unknown): value is {[key: string]: never} => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0; @@ -347,10 +358,8 @@ const predicateOnArray = (method: ArrayMethod, predicate: Predicate, values: unk return method.call(values, predicate); }; -// tslint:disable variable-name is.any = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.some, predicate, values); is.all = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.every, predicate, values); -// tslint:enable variable-name // Some few keywords are reserved, but we'll populate them for Node.js users // See https://github.com/Microsoft/TypeScript/issues/2536 diff --git a/test/test.ts b/test/test.ts index 4097b4a..d25d28b 100644 --- a/test/test.ts +++ b/test/test.ts @@ -2,8 +2,8 @@ import fs from 'fs'; import net from 'net'; import Stream from 'stream'; import util from 'util'; -import tempy from 'tempy'; import {URL} from 'url'; +import tempy from 'tempy'; import test, {ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; @@ -88,26 +88,24 @@ const types = new Map([ is: is.array, fixtures: [ [1, 2], - new Array(2) // tslint:disable-line:prefer-array-literal + new Array(2) ] }], ['emptyArray', { is: is.emptyArray, fixtures: [ [], - new Array() + new Array() // eslint-disable-line @typescript-eslint/no-array-constructor ] }], ['function', { is: is.function_, fixtures: [ - // tslint:disable:no-unused no-empty no-unused-variable only-arrow-functions no-function-expression - function foo() {}, + function foo() {}, // eslint-disable-line func-names function () {}, () => {}, async function () {}, function * (): unknown {} - // tslint:enable:no-unused no-empty no-unused-variable only-arrow-functions no-function-expression ] }], ['buffer', { @@ -153,7 +151,7 @@ const types = new Map([ ['promise', { is: is.promise, fixtures: [ - {then() {}, catch() {}} // tslint:disable-line:no-empty + {then() {}, catch() {}} ] }], ['generator', { @@ -175,27 +173,27 @@ const types = new Map([ ['asyncFunction', { is: is.asyncFunction, fixtures: [ - async function () {}, // tslint:disable-line:no-empty only-arrow-functions no-function-expression - async () => {} // tslint:disable-line:no-empty + async function () {}, + async () => {} ] }], ['boundFunction', { is: is.boundFunction, fixtures: [ - () => {}, // tslint:disable-line:no-empty - function () {}.bind(null), // tslint:disable-line:no-empty only-arrow-functions + () => {}, + function () {}.bind(null) // eslint-disable-line no-extra-bind ] }], ['map', { is: is.map, fixtures: [ - new Map([['one', '1']]), + new Map([['one', '1']]) ] }], ['emptyMap', { is: is.emptyMap, fixtures: [ - new Map(), + new Map() ] }], ['set', { @@ -207,7 +205,7 @@ const types = new Map([ ['emptySet', { is: is.emptySet, fixtures: [ - new Set(), + new Set() ] }], ['weakSet', { @@ -307,7 +305,7 @@ const types = new Map([ fixtures: [ {x: 1}, Object.create(null), - new Object() + new Object() // eslint-disable-line no-new-object ] }], ['integer', { @@ -319,8 +317,8 @@ const types = new Map([ ['safeInteger', { is: is.safeInteger, fixtures: [ - Math.pow(2, 53) - 1, - -Math.pow(2, 53) + 1 + (2 ** 53) - 1, + -(2 ** 53) + 1 ] }], ['domElement', { @@ -332,8 +330,8 @@ const types = new Map([ 'img', 'canvas', 'script' - ].map(createDomElement) } - ], + ].map(createDomElement) + }], ['non-domElements', { is: value => !is.domElement(value), fixtures: [ @@ -350,7 +348,7 @@ const types = new Map([ fixtures: [ new Observable(), new Subject(), - new ZenObservable(() => {}) // tslint:disable-line:no-empty + new ZenObservable(() => {}) ] }], ['nodeStream', { @@ -449,7 +447,7 @@ test('is.function', t => { }); test('is.boundFunction', t => { - t.false(is.boundFunction(function () {})); // tslint:disable-line:no-empty only-arrow-functions + t.false(is.boundFunction(function () {})); // eslint-disable-line prefer-arrow-callback }); test('is.buffer', t => { @@ -491,9 +489,11 @@ if (isNode8orHigher) { testType(t, 'promise', ['nativePromise']); }); - /*test('is.asyncFunction', t => { + /*- + test('is.asyncFunction', t => { testType(t, 'asyncFunction', ['function']); - });*/ + }); + */ } test('is.generator', t => { @@ -642,8 +642,8 @@ test('is.integer', t => { test('is.safeInteger', t => { testType(t, 'safeInteger', ['number', 'integer']); - t.false(is.safeInteger(Math.pow(2, 53))); - t.false(is.safeInteger(-Math.pow(2, 53))); + t.false(is.safeInteger(2 ** 53)); + t.false(is.safeInteger(-(2 ** 53))); }); test('is.plainObject', t => { @@ -665,7 +665,7 @@ test('is.iterable', t => { if (isNode10orHigher) { test('is.asyncIterable', t => { t.true(is.asyncIterable({ - [Symbol.asyncIterator]: () => {} // tslint:disable-line:no-empty + [Symbol.asyncIterator]: () => {} })); t.false(is.asyncIterable(null)); @@ -678,14 +678,15 @@ if (isNode10orHigher) { } test('is.class', t => { - class Foo {} // tslint:disable-line + class Foo {} // eslint-disable-line @typescript-eslint/no-extraneous-class + const classDeclarations = [ Foo, - class Bar extends Foo {} // tslint:disable-line + class Bar extends Foo {} ]; - for (const x of classDeclarations) { - t.true(is.class_(x)); + for (const classDeclaration of classDeclarations) { + t.true(is.class_(classDeclaration)); } }); @@ -714,14 +715,15 @@ test('is.typedArray', t => { }); test('is.arrayLike', t => { - (function () { // tslint:disable-line:only-arrow-functions - t.true(is.arrayLike(arguments)); + (function () { + t.true(is.arrayLike(arguments)); // eslint-disable-line prefer-rest-params })(); + t.true(is.arrayLike([])); t.true(is.arrayLike('unicorn')); t.false(is.arrayLike({})); - t.false(is.arrayLike(() => {})); // tslint:disable-line:no-empty + t.false(is.arrayLike(() => {})); t.false(is.arrayLike(new Map())); }); @@ -800,7 +802,7 @@ test('is.emptyArray', t => { test('is.nonEmptyArray', t => { t.true(is.nonEmptyArray([1, 2, 3])); t.false(is.nonEmptyArray([])); - t.false(is.nonEmptyArray(new Array())); + t.false(is.nonEmptyArray(new Array())); // eslint-disable-line @typescript-eslint/no-array-constructor }); test('is.emptyString', t => { @@ -823,7 +825,7 @@ test('is.emptyStringOrWhitespace', t => { test('is.emptyObject', t => { t.true(is.emptyObject({})); - t.true(is.emptyObject(new Object())); + t.true(is.emptyObject(new Object())); // eslint-disable-line no-new-object t.false(is.emptyObject({unicorn: '🦄'})); }); @@ -832,7 +834,7 @@ test('is.nonEmptyObject', t => { is.nonEmptyObject(foo); t.false(is.nonEmptyObject({})); - t.false(is.nonEmptyObject(new Object())); + t.false(is.nonEmptyObject(new Object())); // eslint-disable-line no-new-object t.true(is.nonEmptyObject({unicorn: '🦄'})); }); diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 6f6ad0e..0000000 --- a/tslint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "tslint-xo", - "rules": { - "interface-over-type-literal": false - } -} From 120f74ab631fc030b27b1acdac18673e97f5d5ae Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 31 Mar 2019 21:50:37 +0700 Subject: [PATCH 096/254] Require Node.js 8 --- .travis.yml | 1 - package.json | 4 +++- source/index.ts | 7 +++--- test/test.ts | 60 +++++++++++++++++++++---------------------------- tsconfig.json | 5 ++--- 5 files changed, 34 insertions(+), 43 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2ae9d62..f3fa8cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,3 @@ language: node_js node_js: - '10' - '8' - - '6' diff --git a/package.json b/package.json index f45f2f3..e34361a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "main": "dist", "engines": { - "node": ">=6" + "node": ">=8" }, "scripts": { "build": "del dist && tsc", @@ -54,11 +54,13 @@ "@typescript-eslint/eslint-plugin": "^1.5.0", "ava": "^1.4.1", "del-cli": "^1.1.0", + "eslint-config-xo-typescript": "^0.9.0", "jsdom": "^11.6.2", "rxjs": "^6.4.0", "tempy": "^0.2.1", "ts-node": "^8.0.3", "typescript": "^3.4.1", + "xo": "^0.24.0", "zen-observable": "^0.8.8" }, "types": "dist", diff --git a/source/index.ts b/source/index.ts index 64943ab..9f9886e 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,5 +1,4 @@ -/// -/// +/// /// /// @@ -145,7 +144,7 @@ is.nativePromise = (value: unknown): value is Promise => const hasPromiseAPI = (value: unknown): value is Promise => !is.null_(value) && isObject(value) as unknown && - is.function_((value as Promise).then) && + is.function_((value as Promise).then) && // eslint-disable-line promise/prefer-await-to-then is.function_((value as Promise).catch); is.promise = (value: unknown): value is Promise => is.nativePromise(value) || hasPromiseAPI(value); @@ -180,7 +179,7 @@ is.arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); is.sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); is.dataView = isObjectOfType(TypeName.DataView); -is.directInstanceOf = (instance: unknown, klass: Class): instance is T => Object.getPrototypeOf(instance) === klass.prototype; +is.directInstanceOf = (instance: unknown, class_: Class): instance is T => Object.getPrototypeOf(instance) === class_.prototype; is.urlInstance = (value: unknown): value is URL => isObjectOfType(TypeName.URL)(value); is.urlString = (value: unknown): value is string => { diff --git a/test/test.ts b/test/test.ts index d25d28b..f34e245 100644 --- a/test/test.ts +++ b/test/test.ts @@ -10,7 +10,6 @@ import {Subject, Observable} from 'rxjs'; import ZenObservable from 'zen-observable'; import is from '../source'; -const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8; const isNode10orHigher = Number(process.versions.node.split('.')[0]) >= 10; class PromiseSubclassFixture extends Promise {} @@ -223,55 +222,55 @@ const types = new Map([ ['int8Array', { is: is.int8Array, fixtures: [ - new Int8Array(0) + new Int8Array() ] }], ['uint8Array', { is: is.uint8Array, fixtures: [ - new Uint8Array(0) + new Uint8Array() ] }], ['uint8ClampedArray', { is: is.uint8ClampedArray, fixtures: [ - new Uint8ClampedArray(0) + new Uint8ClampedArray() ] }], ['int16Array', { is: is.int16Array, fixtures: [ - new Int16Array(0) + new Int16Array() ] }], ['uint16Array', { is: is.uint16Array, fixtures: [ - new Uint16Array(0) + new Uint16Array() ] }], ['int32Array', { is: is.int32Array, fixtures: [ - new Int32Array(0) + new Int32Array() ] }], ['uint32Array', { is: is.uint32Array, fixtures: [ - new Uint32Array(0) + new Uint32Array() ] }], ['float32Array', { is: is.float32Array, fixtures: [ - new Float32Array(0) + new Float32Array() ] }], ['float64Array', { is: is.float64Array, fixtures: [ - new Float64Array(0) + new Float64Array() ] }], ['arrayBuffer', { @@ -480,21 +479,17 @@ test('is.error', t => { testType(t, 'error'); }); -if (isNode8orHigher) { - test('is.nativePromise', t => { - testType(t, 'nativePromise'); - }); +test('is.nativePromise', t => { + testType(t, 'nativePromise'); +}); - test('is.promise', t => { - testType(t, 'promise', ['nativePromise']); - }); +test('is.promise', t => { + testType(t, 'promise', ['nativePromise']); +}); - /*- - test('is.asyncFunction', t => { - testType(t, 'asyncFunction', ['function']); - }); - */ -} +test('is.asyncFunction', t => { + testType(t, 'asyncFunction', ['function']); +}); test('is.generator', t => { testType(t, 'generator'); @@ -691,18 +686,15 @@ test('is.class', t => { }); test('is.typedArray', t => { - // TypeScript currently does not support empty constructors for these - // See https://github.com/Microsoft/TypeScript/issues/19680 - // TODO: Remove the `0` when targeting `es2017` (Node.js 8), in other places of this file too. const typedArrays = [ - new Int8Array(0), - new Uint8Array(0), - new Uint8ClampedArray(0), - new Uint16Array(0), - new Int32Array(0), - new Uint32Array(0), - new Float32Array(0), - new Float64Array(0) + new Int8Array(), + new Uint8Array(), + new Uint8ClampedArray(), + new Uint16Array(), + new Int32Array(), + new Uint32Array(), + new Float32Array(), + new Float64Array() ]; for (const item of typedArrays) { diff --git a/tsconfig.json b/tsconfig.json index 058d3a0..c76ee17 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,10 +2,9 @@ "extends": "@sindresorhus/tsconfig", "compilerOptions": { "outDir": "dist", - "target": "es2016", + "target": "es2017", "lib": [ - "es2016", - "es2017.sharedmemory", + "es2017", "esnext.asynciterable", "dom" ] From 373605e40d85aa5eaa707699568c2cb95cc8b081 Mon Sep 17 00:00:00 2001 From: Adam Babcock Date: Sun, 28 Apr 2019 03:15:11 -0500 Subject: [PATCH 097/254] Test coverage for `is(value)` (#86) --- source/index.ts | 1 + test/test.ts | 171 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 116 insertions(+), 56 deletions(-) diff --git a/source/index.ts b/source/index.ts index 9f9886e..a819a5d 100644 --- a/source/index.ts +++ b/source/index.ts @@ -16,6 +16,7 @@ export const enum TypeName { number = 'number', symbol = 'symbol', Function = 'Function', + Generator = 'Generator', GeneratorFunction = 'GeneratorFunction', AsyncFunction = 'AsyncFunction', Observable = 'Observable', diff --git a/test/test.ts b/test/test.ts index f34e245..363d647 100644 --- a/test/test.ts +++ b/test/test.ts @@ -8,7 +8,7 @@ import test, {ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; import ZenObservable from 'zen-observable'; -import is from '../source'; +import is, {TypeName} from '../source'; const isNode10orHigher = Number(process.versions.node.split('.')[0]) >= 10; @@ -21,6 +21,7 @@ const createDomElement = (element: string) => document.createElement(element); interface Test { fixtures: unknown[]; + typename?: TypeName; is(value: unknown): boolean; } @@ -29,13 +30,15 @@ const types = new Map([ is: is.undefined, fixtures: [ undefined - ] + ], + typename: TypeName.undefined }], ['null', { is: is.null_, fixtures: [ null - ] + ], + typename: TypeName.null }], ['string', { is: is.string, @@ -43,14 +46,16 @@ const types = new Map([ '🦄', 'hello world', '' - ] + ], + typename: TypeName.string }], ['emptyString', { is: is.emptyString, fixtures: [ '', String() - ] + ], + typename: TypeName.string }], ['number', { is: is.number, @@ -61,19 +66,22 @@ const types = new Map([ -0, Infinity, -Infinity - ] + ], + typename: TypeName.number }], ['boolean', { is: is.boolean, fixtures: [ true, false - ] + ], + typename: TypeName.boolean }], ['symbol', { is: is.symbol, fixtures: [ Symbol('🦄') - ] + ], + typename: TypeName.symbol }], ['numericString', { is: is.numericString, @@ -81,21 +89,24 @@ const types = new Map([ '5', '-3.2', 'Infinity' - ] + ], + typename: TypeName.string }], ['array', { is: is.array, fixtures: [ [1, 2], new Array(2) - ] + ], + typename: TypeName.Array }], ['emptyArray', { is: is.emptyArray, fixtures: [ [], new Array() // eslint-disable-line @typescript-eslint/no-array-constructor - ] + ], + typename: TypeName.Array }], ['function', { is: is.function_, @@ -105,53 +116,61 @@ const types = new Map([ () => {}, async function () {}, function * (): unknown {} - ] + ], + typename: TypeName.Function }], ['buffer', { is: is.buffer, fixtures: [ Buffer.from('🦄') - ] + ], + typename: TypeName.Buffer }], ['object', { is: is.object, fixtures: [ {x: 1}, Object.create({x: 1}) - ] + ], + typename: TypeName.Object }], ['regExp', { is: is.regExp, fixtures: [ /\w/, new RegExp('\\w') - ] + ], + typename: TypeName.RegExp }], ['date', { is: is.date, fixtures: [ new Date() - ] + ], + typename: TypeName.Date }], ['error', { is: is.error, fixtures: [ new Error('🦄'), new ErrorSubclassFixture() - ] + ], + typename: TypeName.Error }], ['nativePromise', { is: is.nativePromise, fixtures: [ Promise.resolve(), PromiseSubclassFixture.resolve() - ] + ], + typename: TypeName.Promise }], ['promise', { is: is.promise, fixtures: [ {then() {}, catch() {}} - ] + ], + typename: TypeName.Object }], ['generator', { is: is.generator, @@ -159,7 +178,8 @@ const types = new Map([ (function * () { yield 4; })() - ] + ], + typename: TypeName.Generator }], ['generatorFunction', { is: is.generatorFunction, @@ -167,130 +187,151 @@ const types = new Map([ function * () { yield 4; } - ] + ], + typename: TypeName.Function }], ['asyncFunction', { is: is.asyncFunction, fixtures: [ async function () {}, async () => {} - ] + ], + typename: TypeName.Function }], ['boundFunction', { is: is.boundFunction, fixtures: [ () => {}, function () {}.bind(null) // eslint-disable-line no-extra-bind - ] + ], + typename: TypeName.Function }], ['map', { is: is.map, fixtures: [ new Map([['one', '1']]) - ] + ], + typename: TypeName.Map }], ['emptyMap', { is: is.emptyMap, fixtures: [ new Map() - ] + ], + typename: TypeName.Map }], ['set', { is: is.set, fixtures: [ new Set(['one']) - ] + ], + typename: TypeName.Set }], ['emptySet', { is: is.emptySet, fixtures: [ new Set() - ] + ], + typename: TypeName.Set }], ['weakSet', { is: is.weakSet, fixtures: [ new WeakSet() - ] + ], + typename: TypeName.WeakSet }], ['weakMap', { is: is.weakMap, fixtures: [ new WeakMap() - ] + ], + typename: TypeName.WeakMap }], ['int8Array', { is: is.int8Array, fixtures: [ new Int8Array() - ] + ], + typename: TypeName.Int8Array }], ['uint8Array', { is: is.uint8Array, fixtures: [ new Uint8Array() - ] + ], + typename: TypeName.Uint8Array }], ['uint8ClampedArray', { is: is.uint8ClampedArray, fixtures: [ new Uint8ClampedArray() - ] + ], + typename: TypeName.Uint8ClampedArray }], ['int16Array', { is: is.int16Array, fixtures: [ new Int16Array() - ] + ], + typename: TypeName.Int16Array }], ['uint16Array', { is: is.uint16Array, fixtures: [ new Uint16Array() - ] + ], + typename: TypeName.Uint16Array }], ['int32Array', { is: is.int32Array, fixtures: [ new Int32Array() - ] + ], + typename: TypeName.Int32Array }], ['uint32Array', { is: is.uint32Array, fixtures: [ new Uint32Array() - ] + ], + typename: TypeName.Uint32Array }], ['float32Array', { is: is.float32Array, fixtures: [ new Float32Array() - ] + ], + typename: TypeName.Float32Array }], ['float64Array', { is: is.float64Array, fixtures: [ new Float64Array() - ] + ], + typename: TypeName.Float64Array }], ['arrayBuffer', { is: is.arrayBuffer, fixtures: [ new ArrayBuffer(10) - ] + ], + typename: TypeName.ArrayBuffer }], ['dataView', { is: is.dataView, fixtures: [ new DataView(new ArrayBuffer(10)) - ] + ], + typename: TypeName.DataView }], ['nan', { is: is.nan, fixtures: [ NaN, Number.NaN - ] + ], + typename: TypeName.number }], ['nullOrUndefined', { is: is.nullOrUndefined, @@ -305,20 +346,23 @@ const types = new Map([ {x: 1}, Object.create(null), new Object() // eslint-disable-line no-new-object - ] + ], + typename: TypeName.Object }], ['integer', { is: is.integer, fixtures: [ 6 - ] + ], + typename: TypeName.number }], ['safeInteger', { is: is.safeInteger, fixtures: [ (2 ** 53) - 1, -(2 ** 53) + 1 - ] + ], + typename: TypeName.number }], ['domElement', { is: is.domElement, @@ -348,7 +392,8 @@ const types = new Map([ new Observable(), new Subject(), new ZenObservable(() => {}) - ] + ], + typename: TypeName.Observable }], ['nodeStream', { is: is.nodeStream, @@ -362,14 +407,16 @@ const types = new Map([ new Stream.Transform(), new Stream.Stream(), new Stream.Writable() - ] + ], + typename: TypeName.Object }], ['infinite', { is: is.infinite, fixtures: [ Infinity, -Infinity - ] + ], + typename: TypeName.number }] ]); @@ -383,7 +430,7 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { return; } - const {is: testIs} = testData; + const {is: testIs, typename} = testData; for (const [key, {fixtures}] of types) { // TODO: Automatically exclude value types in other tests that we have in the current one. @@ -392,21 +439,19 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { continue; } - const assert = key === type ? t.true.bind(t) : t.false.bind(t); + const isTypeUnderTest = key === type; + const assert = isTypeUnderTest ? t.true.bind(t) : t.false.bind(t); for (const fixture of fixtures) { assert(testIs(fixture), `Value: ${util.inspect(fixture)}`); + + if (isTypeUnderTest && typename) { + t.is(is(fixture), typename); + } } } }; -test('is', t => { - t.is(is(null), 'null'); - t.is(is(undefined), 'undefined'); - - // TODO: Expand this to all the supported types. Maybe reuse `testType()` somehow. -}); - test('is.undefined', t => { testType(t, 'undefined', ['nullOrUndefined']); }); @@ -753,6 +798,20 @@ test('is.inRange', t => { test('is.domElement', t => { testType(t, 'domElement'); t.false(is.domElement({nodeType: 1, nodeName: 'div'})); + + const htmlTagNameToTypeName = { + div: 'HTMLDivElement', + input: 'HTMLInputElement', + span: 'HTMLSpanElement', + img: 'HTMLImageElement', + canvas: 'HTMLCanvasElement', + script: 'HTMLScriptElement' + }; + + for (const [tagName, typeName] of Object.entries(htmlTagNameToTypeName)) { + const domElement = createDomElement(tagName); + t.is(is(domElement), typeName); + } }); test('is.observable', t => { From dd2a91dce57f2e8bbf8b450d2a5d50ebc6a08ab4 Mon Sep 17 00:00:00 2001 From: Vlad Frangu Date: Sat, 4 May 2019 12:05:23 +0300 Subject: [PATCH 098/254] Add support for BigInt (#87) --- package.json | 5 ++++ source/index.ts | 20 +++++++++---- test/test.ts | 77 +++++++++++++++++++++++++++++++++++++++++++++++-- tsconfig.json | 3 +- 4 files changed, 95 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index e34361a..1ab22a3 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,11 @@ "extensions": [ "ts" ], + "globals": [ + "BigInt", + "BigInt64Array", + "BigUint64Array" + ], "rules": { "@typescript-eslint/explicit-function-return-type": "off" } diff --git a/source/index.ts b/source/index.ts index a819a5d..945b2e4 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,5 +1,4 @@ -/// -/// +/// /// // TODO: Use the `URL` global when targeting Node.js 10 @@ -14,6 +13,7 @@ export const enum TypeName { undefined = 'undefined', string = 'string', number = 'number', + bigint = 'bigint', symbol = 'symbol', Function = 'Function', Generator = 'Generator', @@ -39,6 +39,8 @@ export const enum TypeName { Uint32Array = 'Uint32Array', Float32Array = 'Float32Array', Float64Array = 'Float64Array', + BigInt64Array = 'BigInt64Array', + BigUint64Array = 'BigUint64Array', ArrayBuffer = 'ArrayBuffer', SharedArrayBuffer = 'SharedArrayBuffer', DataView = 'DataView', @@ -77,6 +79,8 @@ function is(value: unknown): TypeName { return TypeName.string; case 'number': return TypeName.number; + case 'bigint': + return TypeName.bigint; case 'symbol': return TypeName.symbol; default: @@ -115,6 +119,7 @@ const isObject = (value: unknown): value is object => typeof value === 'object'; is.undefined = isOfType('undefined'); is.string = isOfType('string'); is.number = isOfType('number'); +is.bigint = isOfType('bigint'); // eslint-disable-next-line @typescript-eslint/ban-types is.function_ = isOfType('function'); @@ -175,6 +180,8 @@ is.int32Array = isObjectOfType(TypeName.Int32Array); is.uint32Array = isObjectOfType(TypeName.Uint32Array); is.float32Array = isObjectOfType(TypeName.Float32Array); is.float64Array = isObjectOfType(TypeName.Float64Array); +is.bigint64Array = isObjectOfType(TypeName.BigInt64Array); +is.biguint64Array = isObjectOfType(TypeName.BigUint64Array); is.arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); is.sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); @@ -208,12 +215,13 @@ const primitiveTypeOfTypes = new Set([ 'undefined', 'string', 'number', + 'bigint', 'boolean', 'symbol' ]); // TODO: This should be able to be `not object` when the `not` operator is out -export type Primitive = null | undefined | string | number | boolean | symbol; +export type Primitive = null | undefined | string | number | bigint | boolean | symbol; is.primitive = (value: unknown): value is Primitive => is.null_(value) || primitiveTypeOfTypes.has(typeof value); @@ -240,10 +248,12 @@ const typedArrayTypes = new Set([ TypeName.Int32Array, TypeName.Uint32Array, TypeName.Float32Array, - TypeName.Float64Array + TypeName.Float64Array, + TypeName.BigInt64Array, + TypeName.BigUint64Array ]); -export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; +export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array; is.typedArray = (value: unknown): value is TypedArray => { const objectType = getObjectType(value); diff --git a/test/test.ts b/test/test.ts index 363d647..75aac80 100644 --- a/test/test.ts +++ b/test/test.ts @@ -2,7 +2,6 @@ import fs from 'fs'; import net from 'net'; import Stream from 'stream'; import util from 'util'; -import {URL} from 'url'; import tempy from 'tempy'; import test, {ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; @@ -10,6 +9,9 @@ import {Subject, Observable} from 'rxjs'; import ZenObservable from 'zen-observable'; import is, {TypeName} from '../source'; +// eslint-disable-next-line @typescript-eslint/no-require-imports +const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; + const isNode10orHigher = Number(process.versions.node.split('.')[0]) >= 10; class PromiseSubclassFixture extends Promise {} @@ -69,6 +71,18 @@ const types = new Map([ ], typename: TypeName.number }], + // TODO: Nodejs 10 only + // ['bigint', { + // is: is.bigint, + // fixtures: [ + // 1n, + // 0n, + // -0n, + // // eslint-disable-next-line new-cap + // BigInt('1234') + // ], + // typename: TypeName.bigint + // }], ['boolean', { is: is.boolean, fixtures: [ @@ -311,6 +325,21 @@ const types = new Map([ ], typename: TypeName.Float64Array }], + // TODO: Nodejs 10 only + // ['bigint64Array', { + // is: is.bigint64Array, + // fixtures: [ + // new BigInt64Array() + // ], + // typename: TypeName.BigInt64Array + // }], + // ['biguint64Array', { + // is: is.biguint64Array, + // fixtures: [ + // new BigUint64Array() + // ], + // typename: TypeName.BigUint64Array + // }], ['arrayBuffer', { is: is.arrayBuffer, fixtures: [ @@ -468,6 +497,11 @@ test('is.number', t => { testType(t, 'number', ['nan', 'integer', 'safeInteger', 'infinite']); }); +// TODO: Nodejs 10 only +// test('is.bigint', t => { +// testType(t, 'bigint'); +// }); + test('is.boolean', t => { testType(t, 'boolean'); }); @@ -596,6 +630,15 @@ test('is.float64Array', t => { testType(t, 'float64Array'); }); +// TODO: Nodejs 10 only +// test('is.bigint64Array', t => { +// testType(t, 'bigint64Array'); +// }); + +// test('is.biguint64Array', t => { +// testType(t, 'biguint64Array'); +// }); + test('is.arrayBuffer', t => { testType(t, 'arrayBuffer'); }); @@ -616,7 +659,7 @@ test('is.directInstanceOf', t => { }); test('is.urlInstance', t => { - const url = new URL('https://example.com'); + const url = new URLGlobal('https://example.com'); t.true(is.urlInstance(url)); t.false(is.urlInstance({})); t.false(is.urlInstance(undefined)); @@ -626,7 +669,7 @@ test('is.urlInstance', t => { test('is.urlString', t => { const url = 'https://example.com'; t.true(is.urlString(url)); - t.false(is.urlString(new URL(url))); + t.false(is.urlString(new URLGlobal(url))); t.false(is.urlString({})); t.false(is.urlString(undefined)); t.false(is.urlString(null)); @@ -638,6 +681,14 @@ test('is.truthy', t => { t.true(is.truthy(new Set())); t.true(is.truthy(Symbol('🦄'))); t.true(is.truthy(true)); + t.true(is.truthy(1)); + + // TODO: Nodejs 10 only + // if (isNode10orHigher) { + // t.true(is.truthy(1n)); + // // eslint-disable-next-line new-cap + // t.true(is.truthy(BigInt(1))); + // } }); test('is.falsy', t => { @@ -647,6 +698,13 @@ test('is.falsy', t => { t.true(is.falsy(null)); t.true(is.falsy(undefined)); t.true(is.falsy(NaN)); + + // TODO: Nodejs 10 only + // if (isNode10orHigher) { + // t.true(is.falsy(0n)); + // // eslint-disable-next-line new-cap + // t.true(is.falsy(BigInt(0))); + // } }); test('is.nan', t => { @@ -670,6 +728,11 @@ test('is.primitive', t => { Symbol('🦄') ]; + // TODO: Nodejs 10 only + // if (isNode10orHigher) { + // primitives.push(6n); + // } + for (const el of primitives) { t.true(is.primitive(el)); } @@ -742,6 +805,14 @@ test('is.typedArray', t => { new Float64Array() ]; + // TODO: Nodejs 10 only + // if (isNode10orHigher) { + // typedArrays.push( + // new BigInt64Array(), + // new BigUint64Array() + // ); + // } + for (const item of typedArrays) { t.true(is.typedArray(item)); } diff --git a/tsconfig.json b/tsconfig.json index c76ee17..2acbd3e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,8 +4,7 @@ "outDir": "dist", "target": "es2017", "lib": [ - "es2017", - "esnext.asynciterable", + "esnext", "dom" ] }, From 2dac8e96f46a0a465d75f9544ceba285faf96ac7 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 4 May 2019 16:11:43 +0700 Subject: [PATCH 099/254] Meta tweaks --- package.json | 9 ++++----- test/test.ts | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 1ab22a3..68c09ee 100644 --- a/package.json +++ b/package.json @@ -48,16 +48,15 @@ "devDependencies": { "@sindresorhus/tsconfig": "^0.3.0", "@types/jsdom": "^12.2.3", - "@types/node": "^11.12.2", - "@types/tempy": "^0.2.0", + "@types/node": "^12.0.0", "@types/zen-observable": "^0.8.0", "@typescript-eslint/eslint-plugin": "^1.5.0", "ava": "^1.4.1", "del-cli": "^1.1.0", - "eslint-config-xo-typescript": "^0.9.0", - "jsdom": "^11.6.2", + "eslint-config-xo-typescript": "^0.10.1", + "jsdom": "^15.0.0", "rxjs": "^6.4.0", - "tempy": "^0.2.1", + "tempy": "^0.3.0", "ts-node": "^8.0.3", "typescript": "^3.4.1", "xo": "^0.24.0", diff --git a/test/test.ts b/test/test.ts index 75aac80..b0a9780 100644 --- a/test/test.ts +++ b/test/test.ts @@ -464,7 +464,7 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { for (const [key, {fixtures}] 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) { + if (exclude && exclude.includes(key)) { continue; } From 9bc8307770453ef3d71dfcdf3ff2d2d28cc03b73 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 4 May 2019 16:13:44 +0700 Subject: [PATCH 100/254] 0.16.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 68c09ee..6330524 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.15.0", + "version": "0.16.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 04cd28226505b88e9529931cc209da4f39eb73f8 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 13 May 2019 11:04:48 +0700 Subject: [PATCH 101/254] Fix the capitalization of `is.bigInt64Array` and `is.bigUint64Array` --- readme.md | 3 +++ source/index.ts | 4 ++-- test/test.ts | 16 ++++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index 848a172..5bfdd72 100644 --- a/readme.md +++ b/readme.md @@ -70,6 +70,7 @@ All the below methods accept a value and returns a boolean for whether the value ##### .number(value) ##### .boolean(value) ##### .symbol(value) +##### .bigint(value) #### Built-in types @@ -143,6 +144,8 @@ is.boundFunction(function () {}); ##### .uint32Array(value) ##### .float32Array(value) ##### .float64Array(value) +##### .bigInt64Array(value) +##### .bigUint64Array(value) #### Structured data diff --git a/source/index.ts b/source/index.ts index 945b2e4..8b0de49 100644 --- a/source/index.ts +++ b/source/index.ts @@ -180,8 +180,8 @@ is.int32Array = isObjectOfType(TypeName.Int32Array); is.uint32Array = isObjectOfType(TypeName.Uint32Array); is.float32Array = isObjectOfType(TypeName.Float32Array); is.float64Array = isObjectOfType(TypeName.Float64Array); -is.bigint64Array = isObjectOfType(TypeName.BigInt64Array); -is.biguint64Array = isObjectOfType(TypeName.BigUint64Array); +is.bigInt64Array = isObjectOfType(TypeName.BigInt64Array); +is.bigUint64Array = isObjectOfType(TypeName.BigUint64Array); is.arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); is.sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); diff --git a/test/test.ts b/test/test.ts index b0a9780..ce01004 100644 --- a/test/test.ts +++ b/test/test.ts @@ -326,15 +326,15 @@ const types = new Map([ typename: TypeName.Float64Array }], // TODO: Nodejs 10 only - // ['bigint64Array', { - // is: is.bigint64Array, + // ['bigInt64Array', { + // is: is.bigInt64Array, // fixtures: [ // new BigInt64Array() // ], // typename: TypeName.BigInt64Array // }], - // ['biguint64Array', { - // is: is.biguint64Array, + // ['bigUint64Array', { + // is: is.bigUint64Array, // fixtures: [ // new BigUint64Array() // ], @@ -631,12 +631,12 @@ test('is.float64Array', t => { }); // TODO: Nodejs 10 only -// test('is.bigint64Array', t => { -// testType(t, 'bigint64Array'); +// test('is.bigInt64Array', t => { +// testType(t, 'bigInt64Array'); // }); -// test('is.biguint64Array', t => { -// testType(t, 'biguint64Array'); +// test('is.bigUint64Array', t => { +// testType(t, 'bigUint64Array'); // }); test('is.arrayBuffer', t => { From b064473589b3586eafa04e49267c806e848b58d9 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 13 May 2019 11:07:28 +0700 Subject: [PATCH 102/254] 0.17.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6330524..c295ab9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.16.0", + "version": "0.17.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 6106086a35dee3314aa41713190732ed4f2bbd36 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 21 May 2019 16:44:18 +0700 Subject: [PATCH 103/254] Revert "Drop duplicate export (#84)" This reverts commit 28913cae88026e785658256c0d89eaf3c8101fd6. Closes #89 Fixes #88 --- source/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/index.ts b/source/index.ts index 8b0de49..4cd388d 100644 --- a/source/index.ts +++ b/source/index.ts @@ -385,5 +385,8 @@ Object.defineProperties(is, { } }); -module.exports = is; // For CommonJS default export support export default is; + +// For CommonJS default export support +module.exports = is; +module.exports.default = is; From 878d111ae734660d4c0238ef7ec6b779e1b52083 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 21 May 2019 16:48:34 +0700 Subject: [PATCH 104/254] 0.17.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c295ab9..90625cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.17.0", + "version": "0.17.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 7bf407fbf1a4ebb2644ba7d70287a38f6381dfbe Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 28 May 2019 12:56:17 +0700 Subject: [PATCH 105/254] Create funding.yml --- .github/funding.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/funding.yml diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000..0038dab --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1 @@ +github: sindresorhus From ffc6ce4586602e5b07f53901b5e29c57b0efd99e Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 15 Jun 2019 02:12:31 +0700 Subject: [PATCH 106/254] Fix linting --- package.json | 11 +++++++---- source/index.ts | 1 - test/test.ts | 15 +++++++-------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 90625cc..dcc997d 100644 --- a/package.json +++ b/package.json @@ -46,14 +46,15 @@ "types" ], "devDependencies": { - "@sindresorhus/tsconfig": "^0.3.0", + "@sindresorhus/tsconfig": "^0.4.0", "@types/jsdom": "^12.2.3", "@types/node": "^12.0.0", "@types/zen-observable": "^0.8.0", "@typescript-eslint/eslint-plugin": "^1.5.0", - "ava": "^1.4.1", - "del-cli": "^1.1.0", - "eslint-config-xo-typescript": "^0.10.1", + "@typescript-eslint/parser": "^1.10.2", + "ava": "^2.1.0", + "del-cli": "^2.0.0", + "eslint-config-xo-typescript": "^0.14.0", "jsdom": "^15.0.0", "rxjs": "^6.4.0", "tempy": "^0.3.0", @@ -85,6 +86,8 @@ "BigUint64Array" ], "rules": { + "import/first": "off", + "import/newline-after-import": "off", "@typescript-eslint/explicit-function-return-type": "off" } } diff --git a/source/index.ts b/source/index.ts index 4cd388d..e8c226e 100644 --- a/source/index.ts +++ b/source/index.ts @@ -2,7 +2,6 @@ /// // TODO: Use the `URL` global when targeting Node.js 10 -// eslint-disable-next-line @typescript-eslint/no-require-imports const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; export type Class = new (...args: any[]) => T; diff --git a/test/test.ts b/test/test.ts index ce01004..cde7374 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,15 +1,14 @@ -import fs from 'fs'; -import net from 'net'; -import Stream from 'stream'; -import util from 'util'; -import tempy from 'tempy'; +import fs = require('fs'); +import net = require('net'); +import Stream = require('stream'); +import {inspect} from 'util'; +import tempy = require('tempy'); import test, {ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; -import ZenObservable from 'zen-observable'; +import ZenObservable = require('zen-observable'); import is, {TypeName} from '../source'; -// eslint-disable-next-line @typescript-eslint/no-require-imports const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; const isNode10orHigher = Number(process.versions.node.split('.')[0]) >= 10; @@ -472,7 +471,7 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { const assert = isTypeUnderTest ? t.true.bind(t) : t.false.bind(t); for (const fixture of fixtures) { - assert(testIs(fixture), `Value: ${util.inspect(fixture)}`); + assert(testIs(fixture), `Value: ${inspect(fixture)}`); if (isTypeUnderTest && typename) { t.is(is(fixture), typename); From f04dffa57544e197ccc9ed32c780f0e21ea2c408 Mon Sep 17 00:00:00 2001 From: Kai Niedziela Date: Sun, 30 Jun 2019 10:09:07 +0200 Subject: [PATCH 107/254] Make `is.number(NaN)` return false (#90) Co-authored-by: Sindre Sorhus --- readme.md | 5 ++++- source/index.ts | 5 ++++- test/test.ts | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 5bfdd72..c3c5adb 100644 --- a/readme.md +++ b/readme.md @@ -68,6 +68,9 @@ All the below methods accept a value and returns a boolean for whether the value ##### .null(value) ##### .string(value) ##### .number(value) + +Note: `is.number(NaN)` returns `false`. This intentionally deviates from `typeof` behavior to increase user-friendliness of `is` type checks. + ##### .boolean(value) ##### .symbol(value) ##### .bigint(value) @@ -83,7 +86,7 @@ Keep in mind that [functions are objects too](https://developer.mozilla.org/en-U ##### .numericString(value) -Returns `true` for a string that represents a number. For example, `'42'` and `'-8'`. +Returns `true` for a string that represents a number satisfying `is.number`, for example, `'42'` and `'-8.3'`. Note: `'NaN'` returns `false`, but `'Infinity'` and `'-Infinity'` return `true`. diff --git a/source/index.ts b/source/index.ts index e8c226e..33b7896 100644 --- a/source/index.ts +++ b/source/index.ts @@ -117,7 +117,10 @@ const isObject = (value: unknown): value is object => typeof value === 'object'; is.undefined = isOfType('undefined'); is.string = isOfType('string'); -is.number = isOfType('number'); + +const isNumberType = isOfType('number'); +is.number = (value: unknown): value is number => isNumberType(value) && !is.nan(value); + is.bigint = isOfType('bigint'); // eslint-disable-next-line @typescript-eslint/ban-types diff --git a/test/test.ts b/test/test.ts index cde7374..748ce7a 100644 --- a/test/test.ts +++ b/test/test.ts @@ -493,7 +493,7 @@ test('is.string', t => { }); test('is.number', t => { - testType(t, 'number', ['nan', 'integer', 'safeInteger', 'infinite']); + testType(t, 'number', ['integer', 'safeInteger', 'infinite']); }); // TODO: Nodejs 10 only From 4f4820ef2fe34f24e6b8eb3b3ce8bdd4ab36b98b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 30 Jun 2019 15:15:27 +0700 Subject: [PATCH 108/254] Meta tweaks --- .travis.yml | 1 + package.json | 14 ++++++++------ readme.md | 7 +------ source/index.ts | 1 - 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index f3fa8cd..f98fed0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: node_js node_js: + - '12' - '10' - '8' diff --git a/package.json b/package.json index dcc997d..8fbfa8b 100644 --- a/package.json +++ b/package.json @@ -47,18 +47,18 @@ ], "devDependencies": { "@sindresorhus/tsconfig": "^0.4.0", - "@types/jsdom": "^12.2.3", - "@types/node": "^12.0.0", + "@types/jsdom": "^12.2.4", + "@types/node": "^12.0.10", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^1.5.0", - "@typescript-eslint/parser": "^1.10.2", + "@typescript-eslint/eslint-plugin": "^1.11.0", + "@typescript-eslint/parser": "^1.11.0", "ava": "^2.1.0", "del-cli": "^2.0.0", - "eslint-config-xo-typescript": "^0.14.0", + "eslint-config-xo-typescript": "^0.15.0", "jsdom": "^15.0.0", "rxjs": "^6.4.0", "tempy": "^0.3.0", - "ts-node": "^8.0.3", + "ts-node": "^8.3.0", "typescript": "^3.4.1", "xo": "^0.24.0", "zen-observable": "^0.8.8" @@ -88,6 +88,8 @@ "rules": { "import/first": "off", "import/newline-after-import": "off", + "@typescript-eslint/promise-function-async": "off", + "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/explicit-function-return-type": "off" } } diff --git a/readme.md b/readme.md index c3c5adb..157ac8a 100644 --- a/readme.md +++ b/readme.md @@ -458,13 +458,8 @@ The most common mistakes I noticed in these modules was using `instanceof` for t - [has-emoji](https://github.com/sindresorhus/has-emoji) - Check whether a string has any emoji -## Created by +## Maintainers - [Sindre Sorhus](https://github.com/sindresorhus) - [Giora Guttsait](https://github.com/gioragutt) - [Brandon Smith](https://github.com/brandon93s) - - -## License - -MIT diff --git a/source/index.ts b/source/index.ts index 33b7896..81a3633 100644 --- a/source/index.ts +++ b/source/index.ts @@ -141,7 +141,6 @@ is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(val is.object = (value: unknown): value is object => !is.nullOrUndefined(value) && (is.function_(value) || isObject(value)); is.iterable = (value: unknown): value is IterableIterator => !is.nullOrUndefined(value) && is.function_((value as IterableIterator)[Symbol.iterator]); -// eslint-disable-next-line no-use-extend-native/no-use-extend-native is.asyncIterable = (value: unknown): value is AsyncIterableIterator => !is.nullOrUndefined(value) && is.function_((value as AsyncIterableIterator)[Symbol.asyncIterator]); is.generator = (value: unknown): value is Generator => is.iterable(value) && is.function_(value.next) && is.function_(value.throw); From e358e44dd5212b8083082727ce0cada959fb2248 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 30 Jun 2019 15:19:21 +0700 Subject: [PATCH 109/254] 1.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8fbfa8b..91e79df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "0.17.1", + "version": "1.0.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 0cbd9df6ce93cb1b3e523fcf5d7bbef152532cb8 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 1 Jul 2019 23:19:25 +0700 Subject: [PATCH 110/254] Minor refactoring --- source/index.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/source/index.ts b/source/index.ts index 81a3633..9bb06dd 100644 --- a/source/index.ts +++ b/source/index.ts @@ -113,8 +113,6 @@ function is(value: unknown): TypeName { return TypeName.Object; } -const isObject = (value: unknown): value is object => typeof value === 'object'; - is.undefined = isOfType('undefined'); is.string = isOfType('string'); @@ -138,7 +136,7 @@ is.array = Array.isArray; is.buffer = (value: unknown): value is Buffer => !is.nullOrUndefined(value) && !is.nullOrUndefined((value as Buffer).constructor) && is.function_((value as Buffer).constructor.isBuffer) && (value as Buffer).constructor.isBuffer(value); is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); -is.object = (value: unknown): value is object => !is.nullOrUndefined(value) && (is.function_(value) || isObject(value)); +is.object = (value: unknown): value is object => !is.null_(value) && (typeof value === 'object' || is.function_(value)); is.iterable = (value: unknown): value is IterableIterator => !is.nullOrUndefined(value) && is.function_((value as IterableIterator)[Symbol.iterator]); is.asyncIterable = (value: unknown): value is AsyncIterableIterator => !is.nullOrUndefined(value) && is.function_((value as AsyncIterableIterator)[Symbol.asyncIterator]); @@ -149,8 +147,7 @@ is.nativePromise = (value: unknown): value is Promise => isObjectOfType>(TypeName.Promise)(value); const hasPromiseAPI = (value: unknown): value is Promise => - !is.null_(value) && - isObject(value) as unknown && + is.object(value) && is.function_((value as Promise).then) && // eslint-disable-line promise/prefer-await-to-then is.function_((value as Promise).catch); @@ -322,7 +319,7 @@ is.observable = (value: unknown): value is ObservableLike => { // eslint-disable-next-line @typescript-eslint/ban-types export type NodeStream = object & {readonly pipe: Function}; -is.nodeStream = (value: unknown): value is NodeStream => !is.nullOrUndefined(value) && isObject(value) as unknown && is.function_((value as NodeStream).pipe) && !is.observable(value); +is.nodeStream = (value: unknown): value is NodeStream => is.object(value) && is.function_((value as NodeStream).pipe) && !is.observable(value); is.infinite = (value: unknown): value is number => value === Infinity || value === -Infinity; From 0f9b8479e95f5b75fb5dc1689cc644570ea22677 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 10 Sep 2019 12:46:18 +0700 Subject: [PATCH 111/254] Tidelift tasks --- .github/funding.yml | 1 + .github/security.md | 3 +++ readme.md | 13 +++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 .github/security.md diff --git a/.github/funding.yml b/.github/funding.yml index 0038dab..226bf96 100644 --- a/.github/funding.yml +++ b/.github/funding.yml @@ -1 +1,2 @@ github: sindresorhus +tidelift: npm/@sindresorhus/is diff --git a/.github/security.md b/.github/security.md new file mode 100644 index 0000000..5358dc5 --- /dev/null +++ b/.github/security.md @@ -0,0 +1,3 @@ +# Security Policy + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. diff --git a/readme.md b/readme.md index 157ac8a..052f565 100644 --- a/readme.md +++ b/readme.md @@ -463,3 +463,16 @@ The most common mistakes I noticed in these modules was using `instanceof` for t - [Sindre Sorhus](https://github.com/sindresorhus) - [Giora Guttsait](https://github.com/gioragutt) - [Brandon Smith](https://github.com/brandon93s) + + +--- + +
+ + Get professional support for this package with a Tidelift subscription + +
+ + Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. +
+
From 7c16f20d16cb5cdb5d94e10ae89dd41e250b1f82 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 2 Oct 2019 23:06:53 +0700 Subject: [PATCH 112/254] Bundle the required DOM types (#93) Closes #92 --- source/index.ts | 43 ++++++++++++++++++++++++++++++++++++++++++- tsconfig.json | 3 +-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/source/index.ts b/source/index.ts index 9bb06dd..d1f3441 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,5 +1,46 @@ /// -/// + +/* -- Copy-pasted DOM types -- */ +// TODO: Remove these when https://github.com/microsoft/TypeScript/issues/33111 is fixed. +interface URLSearchParams { + append(name: string, value: string): void; + delete(name: string): void; + get(name: string): string | null; + getAll(name: string): string[]; + has(name: string): boolean; + set(name: string, value: string): void; + sort(): void; + forEach(callbackfn: (value: string, key: string, parent: URLSearchParams) => void, thisArg?: any): void; +} + +declare var URLSearchParams: { // eslint-disable-line no-var + prototype: URLSearchParams; + new(init?: string[][] | Record | string | URLSearchParams): URLSearchParams; +}; + +interface URL { + hash: string; + host: string; + hostname: string; + href: string; + readonly origin: string; + password: string; + pathname: string; + port: string; + protocol: string; + search: string; + readonly searchParams: URLSearchParams; + username: string; + toJSON(): string; +} + +declare var URL: { // eslint-disable-line no-var + prototype: URL; + new(url: string, base?: string | URL): URL; + createObjectURL(object: any): string; + revokeObjectURL(url: string): void; +}; +/* ---- */ // TODO: Use the `URL` global when targeting Node.js 10 const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; diff --git a/tsconfig.json b/tsconfig.json index 2acbd3e..7c5ead3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,8 +4,7 @@ "outDir": "dist", "target": "es2017", "lib": [ - "esnext", - "dom" + "esnext" ] }, "include": [ From 4a63743feb9557cd36ce07c181b22624be8e5560 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 3 Oct 2019 15:00:03 +0700 Subject: [PATCH 113/254] 1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 91e79df..d8c2b45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "1.0.0", + "version": "1.1.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 3f2caa4835f64c28c52498b8bf88c1ccc1cd17f1 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 4 Oct 2019 11:38:10 +0700 Subject: [PATCH 114/254] Revert "Bundle the required DOM types (#93)" This reverts commit 7c16f20d16cb5cdb5d94e10ae89dd41e250b1f82. --- source/index.ts | 43 +------------------------------------------ tsconfig.json | 3 ++- 2 files changed, 3 insertions(+), 43 deletions(-) diff --git a/source/index.ts b/source/index.ts index d1f3441..9bb06dd 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,46 +1,5 @@ /// - -/* -- Copy-pasted DOM types -- */ -// TODO: Remove these when https://github.com/microsoft/TypeScript/issues/33111 is fixed. -interface URLSearchParams { - append(name: string, value: string): void; - delete(name: string): void; - get(name: string): string | null; - getAll(name: string): string[]; - has(name: string): boolean; - set(name: string, value: string): void; - sort(): void; - forEach(callbackfn: (value: string, key: string, parent: URLSearchParams) => void, thisArg?: any): void; -} - -declare var URLSearchParams: { // eslint-disable-line no-var - prototype: URLSearchParams; - new(init?: string[][] | Record | string | URLSearchParams): URLSearchParams; -}; - -interface URL { - hash: string; - host: string; - hostname: string; - href: string; - readonly origin: string; - password: string; - pathname: string; - port: string; - protocol: string; - search: string; - readonly searchParams: URLSearchParams; - username: string; - toJSON(): string; -} - -declare var URL: { // eslint-disable-line no-var - prototype: URL; - new(url: string, base?: string | URL): URL; - createObjectURL(object: any): string; - revokeObjectURL(url: string): void; -}; -/* ---- */ +/// // TODO: Use the `URL` global when targeting Node.js 10 const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; diff --git a/tsconfig.json b/tsconfig.json index 7c5ead3..2acbd3e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,8 @@ "outDir": "dist", "target": "es2017", "lib": [ - "esnext" + "esnext", + "dom" ] }, "include": [ From e7277de849b27469cb243d1eaaadbeb47e97a886 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 4 Oct 2019 11:39:58 +0700 Subject: [PATCH 115/254] 1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8c2b45..b6bc236 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "1.1.0", + "version": "1.2.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 8ff75abfff740ce2386cf09848e7ae22b5f7e1ff Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 30 Oct 2019 20:01:23 +0700 Subject: [PATCH 116/254] Tidelift tasks --- readme.md | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/readme.md b/readme.md index 052f565..178d2f4 100644 --- a/readme.md +++ b/readme.md @@ -444,6 +444,13 @@ 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. +## For enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of @sindresorhus/is and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm--sindresorhus-is?utm_source=npm-sindresorhus-is&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) + + ## Related - [ow](https://github.com/sindresorhus/ow) - Function argument validation for humans @@ -463,16 +470,3 @@ The most common mistakes I noticed in these modules was using `instanceof` for t - [Sindre Sorhus](https://github.com/sindresorhus) - [Giora Guttsait](https://github.com/gioragutt) - [Brandon Smith](https://github.com/brandon93s) - - ---- - -
- - Get professional support for this package with a Tidelift subscription - -
- - Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. -
-
From 0cc82c583e9bd1cd4d1d5d21bff7a59bda35d8c8 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Wed, 30 Oct 2019 21:20:49 -0700 Subject: [PATCH 117/254] Fix a readme link (#96) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 178d2f4..bb744e7 100644 --- a/readme.md +++ b/readme.md @@ -448,7 +448,7 @@ The most common mistakes I noticed in these modules was using `instanceof` for t Available as part of the Tidelift Subscription. -The maintainers of @sindresorhus/is and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm--sindresorhus-is?utm_source=npm-sindresorhus-is&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) +The maintainers of @sindresorhus/is and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-sindresorhus-is?utm_source=npm-sindresorhus-is&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) ## Related From af6b03d67f2b78838fe63703bbb7e9505009796b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 7 Nov 2019 15:56:02 +0700 Subject: [PATCH 118/254] Require Node.js 10 --- .travis.yml | 1 - package.json | 10 ++-- source/index.ts | 29 +++++++--- test/test.ts | 145 ++++++++++++++++++++---------------------------- tsconfig.json | 4 +- 5 files changed, 87 insertions(+), 102 deletions(-) diff --git a/.travis.yml b/.travis.yml index f98fed0..94ab01f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,3 @@ language: node_js node_js: - '12' - '10' - - '8' diff --git a/package.json b/package.json index b6bc236..1ffa0e6 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "main": "dist", "engines": { - "node": ">=8" + "node": ">=10" }, "scripts": { "build": "del dist && tsc", @@ -46,9 +46,9 @@ "types" ], "devDependencies": { - "@sindresorhus/tsconfig": "^0.4.0", + "@sindresorhus/tsconfig": "^0.6.0", "@types/jsdom": "^12.2.4", - "@types/node": "^12.0.10", + "@types/node": "^12.12.6", "@types/zen-observable": "^0.8.0", "@typescript-eslint/eslint-plugin": "^1.11.0", "@typescript-eslint/parser": "^1.11.0", @@ -60,7 +60,7 @@ "tempy": "^0.3.0", "ts-node": "^8.3.0", "typescript": "^3.4.1", - "xo": "^0.24.0", + "xo": "^0.25.3", "zen-observable": "^0.8.8" }, "types": "dist", @@ -86,8 +86,6 @@ "BigUint64Array" ], "rules": { - "import/first": "off", - "import/newline-after-import": "off", "@typescript-eslint/promise-function-async": "off", "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/explicit-function-return-type": "off" diff --git a/source/index.ts b/source/index.ts index 9bb06dd..2cfe2c3 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,9 +1,6 @@ -/// +/// /// -// TODO: Use the `URL` global when targeting Node.js 10 -const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; - export type Class = new (...args: any[]) => T; export const enum TypeName { @@ -194,7 +191,7 @@ is.urlString = (value: unknown): value is string => { } try { - new URLGlobal(value); // eslint-disable-line no-new + new URL(value); // eslint-disable-line no-new return true; } catch { return false; @@ -219,7 +216,14 @@ const primitiveTypeOfTypes = new Set([ ]); // TODO: This should be able to be `not object` when the `not` operator is out -export type Primitive = null | undefined | string | number | bigint | boolean | symbol; +export type Primitive = + | null + | undefined + | string + | number + | bigint + | boolean + | symbol; is.primitive = (value: unknown): value is Primitive => is.null_(value) || primitiveTypeOfTypes.has(typeof value); @@ -251,7 +255,18 @@ const typedArrayTypes = new Set([ TypeName.BigUint64Array ]); -export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array; +export type TypedArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array + | BigInt64Array + | BigUint64Array; is.typedArray = (value: unknown): value is TypedArray => { const objectType = getObjectType(value); diff --git a/test/test.ts b/test/test.ts index 748ce7a..cbf59b0 100644 --- a/test/test.ts +++ b/test/test.ts @@ -9,10 +9,6 @@ import {Subject, Observable} from 'rxjs'; import ZenObservable = require('zen-observable'); import is, {TypeName} from '../source'; -const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; - -const isNode10orHigher = Number(process.versions.node.split('.')[0]) >= 10; - class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} @@ -70,18 +66,17 @@ const types = new Map([ ], typename: TypeName.number }], - // TODO: Nodejs 10 only - // ['bigint', { - // is: is.bigint, - // fixtures: [ - // 1n, - // 0n, - // -0n, - // // eslint-disable-next-line new-cap - // BigInt('1234') - // ], - // typename: TypeName.bigint - // }], + ['bigint', { + is: is.bigint, + fixtures: [ + // Disabled until TS supports it for an ESnnnn target. + // 1n, + // 0n, + // -0n, + BigInt('1234') + ], + typename: TypeName.bigint + }], ['boolean', { is: is.boolean, fixtures: [ @@ -151,7 +146,7 @@ const types = new Map([ is: is.regExp, fixtures: [ /\w/, - new RegExp('\\w') + new RegExp('\\w') // eslint-disable-line prefer-regex-literals ], typename: TypeName.RegExp }], @@ -324,21 +319,20 @@ const types = new Map([ ], typename: TypeName.Float64Array }], - // TODO: Nodejs 10 only - // ['bigInt64Array', { - // is: is.bigInt64Array, - // fixtures: [ - // new BigInt64Array() - // ], - // typename: TypeName.BigInt64Array - // }], - // ['bigUint64Array', { - // is: is.bigUint64Array, - // fixtures: [ - // new BigUint64Array() - // ], - // typename: TypeName.BigUint64Array - // }], + ['bigInt64Array', { + is: is.bigInt64Array, + fixtures: [ + new BigInt64Array() + ], + typename: TypeName.BigInt64Array + }], + ['bigUint64Array', { + is: is.bigUint64Array, + fixtures: [ + new BigUint64Array() + ], + typename: TypeName.BigUint64Array + }], ['arrayBuffer', { is: is.arrayBuffer, fixtures: [ @@ -496,10 +490,9 @@ test('is.number', t => { testType(t, 'number', ['integer', 'safeInteger', 'infinite']); }); -// TODO: Nodejs 10 only -// test('is.bigint', t => { -// testType(t, 'bigint'); -// }); +test('is.bigint', t => { + testType(t, 'bigint'); +}); test('is.boolean', t => { testType(t, 'boolean'); @@ -629,14 +622,13 @@ test('is.float64Array', t => { testType(t, 'float64Array'); }); -// TODO: Nodejs 10 only -// test('is.bigInt64Array', t => { -// testType(t, 'bigInt64Array'); -// }); +test('is.bigInt64Array', t => { + testType(t, 'bigInt64Array'); +}); -// test('is.bigUint64Array', t => { -// testType(t, 'bigUint64Array'); -// }); +test('is.bigUint64Array', t => { + testType(t, 'bigUint64Array'); +}); test('is.arrayBuffer', t => { testType(t, 'arrayBuffer'); @@ -658,7 +650,7 @@ test('is.directInstanceOf', t => { }); test('is.urlInstance', t => { - const url = new URLGlobal('https://example.com'); + const url = new URL('https://example.com'); t.true(is.urlInstance(url)); t.false(is.urlInstance({})); t.false(is.urlInstance(undefined)); @@ -668,7 +660,7 @@ test('is.urlInstance', t => { test('is.urlString', t => { const url = 'https://example.com'; t.true(is.urlString(url)); - t.false(is.urlString(new URLGlobal(url))); + t.false(is.urlString(new URL(url))); t.false(is.urlString({})); t.false(is.urlString(undefined)); t.false(is.urlString(null)); @@ -681,13 +673,9 @@ test('is.truthy', t => { t.true(is.truthy(Symbol('🦄'))); t.true(is.truthy(true)); t.true(is.truthy(1)); - - // TODO: Nodejs 10 only - // if (isNode10orHigher) { - // t.true(is.truthy(1n)); - // // eslint-disable-next-line new-cap - // t.true(is.truthy(BigInt(1))); - // } + // Disabled until TS supports it for an ESnnnn target. + // t.true(is.truthy(1n)); + t.true(is.truthy(BigInt(1))); }); test('is.falsy', t => { @@ -697,13 +685,9 @@ test('is.falsy', t => { t.true(is.falsy(null)); t.true(is.falsy(undefined)); t.true(is.falsy(NaN)); - - // TODO: Nodejs 10 only - // if (isNode10orHigher) { - // t.true(is.falsy(0n)); - // // eslint-disable-next-line new-cap - // t.true(is.falsy(BigInt(0))); - // } + // Disabled until TS supports it for an ESnnnn target. + // t.true(is.falsy(0n)); + t.true(is.falsy(BigInt(0))); }); test('is.nan', t => { @@ -725,13 +709,10 @@ test('is.primitive', t => { true, false, Symbol('🦄') + // Disabled until TS supports it for an ESnnnn target. + // 6n ]; - // TODO: Nodejs 10 only - // if (isNode10orHigher) { - // primitives.push(6n); - // } - for (const el of primitives) { t.true(is.primitive(el)); } @@ -764,20 +745,18 @@ test('is.iterable', t => { t.false(is.iterable({})); }); -if (isNode10orHigher) { - test('is.asyncIterable', t => { - t.true(is.asyncIterable({ - [Symbol.asyncIterator]: () => {} - })); +test('is.asyncIterable', t => { + t.true(is.asyncIterable({ + [Symbol.asyncIterator]: () => {} + })); - t.false(is.asyncIterable(null)); - t.false(is.asyncIterable(undefined)); - t.false(is.asyncIterable(0)); - t.false(is.asyncIterable(NaN)); - t.false(is.asyncIterable(Infinity)); - t.false(is.asyncIterable({})); - }); -} + t.false(is.asyncIterable(null)); + t.false(is.asyncIterable(undefined)); + t.false(is.asyncIterable(0)); + t.false(is.asyncIterable(NaN)); + t.false(is.asyncIterable(Infinity)); + t.false(is.asyncIterable({})); +}); test('is.class', t => { class Foo {} // eslint-disable-line @typescript-eslint/no-extraneous-class @@ -801,17 +780,11 @@ test('is.typedArray', t => { new Int32Array(), new Uint32Array(), new Float32Array(), - new Float64Array() + new Float64Array(), + new BigInt64Array(), + new BigUint64Array() ]; - // TODO: Nodejs 10 only - // if (isNode10orHigher) { - // typedArrays.push( - // new BigInt64Array(), - // new BigUint64Array() - // ); - // } - for (const item of typedArrays) { t.true(is.typedArray(item)); } diff --git a/tsconfig.json b/tsconfig.json index 2acbd3e..d1f6382 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,9 @@ "extends": "@sindresorhus/tsconfig", "compilerOptions": { "outDir": "dist", - "target": "es2017", + "target": "es2018", "lib": [ - "esnext", + "es2018", "dom" ] }, From c25b606c3bda79f82fbe506e1c3b1116c3e0eaef Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 7 Nov 2019 16:28:20 +0700 Subject: [PATCH 119/254] Improve the type assertion for `is.asyncFunction` --- package.json | 2 +- source/index.ts | 3 +-- test/test.ts | 6 ++++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1ffa0e6..1bb1f91 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "rxjs": "^6.4.0", "tempy": "^0.3.0", "ts-node": "^8.3.0", - "typescript": "^3.4.1", + "typescript": "^3.7.2", "xo": "^0.25.3", "zen-observable": "^0.8.8" }, diff --git a/source/index.ts b/source/index.ts index 2cfe2c3..90ca4c6 100644 --- a/source/index.ts +++ b/source/index.ts @@ -152,8 +152,7 @@ is.promise = (value: unknown): value is Promise => is.nativePromise(val is.generatorFunction = isObjectOfType(TypeName.GeneratorFunction); -// eslint-disable-next-line @typescript-eslint/ban-types -is.asyncFunction = isObjectOfType(TypeName.AsyncFunction); +is.asyncFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === TypeName.AsyncFunction; // eslint-disable-next-line no-prototype-builtins, @typescript-eslint/ban-types is.boundFunction = (value: unknown): value is Function => is.function_(value) && !value.hasOwnProperty('prototype'); diff --git a/test/test.ts b/test/test.ts index cbf59b0..0d9c13a 100644 --- a/test/test.ts +++ b/test/test.ts @@ -560,6 +560,12 @@ test('is.promise', t => { test('is.asyncFunction', t => { testType(t, 'asyncFunction', ['function']); + + const fixture = async () => {}; + if (is.asyncFunction(fixture)) { + // eslint-disable-next-line promise/prefer-await-to-then + t.true(is.function_(fixture().then)); + } }); test('is.generator', t => { From aeb3f74d653cf745abf2460f0896207de00c6059 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 13 Nov 2019 16:06:18 +0700 Subject: [PATCH 120/254] Make the `NodeStream` type more accurate --- source/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/index.ts b/source/index.ts index 90ca4c6..6cb8159 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,5 +1,6 @@ /// /// +/// export type Class = new (...args: any[]) => T; @@ -330,8 +331,9 @@ is.observable = (value: unknown): value is ObservableLike => { return false; }; -// eslint-disable-next-line @typescript-eslint/ban-types -export type NodeStream = object & {readonly pipe: Function}; +export interface NodeStream extends NodeJS.EventEmitter { + pipe(destination: T, options?: {end?: boolean}): T; +} is.nodeStream = (value: unknown): value is NodeStream => is.object(value) && is.function_((value as NodeStream).pipe) && !is.observable(value); From c842cc260f6d773bf4dc800ebebc47dd026cab0a Mon Sep 17 00:00:00 2001 From: Joel Purra Date: Tue, 21 Jan 2020 17:56:44 +0100 Subject: [PATCH 121/254] Upgrade dependencies (#101) --- package.json | 13 ++++++++----- source/index.ts | 2 +- test/test.ts | 2 +- tsconfig.xo.json | 6 ++++++ 4 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 tsconfig.xo.json diff --git a/package.json b/package.json index 1bb1f91..5867b4f 100644 --- a/package.json +++ b/package.json @@ -46,20 +46,20 @@ "types" ], "devDependencies": { - "@sindresorhus/tsconfig": "^0.6.0", + "@sindresorhus/tsconfig": "^0.7.0", "@types/jsdom": "^12.2.4", "@types/node": "^12.12.6", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^1.11.0", - "@typescript-eslint/parser": "^1.11.0", + "@typescript-eslint/eslint-plugin": "^2.17.0", + "@typescript-eslint/parser": "^2.17.0", "ava": "^2.1.0", "del-cli": "^2.0.0", - "eslint-config-xo-typescript": "^0.15.0", + "eslint-config-xo-typescript": "^0.24.1", "jsdom": "^15.0.0", "rxjs": "^6.4.0", "tempy": "^0.3.0", "ts-node": "^8.3.0", - "typescript": "^3.7.2", + "typescript": "^3.7.5", "xo": "^0.25.3", "zen-observable": "^0.8.8" }, @@ -80,6 +80,9 @@ "extensions": [ "ts" ], + "parserOptions": { + "project": "./tsconfig.xo.json" + }, "globals": [ "BigInt", "BigInt64Array", diff --git a/source/index.ts b/source/index.ts index 6cb8159..94ba314 100644 --- a/source/index.ts +++ b/source/index.ts @@ -278,8 +278,8 @@ is.typedArray = (value: unknown): value is TypedArray => { }; export interface ArrayLike { - readonly length: number; readonly [index: number]: T; + readonly length: number; } const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0; diff --git a/test/test.ts b/test/test.ts index 0d9c13a..b7d017d 100644 --- a/test/test.ts +++ b/test/test.ts @@ -457,7 +457,7 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { for (const [key, {fixtures}] 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.includes(key)) { + if (exclude?.includes(key)) { continue; } diff --git a/tsconfig.xo.json b/tsconfig.xo.json new file mode 100644 index 0000000..b01049f --- /dev/null +++ b/tsconfig.xo.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "test" + ] +} From 0c3f11038638920377c44b0832ad0a89088bfe16 Mon Sep 17 00:00:00 2001 From: Joel Purra Date: Wed, 22 Jan 2020 12:08:35 +0100 Subject: [PATCH 122/254] Add assertion type guards (#97) --- readme.md | 34 +++++ source/index.ts | 237 +++++++++++++++++++++++++++++++++ test/test.ts | 347 ++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 593 insertions(+), 25 deletions(-) diff --git a/readme.md b/readme.md index bb744e7..2d55818 100644 --- a/readme.md +++ b/readme.md @@ -11,6 +11,7 @@ For example, `is.string('🦄') //=> true` - Written in TypeScript - [Extensive use of type guards](#type-guards) +- [Supports type assertions](#type-assertions) - Actively maintained - 2 million weekly downloads @@ -37,6 +38,15 @@ is.number(6); //=> true ``` +[Assertions](#type-assertions) perform the same type checks, but throw errors if the type does not match. + +```js +const {assert} = require('@sindresorhus/is'); + +assert.string(foo); +// foo is known to be a string here +``` + ## API @@ -425,6 +435,30 @@ padLeft('🦄', '🌈'); //=> '🌈🦄' ``` +## Type assertions + +The type guards are also available as [typescript assertions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions), which throw an error for unexpected types. It is a convenient one-line version of the often repetitive "if-not-expected-type-throw" pattern. + +```ts +import {assert} from '@sindresorhus/is'; + +const handleMovieRatingApiResponse = (response: unknown) => { + assert.plainObject(response); + // `response` is typed as a plain `object` with `unknown` properties + assert.number(response.rating); + // `response.rating` is typed as a `number` + assert.string(response.title); + // `response.title` is typed as a `string` + + return `${response.title} (${response.rating * 10})`; +}; + +handleMovieRatingApiResponse({rating: 0.87, title: 'The Matrix'}); +//=> 'The Matrix (8.7)' + +handleMovieRatingApiResponse({status: 'system maintenance'}); +//=> throws error +``` ## FAQ diff --git a/source/index.ts b/source/index.ts index 94ba314..36004d2 100644 --- a/source/index.ts +++ b/source/index.ts @@ -385,6 +385,231 @@ const predicateOnArray = (method: ArrayMethod, predicate: Predicate, values: unk is.any = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.some, predicate, values); is.all = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.every, predicate, values); +const assertType = (condition: boolean, description: string, value: unknown): asserts condition => { + if (!condition) { + throw new TypeError(`Expected value which is "${description}", received value of type ${is(value)}.`); + } +}; + +export const enum AssertionTypeDescription { + class_ = 'Class', + numericString = 'string with a number', + nullOrUndefined = 'null or undefined', + iterable = 'Iterable', + asyncIterable = 'AsyncIterable', + nativePromise = 'native Promise', + urlString = 'string with a URL', + truthy = 'truthy', + falsy = 'falsy', + nan = 'NaN', + primitive = 'primitive', + integer = 'integer', + safeInteger = 'integer', + plainObject = 'plain object', + arrayLike = 'array-like', + typedArray = 'TypedArray', + domElement = 'Element', + nodeStream = 'Node.js Stream', + infinite = 'infinite number', + emptyArray = 'empty array', + nonEmptyArray = 'non-empty array', + emptyString = 'empty string', + nonEmptyString = 'non-empty string', + emptyStringOrWhitespace = 'empty string or whitespace', + emptyObject = 'empty object', + nonEmptyObject = 'non-empty object', + emptySet = 'empty set', + nonEmptySet = 'non-empty set', + emptyMap = 'empty map', + nonEmptyMap = 'non-empty map', + + evenInteger = 'even integer', + oddInteger = 'odd integer', + + directInstanceOf = 'T', + inRange = 'in range', + + any = 'predicate returns truthy for any value', + all = 'predicate returns truthy for all values', +} + +// Type assertions have to be declared with an explicit type. +interface Assert { + // Unknowns. + undefined: (value: unknown) => asserts value is undefined; + string: (value: unknown) => asserts value is string; + number: (value: unknown) => asserts value is number; + bigint: (value: unknown) => asserts value is bigint; + // eslint-disable-next-line @typescript-eslint/ban-types + function_: (value: unknown) => asserts value is Function; + null_: (value: unknown) => asserts value is null; + class_: (value: unknown) => asserts value is Class; + boolean: (value: unknown) => asserts value is boolean; + symbol: (value: unknown) => asserts value is symbol; + numericString: (value: unknown) => asserts value is string; + array: (value: unknown) => asserts value is T[]; + buffer: (value: unknown) => asserts value is Buffer; + nullOrUndefined: (value: unknown) => asserts value is null | undefined; + object: (value: unknown) => asserts value is Record; + iterable: (value: unknown) => asserts value is Iterable; + asyncIterable: (value: unknown) => asserts value is AsyncIterable; + generator: (value: unknown) => asserts value is Generator; + nativePromise: (value: unknown) => asserts value is Promise; + promise: (value: unknown) => asserts value is Promise; + generatorFunction: (value: unknown) => asserts value is GeneratorFunction; + // eslint-disable-next-line @typescript-eslint/ban-types + asyncFunction: (value: unknown) => asserts value is Function; + // eslint-disable-next-line @typescript-eslint/ban-types + boundFunction: (value: unknown) => asserts value is Function; + regExp: (value: unknown) => asserts value is RegExp; + date: (value: unknown) => asserts value is Date; + error: (value: unknown) => asserts value is Error; + map: (value: unknown) => asserts value is Map; + set: (value: unknown) => asserts value is Set; + weakMap: (value: unknown) => asserts value is WeakMap; + weakSet: (value: unknown) => asserts value is WeakSet; + int8Array: (value: unknown) => asserts value is Int8Array; + uint8Array: (value: unknown) => asserts value is Uint8Array; + uint8ClampedArray: (value: unknown) => asserts value is Uint8ClampedArray; + int16Array: (value: unknown) => asserts value is Int16Array; + uint16Array: (value: unknown) => asserts value is Uint16Array; + int32Array: (value: unknown) => asserts value is Int32Array; + uint32Array: (value: unknown) => asserts value is Uint32Array; + float32Array: (value: unknown) => asserts value is Float32Array; + float64Array: (value: unknown) => asserts value is Float64Array; + bigInt64Array: (value: unknown) => asserts value is BigInt64Array; + bigUint64Array: (value: unknown) => asserts value is BigUint64Array; + arrayBuffer: (value: unknown) => asserts value is ArrayBuffer; + sharedArrayBuffer: (value: unknown) => asserts value is SharedArrayBuffer; + dataView: (value: unknown) => asserts value is DataView; + urlInstance: (value: unknown) => asserts value is URL; + urlString: (value: unknown) => asserts value is string; + truthy: (value: unknown) => asserts value is unknown; + falsy: (value: unknown) => asserts value is unknown; + nan: (value: unknown) => asserts value is unknown; + primitive: (value: unknown) => asserts value is Primitive; + integer: (value: unknown) => asserts value is number; + safeInteger: (value: unknown) => asserts value is number; + plainObject: (value: unknown) => asserts value is {[key: string]: unknown}; + typedArray: (value: unknown) => asserts value is TypedArray; + arrayLike: (value: unknown) => asserts value is ArrayLike; + domElement: (value: unknown) => asserts value is Element; + observable: (value: unknown) => asserts value is ObservableLike; + nodeStream: (value: unknown) => asserts value is NodeStream; + infinite: (value: unknown) => asserts value is number; + emptyArray: (value: unknown) => asserts value is never[]; + nonEmptyArray: (value: unknown) => asserts value is unknown[]; + emptyString: (value: unknown) => asserts value is ''; + nonEmptyString: (value: unknown) => asserts value is string; + emptyStringOrWhitespace: (value: unknown) => asserts value is string; + emptyObject: (value: unknown) => asserts value is {[key: string]: never}; + nonEmptyObject: (value: unknown) => asserts value is {[key: string]: unknown}; + emptySet: (value: unknown) => asserts value is Set; + nonEmptySet: (value: unknown) => asserts value is Set; + emptyMap: (value: unknown) => asserts value is Map; + nonEmptyMap: (value: unknown) => asserts value is Map; + + // Numbers. + evenInteger: (value: number) => asserts value is number; + oddInteger: (value: number) => asserts value is number; + + // Two arguments. + directInstanceOf: (instance: unknown, class_: Class) => asserts instance is T; + inRange: (value: number, range: number | number[]) => asserts value is number; + + // Variadic functions. + any: (predicate: Predicate, ...values: unknown[]) => void | never; + all: (predicate: Predicate, ...values: unknown[]) => void | never; +} + +export const assert: Assert = { + // Unknowns. + undefined: (value: unknown): asserts value is undefined => assertType(is.undefined(value), TypeName.undefined, value), + string: (value: unknown): asserts value is string => assertType(is.string(value), TypeName.string, value), + number: (value: unknown): asserts value is number => assertType(is.number(value), TypeName.number, value), + bigint: (value: unknown): asserts value is bigint => assertType(is.bigint(value), TypeName.bigint, value), + // eslint-disable-next-line @typescript-eslint/ban-types + function_: (value: unknown): asserts value is Function => assertType(is.function_(value), TypeName.Function, value), + null_: (value: unknown): asserts value is null => assertType(is.null_(value), TypeName.null, value), + class_: (value: unknown): asserts value is Class => assertType(is.class_(value), AssertionTypeDescription.class_, value), + boolean: (value: unknown): asserts value is boolean => assertType(is.boolean(value), TypeName.boolean, value), + symbol: (value: unknown): asserts value is symbol => assertType(is.symbol(value), TypeName.symbol, value), + numericString: (value: unknown): asserts value is string => assertType(is.numericString(value), AssertionTypeDescription.numericString, value), + array: (value: unknown): asserts value is T[] => assertType(is.array(value), TypeName.Array, value), + buffer: (value: unknown): asserts value is Buffer => assertType(is.buffer(value), TypeName.Buffer, value), + nullOrUndefined: (value: unknown): asserts value is null | undefined => assertType(is.nullOrUndefined(value), AssertionTypeDescription.nullOrUndefined, value), + object: (value: unknown): asserts value is Record => assertType(is.object(value), TypeName.Object, value), + iterable: (value: unknown): asserts value is Iterable => assertType(is.iterable(value), AssertionTypeDescription.iterable, value), + asyncIterable: (value: unknown): asserts value is AsyncIterable => assertType(is.asyncIterable(value), AssertionTypeDescription.asyncIterable, value), + generator: (value: unknown): asserts value is Generator => assertType(is.generator(value), TypeName.Generator, value), + nativePromise: (value: unknown): asserts value is Promise => assertType(is.nativePromise(value), AssertionTypeDescription.nativePromise, value), + promise: (value: unknown): asserts value is Promise => assertType(is.promise(value), TypeName.Promise, value), + generatorFunction: (value: unknown): asserts value is GeneratorFunction => assertType(is.generatorFunction(value), TypeName.GeneratorFunction, value), + // eslint-disable-next-line @typescript-eslint/ban-types + asyncFunction: (value: unknown): asserts value is Function => assertType(is.asyncFunction(value), TypeName.AsyncFunction, value), + // eslint-disable-next-line @typescript-eslint/ban-types + boundFunction: (value: unknown): asserts value is Function => assertType(is.boundFunction(value), TypeName.Function, value), + regExp: (value: unknown): asserts value is RegExp => assertType(is.regExp(value), TypeName.RegExp, value), + date: (value: unknown): asserts value is Date => assertType(is.date(value), TypeName.Date, value), + error: (value: unknown): asserts value is Error => assertType(is.error(value), TypeName.Error, value), + map: (value: unknown): asserts value is Map => assertType(is.map(value), TypeName.Map, value), + set: (value: unknown): asserts value is Set => assertType(is.set(value), TypeName.Set, value), + weakMap: (value: unknown): asserts value is WeakMap => assertType(is.weakMap(value), TypeName.WeakMap, value), + weakSet: (value: unknown): asserts value is WeakSet => assertType(is.weakSet(value), TypeName.WeakSet, value), + int8Array: (value: unknown): asserts value is Int8Array => assertType(is.int8Array(value), TypeName.Int8Array, value), + uint8Array: (value: unknown): asserts value is Uint8Array => assertType(is.uint8Array(value), TypeName.Uint8Array, value), + uint8ClampedArray: (value: unknown): asserts value is Uint8ClampedArray => assertType(is.uint8ClampedArray(value), TypeName.Uint8ClampedArray, value), + int16Array: (value: unknown): asserts value is Int16Array => assertType(is.int16Array(value), TypeName.Int16Array, value), + uint16Array: (value: unknown): asserts value is Uint16Array => assertType(is.uint16Array(value), TypeName.Uint16Array, value), + int32Array: (value: unknown): asserts value is Int32Array => assertType(is.int32Array(value), TypeName.Int32Array, value), + uint32Array: (value: unknown): asserts value is Uint32Array => assertType(is.uint32Array(value), TypeName.Uint32Array, value), + float32Array: (value: unknown): asserts value is Float32Array => assertType(is.float32Array(value), TypeName.Float32Array, value), + float64Array: (value: unknown): asserts value is Float64Array => assertType(is.float64Array(value), TypeName.Float64Array, value), + bigInt64Array: (value: unknown): asserts value is BigInt64Array => assertType(is.bigInt64Array(value), TypeName.BigInt64Array, value), + bigUint64Array: (value: unknown): asserts value is BigUint64Array => assertType(is.bigUint64Array(value), TypeName.BigUint64Array, value), + arrayBuffer: (value: unknown): asserts value is ArrayBuffer => assertType(is.arrayBuffer(value), TypeName.ArrayBuffer, value), + sharedArrayBuffer: (value: unknown): asserts value is SharedArrayBuffer => assertType(is.sharedArrayBuffer(value), TypeName.SharedArrayBuffer, value), + dataView: (value: unknown): asserts value is DataView => assertType(is.dataView(value), TypeName.DataView, value), + urlInstance: (value: unknown): asserts value is URL => assertType(is.urlInstance(value), TypeName.URL, value), + urlString: (value: unknown): asserts value is string => assertType(is.urlString(value), AssertionTypeDescription.urlString, value), + truthy: (value: unknown): asserts value is unknown => assertType(is.truthy(value), AssertionTypeDescription.truthy, value), + falsy: (value: unknown): asserts value is unknown => assertType(is.falsy(value), AssertionTypeDescription.falsy, value), + nan: (value: unknown): asserts value is unknown => assertType(is.nan(value), AssertionTypeDescription.nan, value), + primitive: (value: unknown): asserts value is Primitive => assertType(is.primitive(value), AssertionTypeDescription.primitive, value), + integer: (value: unknown): asserts value is number => assertType(is.integer(value), AssertionTypeDescription.integer, value), + safeInteger: (value: unknown): asserts value is number => assertType(is.safeInteger(value), AssertionTypeDescription.safeInteger, value), + plainObject: (value: unknown): asserts value is {[key: string]: unknown} => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), + typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value), + arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value), + domElement: (value: unknown): asserts value is Element => assertType(is.domElement(value), AssertionTypeDescription.domElement, value), + observable: (value: unknown): asserts value is ObservableLike => assertType(is.observable(value), TypeName.Observable, value), + nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), AssertionTypeDescription.nodeStream, value), + infinite: (value: unknown): asserts value is number => assertType(is.infinite(value), AssertionTypeDescription.infinite, value), + emptyArray: (value: unknown): asserts value is never[] => assertType(is.emptyArray(value), AssertionTypeDescription.emptyArray, value), + nonEmptyArray: (value: unknown): asserts value is unknown[] => assertType(is.nonEmptyArray(value), AssertionTypeDescription.nonEmptyArray, value), + emptyString: (value: unknown): asserts value is '' => assertType(is.emptyString(value), AssertionTypeDescription.emptyString, value), + nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), AssertionTypeDescription.nonEmptyString, value), + emptyStringOrWhitespace: (value: unknown): asserts value is string => assertType(is.emptyStringOrWhitespace(value), AssertionTypeDescription.emptyStringOrWhitespace, value), + emptyObject: (value: unknown): asserts value is {[key: string]: never} => assertType(is.emptyObject(value), AssertionTypeDescription.emptyObject, value), + nonEmptyObject: (value: unknown): asserts value is {[key: string]: unknown} => assertType(is.nonEmptyObject(value), AssertionTypeDescription.nonEmptyObject, value), + emptySet: (value: unknown): asserts value is Set => assertType(is.emptySet(value), AssertionTypeDescription.emptySet, value), + nonEmptySet: (value: unknown): asserts value is Set => assertType(is.nonEmptySet(value), AssertionTypeDescription.nonEmptySet, value), + emptyMap: (value: unknown): asserts value is Map => assertType(is.emptyMap(value), AssertionTypeDescription.emptyMap, value), + nonEmptyMap: (value: unknown): asserts value is Map => assertType(is.nonEmptyMap(value), AssertionTypeDescription.nonEmptyMap, value), + + // Numbers. + evenInteger: (value: number): asserts value is number => assertType(is.evenInteger(value), AssertionTypeDescription.evenInteger, value), + oddInteger: (value: number): asserts value is number => assertType(is.oddInteger(value), AssertionTypeDescription.oddInteger, value), + + // Two arguments. + directInstanceOf: (instance: unknown, class_: Class): asserts instance is T => assertType(is.directInstanceOf(instance, class_), AssertionTypeDescription.directInstanceOf, instance), + inRange: (value: number, range: number | number[]): asserts value is number => assertType(is.inRange(value, range), AssertionTypeDescription.inRange, value), + + // Variadic functions. + any: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.any(predicate, ...values), AssertionTypeDescription.any, values), + all: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.all(predicate, ...values), AssertionTypeDescription.all, values) +}; + // Some few keywords are reserved, but we'll populate them for Node.js users // See https://github.com/Microsoft/TypeScript/issues/2536 Object.defineProperties(is, { @@ -398,9 +623,21 @@ Object.defineProperties(is, { value: is.null_ } }); +Object.defineProperties(assert, { + class: { + value: assert.class_ + }, + function: { + value: assert.function_ + }, + null: { + value: assert.null_ + } +}); export default is; // For CommonJS default export support module.exports = is; module.exports.default = is; +module.exports.assert = assert; diff --git a/test/test.ts b/test/test.ts index b7d017d..6e6c4be 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,13 +1,14 @@ -import fs = require('fs'); -import net = require('net'); -import Stream = require('stream'); import {inspect} from 'util'; -import tempy = require('tempy'); import test, {ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; +import is, {assert, AssertionTypeDescription, TypeName} from '../source'; + +import fs = require('fs'); +import net = require('net'); +import Stream = require('stream'); +import tempy = require('tempy'); import ZenObservable = require('zen-observable'); -import is, {TypeName} from '../source'; class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} @@ -17,14 +18,33 @@ const {document} = window; const createDomElement = (element: string) => document.createElement(element); interface Test { + assert: (...args: any[]) => void | never; fixtures: unknown[]; typename?: TypeName; + typeDescription?: AssertionTypeDescription | TypeName; is(value: unknown): boolean; } +const invertAssertThrow = (description: string, fn: () => void | never, value: unknown): void | never => { + const expectedAssertErrorMessage = `Expected value which is "${description}", received value of type ${is(value)}.`; + + try { + fn(); + } catch (error) { + if (error instanceof TypeError && error.message.includes(expectedAssertErrorMessage)) { + return; + } + + throw error; + } + + throw new Error(`Function did not throw any error, expected: ${expectedAssertErrorMessage}`); +}; + const types = new Map([ ['undefined', { is: is.undefined, + assert: assert.undefined, fixtures: [ undefined ], @@ -32,6 +52,7 @@ const types = new Map([ }], ['null', { is: is.null_, + assert: assert.null_, fixtures: [ null ], @@ -39,6 +60,7 @@ const types = new Map([ }], ['string', { is: is.string, + assert: assert.string, fixtures: [ '🦄', 'hello world', @@ -48,14 +70,17 @@ const types = new Map([ }], ['emptyString', { is: is.emptyString, + assert: assert.emptyString, fixtures: [ '', String() ], - typename: TypeName.string + typename: TypeName.string, + typeDescription: AssertionTypeDescription.emptyString }], ['number', { is: is.number, + assert: assert.number, fixtures: [ 6, 1.4, @@ -68,6 +93,7 @@ const types = new Map([ }], ['bigint', { is: is.bigint, + assert: assert.bigint, fixtures: [ // Disabled until TS supports it for an ESnnnn target. // 1n, @@ -79,6 +105,7 @@ const types = new Map([ }], ['boolean', { is: is.boolean, + assert: assert.boolean, fixtures: [ true, false ], @@ -86,6 +113,7 @@ const types = new Map([ }], ['symbol', { is: is.symbol, + assert: assert.symbol, fixtures: [ Symbol('🦄') ], @@ -93,15 +121,18 @@ const types = new Map([ }], ['numericString', { is: is.numericString, + assert: assert.numericString, fixtures: [ '5', '-3.2', 'Infinity' ], - typename: TypeName.string + typename: TypeName.string, + typeDescription: AssertionTypeDescription.numericString }], ['array', { is: is.array, + assert: assert.array, fixtures: [ [1, 2], new Array(2) @@ -110,14 +141,17 @@ const types = new Map([ }], ['emptyArray', { is: is.emptyArray, + assert: assert.emptyArray, fixtures: [ [], new Array() // eslint-disable-line @typescript-eslint/no-array-constructor ], - typename: TypeName.Array + typename: TypeName.Array, + typeDescription: AssertionTypeDescription.emptyArray }], ['function', { is: is.function_, + assert: assert.function_, fixtures: [ function foo() {}, // eslint-disable-line func-names function () {}, @@ -129,6 +163,7 @@ const types = new Map([ }], ['buffer', { is: is.buffer, + assert: assert.buffer, fixtures: [ Buffer.from('🦄') ], @@ -136,6 +171,7 @@ const types = new Map([ }], ['object', { is: is.object, + assert: assert.object, fixtures: [ {x: 1}, Object.create({x: 1}) @@ -144,6 +180,7 @@ const types = new Map([ }], ['regExp', { is: is.regExp, + assert: assert.regExp, fixtures: [ /\w/, new RegExp('\\w') // eslint-disable-line prefer-regex-literals @@ -152,6 +189,7 @@ const types = new Map([ }], ['date', { is: is.date, + assert: assert.date, fixtures: [ new Date() ], @@ -159,6 +197,7 @@ const types = new Map([ }], ['error', { is: is.error, + assert: assert.error, fixtures: [ new Error('🦄'), new ErrorSubclassFixture() @@ -167,21 +206,26 @@ const types = new Map([ }], ['nativePromise', { is: is.nativePromise, + assert: assert.nativePromise, fixtures: [ Promise.resolve(), PromiseSubclassFixture.resolve() ], - typename: TypeName.Promise + typename: TypeName.Promise, + typeDescription: AssertionTypeDescription.nativePromise }], ['promise', { is: is.promise, + assert: assert.promise, fixtures: [ {then() {}, catch() {}} ], - typename: TypeName.Object + typename: TypeName.Object, + typeDescription: TypeName.Promise }], ['generator', { is: is.generator, + assert: assert.generator, fixtures: [ (function * () { yield 4; @@ -191,23 +235,28 @@ const types = new Map([ }], ['generatorFunction', { is: is.generatorFunction, + assert: assert.generatorFunction, fixtures: [ function * () { yield 4; } ], - typename: TypeName.Function + typename: TypeName.Function, + typeDescription: TypeName.GeneratorFunction }], ['asyncFunction', { is: is.asyncFunction, + assert: assert.asyncFunction, fixtures: [ async function () {}, async () => {} ], - typename: TypeName.Function + typename: TypeName.Function, + typeDescription: TypeName.AsyncFunction }], ['boundFunction', { is: is.boundFunction, + assert: assert.boundFunction, fixtures: [ () => {}, function () {}.bind(null) // eslint-disable-line no-extra-bind @@ -216,6 +265,7 @@ const types = new Map([ }], ['map', { is: is.map, + assert: assert.map, fixtures: [ new Map([['one', '1']]) ], @@ -223,13 +273,16 @@ const types = new Map([ }], ['emptyMap', { is: is.emptyMap, + assert: assert.emptyMap, fixtures: [ new Map() ], - typename: TypeName.Map + typename: TypeName.Map, + typeDescription: AssertionTypeDescription.emptyMap }], ['set', { is: is.set, + assert: assert.set, fixtures: [ new Set(['one']) ], @@ -237,13 +290,16 @@ const types = new Map([ }], ['emptySet', { is: is.emptySet, + assert: assert.emptySet, fixtures: [ new Set() ], - typename: TypeName.Set + typename: TypeName.Set, + typeDescription: AssertionTypeDescription.emptySet }], ['weakSet', { is: is.weakSet, + assert: assert.weakSet, fixtures: [ new WeakSet() ], @@ -251,6 +307,7 @@ const types = new Map([ }], ['weakMap', { is: is.weakMap, + assert: assert.weakMap, fixtures: [ new WeakMap() ], @@ -258,6 +315,7 @@ const types = new Map([ }], ['int8Array', { is: is.int8Array, + assert: assert.int8Array, fixtures: [ new Int8Array() ], @@ -265,6 +323,7 @@ const types = new Map([ }], ['uint8Array', { is: is.uint8Array, + assert: assert.uint8Array, fixtures: [ new Uint8Array() ], @@ -272,6 +331,7 @@ const types = new Map([ }], ['uint8ClampedArray', { is: is.uint8ClampedArray, + assert: assert.uint8ClampedArray, fixtures: [ new Uint8ClampedArray() ], @@ -279,6 +339,7 @@ const types = new Map([ }], ['int16Array', { is: is.int16Array, + assert: assert.int16Array, fixtures: [ new Int16Array() ], @@ -286,6 +347,7 @@ const types = new Map([ }], ['uint16Array', { is: is.uint16Array, + assert: assert.uint16Array, fixtures: [ new Uint16Array() ], @@ -293,6 +355,7 @@ const types = new Map([ }], ['int32Array', { is: is.int32Array, + assert: assert.int32Array, fixtures: [ new Int32Array() ], @@ -300,6 +363,7 @@ const types = new Map([ }], ['uint32Array', { is: is.uint32Array, + assert: assert.uint32Array, fixtures: [ new Uint32Array() ], @@ -307,6 +371,7 @@ const types = new Map([ }], ['float32Array', { is: is.float32Array, + assert: assert.float32Array, fixtures: [ new Float32Array() ], @@ -314,6 +379,7 @@ const types = new Map([ }], ['float64Array', { is: is.float64Array, + assert: assert.float64Array, fixtures: [ new Float64Array() ], @@ -321,6 +387,7 @@ const types = new Map([ }], ['bigInt64Array', { is: is.bigInt64Array, + assert: assert.bigInt64Array, fixtures: [ new BigInt64Array() ], @@ -328,6 +395,7 @@ const types = new Map([ }], ['bigUint64Array', { is: is.bigUint64Array, + assert: assert.bigUint64Array, fixtures: [ new BigUint64Array() ], @@ -335,6 +403,7 @@ const types = new Map([ }], ['arrayBuffer', { is: is.arrayBuffer, + assert: assert.arrayBuffer, fixtures: [ new ArrayBuffer(10) ], @@ -342,6 +411,7 @@ const types = new Map([ }], ['dataView', { is: is.dataView, + assert: assert.dataView, fixtures: [ new DataView(new ArrayBuffer(10)) ], @@ -349,45 +419,56 @@ const types = new Map([ }], ['nan', { is: is.nan, + assert: assert.nan, fixtures: [ NaN, Number.NaN ], - typename: TypeName.number + typename: TypeName.number, + typeDescription: AssertionTypeDescription.nan }], ['nullOrUndefined', { is: is.nullOrUndefined, + assert: assert.nullOrUndefined, fixtures: [ null, undefined - ] + ], + typeDescription: AssertionTypeDescription.nullOrUndefined }], ['plainObject', { is: is.plainObject, + assert: assert.plainObject, fixtures: [ {x: 1}, Object.create(null), new Object() // eslint-disable-line no-new-object ], - typename: TypeName.Object + typename: TypeName.Object, + typeDescription: AssertionTypeDescription.plainObject }], ['integer', { is: is.integer, + assert: assert.integer, fixtures: [ 6 ], - typename: TypeName.number + typename: TypeName.number, + typeDescription: AssertionTypeDescription.integer }], ['safeInteger', { is: is.safeInteger, + assert: assert.safeInteger, fixtures: [ (2 ** 53) - 1, -(2 ** 53) + 1 ], - typename: TypeName.number + typename: TypeName.number, + typeDescription: AssertionTypeDescription.safeInteger }], ['domElement', { is: is.domElement, + assert: assert.domElement, fixtures: [ 'div', 'input', @@ -395,10 +476,12 @@ const types = new Map([ 'img', 'canvas', 'script' - ].map(createDomElement) + ].map(createDomElement), + typeDescription: AssertionTypeDescription.domElement }], ['non-domElements', { is: value => !is.domElement(value), + assert: (value: unknown) => invertAssertThrow(AssertionTypeDescription.domElement, () => assert.domElement(value), value), fixtures: [ document.createTextNode('data'), document.createProcessingInstruction('xml-stylesheet', 'href="mycss.css" type="text/css"'), @@ -410,6 +493,7 @@ const types = new Map([ }], ['observable', { is: is.observable, + assert: assert.observable, fixtures: [ new Observable(), new Subject(), @@ -419,6 +503,7 @@ const types = new Map([ }], ['nodeStream', { is: is.nodeStream, + assert: assert.nodeStream, fixtures: [ fs.createReadStream('readme.md'), fs.createWriteStream(tempy.file()), @@ -430,15 +515,18 @@ const types = new Map([ new Stream.Stream(), new Stream.Writable() ], - typename: TypeName.Object + typename: TypeName.Object, + typeDescription: AssertionTypeDescription.nodeStream }], ['infinite', { is: is.infinite, + assert: assert.infinite, fixtures: [ Infinity, -Infinity ], - typename: TypeName.number + typename: TypeName.number, + typeDescription: AssertionTypeDescription.infinite }] ]); @@ -452,7 +540,7 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { return; } - const {is: testIs, typename} = testData; + const {is: testIs, assert: testAssert, typename, typeDescription} = testData; for (const [key, {fixtures}] of types) { // TODO: Automatically exclude value types in other tests that we have in the current one. @@ -462,10 +550,13 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { } const isTypeUnderTest = key === type; - const assert = isTypeUnderTest ? t.true.bind(t) : t.false.bind(t); + const assertIs = isTypeUnderTest ? t.true.bind(t) : t.false.bind(t); + const assertAsserts = isTypeUnderTest ? t.notThrows.bind(t) : t.throws.bind(t); for (const fixture of fixtures) { - assert(testIs(fixture), `Value: ${inspect(fixture)}`); + assertIs(testIs(fixture), `Value: ${inspect(fixture)}`); + const valueType = JSON.stringify(typeDescription ? typeDescription : typename); + assertAsserts(() => testAssert(fixture), `Expected value which is ${valueType}, received value of type ${is(fixture)}.`); if (isTypeUnderTest && typename) { t.is(is(fixture), typename); @@ -506,6 +597,8 @@ test('is.numericString', t => { testType(t, 'numericString'); t.false(is.numericString('')); t.false(is.numericString(1)); + t.throws(() => assert.numericString('')); + t.throws(() => assert.numericString(1)); }); test('is.array', t => { @@ -518,6 +611,7 @@ test('is.function', t => { test('is.boundFunction', t => { t.false(is.boundFunction(function () {})); // eslint-disable-line prefer-arrow-callback + t.throws(() => assert.boundFunction(function () {})); // eslint-disable-line prefer-arrow-callback }); test('is.buffer', t => { @@ -535,6 +629,7 @@ test('is.object', t => { for (const el of testData.fixtures) { t.true(is.object(el)); + t.notThrows(() => assert.object(el)); } }); @@ -565,6 +660,8 @@ test('is.asyncFunction', t => { if (is.asyncFunction(fixture)) { // eslint-disable-next-line promise/prefer-await-to-then t.true(is.function_(fixture().then)); + // eslint-disable-next-line promise/prefer-await-to-then + t.notThrows(() => assert.function_(fixture().then)); } }); @@ -650,9 +747,13 @@ test('is.directInstanceOf', t => { t.true(is.directInstanceOf(error, Error)); t.true(is.directInstanceOf(errorSubclass, ErrorSubclassFixture)); + t.notThrows(() => assert.directInstanceOf(error, Error)); + t.notThrows(() => assert.directInstanceOf(errorSubclass, ErrorSubclassFixture)); t.false(is.directInstanceOf(error, ErrorSubclassFixture)); t.false(is.directInstanceOf(errorSubclass, Error)); + t.throws(() => assert.directInstanceOf(error, ErrorSubclassFixture)); + t.throws(() => assert.directInstanceOf(errorSubclass, Error)); }); test('is.urlInstance', t => { @@ -661,6 +762,11 @@ test('is.urlInstance', t => { t.false(is.urlInstance({})); t.false(is.urlInstance(undefined)); t.false(is.urlInstance(null)); + + t.notThrows(() => assert.urlInstance(url)); + t.throws(() => assert.urlInstance({})); + t.throws(() => assert.urlInstance(undefined)); + t.throws(() => assert.urlInstance(null)); }); test('is.urlString', t => { @@ -670,6 +776,12 @@ test('is.urlString', t => { t.false(is.urlString({})); t.false(is.urlString(undefined)); t.false(is.urlString(null)); + + t.notThrows(() => assert.urlString(url)); + t.throws(() => assert.urlString(new URL(url))); + t.throws(() => assert.urlString({})); + t.throws(() => assert.urlString(undefined)); + t.throws(() => assert.urlString(null)); }); test('is.truthy', t => { @@ -682,6 +794,17 @@ test('is.truthy', t => { // Disabled until TS supports it for an ESnnnn target. // t.true(is.truthy(1n)); t.true(is.truthy(BigInt(1))); + + t.notThrows(() => assert.truthy('unicorn')); + t.notThrows(() => assert.truthy('🦄')); + t.notThrows(() => assert.truthy(new Set())); + t.notThrows(() => assert.truthy(Symbol('🦄'))); + t.notThrows(() => assert.truthy(true)); + t.notThrows(() => assert.truthy(1)); + + // Disabled until TS supports it for an ESnnnn target. + // t.notThrows(() => assert.truthy(1n)); + t.notThrows(() => assert.truthy(BigInt(1))); }); test('is.falsy', t => { @@ -694,6 +817,16 @@ test('is.falsy', t => { // Disabled until TS supports it for an ESnnnn target. // t.true(is.falsy(0n)); t.true(is.falsy(BigInt(0))); + + t.notThrows(() => assert.falsy(false)); + t.notThrows(() => assert.falsy(0)); + t.notThrows(() => assert.falsy('')); + t.notThrows(() => assert.falsy(null)); + t.notThrows(() => assert.falsy(undefined)); + t.notThrows(() => assert.falsy(NaN)); + // Disabled until TS supports it for an ESnnnn target. + // t.notThrows(() => assert.falsy(0n)); + t.notThrows(() => assert.falsy(BigInt(0))); }); test('is.nan', t => { @@ -721,18 +854,22 @@ test('is.primitive', t => { for (const el of primitives) { t.true(is.primitive(el)); + t.notThrows(() => assert.primitive(el)); } }); test('is.integer', t => { testType(t, 'integer', ['number', 'safeInteger']); t.false(is.integer(1.4)); + t.throws(() => assert.integer(1.4)); }); test('is.safeInteger', t => { testType(t, 'safeInteger', ['number', 'integer']); t.false(is.safeInteger(2 ** 53)); t.false(is.safeInteger(-(2 ** 53))); + t.throws(() => assert.safeInteger(2 ** 53)); + t.throws(() => assert.safeInteger(-(2 ** 53))); }); test('is.plainObject', t => { @@ -749,6 +886,16 @@ test('is.iterable', t => { t.false(is.iterable(NaN)); t.false(is.iterable(Infinity)); t.false(is.iterable({})); + + t.notThrows(() => assert.iterable('')); + t.notThrows(() => assert.iterable([])); + t.notThrows(() => assert.iterable(new Map())); + t.throws(() => assert.iterable(null)); + t.throws(() => assert.iterable(undefined)); + t.throws(() => assert.iterable(0)); + t.throws(() => assert.iterable(NaN)); + t.throws(() => assert.iterable(Infinity)); + t.throws(() => assert.iterable({})); }); test('is.asyncIterable', t => { @@ -762,6 +909,17 @@ test('is.asyncIterable', t => { t.false(is.asyncIterable(NaN)); t.false(is.asyncIterable(Infinity)); t.false(is.asyncIterable({})); + + t.notThrows(() => assert.asyncIterable({ + [Symbol.asyncIterator]: () => { } + })); + + t.throws(() => assert.asyncIterable(null)); + t.throws(() => assert.asyncIterable(undefined)); + t.throws(() => assert.asyncIterable(0)); + t.throws(() => assert.asyncIterable(NaN)); + t.throws(() => assert.asyncIterable(Infinity)); + t.throws(() => assert.asyncIterable({})); }); test('is.class', t => { @@ -774,6 +932,7 @@ test('is.class', t => { for (const classDeclaration of classDeclarations) { t.true(is.class_(classDeclaration)); + t.notThrows(() => assert.class_(classDeclaration)); } }); @@ -793,11 +952,16 @@ test('is.typedArray', t => { for (const item of typedArrays) { t.true(is.typedArray(item)); + t.notThrows(() => assert.typedArray(item)); } t.false(is.typedArray(new ArrayBuffer(1))); t.false(is.typedArray([])); t.false(is.typedArray({})); + + t.throws(() => assert.typedArray(new ArrayBuffer(1))); + t.throws(() => assert.typedArray([])); + t.throws(() => assert.typedArray({})); }); test('is.arrayLike', t => { @@ -811,6 +975,17 @@ test('is.arrayLike', t => { t.false(is.arrayLike({})); t.false(is.arrayLike(() => {})); t.false(is.arrayLike(new Map())); + + (function () { + t.notThrows(() => assert.arrayLike(arguments)); // eslint-disable-line prefer-rest-params + })(); + + t.notThrows(() => assert.arrayLike([])); + t.notThrows(() => assert.arrayLike('unicorn')); + + t.throws(() => assert.arrayLike({})); + t.throws(() => assert.arrayLike(() => { })); + t.throws(() => assert.arrayLike(new Map())); }); test('is.inRange', t => { @@ -842,11 +1017,39 @@ test('is.inRange', t => { t.throws(() => { is.inRange(0, [1, 2, 3]); }); + + t.notThrows(() => assert.inRange(x, [0, 5])); + t.notThrows(() => assert.inRange(x, [5, 0])); + t.notThrows(() => assert.inRange(x, [-5, 5])); + t.notThrows(() => assert.inRange(x, [5, -5])); + t.throws(() => assert.inRange(x, [4, 8])); + t.notThrows(() => assert.inRange(-7, [-5, -10])); + t.notThrows(() => assert.inRange(-5, [-5, -10])); + t.notThrows(() => assert.inRange(-10, [-5, -10])); + + t.notThrows(() => assert.inRange(x, 10)); + t.notThrows(() => assert.inRange(0, 0)); + t.notThrows(() => assert.inRange(-2, -3)); + t.throws(() => assert.inRange(x, 2)); + t.throws(() => assert.inRange(-3, -2)); + + t.throws(() => { + assert.inRange(0, []); + }); + + t.throws(() => { + assert.inRange(0, [5]); + }); + + t.throws(() => { + assert.inRange(0, [1, 2, 3]); + }); }); test('is.domElement', t => { testType(t, 'domElement'); t.false(is.domElement({nodeType: 1, nodeName: 'div'})); + t.throws(() => assert.domElement({nodeType: 1, nodeName: 'div'})); const htmlTagNameToTypeName = { div: 'HTMLDivElement', @@ -878,20 +1081,24 @@ test('is.infinite', t => { test('is.evenInteger', t => { for (const el of [-6, 2, 4]) { t.true(is.evenInteger(el)); + t.notThrows(() => assert.evenInteger(el)); } for (const el of [-3, 1, 5]) { t.false(is.evenInteger(el)); + t.throws(() => assert.evenInteger(el)); } }); test('is.oddInteger', t => { for (const el of [-5, 7, 13]) { t.true(is.oddInteger(el)); + t.notThrows(() => assert.oddInteger(el)); } for (const el of [-8, 8, 10]) { t.false(is.oddInteger(el)); + t.throws(() => assert.oddInteger(el)); } }); @@ -903,17 +1110,26 @@ test('is.nonEmptyArray', t => { t.true(is.nonEmptyArray([1, 2, 3])); t.false(is.nonEmptyArray([])); t.false(is.nonEmptyArray(new Array())); // eslint-disable-line @typescript-eslint/no-array-constructor + + t.notThrows(() => assert.nonEmptyArray([1, 2, 3])); + t.throws(() => assert.nonEmptyArray([])); + t.throws(() => assert.nonEmptyArray(new Array())); // eslint-disable-line @typescript-eslint/no-array-constructor }); test('is.emptyString', t => { testType(t, 'emptyString', ['string']); t.false(is.emptyString('🦄')); + t.throws(() => assert.emptyString('🦄')); }); test('is.nonEmptyString', t => { t.false(is.nonEmptyString('')); t.false(is.nonEmptyString(String())); t.true(is.nonEmptyString('🦄')); + + t.throws(() => assert.nonEmptyString('')); + t.throws(() => assert.nonEmptyString(String())); + t.notThrows(() => assert.nonEmptyString('🦄')); }); test('is.emptyStringOrWhitespace', t => { @@ -921,12 +1137,20 @@ test('is.emptyStringOrWhitespace', t => { t.true(is.emptyStringOrWhitespace(' ')); t.false(is.emptyStringOrWhitespace('🦄')); t.false(is.emptyStringOrWhitespace('unicorn')); + + t.notThrows(() => assert.emptyStringOrWhitespace(' ')); + t.throws(() => assert.emptyStringOrWhitespace('🦄')); + t.throws(() => assert.emptyStringOrWhitespace('unicorn')); }); test('is.emptyObject', t => { t.true(is.emptyObject({})); t.true(is.emptyObject(new Object())); // eslint-disable-line no-new-object t.false(is.emptyObject({unicorn: '🦄'})); + + t.notThrows(() => assert.emptyObject({})); + t.notThrows(() => assert.emptyObject(new Object())); // eslint-disable-line no-new-object + t.throws(() => assert.emptyObject({unicorn: '🦄'})); }); test('is.nonEmptyObject', t => { @@ -936,6 +1160,10 @@ test('is.nonEmptyObject', t => { t.false(is.nonEmptyObject({})); t.false(is.nonEmptyObject(new Object())); // eslint-disable-line no-new-object t.true(is.nonEmptyObject({unicorn: '🦄'})); + + t.throws(() => assert.nonEmptyObject({})); + t.throws(() => assert.nonEmptyObject(new Object())); // eslint-disable-line no-new-object + t.notThrows(() => assert.nonEmptyObject({unicorn: '🦄'})); }); test('is.emptySet', t => { @@ -945,9 +1173,11 @@ test('is.emptySet', t => { test('is.nonEmptySet', t => { const tempSet = new Set(); t.false(is.nonEmptySet(tempSet)); + t.throws(() => assert.nonEmptySet(tempSet)); tempSet.add(1); t.true(is.nonEmptySet(tempSet)); + t.notThrows(() => assert.nonEmptySet(tempSet)); }); test('is.emptyMap', t => { @@ -957,9 +1187,11 @@ test('is.emptyMap', t => { test('is.nonEmptyMap', t => { const tempMap = new Map(); t.false(is.nonEmptyMap(tempMap)); + t.throws(() => assert.nonEmptyMap(tempMap)); tempMap.set('unicorn', '🦄'); t.true(is.nonEmptyMap(tempMap)); + t.notThrows(() => assert.nonEmptyMap(tempMap)); }); test('is.any', t => { @@ -975,6 +1207,19 @@ test('is.any', t => { t.throws(() => { is.any(is.string); }); + + t.notThrows(() => assert.any(is.string, {}, true, '🦄')); + t.notThrows(() => assert.any(is.object, false, {}, 'unicorns')); + t.throws(() => assert.any(is.boolean, '🦄', [], 3)); + t.throws(() => assert.any(is.integer, true, 'lol', {})); + + t.throws(() => { + assert.any(null as any, true); + }); + + t.throws(() => { + assert.any(is.string); + }); }); test('is.all', t => { @@ -990,4 +1235,56 @@ test('is.all', t => { t.throws(() => { is.all(is.string); }); + + t.notThrows(() => assert.all(is.object, {}, new Set(), new Map())); + t.notThrows(() => assert.all(is.boolean, true, false)); + t.throws(() => assert.all(is.string, '🦄', [])); + t.throws(() => assert.all(is.set, new Map(), {})); + + t.throws(() => { + assert.all(null as any, true); + }); + + t.throws(() => { + assert.all(is.string); + }); +}); + +test('assert', t => { + // Contrived test showing that typescript acknowledges the type assertion in assert.number(). + // Real world usage includes asserting user input, but here we use a random number/string generator. + t.plan(2); + + const getNumberOrStringRandomly = (): number | string => { + const rnd = Math.random(); + + if (rnd < 0.5) { + return 'sometimes this function returns text'; + } + + return rnd; + }; + + const canUseOnlyNumber = (badlyTypedArgument: any): number => { + // Narrow the type to number, or throw an error at runtime for non-numbers. + assert.number(badlyTypedArgument); + + // Both the type and runtime value is number. + return 1000 * badlyTypedArgument; + }; + + const badlyTypedVariable: any = getNumberOrStringRandomly(); + + t.true(is.number(badlyTypedVariable) || is.string(badlyTypedVariable)); + + // Using try/catch for test purposes only. + try { + const result = canUseOnlyNumber(badlyTypedVariable); + + // Got lucky, the input was a number yielding a good result. + t.true(is.number(result)); + } catch { + // Assertion was tripped. + t.true(is.string(badlyTypedVariable)); + } }); From 11003b925e78aa7b910fc845707cacb3a80d2fe7 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 22 Jan 2020 18:47:01 +0700 Subject: [PATCH 123/254] Meta tweaks --- package.json | 1 + readme.md | 35 ++-- source/index.ts | 2 +- test/test.ts | 494 +++++++++++++++++++++++++++++++++++------------- 4 files changed, 386 insertions(+), 146 deletions(-) diff --git a/package.json b/package.json index 5867b4f..4f99826 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", + "funding": "https://github.com/sindresorhus/is?sponsor=1", "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", diff --git a/readme.md b/readme.md index 2d55818..45986b8 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,6 @@ For example, `is.string('🦄') //=> true` - ## Highlights - Written in TypeScript @@ -15,14 +14,12 @@ For example, `is.string('🦄') //=> true` - Actively maintained - 2 million weekly downloads - ## Install ``` $ npm install @sindresorhus/is ``` - ## Usage ```js @@ -38,15 +35,23 @@ is.number(6); //=> true ``` -[Assertions](#type-assertions) perform the same type checks, but throw errors if the type does not match. +[Assertions](#type-assertions) perform the same type checks, but throw an error if the type does not match. ```js const {assert} = require('@sindresorhus/is'); -assert.string(foo); -// foo is known to be a string here +assert.string(2); +//=> Error: Expected value which is `string`, received value of type `number`. ``` +And with TypeScript: + +```ts +import {assert} from '@sindresorhus/is'; + +assert.string(foo); +// `foo` is now typed as a `string`. +``` ## API @@ -406,7 +411,6 @@ is.all(is.string, '🦄', [], 'unicorns'); //=> false ``` - ## Type guards When using `is` together with TypeScript, [type guards](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types) are being used extensively to infer the correct type inside if-else statements. @@ -437,18 +441,20 @@ padLeft('🦄', '🌈'); ## Type assertions -The type guards are also available as [typescript assertions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions), which throw an error for unexpected types. It is a convenient one-line version of the often repetitive "if-not-expected-type-throw" pattern. +The type guards are also available as [type assertions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions), which throw an error for unexpected types. It is a convenient one-line version of the often repetitive "if-not-expected-type-throw" pattern. ```ts import {assert} from '@sindresorhus/is'; const handleMovieRatingApiResponse = (response: unknown) => { assert.plainObject(response); - // `response` is typed as a plain `object` with `unknown` properties + // `response` is now typed as a plain `object` with `unknown` properties. + assert.number(response.rating); - // `response.rating` is typed as a `number` + // `response.rating` is now typed as a `number`. + assert.string(response.title); - // `response.title` is typed as a `string` + // `response.title` is now typed as a `string`. return `${response.title} (${response.rating * 10})`; }; @@ -456,8 +462,8 @@ const handleMovieRatingApiResponse = (response: unknown) => { handleMovieRatingApiResponse({rating: 0.87, title: 'The Matrix'}); //=> 'The Matrix (8.7)' -handleMovieRatingApiResponse({status: 'system maintenance'}); -//=> throws error +// This throws an error. +handleMovieRatingApiResponse({rating: '🦄'}); ``` ## FAQ @@ -477,14 +483,12 @@ 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. - ## For enterprise Available as part of the Tidelift Subscription. The maintainers of @sindresorhus/is and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-sindresorhus-is?utm_source=npm-sindresorhus-is&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) - ## Related - [ow](https://github.com/sindresorhus/ow) - Function argument validation for humans @@ -498,7 +502,6 @@ The maintainers of @sindresorhus/is and thousands of other packages are working - [is-blob](https://github.com/sindresorhus/is-blob) - Check if a value is a Blob - File-like object of immutable, raw data - [has-emoji](https://github.com/sindresorhus/has-emoji) - Check whether a string has any emoji - ## Maintainers - [Sindre Sorhus](https://github.com/sindresorhus) diff --git a/source/index.ts b/source/index.ts index 36004d2..a6bc71d 100644 --- a/source/index.ts +++ b/source/index.ts @@ -387,7 +387,7 @@ is.all = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArr const assertType = (condition: boolean, description: string, value: unknown): asserts condition => { if (!condition) { - throw new TypeError(`Expected value which is "${description}", received value of type ${is(value)}.`); + throw new TypeError(`Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`); } }; diff --git a/test/test.ts b/test/test.ts index 6e6c4be..eb681bc 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,14 +1,13 @@ +import fs = require('fs'); +import net = require('net'); +import Stream = require('stream'); import {inspect} from 'util'; import test, {ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; -import is, {assert, AssertionTypeDescription, TypeName} from '../source'; - -import fs = require('fs'); -import net = require('net'); -import Stream = require('stream'); import tempy = require('tempy'); import ZenObservable = require('zen-observable'); +import is, {assert, AssertionTypeDescription, TypeName} from '../source'; class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} @@ -26,7 +25,7 @@ interface Test { } const invertAssertThrow = (description: string, fn: () => void | never, value: unknown): void | never => { - const expectedAssertErrorMessage = `Expected value which is "${description}", received value of type ${is(value)}.`; + const expectedAssertErrorMessage = `Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`; try { fn(); @@ -555,8 +554,8 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { for (const fixture of fixtures) { assertIs(testIs(fixture), `Value: ${inspect(fixture)}`); - const valueType = JSON.stringify(typeDescription ? typeDescription : typename); - assertAsserts(() => testAssert(fixture), `Expected value which is ${valueType}, received value of type ${is(fixture)}.`); + const valueType = typeDescription ? typeDescription : typename; + assertAsserts(() => testAssert(fixture), `Expected value which is \`${valueType!}\`, received value of type \`${is(fixture)}\`.`); if (isTypeUnderTest && typename) { t.is(is(fixture), typename); @@ -597,8 +596,12 @@ test('is.numericString', t => { testType(t, 'numericString'); t.false(is.numericString('')); t.false(is.numericString(1)); - t.throws(() => assert.numericString('')); - t.throws(() => assert.numericString(1)); + t.throws(() => { + assert.numericString(''); + }); + t.throws(() => { + assert.numericString(1); + }); }); test('is.array', t => { @@ -611,7 +614,10 @@ test('is.function', t => { test('is.boundFunction', t => { t.false(is.boundFunction(function () {})); // eslint-disable-line prefer-arrow-callback - t.throws(() => assert.boundFunction(function () {})); // eslint-disable-line prefer-arrow-callback + + t.throws(() => { + assert.boundFunction(function () {}); // eslint-disable-line prefer-arrow-callback + }); }); test('is.buffer', t => { @@ -660,8 +666,11 @@ test('is.asyncFunction', t => { if (is.asyncFunction(fixture)) { // eslint-disable-next-line promise/prefer-await-to-then t.true(is.function_(fixture().then)); - // eslint-disable-next-line promise/prefer-await-to-then - t.notThrows(() => assert.function_(fixture().then)); + + t.notThrows(() => { + // eslint-disable-next-line promise/prefer-await-to-then + assert.function_(fixture().then); + }); } }); @@ -747,13 +756,21 @@ test('is.directInstanceOf', t => { t.true(is.directInstanceOf(error, Error)); t.true(is.directInstanceOf(errorSubclass, ErrorSubclassFixture)); - t.notThrows(() => assert.directInstanceOf(error, Error)); - t.notThrows(() => assert.directInstanceOf(errorSubclass, ErrorSubclassFixture)); + t.notThrows(() => { + assert.directInstanceOf(error, Error); + }); + t.notThrows(() => { + assert.directInstanceOf(errorSubclass, ErrorSubclassFixture); + }); t.false(is.directInstanceOf(error, ErrorSubclassFixture)); t.false(is.directInstanceOf(errorSubclass, Error)); - t.throws(() => assert.directInstanceOf(error, ErrorSubclassFixture)); - t.throws(() => assert.directInstanceOf(errorSubclass, Error)); + t.throws(() => { + assert.directInstanceOf(error, ErrorSubclassFixture); + }); + t.throws(() => { + assert.directInstanceOf(errorSubclass, Error); + }); }); test('is.urlInstance', t => { @@ -763,10 +780,18 @@ test('is.urlInstance', t => { t.false(is.urlInstance(undefined)); t.false(is.urlInstance(null)); - t.notThrows(() => assert.urlInstance(url)); - t.throws(() => assert.urlInstance({})); - t.throws(() => assert.urlInstance(undefined)); - t.throws(() => assert.urlInstance(null)); + t.notThrows(() => { + assert.urlInstance(url); + }); + t.throws(() => { + assert.urlInstance({}); + }); + t.throws(() => { + assert.urlInstance(undefined); + }); + t.throws(() => { + assert.urlInstance(null); + }); }); test('is.urlString', t => { @@ -777,11 +802,21 @@ test('is.urlString', t => { t.false(is.urlString(undefined)); t.false(is.urlString(null)); - t.notThrows(() => assert.urlString(url)); - t.throws(() => assert.urlString(new URL(url))); - t.throws(() => assert.urlString({})); - t.throws(() => assert.urlString(undefined)); - t.throws(() => assert.urlString(null)); + t.notThrows(() => { + assert.urlString(url); + }); + t.throws(() => { + assert.urlString(new URL(url)); + }); + t.throws(() => { + assert.urlString({}); + }); + t.throws(() => { + assert.urlString(undefined); + }); + t.throws(() => { + assert.urlString(null); + }); }); test('is.truthy', t => { @@ -795,16 +830,30 @@ test('is.truthy', t => { // t.true(is.truthy(1n)); t.true(is.truthy(BigInt(1))); - t.notThrows(() => assert.truthy('unicorn')); - t.notThrows(() => assert.truthy('🦄')); - t.notThrows(() => assert.truthy(new Set())); - t.notThrows(() => assert.truthy(Symbol('🦄'))); - t.notThrows(() => assert.truthy(true)); - t.notThrows(() => assert.truthy(1)); + t.notThrows(() => { + assert.truthy('unicorn'); + }); + t.notThrows(() => { + assert.truthy('🦄'); + }); + t.notThrows(() => { + assert.truthy(new Set()); + }); + t.notThrows(() => { + assert.truthy(Symbol('🦄')); + }); + t.notThrows(() => { + assert.truthy(true); + }); + t.notThrows(() => { + assert.truthy(1); + }); - // Disabled until TS supports it for an ESnnnn target. + // TODO: Disabled until TS supports it for an ESnnnn target. // t.notThrows(() => assert.truthy(1n)); - t.notThrows(() => assert.truthy(BigInt(1))); + t.notThrows(() => { + assert.truthy(BigInt(1)); + }); }); test('is.falsy', t => { @@ -814,19 +863,33 @@ test('is.falsy', t => { t.true(is.falsy(null)); t.true(is.falsy(undefined)); t.true(is.falsy(NaN)); - // Disabled until TS supports it for an ESnnnn target. + // TODO: Disabled until TS supports it for an ESnnnn target. // t.true(is.falsy(0n)); t.true(is.falsy(BigInt(0))); - t.notThrows(() => assert.falsy(false)); - t.notThrows(() => assert.falsy(0)); - t.notThrows(() => assert.falsy('')); - t.notThrows(() => assert.falsy(null)); - t.notThrows(() => assert.falsy(undefined)); - t.notThrows(() => assert.falsy(NaN)); - // Disabled until TS supports it for an ESnnnn target. + t.notThrows(() => { + assert.falsy(false); + }); + t.notThrows(() => { + assert.falsy(0); + }); + t.notThrows(() => { + assert.falsy(''); + }); + t.notThrows(() => { + assert.falsy(null); + }); + t.notThrows(() => { + assert.falsy(undefined); + }); + t.notThrows(() => { + assert.falsy(NaN); + }); + // TODO: Disabled until TS supports it for an ESnnnn target. // t.notThrows(() => assert.falsy(0n)); - t.notThrows(() => assert.falsy(BigInt(0))); + t.notThrows(() => { + assert.falsy(BigInt(0)); + }); }); test('is.nan', t => { @@ -852,24 +915,32 @@ test('is.primitive', t => { // 6n ]; - for (const el of primitives) { - t.true(is.primitive(el)); - t.notThrows(() => assert.primitive(el)); + for (const element of primitives) { + t.true(is.primitive(element)); + t.notThrows(() => { + assert.primitive(element); + }); } }); test('is.integer', t => { testType(t, 'integer', ['number', 'safeInteger']); t.false(is.integer(1.4)); - t.throws(() => assert.integer(1.4)); + t.throws(() => { + assert.integer(1.4); + }); }); test('is.safeInteger', t => { testType(t, 'safeInteger', ['number', 'integer']); t.false(is.safeInteger(2 ** 53)); t.false(is.safeInteger(-(2 ** 53))); - t.throws(() => assert.safeInteger(2 ** 53)); - t.throws(() => assert.safeInteger(-(2 ** 53))); + t.throws(() => { + assert.safeInteger(2 ** 53); + }); + t.throws(() => { + assert.safeInteger(-(2 ** 53)); + }); }); test('is.plainObject', t => { @@ -887,15 +958,33 @@ test('is.iterable', t => { t.false(is.iterable(Infinity)); t.false(is.iterable({})); - t.notThrows(() => assert.iterable('')); - t.notThrows(() => assert.iterable([])); - t.notThrows(() => assert.iterable(new Map())); - t.throws(() => assert.iterable(null)); - t.throws(() => assert.iterable(undefined)); - t.throws(() => assert.iterable(0)); - t.throws(() => assert.iterable(NaN)); - t.throws(() => assert.iterable(Infinity)); - t.throws(() => assert.iterable({})); + t.notThrows(() => { + assert.iterable(''); + }); + t.notThrows(() => { + assert.iterable([]); + }); + t.notThrows(() => { + assert.iterable(new Map()); + }); + t.throws(() => { + assert.iterable(null); + }); + t.throws(() => { + assert.iterable(undefined); + }); + t.throws(() => { + assert.iterable(0); + }); + t.throws(() => { + assert.iterable(NaN); + }); + t.throws(() => { + assert.iterable(Infinity); + }); + t.throws(() => { + assert.iterable({}); + }); }); test('is.asyncIterable', t => { @@ -910,16 +999,30 @@ test('is.asyncIterable', t => { t.false(is.asyncIterable(Infinity)); t.false(is.asyncIterable({})); - t.notThrows(() => assert.asyncIterable({ - [Symbol.asyncIterator]: () => { } - })); + t.notThrows(() => { + assert.asyncIterable({ + [Symbol.asyncIterator]: () => { } + }); + }); - t.throws(() => assert.asyncIterable(null)); - t.throws(() => assert.asyncIterable(undefined)); - t.throws(() => assert.asyncIterable(0)); - t.throws(() => assert.asyncIterable(NaN)); - t.throws(() => assert.asyncIterable(Infinity)); - t.throws(() => assert.asyncIterable({})); + t.throws(() => { + assert.asyncIterable(null); + }); + t.throws(() => { + assert.asyncIterable(undefined); + }); + t.throws(() => { + assert.asyncIterable(0); + }); + t.throws(() => { + assert.asyncIterable(NaN); + }); + t.throws(() => { + assert.asyncIterable(Infinity); + }); + t.throws(() => { + assert.asyncIterable({}); + }); }); test('is.class', t => { @@ -932,7 +1035,10 @@ test('is.class', t => { for (const classDeclaration of classDeclarations) { t.true(is.class_(classDeclaration)); - t.notThrows(() => assert.class_(classDeclaration)); + + t.notThrows(() => { + assert.class_(classDeclaration); + }); } }); @@ -952,16 +1058,25 @@ test('is.typedArray', t => { for (const item of typedArrays) { t.true(is.typedArray(item)); - t.notThrows(() => assert.typedArray(item)); + + t.notThrows(() => { + assert.typedArray(item); + }); } t.false(is.typedArray(new ArrayBuffer(1))); t.false(is.typedArray([])); t.false(is.typedArray({})); - t.throws(() => assert.typedArray(new ArrayBuffer(1))); - t.throws(() => assert.typedArray([])); - t.throws(() => assert.typedArray({})); + t.throws(() => { + assert.typedArray(new ArrayBuffer(1)); + }); + t.throws(() => { + assert.typedArray([]); + }); + t.throws(() => { + assert.typedArray({}); + }); }); test('is.arrayLike', t => { @@ -977,15 +1092,27 @@ test('is.arrayLike', t => { t.false(is.arrayLike(new Map())); (function () { - t.notThrows(() => assert.arrayLike(arguments)); // eslint-disable-line prefer-rest-params + t.notThrows(() => { + assert.arrayLike(arguments); // eslint-disable-line prefer-rest-params + }); })(); - t.notThrows(() => assert.arrayLike([])); - t.notThrows(() => assert.arrayLike('unicorn')); + t.notThrows(() => { + assert.arrayLike([]); + }); + t.notThrows(() => { + assert.arrayLike('unicorn'); + }); - t.throws(() => assert.arrayLike({})); - t.throws(() => assert.arrayLike(() => { })); - t.throws(() => assert.arrayLike(new Map())); + t.throws(() => { + assert.arrayLike({}); + }); + t.throws(() => { + assert.arrayLike(() => {}); + }); + t.throws(() => { + assert.arrayLike(new Map()); + }); }); test('is.inRange', t => { @@ -1018,20 +1145,57 @@ test('is.inRange', t => { is.inRange(0, [1, 2, 3]); }); - t.notThrows(() => assert.inRange(x, [0, 5])); - t.notThrows(() => assert.inRange(x, [5, 0])); - t.notThrows(() => assert.inRange(x, [-5, 5])); - t.notThrows(() => assert.inRange(x, [5, -5])); - t.throws(() => assert.inRange(x, [4, 8])); - t.notThrows(() => assert.inRange(-7, [-5, -10])); - t.notThrows(() => assert.inRange(-5, [-5, -10])); - t.notThrows(() => assert.inRange(-10, [-5, -10])); + t.notThrows(() => { + assert.inRange(x, [0, 5]); + }); - t.notThrows(() => assert.inRange(x, 10)); - t.notThrows(() => assert.inRange(0, 0)); - t.notThrows(() => assert.inRange(-2, -3)); - t.throws(() => assert.inRange(x, 2)); - t.throws(() => assert.inRange(-3, -2)); + t.notThrows(() => { + assert.inRange(x, [5, 0]); + }); + + t.notThrows(() => { + assert.inRange(x, [-5, 5]); + }); + + t.notThrows(() => { + assert.inRange(x, [5, -5]); + }); + + t.throws(() => { + assert.inRange(x, [4, 8]); + }); + + t.notThrows(() => { + assert.inRange(-7, [-5, -10]); + }); + + t.notThrows(() => { + assert.inRange(-5, [-5, -10]); + }); + + t.notThrows(() => { + assert.inRange(-10, [-5, -10]); + }); + + t.notThrows(() => { + assert.inRange(x, 10); + }); + + t.notThrows(() => { + assert.inRange(0, 0); + }); + + t.notThrows(() => { + assert.inRange(-2, -3); + }); + + t.throws(() => { + assert.inRange(x, 2); + }); + + t.throws(() => { + assert.inRange(-3, -2); + }); t.throws(() => { assert.inRange(0, []); @@ -1049,7 +1213,9 @@ test('is.inRange', t => { test('is.domElement', t => { testType(t, 'domElement'); t.false(is.domElement({nodeType: 1, nodeName: 'div'})); - t.throws(() => assert.domElement({nodeType: 1, nodeName: 'div'})); + t.throws(() => { + assert.domElement({nodeType: 1, nodeName: 'div'}); + }); const htmlTagNameToTypeName = { div: 'HTMLDivElement', @@ -1081,24 +1247,32 @@ test('is.infinite', t => { test('is.evenInteger', t => { for (const el of [-6, 2, 4]) { t.true(is.evenInteger(el)); - t.notThrows(() => assert.evenInteger(el)); + t.notThrows(() => { + assert.evenInteger(el); + }); } for (const el of [-3, 1, 5]) { t.false(is.evenInteger(el)); - t.throws(() => assert.evenInteger(el)); + t.throws(() => { + assert.evenInteger(el); + }); } }); test('is.oddInteger', t => { for (const el of [-5, 7, 13]) { t.true(is.oddInteger(el)); - t.notThrows(() => assert.oddInteger(el)); + t.notThrows(() => { + assert.oddInteger(el); + }); } for (const el of [-8, 8, 10]) { t.false(is.oddInteger(el)); - t.throws(() => assert.oddInteger(el)); + t.throws(() => { + assert.oddInteger(el); + }); } }); @@ -1111,15 +1285,23 @@ test('is.nonEmptyArray', t => { t.false(is.nonEmptyArray([])); t.false(is.nonEmptyArray(new Array())); // eslint-disable-line @typescript-eslint/no-array-constructor - t.notThrows(() => assert.nonEmptyArray([1, 2, 3])); - t.throws(() => assert.nonEmptyArray([])); - t.throws(() => assert.nonEmptyArray(new Array())); // eslint-disable-line @typescript-eslint/no-array-constructor + t.notThrows(() => { + assert.nonEmptyArray([1, 2, 3]); + }); + t.throws(() => { + assert.nonEmptyArray([]); + }); + t.throws(() => { + assert.nonEmptyArray(new Array()); // eslint-disable-line @typescript-eslint/no-array-constructor + }); }); test('is.emptyString', t => { testType(t, 'emptyString', ['string']); t.false(is.emptyString('🦄')); - t.throws(() => assert.emptyString('🦄')); + t.throws(() => { + assert.emptyString('🦄'); + }); }); test('is.nonEmptyString', t => { @@ -1127,9 +1309,15 @@ test('is.nonEmptyString', t => { t.false(is.nonEmptyString(String())); t.true(is.nonEmptyString('🦄')); - t.throws(() => assert.nonEmptyString('')); - t.throws(() => assert.nonEmptyString(String())); - t.notThrows(() => assert.nonEmptyString('🦄')); + t.throws(() => { + assert.nonEmptyString(''); + }); + t.throws(() => { + assert.nonEmptyString(String()); + }); + t.notThrows(() => { + assert.nonEmptyString('🦄'); + }); }); test('is.emptyStringOrWhitespace', t => { @@ -1138,9 +1326,15 @@ test('is.emptyStringOrWhitespace', t => { t.false(is.emptyStringOrWhitespace('🦄')); t.false(is.emptyStringOrWhitespace('unicorn')); - t.notThrows(() => assert.emptyStringOrWhitespace(' ')); - t.throws(() => assert.emptyStringOrWhitespace('🦄')); - t.throws(() => assert.emptyStringOrWhitespace('unicorn')); + t.notThrows(() => { + assert.emptyStringOrWhitespace(' '); + }); + t.throws(() => { + assert.emptyStringOrWhitespace('🦄'); + }); + t.throws(() => { + assert.emptyStringOrWhitespace('unicorn'); + }); }); test('is.emptyObject', t => { @@ -1148,9 +1342,15 @@ test('is.emptyObject', t => { t.true(is.emptyObject(new Object())); // eslint-disable-line no-new-object t.false(is.emptyObject({unicorn: '🦄'})); - t.notThrows(() => assert.emptyObject({})); - t.notThrows(() => assert.emptyObject(new Object())); // eslint-disable-line no-new-object - t.throws(() => assert.emptyObject({unicorn: '🦄'})); + t.notThrows(() => { + assert.emptyObject({}); + }); + t.notThrows(() => { + assert.emptyObject(new Object()); // eslint-disable-line no-new-object + }); + t.throws(() => { + assert.emptyObject({unicorn: '🦄'}); + }); }); test('is.nonEmptyObject', t => { @@ -1161,9 +1361,15 @@ test('is.nonEmptyObject', t => { t.false(is.nonEmptyObject(new Object())); // eslint-disable-line no-new-object t.true(is.nonEmptyObject({unicorn: '🦄'})); - t.throws(() => assert.nonEmptyObject({})); - t.throws(() => assert.nonEmptyObject(new Object())); // eslint-disable-line no-new-object - t.notThrows(() => assert.nonEmptyObject({unicorn: '🦄'})); + t.throws(() => { + assert.nonEmptyObject({}); + }); + t.throws(() => { + assert.nonEmptyObject(new Object()); // eslint-disable-line no-new-object + }); + t.notThrows(() => { + assert.nonEmptyObject({unicorn: '🦄'}); + }); }); test('is.emptySet', t => { @@ -1173,11 +1379,15 @@ test('is.emptySet', t => { test('is.nonEmptySet', t => { const tempSet = new Set(); t.false(is.nonEmptySet(tempSet)); - t.throws(() => assert.nonEmptySet(tempSet)); + t.throws(() => { + assert.nonEmptySet(tempSet); + }); tempSet.add(1); t.true(is.nonEmptySet(tempSet)); - t.notThrows(() => assert.nonEmptySet(tempSet)); + t.notThrows(() => { + assert.nonEmptySet(tempSet); + }); }); test('is.emptyMap', t => { @@ -1187,11 +1397,15 @@ test('is.emptyMap', t => { test('is.nonEmptyMap', t => { const tempMap = new Map(); t.false(is.nonEmptyMap(tempMap)); - t.throws(() => assert.nonEmptyMap(tempMap)); + t.throws(() => { + assert.nonEmptyMap(tempMap); + }); tempMap.set('unicorn', '🦄'); t.true(is.nonEmptyMap(tempMap)); - t.notThrows(() => assert.nonEmptyMap(tempMap)); + t.notThrows(() => { + assert.nonEmptyMap(tempMap); + }); }); test('is.any', t => { @@ -1208,10 +1422,21 @@ test('is.any', t => { is.any(is.string); }); - t.notThrows(() => assert.any(is.string, {}, true, '🦄')); - t.notThrows(() => assert.any(is.object, false, {}, 'unicorns')); - t.throws(() => assert.any(is.boolean, '🦄', [], 3)); - t.throws(() => assert.any(is.integer, true, 'lol', {})); + t.notThrows(() => { + assert.any(is.string, {}, true, '🦄'); + }); + + t.notThrows(() => { + assert.any(is.object, false, {}, 'unicorns'); + }); + + t.throws(() => { + assert.any(is.boolean, '🦄', [], 3); + }); + + t.throws(() => { + assert.any(is.integer, true, 'lol', {}); + }); t.throws(() => { assert.any(null as any, true); @@ -1236,10 +1461,21 @@ test('is.all', t => { is.all(is.string); }); - t.notThrows(() => assert.all(is.object, {}, new Set(), new Map())); - t.notThrows(() => assert.all(is.boolean, true, false)); - t.throws(() => assert.all(is.string, '🦄', [])); - t.throws(() => assert.all(is.set, new Map(), {})); + t.notThrows(() => { + assert.all(is.object, {}, new Set(), new Map()); + }); + + t.notThrows(() => { + assert.all(is.boolean, true, false); + }); + + t.throws(() => { + assert.all(is.string, '🦄', []); + }); + + t.throws(() => { + assert.all(is.set, new Map(), {}); + }); t.throws(() => { assert.all(null as any, true); @@ -1251,18 +1487,18 @@ test('is.all', t => { }); test('assert', t => { - // Contrived test showing that typescript acknowledges the type assertion in assert.number(). - // Real world usage includes asserting user input, but here we use a random number/string generator. + // Contrived test showing that TypeScript acknowledges the type assertion in `assert.number()`. + // Real--world usage includes asserting user input, but here we use a random number/string generator. t.plan(2); const getNumberOrStringRandomly = (): number | string => { - const rnd = Math.random(); + const random = Math.random(); - if (rnd < 0.5) { + if (random < 0.5) { return 'sometimes this function returns text'; } - return rnd; + return random; }; const canUseOnlyNumber = (badlyTypedArgument: any): number => { From d28c86ee27620f99c9d922a8b4a730632fd090bf Mon Sep 17 00:00:00 2001 From: Joel Purra Date: Wed, 29 Jan 2020 17:22:35 +0100 Subject: [PATCH 124/254] Propagate generic types where possible (#103) --- readme.md | 43 +++++++++++++++++++++++++++++++ source/index.ts | 68 ++++++++++++++++++++++++------------------------- 2 files changed, 77 insertions(+), 34 deletions(-) diff --git a/readme.md b/readme.md index 45986b8..12a4809 100644 --- a/readme.md +++ b/readme.md @@ -11,6 +11,7 @@ For example, `is.string('🦄') //=> true` - Written in TypeScript - [Extensive use of type guards](#type-guards) - [Supports type assertions](#type-assertions) +- [Aware of generic type parameters](#generic-type-parameters) (use with caution) - Actively maintained - 2 million weekly downloads @@ -466,6 +467,48 @@ handleMovieRatingApiResponse({rating: 0.87, title: 'The Matrix'}); handleMovieRatingApiResponse({rating: '🦄'}); ``` +## Generic type parameters + +The type guards and type assertions are aware of [generic type parameters](https://www.typescriptlang.org/docs/handbook/generics.html), such as `Promise` and `Map`. The default is `unknown` for most cases, since `is` can not check them at runtime. If the generic type is known at compile-time, either implicitly (inferred) or explicitly (provided), `is` propagates the type so it can be used later. + +Use generic type parameters with caution. They are only checked by the TypeScript compiler, and not checked by `is` at runtime. This can lead to unexpected behavior, where the generic type is _assumed_ at compile-time, but actually is something completely different at runtime. It is best to use `unknown` (default) and type-check the value of the generic type parameter at runtime with `is` or `assert`. + +```ts +import { assert } from '@sindresorhus/is'; + +async function badNumberAssumption(input: unknown) { + // Bad assumption about the generic type parameter fools the compile-time type system. + assert.promise(input); + // `input` is a `Promise` but only assumed to be `Promise`. + + const resolved = await input; + // `resolved` is typed as `number` but was not actually checked at runtime. + + // Multiplication will return NaN if the input promise did not actually contain a number. + return 2 * resolved; +} + +async function goodNumberAssertion(input: unknown) { + assert.promise(input); + // `input` is typed as `Promise` + + const resolved = await input; + // `resolved` is typed as `unknown` + + assert.number(resolved); + // `resolved` is typed as `number` + + // Uses runtime checks so only numbers will reach the multiplication. + return 2 * resolved; +} + +badNumberAssumption(Promise.resolve("an unexpected string")); +//=> 'NaN' + +// This correctly throws an error because of the unexpected string value. +goodNumberAssertion(Promise.resolve("an unexpected string")); +``` + ## FAQ ### Why yet another type checking module? diff --git a/source/index.ts b/source/index.ts index a6bc71d..8eb85d2 100644 --- a/source/index.ts +++ b/source/index.ts @@ -135,25 +135,25 @@ is.buffer = (value: unknown): value is Buffer => !is.nullOrUndefined(value) && ! is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); is.object = (value: unknown): value is object => !is.null_(value) && (typeof value === 'object' || is.function_(value)); -is.iterable = (value: unknown): value is IterableIterator => !is.nullOrUndefined(value) && is.function_((value as IterableIterator)[Symbol.iterator]); +is.iterable = (value: unknown): value is IterableIterator => !is.nullOrUndefined(value) && is.function_((value as IterableIterator)[Symbol.iterator]); -is.asyncIterable = (value: unknown): value is AsyncIterableIterator => !is.nullOrUndefined(value) && is.function_((value as AsyncIterableIterator)[Symbol.asyncIterator]); +is.asyncIterable = (value: unknown): value is AsyncIterableIterator => !is.nullOrUndefined(value) && is.function_((value as AsyncIterableIterator)[Symbol.asyncIterator]); is.generator = (value: unknown): value is Generator => is.iterable(value) && is.function_(value.next) && is.function_(value.throw); -is.nativePromise = (value: unknown): value is Promise => - isObjectOfType>(TypeName.Promise)(value); +is.nativePromise = (value: unknown): value is Promise => + isObjectOfType>(TypeName.Promise)(value); -const hasPromiseAPI = (value: unknown): value is Promise => +const hasPromiseAPI = (value: unknown): value is Promise => is.object(value) && - is.function_((value as Promise).then) && // eslint-disable-line promise/prefer-await-to-then - is.function_((value as Promise).catch); + is.function_((value as Promise).then) && // eslint-disable-line promise/prefer-await-to-then + is.function_((value as Promise).catch); -is.promise = (value: unknown): value is Promise => is.nativePromise(value) || hasPromiseAPI(value); +is.promise = (value: unknown): value is Promise => is.nativePromise(value) || hasPromiseAPI(value); is.generatorFunction = isObjectOfType(TypeName.GeneratorFunction); -is.asyncFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === TypeName.AsyncFunction; +is.asyncFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === TypeName.AsyncFunction; // eslint-disable-next-line no-prototype-builtins, @typescript-eslint/ban-types is.boundFunction = (value: unknown): value is Function => is.function_(value) && !value.hasOwnProperty('prototype'); @@ -161,9 +161,9 @@ is.boundFunction = (value: unknown): value is Function => is.function_(value) && is.regExp = isObjectOfType(TypeName.RegExp); is.date = isObjectOfType(TypeName.Date); is.error = isObjectOfType(TypeName.Error); -is.map = (value: unknown): value is Map => isObjectOfType>(TypeName.Map)(value); -is.set = (value: unknown): value is Set => isObjectOfType>(TypeName.Set)(value); -is.weakMap = (value: unknown): value is WeakMap => isObjectOfType>(TypeName.WeakMap)(value); +is.map = (value: unknown): value is Map => isObjectOfType>(TypeName.Map)(value); +is.set = (value: unknown): value is Set => isObjectOfType>(TypeName.Set)(value); +is.weakMap = (value: unknown): value is WeakMap => isObjectOfType>(TypeName.WeakMap)(value); is.weakSet = (value: unknown): value is WeakSet => isObjectOfType>(TypeName.WeakSet)(value); is.int8Array = isObjectOfType(TypeName.Int8Array); @@ -230,7 +230,7 @@ is.primitive = (value: unknown): value is Primitive => is.null_(value) || primit is.integer = (value: unknown): value is number => Number.isInteger(value as number); is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); -is.plainObject = (value: unknown): value is {[key: string]: unknown} => { +is.plainObject = (value: unknown): value is Record => { // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js if (getObjectType(value) !== TypeName.Object) { return false; @@ -283,7 +283,7 @@ export interface ArrayLike { } const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0; -is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); +is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); is.inRange = (value: number, range: number | number[]): value is number => { if (is.number(range)) { @@ -354,17 +354,17 @@ is.nonEmptyString = (value: unknown): value is string => is.string(value) && val const isWhiteSpaceString = (value: unknown): value is string => is.string(value) && /\S/.test(value) === false; is.emptyStringOrWhitespace = (value: unknown): value is string => is.emptyString(value) || isWhiteSpaceString(value); -is.emptyObject = (value: unknown): value is {[key: string]: never} => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0; +is.emptyObject = (value: unknown): value is Record => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0; // TODO: Use `not` operator here to remove `Map` and `Set` from type guard: // - https://github.com/Microsoft/TypeScript/pull/29317 -is.nonEmptyObject = (value: unknown): value is {[key: string]: unknown} => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length > 0; +is.nonEmptyObject = (value: unknown): value is Record => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length > 0; is.emptySet = (value: unknown): value is Set => is.set(value) && value.size === 0; -is.nonEmptySet = (value: unknown): value is Set => is.set(value) && value.size > 0; +is.nonEmptySet = (value: unknown): value is Set => is.set(value) && value.size > 0; is.emptyMap = (value: unknown): value is Map => is.map(value) && value.size === 0; -is.nonEmptyMap = (value: unknown): value is Map => is.map(value) && value.size > 0; +is.nonEmptyMap = (value: unknown): value is Map => is.map(value) && value.size > 0; export type Predicate = (value: unknown) => boolean; @@ -450,7 +450,7 @@ interface Assert { array: (value: unknown) => asserts value is T[]; buffer: (value: unknown) => asserts value is Buffer; nullOrUndefined: (value: unknown) => asserts value is null | undefined; - object: (value: unknown) => asserts value is Record; + object: (value: unknown) => asserts value is Record; iterable: (value: unknown) => asserts value is Iterable; asyncIterable: (value: unknown) => asserts value is AsyncIterable; generator: (value: unknown) => asserts value is Generator; @@ -464,9 +464,9 @@ interface Assert { regExp: (value: unknown) => asserts value is RegExp; date: (value: unknown) => asserts value is Date; error: (value: unknown) => asserts value is Error; - map: (value: unknown) => asserts value is Map; + map: (value: unknown) => asserts value is Map; set: (value: unknown) => asserts value is Set; - weakMap: (value: unknown) => asserts value is WeakMap; + weakMap: (value: unknown) => asserts value is WeakMap; weakSet: (value: unknown) => asserts value is WeakSet; int8Array: (value: unknown) => asserts value is Int8Array; uint8Array: (value: unknown) => asserts value is Uint8Array; @@ -490,7 +490,7 @@ interface Assert { primitive: (value: unknown) => asserts value is Primitive; integer: (value: unknown) => asserts value is number; safeInteger: (value: unknown) => asserts value is number; - plainObject: (value: unknown) => asserts value is {[key: string]: unknown}; + plainObject: (value: unknown) => asserts value is Record; typedArray: (value: unknown) => asserts value is TypedArray; arrayLike: (value: unknown) => asserts value is ArrayLike; domElement: (value: unknown) => asserts value is Element; @@ -502,12 +502,12 @@ interface Assert { emptyString: (value: unknown) => asserts value is ''; nonEmptyString: (value: unknown) => asserts value is string; emptyStringOrWhitespace: (value: unknown) => asserts value is string; - emptyObject: (value: unknown) => asserts value is {[key: string]: never}; - nonEmptyObject: (value: unknown) => asserts value is {[key: string]: unknown}; + emptyObject: (value: unknown) => asserts value is Record; + nonEmptyObject: (value: unknown) => asserts value is Record; emptySet: (value: unknown) => asserts value is Set; - nonEmptySet: (value: unknown) => asserts value is Set; + nonEmptySet: (value: unknown) => asserts value is Set; emptyMap: (value: unknown) => asserts value is Map; - nonEmptyMap: (value: unknown) => asserts value is Map; + nonEmptyMap: (value: unknown) => asserts value is Map; // Numbers. evenInteger: (value: number) => asserts value is number; @@ -538,7 +538,7 @@ export const assert: Assert = { array: (value: unknown): asserts value is T[] => assertType(is.array(value), TypeName.Array, value), buffer: (value: unknown): asserts value is Buffer => assertType(is.buffer(value), TypeName.Buffer, value), nullOrUndefined: (value: unknown): asserts value is null | undefined => assertType(is.nullOrUndefined(value), AssertionTypeDescription.nullOrUndefined, value), - object: (value: unknown): asserts value is Record => assertType(is.object(value), TypeName.Object, value), + object: (value: unknown): asserts value is object => assertType(is.object(value), TypeName.Object, value), iterable: (value: unknown): asserts value is Iterable => assertType(is.iterable(value), AssertionTypeDescription.iterable, value), asyncIterable: (value: unknown): asserts value is AsyncIterable => assertType(is.asyncIterable(value), AssertionTypeDescription.asyncIterable, value), generator: (value: unknown): asserts value is Generator => assertType(is.generator(value), TypeName.Generator, value), @@ -552,9 +552,9 @@ export const assert: Assert = { regExp: (value: unknown): asserts value is RegExp => assertType(is.regExp(value), TypeName.RegExp, value), date: (value: unknown): asserts value is Date => assertType(is.date(value), TypeName.Date, value), error: (value: unknown): asserts value is Error => assertType(is.error(value), TypeName.Error, value), - map: (value: unknown): asserts value is Map => assertType(is.map(value), TypeName.Map, value), + map: (value: unknown): asserts value is Map => assertType(is.map(value), TypeName.Map, value), set: (value: unknown): asserts value is Set => assertType(is.set(value), TypeName.Set, value), - weakMap: (value: unknown): asserts value is WeakMap => assertType(is.weakMap(value), TypeName.WeakMap, value), + weakMap: (value: unknown): asserts value is WeakMap => assertType(is.weakMap(value), TypeName.WeakMap, value), weakSet: (value: unknown): asserts value is WeakSet => assertType(is.weakSet(value), TypeName.WeakSet, value), int8Array: (value: unknown): asserts value is Int8Array => assertType(is.int8Array(value), TypeName.Int8Array, value), uint8Array: (value: unknown): asserts value is Uint8Array => assertType(is.uint8Array(value), TypeName.Uint8Array, value), @@ -578,7 +578,7 @@ export const assert: Assert = { primitive: (value: unknown): asserts value is Primitive => assertType(is.primitive(value), AssertionTypeDescription.primitive, value), integer: (value: unknown): asserts value is number => assertType(is.integer(value), AssertionTypeDescription.integer, value), safeInteger: (value: unknown): asserts value is number => assertType(is.safeInteger(value), AssertionTypeDescription.safeInteger, value), - plainObject: (value: unknown): asserts value is {[key: string]: unknown} => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), + plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value), arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value), domElement: (value: unknown): asserts value is Element => assertType(is.domElement(value), AssertionTypeDescription.domElement, value), @@ -590,12 +590,12 @@ export const assert: Assert = { emptyString: (value: unknown): asserts value is '' => assertType(is.emptyString(value), AssertionTypeDescription.emptyString, value), nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), AssertionTypeDescription.nonEmptyString, value), emptyStringOrWhitespace: (value: unknown): asserts value is string => assertType(is.emptyStringOrWhitespace(value), AssertionTypeDescription.emptyStringOrWhitespace, value), - emptyObject: (value: unknown): asserts value is {[key: string]: never} => assertType(is.emptyObject(value), AssertionTypeDescription.emptyObject, value), - nonEmptyObject: (value: unknown): asserts value is {[key: string]: unknown} => assertType(is.nonEmptyObject(value), AssertionTypeDescription.nonEmptyObject, value), + emptyObject: (value: unknown): asserts value is Record => assertType(is.emptyObject(value), AssertionTypeDescription.emptyObject, value), + nonEmptyObject: (value: unknown): asserts value is Record => assertType(is.nonEmptyObject(value), AssertionTypeDescription.nonEmptyObject, value), emptySet: (value: unknown): asserts value is Set => assertType(is.emptySet(value), AssertionTypeDescription.emptySet, value), - nonEmptySet: (value: unknown): asserts value is Set => assertType(is.nonEmptySet(value), AssertionTypeDescription.nonEmptySet, value), + nonEmptySet: (value: unknown): asserts value is Set => assertType(is.nonEmptySet(value), AssertionTypeDescription.nonEmptySet, value), emptyMap: (value: unknown): asserts value is Map => assertType(is.emptyMap(value), AssertionTypeDescription.emptyMap, value), - nonEmptyMap: (value: unknown): asserts value is Map => assertType(is.nonEmptyMap(value), AssertionTypeDescription.nonEmptyMap, value), + nonEmptyMap: (value: unknown): asserts value is Map => assertType(is.nonEmptyMap(value), AssertionTypeDescription.nonEmptyMap, value), // Numbers. evenInteger: (value: number): asserts value is number => assertType(is.evenInteger(value), AssertionTypeDescription.evenInteger, value), From 446a7a081ea06032fc4f89d1afb1607d62c392e8 Mon Sep 17 00:00:00 2001 From: Forresst Date: Wed, 29 Jan 2020 18:35:58 +0100 Subject: [PATCH 125/254] Add `.asyncGenerator` and `.asyncGeneratorFunction` detection (#100) --- readme.md | 32 ++++++++++++++++++++++++++++++++ source/index.ts | 10 ++++++++++ test/test.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 12a4809..d274bb3 100644 --- a/readme.md +++ b/readme.md @@ -132,6 +132,38 @@ is.asyncFunction(() => {}); // => false ``` +##### .asyncGenerator(value) + +```js +is.asyncGenerator( + (async function * () { + yield 4; + })() +); +// => true + +is.asyncGenerator( + (function * () { + yield 4; + })() +); +// => false +``` + +##### .asyncGeneratorFunction(value) + +```js +is.asyncGeneratorFunction(async function * () { + yield 4; +}); +// => true + +is.asyncGeneratorFunction(function * () { + yield 4; +}); +// => false +``` + ##### .boundFunction(value) Returns `true` for any `bound` function. diff --git a/source/index.ts b/source/index.ts index 8eb85d2..4d42bfe 100644 --- a/source/index.ts +++ b/source/index.ts @@ -14,7 +14,9 @@ export const enum TypeName { symbol = 'symbol', Function = 'Function', Generator = 'Generator', + AsyncGenerator = 'AsyncGenerator', GeneratorFunction = 'GeneratorFunction', + AsyncGeneratorFunction = 'AsyncGeneratorFunction', AsyncFunction = 'AsyncFunction', Observable = 'Observable', Array = 'Array', @@ -141,6 +143,8 @@ is.asyncIterable = (value: unknown): value is AsyncIterableIterator is.generator = (value: unknown): value is Generator => is.iterable(value) && is.function_(value.next) && is.function_(value.throw); +is.asyncGenerator = (value: unknown): value is AsyncGenerator => is.asyncIterable(value) && is.function_(value.next) && is.function_(value.throw); + is.nativePromise = (value: unknown): value is Promise => isObjectOfType>(TypeName.Promise)(value); @@ -153,6 +157,8 @@ is.promise = (value: unknown): value is Promise => is.nativeProm is.generatorFunction = isObjectOfType(TypeName.GeneratorFunction); +is.asyncGeneratorFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === TypeName.AsyncGeneratorFunction; + is.asyncFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === TypeName.AsyncFunction; // eslint-disable-next-line no-prototype-builtins, @typescript-eslint/ban-types @@ -454,9 +460,11 @@ interface Assert { iterable: (value: unknown) => asserts value is Iterable; asyncIterable: (value: unknown) => asserts value is AsyncIterable; generator: (value: unknown) => asserts value is Generator; + asyncGenerator: (value: unknown) => asserts value is AsyncGenerator; nativePromise: (value: unknown) => asserts value is Promise; promise: (value: unknown) => asserts value is Promise; generatorFunction: (value: unknown) => asserts value is GeneratorFunction; + asyncGeneratorFunction: (value: unknown) => asserts value is AsyncGeneratorFunction; // eslint-disable-next-line @typescript-eslint/ban-types asyncFunction: (value: unknown) => asserts value is Function; // eslint-disable-next-line @typescript-eslint/ban-types @@ -542,9 +550,11 @@ export const assert: Assert = { iterable: (value: unknown): asserts value is Iterable => assertType(is.iterable(value), AssertionTypeDescription.iterable, value), asyncIterable: (value: unknown): asserts value is AsyncIterable => assertType(is.asyncIterable(value), AssertionTypeDescription.asyncIterable, value), generator: (value: unknown): asserts value is Generator => assertType(is.generator(value), TypeName.Generator, value), + asyncGenerator: (value: unknown): asserts value is AsyncGenerator => assertType(is.asyncGenerator(value), TypeName.AsyncGenerator, value), nativePromise: (value: unknown): asserts value is Promise => assertType(is.nativePromise(value), AssertionTypeDescription.nativePromise, value), promise: (value: unknown): asserts value is Promise => assertType(is.promise(value), TypeName.Promise, value), generatorFunction: (value: unknown): asserts value is GeneratorFunction => assertType(is.generatorFunction(value), TypeName.GeneratorFunction, value), + asyncGeneratorFunction: (value: unknown): asserts value is AsyncGeneratorFunction => assertType(is.asyncGeneratorFunction(value), TypeName.AsyncGeneratorFunction, value), // eslint-disable-next-line @typescript-eslint/ban-types asyncFunction: (value: unknown): asserts value is Function => assertType(is.asyncFunction(value), TypeName.AsyncFunction, value), // eslint-disable-next-line @typescript-eslint/ban-types diff --git a/test/test.ts b/test/test.ts index eb681bc..f48b7c3 100644 --- a/test/test.ts +++ b/test/test.ts @@ -156,7 +156,8 @@ const types = new Map([ function () {}, () => {}, async function () {}, - function * (): unknown {} + function * (): unknown {}, + async function * (): unknown {} ], typename: TypeName.Function }], @@ -232,6 +233,16 @@ const types = new Map([ ], typename: TypeName.Generator }], + ['asyncGenerator', { + is: is.asyncGenerator, + assert: assert.asyncGenerator, + fixtures: [ + (async function * () { + yield 4; + })() + ], + typename: TypeName.AsyncGenerator + }], ['generatorFunction', { is: is.generatorFunction, assert: assert.generatorFunction, @@ -243,6 +254,17 @@ const types = new Map([ typename: TypeName.Function, typeDescription: TypeName.GeneratorFunction }], + ['asyncGeneratorFunction', { + is: is.asyncGeneratorFunction, + assert: assert.asyncGeneratorFunction, + fixtures: [ + async function * () { + yield 4; + } + ], + typename: TypeName.Function, + typeDescription: TypeName.AsyncGeneratorFunction + }], ['asyncFunction', { is: is.asyncFunction, assert: assert.asyncFunction, @@ -609,7 +631,7 @@ test('is.array', t => { }); test('is.function', t => { - testType(t, 'function', ['generatorFunction', 'asyncFunction', 'boundFunction']); + testType(t, 'function', ['generatorFunction', 'asyncGeneratorFunction', 'asyncFunction', 'boundFunction']); }); test('is.boundFunction', t => { @@ -678,10 +700,33 @@ test('is.generator', t => { testType(t, 'generator'); }); +test('is.asyncGenerator', t => { + testType(t, 'asyncGenerator'); + + const fixture = (async function * () { + yield 4; + })(); + if (is.asyncGenerator(fixture)) { + t.true(is.function_(fixture.next)); + } +}); + test('is.generatorFunction', t => { testType(t, 'generatorFunction', ['function']); }); +test('is.asyncGeneratorFunction', t => { + testType(t, 'asyncGeneratorFunction', ['function']); + + const fixture = async function * () { + yield 4; + }; + + if (is.asyncGeneratorFunction(fixture)) { + t.true(is.function_(fixture().next)); + } +}); + test('is.map', t => { testType(t, 'map', ['emptyMap']); }); From 5d0ccec21c621210b84b4c0c9e5be559eba61c10 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 30 Jan 2020 00:38:17 +0700 Subject: [PATCH 126/254] Meta tweaks --- package.json | 6 +++--- readme.md | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 4f99826..fdeea3e 100644 --- a/package.json +++ b/package.json @@ -51,12 +51,12 @@ "@types/jsdom": "^12.2.4", "@types/node": "^12.12.6", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.17.0", - "@typescript-eslint/parser": "^2.17.0", + "@typescript-eslint/eslint-plugin": "^2.18.0", + "@typescript-eslint/parser": "^2.18.0", "ava": "^2.1.0", "del-cli": "^2.0.0", "eslint-config-xo-typescript": "^0.24.1", - "jsdom": "^15.0.0", + "jsdom": "^16.0.1", "rxjs": "^6.4.0", "tempy": "^0.3.0", "ts-node": "^8.3.0", diff --git a/readme.md b/readme.md index d274bb3..8f541ed 100644 --- a/readme.md +++ b/readme.md @@ -126,10 +126,10 @@ Returns `true` for any `async` function that can be called with the `await` oper ```js is.asyncFunction(async () => {}); -// => true +//=> true is.asyncFunction(() => {}); -// => false +//=> false ``` ##### .asyncGenerator(value) @@ -140,14 +140,14 @@ is.asyncGenerator( yield 4; })() ); -// => true +//=> true is.asyncGenerator( (function * () { yield 4; })() ); -// => false +//=> false ``` ##### .asyncGeneratorFunction(value) @@ -156,12 +156,12 @@ is.asyncGenerator( is.asyncGeneratorFunction(async function * () { yield 4; }); -// => true +//=> true is.asyncGeneratorFunction(function * () { yield 4; }); -// => false +//=> false ``` ##### .boundFunction(value) @@ -170,13 +170,13 @@ Returns `true` for any `bound` function. ```js is.boundFunction(() => {}); -// => true +//=> true is.boundFunction(function () {}.bind(null)); -// => true +//=> true is.boundFunction(function () {}); -// => false +//=> false ``` ##### .map(value) @@ -243,7 +243,7 @@ Object.defineProperty(object1, 'property1', { }); is.emptyObject(object1); -// => true +//=> true ``` ##### .nonEmptyObject(value) @@ -501,12 +501,12 @@ handleMovieRatingApiResponse({rating: '🦄'}); ## Generic type parameters -The type guards and type assertions are aware of [generic type parameters](https://www.typescriptlang.org/docs/handbook/generics.html), such as `Promise` and `Map`. The default is `unknown` for most cases, since `is` can not check them at runtime. If the generic type is known at compile-time, either implicitly (inferred) or explicitly (provided), `is` propagates the type so it can be used later. +The type guards and type assertions are aware of [generic type parameters](https://www.typescriptlang.org/docs/handbook/generics.html), such as `Promise` and `Map`. The default is `unknown` for most cases, since `is` cannot check them at runtime. If the generic type is known at compile-time, either implicitly (inferred) or explicitly (provided), `is` propagates the type so it can be used later. Use generic type parameters with caution. They are only checked by the TypeScript compiler, and not checked by `is` at runtime. This can lead to unexpected behavior, where the generic type is _assumed_ at compile-time, but actually is something completely different at runtime. It is best to use `unknown` (default) and type-check the value of the generic type parameter at runtime with `is` or `assert`. ```ts -import { assert } from '@sindresorhus/is'; +import {assert} from '@sindresorhus/is'; async function badNumberAssumption(input: unknown) { // Bad assumption about the generic type parameter fools the compile-time type system. @@ -534,11 +534,11 @@ async function goodNumberAssertion(input: unknown) { return 2 * resolved; } -badNumberAssumption(Promise.resolve("an unexpected string")); -//=> 'NaN' +badNumberAssumption(Promise.resolve('An unexpected string')); +//=> NaN // This correctly throws an error because of the unexpected string value. -goodNumberAssertion(Promise.resolve("an unexpected string")); +goodNumberAssertion(Promise.resolve('An unexpected string')); ``` ## FAQ From 863e26ad6af358900a00c65d04ecae8021cc8bce Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 30 Jan 2020 00:47:01 +0700 Subject: [PATCH 127/254] 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fdeea3e..0925534 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "1.2.0", + "version": "2.0.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From f97029fd73b20fb9784254feb37584c8e9b87adb Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 30 Jan 2020 00:51:52 +0700 Subject: [PATCH 128/254] Minor readme tweaks --- readme.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/readme.md b/readme.md index 8f541ed..94453a9 100644 --- a/readme.md +++ b/readme.md @@ -509,29 +509,29 @@ Use generic type parameters with caution. They are only checked by the TypeScrip import {assert} from '@sindresorhus/is'; async function badNumberAssumption(input: unknown) { - // Bad assumption about the generic type parameter fools the compile-time type system. - assert.promise(input); - // `input` is a `Promise` but only assumed to be `Promise`. + // Bad assumption about the generic type parameter fools the compile-time type system. + assert.promise(input); + // `input` is a `Promise` but only assumed to be `Promise`. - const resolved = await input; - // `resolved` is typed as `number` but was not actually checked at runtime. + const resolved = await input; + // `resolved` is typed as `number` but was not actually checked at runtime. - // Multiplication will return NaN if the input promise did not actually contain a number. - return 2 * resolved; + // Multiplication will return NaN if the input promise did not actually contain a number. + return 2 * resolved; } async function goodNumberAssertion(input: unknown) { - assert.promise(input); - // `input` is typed as `Promise` + assert.promise(input); + // `input` is typed as `Promise` - const resolved = await input; - // `resolved` is typed as `unknown` + const resolved = await input; + // `resolved` is typed as `unknown` - assert.number(resolved); - // `resolved` is typed as `number` + assert.number(resolved); + // `resolved` is typed as `number` - // Uses runtime checks so only numbers will reach the multiplication. - return 2 * resolved; + // Uses runtime checks so only numbers will reach the multiplication. + return 2 * resolved; } badNumberAssumption(Promise.resolve('An unexpected string')); From d1929ad47cf8dd130bca968ae6ed9a2fb87b5a54 Mon Sep 17 00:00:00 2001 From: Dave Cohen Date: Sun, 16 Feb 2020 19:06:19 -0600 Subject: [PATCH 129/254] Add support for multiple predicates to `is.any` (#104) --- readme.md | 14 ++++++++++++-- source/index.ts | 12 +++++++++--- test/test.ts | 2 ++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 94453a9..294cfa7 100644 --- a/readme.md +++ b/readme.md @@ -420,9 +420,9 @@ Returns `true` if `value` is an even integer. Returns `true` if `value` is an odd integer. -##### .any(predicate, ...values) +##### .any(predicate | predicate[], ...values) -Returns `true` if **any** of the input `values` returns true in the `predicate`: +Using a single `predicate` argument, returns `true` if **any** of the input `values` returns true in the `predicate`: ```js is.any(is.string, {}, true, '🦄'); @@ -432,6 +432,16 @@ is.any(is.boolean, 'unicorns', [], new Map()); //=> false ``` +Using an array of `predicate[]`, returns `true` if **any** of the input `values` returns true for **any** of the `predicates` provided in an array: + +```js +is.any([is.string, is.number], {}, true, '🦄'); +//=> true + +is.any([is.boolean, is.number], 'unicorns', [], new Map()); +//=> false +``` + ##### .all(predicate, ...values) Returns `true` if **all** of the input `values` returns true in the `predicate`: diff --git a/source/index.ts b/source/index.ts index 4d42bfe..faabc65 100644 --- a/source/index.ts +++ b/source/index.ts @@ -388,7 +388,13 @@ const predicateOnArray = (method: ArrayMethod, predicate: Predicate, values: unk return method.call(values, predicate); }; -is.any = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.some, predicate, values); +is.any = (predicate: Predicate | Predicate[], ...values: unknown[]): boolean => { + const predicates = is.array(predicate) ? predicate : [predicate]; + return predicates.some(singlePredicate => + predicateOnArray(Array.prototype.some, singlePredicate, values) + ); +}; + is.all = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.every, predicate, values); const assertType = (condition: boolean, description: string, value: unknown): asserts condition => { @@ -526,7 +532,7 @@ interface Assert { inRange: (value: number, range: number | number[]) => asserts value is number; // Variadic functions. - any: (predicate: Predicate, ...values: unknown[]) => void | never; + any: (predicate: Predicate | Predicate[], ...values: unknown[]) => void | never; all: (predicate: Predicate, ...values: unknown[]) => void | never; } @@ -616,7 +622,7 @@ export const assert: Assert = { inRange: (value: number, range: number | number[]): asserts value is number => assertType(is.inRange(value, range), AssertionTypeDescription.inRange, value), // Variadic functions. - any: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.any(predicate, ...values), AssertionTypeDescription.any, values), + any: (predicate: Predicate | Predicate[], ...values: unknown[]): void | never => assertType(is.any(predicate, ...values), AssertionTypeDescription.any, values), all: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.all(predicate, ...values), AssertionTypeDescription.all, values) }; diff --git a/test/test.ts b/test/test.ts index f48b7c3..2909247 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1458,6 +1458,8 @@ test('is.any', t => { t.true(is.any(is.object, false, {}, 'unicorns')); t.false(is.any(is.boolean, '🦄', [], 3)); t.false(is.any(is.integer, true, 'lol', {})); + t.true(is.any([is.string, is.number], {}, true, '🦄')); + t.false(is.any([is.boolean, is.number], 'unicorns', [], new Map())); t.throws(() => { is.any(null as any, true); From 1ff389cabb53a2b921ad9da00759823b57b6d0a3 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 17 Feb 2020 14:18:18 +0700 Subject: [PATCH 130/254] 2.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0925534..cf2b36d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "2.0.0", + "version": "2.1.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 05cdaccf924c37b54f4fdd149caab5ebe2af2f1b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 22 Feb 2020 02:01:58 +0700 Subject: [PATCH 131/254] Update TypeScript --- license | 2 +- package.json | 22 ++++++++++------------ source/index.ts | 19 +++++++++---------- test/test.ts | 16 +++++++++++++--- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/license b/license index e7af2f7..fa7ceba 100644 --- a/license +++ b/license @@ -1,6 +1,6 @@ MIT License -Copyright (c) Sindre Sorhus (sindresorhus.com) +Copyright (c) Sindre Sorhus (https://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: diff --git a/package.json b/package.json index cf2b36d..be6df98 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", - "url": "sindresorhus.com" + "url": "https://sindresorhus.com" }, "main": "dist", "engines": { @@ -48,27 +48,25 @@ ], "devDependencies": { "@sindresorhus/tsconfig": "^0.7.0", - "@types/jsdom": "^12.2.4", - "@types/node": "^12.12.6", + "@types/jsdom": "^16.1.0", + "@types/node": "^13.7.4", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.18.0", - "@typescript-eslint/parser": "^2.18.0", - "ava": "^2.1.0", + "@typescript-eslint/eslint-plugin": "^2.20.0", + "@typescript-eslint/parser": "^2.20.0", + "ava": "^3.3.0", "del-cli": "^2.0.0", - "eslint-config-xo-typescript": "^0.24.1", + "eslint-config-xo-typescript": "^0.26.0", "jsdom": "^16.0.1", "rxjs": "^6.4.0", - "tempy": "^0.3.0", + "tempy": "^0.4.0", "ts-node": "^8.3.0", - "typescript": "^3.7.5", - "xo": "^0.25.3", + "typescript": "~3.8.2", + "xo": "^0.26.1", "zen-observable": "^0.8.8" }, "types": "dist", "sideEffects": false, "ava": { - "babel": false, - "compileEnhancements": false, "extensions": [ "ts" ], diff --git a/source/index.ts b/source/index.ts index faabc65..cd8af29 100644 --- a/source/index.ts +++ b/source/index.ts @@ -133,13 +133,13 @@ is.numericString = (value: unknown): value is string => is.string(value) && value.length > 0 && !Number.isNaN(Number(value)); is.array = Array.isArray; -is.buffer = (value: unknown): value is Buffer => !is.nullOrUndefined(value) && !is.nullOrUndefined((value as Buffer).constructor) && is.function_((value as Buffer).constructor.isBuffer) && (value as Buffer).constructor.isBuffer(value); +is.buffer = (value: unknown): value is Buffer => (value as any)?.constructor?.isBuffer?.(value) ?? false; is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); is.object = (value: unknown): value is object => !is.null_(value) && (typeof value === 'object' || is.function_(value)); -is.iterable = (value: unknown): value is IterableIterator => !is.nullOrUndefined(value) && is.function_((value as IterableIterator)[Symbol.iterator]); +is.iterable = (value: unknown): value is IterableIterator => is.function_((value as IterableIterator)?.[Symbol.iterator]); -is.asyncIterable = (value: unknown): value is AsyncIterableIterator => !is.nullOrUndefined(value) && is.function_((value as AsyncIterableIterator)[Symbol.asyncIterator]); +is.asyncIterable = (value: unknown): value is AsyncIterableIterator => is.function_((value as AsyncIterableIterator)?.[Symbol.asyncIterator]); is.generator = (value: unknown): value is Generator => is.iterable(value) && is.function_(value.next) && is.function_(value.throw); @@ -149,9 +149,8 @@ is.nativePromise = (value: unknown): value is Promise => isObjectOfType>(TypeName.Promise)(value); const hasPromiseAPI = (value: unknown): value is Promise => - is.object(value) && - is.function_((value as Promise).then) && // eslint-disable-line promise/prefer-await-to-then - is.function_((value as Promise).catch); + is.function_((value as Promise)?.then) && + is.function_((value as Promise)?.catch); is.promise = (value: unknown): value is Promise => is.nativePromise(value) || hasPromiseAPI(value); @@ -326,11 +325,11 @@ is.observable = (value: unknown): value is ObservableLike => { } // eslint-disable-next-line no-use-extend-native/no-use-extend-native - if ((value as any)[Symbol.observable] && value === (value as any)[Symbol.observable]()) { + if (value === (value as any)[Symbol.observable]?.()) { return true; } - if ((value as any)['@@observable'] && value === (value as any)['@@observable']()) { + if (value === (value as any)['@@observable']?.()) { return true; } @@ -357,7 +356,7 @@ is.emptyString = (value: unknown): value is '' => is.string(value) && value.leng // TODO: Use `not ''` when the `not` operator is available. is.nonEmptyString = (value: unknown): value is string => is.string(value) && value.length > 0; -const isWhiteSpaceString = (value: unknown): value is string => is.string(value) && /\S/.test(value) === false; +const isWhiteSpaceString = (value: unknown): value is string => is.string(value) && !/\S/.test(value); is.emptyStringOrWhitespace = (value: unknown): value is string => is.emptyString(value) || isWhiteSpaceString(value); is.emptyObject = (value: unknown): value is Record => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0; @@ -377,7 +376,7 @@ export type Predicate = (value: unknown) => boolean; type ArrayMethod = (fn: (value: unknown, index: number, array: unknown[]) => boolean, thisArg?: unknown) => boolean; const predicateOnArray = (method: ArrayMethod, predicate: Predicate, values: unknown[]) => { - if (is.function_(predicate) === false) { + if (!is.function_(predicate)) { throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); } diff --git a/test/test.ts b/test/test.ts index 2909247..6d6a4ce 100644 --- a/test/test.ts +++ b/test/test.ts @@ -571,13 +571,23 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { } const isTypeUnderTest = key === type; - const assertIs = isTypeUnderTest ? t.true.bind(t) : t.false.bind(t); - const assertAsserts = isTypeUnderTest ? t.notThrows.bind(t) : t.throws.bind(t); + const assertIs = isTypeUnderTest ? t.true : t.false; for (const fixture of fixtures) { assertIs(testIs(fixture), `Value: ${inspect(fixture)}`); const valueType = typeDescription ? typeDescription : typename; - assertAsserts(() => testAssert(fixture), `Expected value which is \`${valueType!}\`, received value of type \`${is(fixture)}\`.`); + + if (isTypeUnderTest) { + t.notThrows(() => { + testAssert(fixture); + }); + } else { + t.throws(() => { + testAssert(fixture); + }, { + message: `Expected value which is \`${valueType!}\`, received value of type \`${is(fixture)}\`.` + }); + } if (isTypeUnderTest && typename) { t.is(is(fixture), typename); From 402fbb5a7e75ca478c135c4d8b6bf1e1ae34cce9 Mon Sep 17 00:00:00 2001 From: Arfat Salman Date: Sat, 18 Apr 2020 03:25:02 +0530 Subject: [PATCH 132/254] Fix #109: `is.numericString` behaves correctly on whitespaces (#110) --- source/index.ts | 2 +- test/test.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/source/index.ts b/source/index.ts index cd8af29..9c6d8cf 100644 --- a/source/index.ts +++ b/source/index.ts @@ -130,7 +130,7 @@ is.boolean = (value: unknown): value is boolean => value === true || value === f is.symbol = isOfType('symbol'); is.numericString = (value: unknown): value is string => - is.string(value) && value.length > 0 && !Number.isNaN(Number(value)); + is.string(value) && !is.emptyStringOrWhitespace(value) && !Number.isNaN(Number(value)); is.array = Array.isArray; is.buffer = (value: unknown): value is Buffer => (value as any)?.constructor?.isBuffer?.(value) ?? false; diff --git a/test/test.ts b/test/test.ts index 6d6a4ce..f540828 100644 --- a/test/test.ts +++ b/test/test.ts @@ -124,7 +124,8 @@ const types = new Map([ fixtures: [ '5', '-3.2', - 'Infinity' + 'Infinity', + '0x56' ], typename: TypeName.string, typeDescription: AssertionTypeDescription.numericString @@ -627,6 +628,8 @@ test('is.symbol', t => { test('is.numericString', t => { testType(t, 'numericString'); t.false(is.numericString('')); + t.false(is.numericString(' ')); + t.false(is.numericString(' \t\t\n')); t.false(is.numericString(1)); t.throws(() => { assert.numericString(''); From fae0096eba33ebae4134ac9d3e23a2e337475cf5 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 19 Apr 2020 18:18:00 +0800 Subject: [PATCH 133/254] 2.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index be6df98..26f7f9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "2.1.0", + "version": "2.1.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 9d404cad2e712ea83f1ea9e928dae60b49063c2d Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Mon, 15 Jun 2020 16:43:26 +0900 Subject: [PATCH 134/254] Test on Node.js 14 (#115) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 94ab01f..9d7745e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: node_js node_js: + - '14' - '12' - '10' From 1ffe2fb6a7ae3e029d676e09e30464f869fc7838 Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Sat, 20 Jun 2020 16:28:47 +0900 Subject: [PATCH 135/254] Use string literals instead of enums (#113) --- source/index.ts | 386 ++++++++++++++++++++++++------------------------ test/test.ts | 118 +++++++-------- 2 files changed, 250 insertions(+), 254 deletions(-) diff --git a/source/index.ts b/source/index.ts index 9c6d8cf..3d96809 100644 --- a/source/index.ts +++ b/source/index.ts @@ -4,101 +4,145 @@ export type Class = new (...args: any[]) => T; -export const enum TypeName { - null = 'null', - boolean = 'boolean', - undefined = 'undefined', - string = 'string', - number = 'number', - bigint = 'bigint', - symbol = 'symbol', - Function = 'Function', - Generator = 'Generator', - AsyncGenerator = 'AsyncGenerator', - GeneratorFunction = 'GeneratorFunction', - AsyncGeneratorFunction = 'AsyncGeneratorFunction', - AsyncFunction = 'AsyncFunction', - Observable = 'Observable', - Array = 'Array', - Buffer = 'Buffer', - Object = 'Object', - RegExp = 'RegExp', - Date = 'Date', - Error = 'Error', - Map = 'Map', - Set = 'Set', - WeakMap = 'WeakMap', - WeakSet = 'WeakSet', - Int8Array = 'Int8Array', - Uint8Array = 'Uint8Array', - Uint8ClampedArray = 'Uint8ClampedArray', - Int16Array = 'Int16Array', - Uint16Array = 'Uint16Array', - Int32Array = 'Int32Array', - Uint32Array = 'Uint32Array', - Float32Array = 'Float32Array', - Float64Array = 'Float64Array', - BigInt64Array = 'BigInt64Array', - BigUint64Array = 'BigUint64Array', - ArrayBuffer = 'ArrayBuffer', - SharedArrayBuffer = 'SharedArrayBuffer', - DataView = 'DataView', - Promise = 'Promise', - URL = 'URL' +const typedArrayTypeNames = [ + 'Int8Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'Int16Array', + 'Uint16Array', + 'Int32Array', + 'Uint32Array', + 'Float32Array', + 'Float64Array', + 'BigInt64Array', + 'BigUint64Array' +] as const; + +export type TypedArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array + | BigInt64Array + | BigUint64Array; + +type TypedArrayTypeName = typeof typedArrayTypeNames[number]; + +function isTypedArrayName(name: unknown): name is TypedArrayTypeName { + return typedArrayTypeNames.includes(name as TypedArrayTypeName); +} + +const objectTypeNames = [ + 'Function', + 'Generator', + 'AsyncGenerator', + 'GeneratorFunction', + 'AsyncGeneratorFunction', + 'AsyncFunction', + 'Observable', + 'Array', + 'Buffer', + 'Object', + 'RegExp', + 'Date', + 'Error', + 'Map', + 'Set', + 'WeakMap', + 'WeakSet', + 'ArrayBuffer', + 'SharedArrayBuffer', + 'DataView', + 'Promise', + 'URL', + 'HTMLElement', + ...typedArrayTypeNames +] as const; + +type ObjectTypeName = typeof objectTypeNames[number]; + +const primitiveTypeNames = [ + 'null', + 'undefined', + 'string', + 'number', + 'bigint', + 'boolean', + 'symbol' +] as const; + +export type Primitive = + | null + | undefined + | string + | number + | bigint + | boolean + | symbol; + +type PrimitiveTypeName = typeof primitiveTypeNames[number]; + +function isPrimitiveTypeName(name: unknown): name is PrimitiveTypeName { + return primitiveTypeNames.includes(name as PrimitiveTypeName); +} + +export type TypeName = ObjectTypeName | PrimitiveTypeName; + +// eslint-disable-next-line @typescript-eslint/ban-types +function isOfType(type: PrimitiveTypeName | 'function') { + return (value: unknown): value is T => typeof value === type; } const {toString} = Object.prototype; -const isOfType = (type: string) => (value: unknown): value is T => typeof value === type; - -const getObjectType = (value: unknown): TypeName | undefined => { +const getObjectType = (value: unknown): ObjectTypeName | undefined => { const objectName = toString.call(value).slice(8, -1); if (objectName) { - return objectName as TypeName; + return objectName as ObjectTypeName; } return undefined; }; -const isObjectOfType = (type: TypeName) => (value: unknown): value is T => getObjectType(value) === type; +const isObjectOfType = (type: ObjectTypeName) => (value: unknown): value is T => getObjectType(value) === type; function is(value: unknown): TypeName { - switch (value) { - case null: - return TypeName.null; - case true: - case false: - return TypeName.boolean; - default: + if (value === null) { + return 'null'; } switch (typeof value) { case 'undefined': - return TypeName.undefined; + return 'undefined'; case 'string': - return TypeName.string; + return 'string'; case 'number': - return TypeName.number; + return 'number'; + case 'boolean': + return 'boolean'; + case 'function': + return 'Function'; case 'bigint': - return TypeName.bigint; + return 'bigint'; case 'symbol': - return TypeName.symbol; + return 'symbol'; default: } - if (is.function_(value)) { - return TypeName.Function; - } - if (is.observable(value)) { - return TypeName.Observable; + return 'Observable'; } if (is.array(value)) { - return TypeName.Array; + return 'Array'; } if (is.buffer(value)) { - return TypeName.Buffer; + return 'Buffer'; } const tagType = getObjectType(value); @@ -110,7 +154,7 @@ function is(value: unknown): TypeName { throw new TypeError('Please don\'t use object wrappers for primitive types'); } - return TypeName.Object; + return 'Object'; } is.undefined = isOfType('undefined'); @@ -146,7 +190,7 @@ is.generator = (value: unknown): value is Generator => is.iterable(value) && is. is.asyncGenerator = (value: unknown): value is AsyncGenerator => is.asyncIterable(value) && is.function_(value.next) && is.function_(value.throw); is.nativePromise = (value: unknown): value is Promise => - isObjectOfType>(TypeName.Promise)(value); + isObjectOfType>('Promise')(value); const hasPromiseAPI = (value: unknown): value is Promise => is.function_((value as Promise)?.then) && @@ -154,41 +198,41 @@ const hasPromiseAPI = (value: unknown): value is Promise => is.promise = (value: unknown): value is Promise => is.nativePromise(value) || hasPromiseAPI(value); -is.generatorFunction = isObjectOfType(TypeName.GeneratorFunction); +is.generatorFunction = isObjectOfType('GeneratorFunction'); -is.asyncGeneratorFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === TypeName.AsyncGeneratorFunction; +is.asyncGeneratorFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === 'AsyncGeneratorFunction'; -is.asyncFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === TypeName.AsyncFunction; +is.asyncFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === 'AsyncFunction'; // eslint-disable-next-line no-prototype-builtins, @typescript-eslint/ban-types is.boundFunction = (value: unknown): value is Function => is.function_(value) && !value.hasOwnProperty('prototype'); -is.regExp = isObjectOfType(TypeName.RegExp); -is.date = isObjectOfType(TypeName.Date); -is.error = isObjectOfType(TypeName.Error); -is.map = (value: unknown): value is Map => isObjectOfType>(TypeName.Map)(value); -is.set = (value: unknown): value is Set => isObjectOfType>(TypeName.Set)(value); -is.weakMap = (value: unknown): value is WeakMap => isObjectOfType>(TypeName.WeakMap)(value); -is.weakSet = (value: unknown): value is WeakSet => isObjectOfType>(TypeName.WeakSet)(value); +is.regExp = isObjectOfType('RegExp'); +is.date = isObjectOfType('Date'); +is.error = isObjectOfType('Error'); +is.map = (value: unknown): value is Map => isObjectOfType>('Map')(value); +is.set = (value: unknown): value is Set => isObjectOfType>('Set')(value); +is.weakMap = (value: unknown): value is WeakMap => isObjectOfType>('WeakMap')(value); +is.weakSet = (value: unknown): value is WeakSet => isObjectOfType>('WeakSet')(value); -is.int8Array = isObjectOfType(TypeName.Int8Array); -is.uint8Array = isObjectOfType(TypeName.Uint8Array); -is.uint8ClampedArray = isObjectOfType(TypeName.Uint8ClampedArray); -is.int16Array = isObjectOfType(TypeName.Int16Array); -is.uint16Array = isObjectOfType(TypeName.Uint16Array); -is.int32Array = isObjectOfType(TypeName.Int32Array); -is.uint32Array = isObjectOfType(TypeName.Uint32Array); -is.float32Array = isObjectOfType(TypeName.Float32Array); -is.float64Array = isObjectOfType(TypeName.Float64Array); -is.bigInt64Array = isObjectOfType(TypeName.BigInt64Array); -is.bigUint64Array = isObjectOfType(TypeName.BigUint64Array); +is.int8Array = isObjectOfType('Int8Array'); +is.uint8Array = isObjectOfType('Uint8Array'); +is.uint8ClampedArray = isObjectOfType('Uint8ClampedArray'); +is.int16Array = isObjectOfType('Int16Array'); +is.uint16Array = isObjectOfType('Uint16Array'); +is.int32Array = isObjectOfType('Int32Array'); +is.uint32Array = isObjectOfType('Uint32Array'); +is.float32Array = isObjectOfType('Float32Array'); +is.float64Array = isObjectOfType('Float64Array'); +is.bigInt64Array = isObjectOfType('BigInt64Array'); +is.bigUint64Array = isObjectOfType('BigUint64Array'); -is.arrayBuffer = isObjectOfType(TypeName.ArrayBuffer); -is.sharedArrayBuffer = isObjectOfType(TypeName.SharedArrayBuffer); -is.dataView = isObjectOfType(TypeName.DataView); +is.arrayBuffer = isObjectOfType('ArrayBuffer'); +is.sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); +is.dataView = isObjectOfType('DataView'); is.directInstanceOf = (instance: unknown, class_: Class): instance is T => Object.getPrototypeOf(instance) === class_.prototype; -is.urlInstance = (value: unknown): value is URL => isObjectOfType(TypeName.URL)(value); +is.urlInstance = (value: unknown): value is URL => isObjectOfType('URL')(value); is.urlString = (value: unknown): value is string => { if (!is.string(value)) { @@ -211,33 +255,14 @@ is.falsy = (value: unknown) => !value; is.nan = (value: unknown) => Number.isNaN(value as number); -const primitiveTypeOfTypes = new Set([ - 'undefined', - 'string', - 'number', - 'bigint', - 'boolean', - 'symbol' -]); - -// TODO: This should be able to be `not object` when the `not` operator is out -export type Primitive = - | null - | undefined - | string - | number - | bigint - | boolean - | symbol; - -is.primitive = (value: unknown): value is Primitive => is.null_(value) || primitiveTypeOfTypes.has(typeof value); +is.primitive = (value: unknown): value is Primitive => is.null_(value) || isPrimitiveTypeName(typeof value); is.integer = (value: unknown): value is number => Number.isInteger(value as number); is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); is.plainObject = (value: unknown): value is Record => { // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js - if (getObjectType(value) !== TypeName.Object) { + if (toString.call(value) !== '[object Object]') { return false; } @@ -246,41 +271,7 @@ is.plainObject = (value: unknown): value is Record { - const objectType = getObjectType(value); - if (objectType === undefined) { - return false; - } - - return typedArrayTypes.has(objectType); -}; +is.typedArray = (value: unknown): value is TypedArray => isTypedArrayName(getObjectType(value)); export interface ArrayLike { readonly [index: number]: T; @@ -303,7 +294,7 @@ is.inRange = (value: number, range: number | number[]): value is number => { }; const NODE_TYPE_ELEMENT = 1; -const DOM_PROPERTIES_TO_CHECK = [ +const DOM_PROPERTIES_TO_CHECK: Array<(keyof HTMLElement)> = [ 'innerHTML', 'ownerDocument', 'style', @@ -311,8 +302,13 @@ const DOM_PROPERTIES_TO_CHECK = [ 'nodeValue' ]; -is.domElement = (value: unknown): value is Element => is.object(value) && (value as Element).nodeType === NODE_TYPE_ELEMENT && is.string((value as Element).nodeName) && - !is.plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in (value as Element)); +is.domElement = (value: unknown): value is HTMLElement => { + return is.object(value) && + (value as HTMLElement).nodeType === NODE_TYPE_ELEMENT && + is.string((value as HTMLElement).nodeName) && + !is.plainObject(value) && + DOM_PROPERTIES_TO_CHECK.every(property => property in value); +}; export interface ObservableLike { subscribe(observer: (value: unknown) => void): void; @@ -419,7 +415,7 @@ export const enum AssertionTypeDescription { plainObject = 'plain object', arrayLike = 'array-like', typedArray = 'TypedArray', - domElement = 'Element', + domElement = 'HTMLElement', nodeStream = 'Node.js Stream', infinite = 'infinite number', emptyArray = 'empty array', @@ -506,7 +502,7 @@ interface Assert { plainObject: (value: unknown) => asserts value is Record; typedArray: (value: unknown) => asserts value is TypedArray; arrayLike: (value: unknown) => asserts value is ArrayLike; - domElement: (value: unknown) => asserts value is Element; + domElement: (value: unknown) => asserts value is HTMLElement; observable: (value: unknown) => asserts value is ObservableLike; nodeStream: (value: unknown) => asserts value is NodeStream; infinite: (value: unknown) => asserts value is number; @@ -537,55 +533,55 @@ interface Assert { export const assert: Assert = { // Unknowns. - undefined: (value: unknown): asserts value is undefined => assertType(is.undefined(value), TypeName.undefined, value), - string: (value: unknown): asserts value is string => assertType(is.string(value), TypeName.string, value), - number: (value: unknown): asserts value is number => assertType(is.number(value), TypeName.number, value), - bigint: (value: unknown): asserts value is bigint => assertType(is.bigint(value), TypeName.bigint, value), + undefined: (value: unknown): asserts value is undefined => assertType(is.undefined(value), 'undefined', value), + string: (value: unknown): asserts value is string => assertType(is.string(value), 'string', value), + number: (value: unknown): asserts value is number => assertType(is.number(value), 'number', value), + bigint: (value: unknown): asserts value is bigint => assertType(is.bigint(value), 'bigint', value), // eslint-disable-next-line @typescript-eslint/ban-types - function_: (value: unknown): asserts value is Function => assertType(is.function_(value), TypeName.Function, value), - null_: (value: unknown): asserts value is null => assertType(is.null_(value), TypeName.null, value), + function_: (value: unknown): asserts value is Function => assertType(is.function_(value), 'Function', value), + null_: (value: unknown): asserts value is null => assertType(is.null_(value), 'null', value), class_: (value: unknown): asserts value is Class => assertType(is.class_(value), AssertionTypeDescription.class_, value), - boolean: (value: unknown): asserts value is boolean => assertType(is.boolean(value), TypeName.boolean, value), - symbol: (value: unknown): asserts value is symbol => assertType(is.symbol(value), TypeName.symbol, value), + boolean: (value: unknown): asserts value is boolean => assertType(is.boolean(value), 'boolean', value), + symbol: (value: unknown): asserts value is symbol => assertType(is.symbol(value), 'symbol', value), numericString: (value: unknown): asserts value is string => assertType(is.numericString(value), AssertionTypeDescription.numericString, value), - array: (value: unknown): asserts value is T[] => assertType(is.array(value), TypeName.Array, value), - buffer: (value: unknown): asserts value is Buffer => assertType(is.buffer(value), TypeName.Buffer, value), + array: (value: unknown): asserts value is T[] => assertType(is.array(value), 'Array', value), + buffer: (value: unknown): asserts value is Buffer => assertType(is.buffer(value), 'Buffer', value), nullOrUndefined: (value: unknown): asserts value is null | undefined => assertType(is.nullOrUndefined(value), AssertionTypeDescription.nullOrUndefined, value), - object: (value: unknown): asserts value is object => assertType(is.object(value), TypeName.Object, value), + object: (value: unknown): asserts value is object => assertType(is.object(value), 'Object', value), iterable: (value: unknown): asserts value is Iterable => assertType(is.iterable(value), AssertionTypeDescription.iterable, value), asyncIterable: (value: unknown): asserts value is AsyncIterable => assertType(is.asyncIterable(value), AssertionTypeDescription.asyncIterable, value), - generator: (value: unknown): asserts value is Generator => assertType(is.generator(value), TypeName.Generator, value), - asyncGenerator: (value: unknown): asserts value is AsyncGenerator => assertType(is.asyncGenerator(value), TypeName.AsyncGenerator, value), + generator: (value: unknown): asserts value is Generator => assertType(is.generator(value), 'Generator', value), + asyncGenerator: (value: unknown): asserts value is AsyncGenerator => assertType(is.asyncGenerator(value), 'AsyncGenerator', value), nativePromise: (value: unknown): asserts value is Promise => assertType(is.nativePromise(value), AssertionTypeDescription.nativePromise, value), - promise: (value: unknown): asserts value is Promise => assertType(is.promise(value), TypeName.Promise, value), - generatorFunction: (value: unknown): asserts value is GeneratorFunction => assertType(is.generatorFunction(value), TypeName.GeneratorFunction, value), - asyncGeneratorFunction: (value: unknown): asserts value is AsyncGeneratorFunction => assertType(is.asyncGeneratorFunction(value), TypeName.AsyncGeneratorFunction, value), + promise: (value: unknown): asserts value is Promise => assertType(is.promise(value), 'Promise', value), + generatorFunction: (value: unknown): asserts value is GeneratorFunction => assertType(is.generatorFunction(value), 'GeneratorFunction', value), + asyncGeneratorFunction: (value: unknown): asserts value is AsyncGeneratorFunction => assertType(is.asyncGeneratorFunction(value), 'AsyncGeneratorFunction', value), // eslint-disable-next-line @typescript-eslint/ban-types - asyncFunction: (value: unknown): asserts value is Function => assertType(is.asyncFunction(value), TypeName.AsyncFunction, value), + asyncFunction: (value: unknown): asserts value is Function => assertType(is.asyncFunction(value), 'AsyncFunction', value), // eslint-disable-next-line @typescript-eslint/ban-types - boundFunction: (value: unknown): asserts value is Function => assertType(is.boundFunction(value), TypeName.Function, value), - regExp: (value: unknown): asserts value is RegExp => assertType(is.regExp(value), TypeName.RegExp, value), - date: (value: unknown): asserts value is Date => assertType(is.date(value), TypeName.Date, value), - error: (value: unknown): asserts value is Error => assertType(is.error(value), TypeName.Error, value), - map: (value: unknown): asserts value is Map => assertType(is.map(value), TypeName.Map, value), - set: (value: unknown): asserts value is Set => assertType(is.set(value), TypeName.Set, value), - weakMap: (value: unknown): asserts value is WeakMap => assertType(is.weakMap(value), TypeName.WeakMap, value), - weakSet: (value: unknown): asserts value is WeakSet => assertType(is.weakSet(value), TypeName.WeakSet, value), - int8Array: (value: unknown): asserts value is Int8Array => assertType(is.int8Array(value), TypeName.Int8Array, value), - uint8Array: (value: unknown): asserts value is Uint8Array => assertType(is.uint8Array(value), TypeName.Uint8Array, value), - uint8ClampedArray: (value: unknown): asserts value is Uint8ClampedArray => assertType(is.uint8ClampedArray(value), TypeName.Uint8ClampedArray, value), - int16Array: (value: unknown): asserts value is Int16Array => assertType(is.int16Array(value), TypeName.Int16Array, value), - uint16Array: (value: unknown): asserts value is Uint16Array => assertType(is.uint16Array(value), TypeName.Uint16Array, value), - int32Array: (value: unknown): asserts value is Int32Array => assertType(is.int32Array(value), TypeName.Int32Array, value), - uint32Array: (value: unknown): asserts value is Uint32Array => assertType(is.uint32Array(value), TypeName.Uint32Array, value), - float32Array: (value: unknown): asserts value is Float32Array => assertType(is.float32Array(value), TypeName.Float32Array, value), - float64Array: (value: unknown): asserts value is Float64Array => assertType(is.float64Array(value), TypeName.Float64Array, value), - bigInt64Array: (value: unknown): asserts value is BigInt64Array => assertType(is.bigInt64Array(value), TypeName.BigInt64Array, value), - bigUint64Array: (value: unknown): asserts value is BigUint64Array => assertType(is.bigUint64Array(value), TypeName.BigUint64Array, value), - arrayBuffer: (value: unknown): asserts value is ArrayBuffer => assertType(is.arrayBuffer(value), TypeName.ArrayBuffer, value), - sharedArrayBuffer: (value: unknown): asserts value is SharedArrayBuffer => assertType(is.sharedArrayBuffer(value), TypeName.SharedArrayBuffer, value), - dataView: (value: unknown): asserts value is DataView => assertType(is.dataView(value), TypeName.DataView, value), - urlInstance: (value: unknown): asserts value is URL => assertType(is.urlInstance(value), TypeName.URL, value), + boundFunction: (value: unknown): asserts value is Function => assertType(is.boundFunction(value), 'Function', value), + regExp: (value: unknown): asserts value is RegExp => assertType(is.regExp(value), 'RegExp', value), + date: (value: unknown): asserts value is Date => assertType(is.date(value), 'Date', value), + error: (value: unknown): asserts value is Error => assertType(is.error(value), 'Error', value), + map: (value: unknown): asserts value is Map => assertType(is.map(value), 'Map', value), + set: (value: unknown): asserts value is Set => assertType(is.set(value), 'Set', value), + weakMap: (value: unknown): asserts value is WeakMap => assertType(is.weakMap(value), 'WeakMap', value), + weakSet: (value: unknown): asserts value is WeakSet => assertType(is.weakSet(value), 'WeakSet', value), + int8Array: (value: unknown): asserts value is Int8Array => assertType(is.int8Array(value), 'Int8Array', value), + uint8Array: (value: unknown): asserts value is Uint8Array => assertType(is.uint8Array(value), 'Uint8Array', value), + uint8ClampedArray: (value: unknown): asserts value is Uint8ClampedArray => assertType(is.uint8ClampedArray(value), 'Uint8ClampedArray', value), + int16Array: (value: unknown): asserts value is Int16Array => assertType(is.int16Array(value), 'Int16Array', value), + uint16Array: (value: unknown): asserts value is Uint16Array => assertType(is.uint16Array(value), 'Uint16Array', value), + int32Array: (value: unknown): asserts value is Int32Array => assertType(is.int32Array(value), 'Int32Array', value), + uint32Array: (value: unknown): asserts value is Uint32Array => assertType(is.uint32Array(value), 'Uint32Array', value), + float32Array: (value: unknown): asserts value is Float32Array => assertType(is.float32Array(value), 'Float32Array', value), + float64Array: (value: unknown): asserts value is Float64Array => assertType(is.float64Array(value), 'Float64Array', value), + bigInt64Array: (value: unknown): asserts value is BigInt64Array => assertType(is.bigInt64Array(value), 'BigInt64Array', value), + bigUint64Array: (value: unknown): asserts value is BigUint64Array => assertType(is.bigUint64Array(value), 'BigUint64Array', value), + arrayBuffer: (value: unknown): asserts value is ArrayBuffer => assertType(is.arrayBuffer(value), 'ArrayBuffer', value), + sharedArrayBuffer: (value: unknown): asserts value is SharedArrayBuffer => assertType(is.sharedArrayBuffer(value), 'SharedArrayBuffer', value), + dataView: (value: unknown): asserts value is DataView => assertType(is.dataView(value), 'DataView', value), + urlInstance: (value: unknown): asserts value is URL => assertType(is.urlInstance(value), 'URL', value), urlString: (value: unknown): asserts value is string => assertType(is.urlString(value), AssertionTypeDescription.urlString, value), truthy: (value: unknown): asserts value is unknown => assertType(is.truthy(value), AssertionTypeDescription.truthy, value), falsy: (value: unknown): asserts value is unknown => assertType(is.falsy(value), AssertionTypeDescription.falsy, value), @@ -596,8 +592,8 @@ export const assert: Assert = { plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value), arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value), - domElement: (value: unknown): asserts value is Element => assertType(is.domElement(value), AssertionTypeDescription.domElement, value), - observable: (value: unknown): asserts value is ObservableLike => assertType(is.observable(value), TypeName.Observable, value), + domElement: (value: unknown): asserts value is HTMLElement => assertType(is.domElement(value), AssertionTypeDescription.domElement, value), + observable: (value: unknown): asserts value is ObservableLike => assertType(is.observable(value), 'Observable', value), nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), AssertionTypeDescription.nodeStream, value), infinite: (value: unknown): asserts value is number => assertType(is.infinite(value), AssertionTypeDescription.infinite, value), emptyArray: (value: unknown): asserts value is never[] => assertType(is.emptyArray(value), AssertionTypeDescription.emptyArray, value), diff --git a/test/test.ts b/test/test.ts index f540828..bd62afc 100644 --- a/test/test.ts +++ b/test/test.ts @@ -7,7 +7,7 @@ import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; import tempy = require('tempy'); import ZenObservable = require('zen-observable'); -import is, {assert, AssertionTypeDescription, TypeName} from '../source'; +import is, {assert, AssertionTypeDescription, Primitive, TypedArray, TypeName} from '../source'; class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} @@ -47,7 +47,7 @@ const types = new Map([ fixtures: [ undefined ], - typename: TypeName.undefined + typename: 'undefined' }], ['null', { is: is.null_, @@ -55,7 +55,7 @@ const types = new Map([ fixtures: [ null ], - typename: TypeName.null + typename: 'null' }], ['string', { is: is.string, @@ -65,7 +65,7 @@ const types = new Map([ 'hello world', '' ], - typename: TypeName.string + typename: 'string' }], ['emptyString', { is: is.emptyString, @@ -74,7 +74,7 @@ const types = new Map([ '', String() ], - typename: TypeName.string, + typename: 'string', typeDescription: AssertionTypeDescription.emptyString }], ['number', { @@ -88,7 +88,7 @@ const types = new Map([ Infinity, -Infinity ], - typename: TypeName.number + typename: 'number' }], ['bigint', { is: is.bigint, @@ -100,7 +100,7 @@ const types = new Map([ // -0n, BigInt('1234') ], - typename: TypeName.bigint + typename: 'bigint' }], ['boolean', { is: is.boolean, @@ -108,7 +108,7 @@ const types = new Map([ fixtures: [ true, false ], - typename: TypeName.boolean + typename: 'boolean' }], ['symbol', { is: is.symbol, @@ -116,7 +116,7 @@ const types = new Map([ fixtures: [ Symbol('🦄') ], - typename: TypeName.symbol + typename: 'symbol' }], ['numericString', { is: is.numericString, @@ -127,7 +127,7 @@ const types = new Map([ 'Infinity', '0x56' ], - typename: TypeName.string, + typename: 'string', typeDescription: AssertionTypeDescription.numericString }], ['array', { @@ -137,7 +137,7 @@ const types = new Map([ [1, 2], new Array(2) ], - typename: TypeName.Array + typename: 'Array' }], ['emptyArray', { is: is.emptyArray, @@ -146,7 +146,7 @@ const types = new Map([ [], new Array() // eslint-disable-line @typescript-eslint/no-array-constructor ], - typename: TypeName.Array, + typename: 'Array', typeDescription: AssertionTypeDescription.emptyArray }], ['function', { @@ -160,7 +160,7 @@ const types = new Map([ function * (): unknown {}, async function * (): unknown {} ], - typename: TypeName.Function + typename: 'Function' }], ['buffer', { is: is.buffer, @@ -168,7 +168,7 @@ const types = new Map([ fixtures: [ Buffer.from('🦄') ], - typename: TypeName.Buffer + typename: 'Buffer' }], ['object', { is: is.object, @@ -177,7 +177,7 @@ const types = new Map([ {x: 1}, Object.create({x: 1}) ], - typename: TypeName.Object + typename: 'Object' }], ['regExp', { is: is.regExp, @@ -186,7 +186,7 @@ const types = new Map([ /\w/, new RegExp('\\w') // eslint-disable-line prefer-regex-literals ], - typename: TypeName.RegExp + typename: 'RegExp' }], ['date', { is: is.date, @@ -194,7 +194,7 @@ const types = new Map([ fixtures: [ new Date() ], - typename: TypeName.Date + typename: 'Date' }], ['error', { is: is.error, @@ -203,7 +203,7 @@ const types = new Map([ new Error('🦄'), new ErrorSubclassFixture() ], - typename: TypeName.Error + typename: 'Error' }], ['nativePromise', { is: is.nativePromise, @@ -212,7 +212,7 @@ const types = new Map([ Promise.resolve(), PromiseSubclassFixture.resolve() ], - typename: TypeName.Promise, + typename: 'Promise', typeDescription: AssertionTypeDescription.nativePromise }], ['promise', { @@ -221,8 +221,8 @@ const types = new Map([ fixtures: [ {then() {}, catch() {}} ], - typename: TypeName.Object, - typeDescription: TypeName.Promise + typename: 'Object', + typeDescription: 'Promise' }], ['generator', { is: is.generator, @@ -232,7 +232,7 @@ const types = new Map([ yield 4; })() ], - typename: TypeName.Generator + typename: 'Generator' }], ['asyncGenerator', { is: is.asyncGenerator, @@ -242,7 +242,7 @@ const types = new Map([ yield 4; })() ], - typename: TypeName.AsyncGenerator + typename: 'AsyncGenerator' }], ['generatorFunction', { is: is.generatorFunction, @@ -252,8 +252,8 @@ const types = new Map([ yield 4; } ], - typename: TypeName.Function, - typeDescription: TypeName.GeneratorFunction + typename: 'Function', + typeDescription: 'GeneratorFunction' }], ['asyncGeneratorFunction', { is: is.asyncGeneratorFunction, @@ -263,8 +263,8 @@ const types = new Map([ yield 4; } ], - typename: TypeName.Function, - typeDescription: TypeName.AsyncGeneratorFunction + typename: 'Function', + typeDescription: 'AsyncGeneratorFunction' }], ['asyncFunction', { is: is.asyncFunction, @@ -273,8 +273,8 @@ const types = new Map([ async function () {}, async () => {} ], - typename: TypeName.Function, - typeDescription: TypeName.AsyncFunction + typename: 'Function', + typeDescription: 'AsyncFunction' }], ['boundFunction', { is: is.boundFunction, @@ -283,7 +283,7 @@ const types = new Map([ () => {}, function () {}.bind(null) // eslint-disable-line no-extra-bind ], - typename: TypeName.Function + typename: 'Function' }], ['map', { is: is.map, @@ -291,7 +291,7 @@ const types = new Map([ fixtures: [ new Map([['one', '1']]) ], - typename: TypeName.Map + typename: 'Map' }], ['emptyMap', { is: is.emptyMap, @@ -299,7 +299,7 @@ const types = new Map([ fixtures: [ new Map() ], - typename: TypeName.Map, + typename: 'Map', typeDescription: AssertionTypeDescription.emptyMap }], ['set', { @@ -308,7 +308,7 @@ const types = new Map([ fixtures: [ new Set(['one']) ], - typename: TypeName.Set + typename: 'Set' }], ['emptySet', { is: is.emptySet, @@ -316,7 +316,7 @@ const types = new Map([ fixtures: [ new Set() ], - typename: TypeName.Set, + typename: 'Set', typeDescription: AssertionTypeDescription.emptySet }], ['weakSet', { @@ -325,7 +325,7 @@ const types = new Map([ fixtures: [ new WeakSet() ], - typename: TypeName.WeakSet + typename: 'WeakSet' }], ['weakMap', { is: is.weakMap, @@ -333,7 +333,7 @@ const types = new Map([ fixtures: [ new WeakMap() ], - typename: TypeName.WeakMap + typename: 'WeakMap' }], ['int8Array', { is: is.int8Array, @@ -341,7 +341,7 @@ const types = new Map([ fixtures: [ new Int8Array() ], - typename: TypeName.Int8Array + typename: 'Int8Array' }], ['uint8Array', { is: is.uint8Array, @@ -349,7 +349,7 @@ const types = new Map([ fixtures: [ new Uint8Array() ], - typename: TypeName.Uint8Array + typename: 'Uint8Array' }], ['uint8ClampedArray', { is: is.uint8ClampedArray, @@ -357,7 +357,7 @@ const types = new Map([ fixtures: [ new Uint8ClampedArray() ], - typename: TypeName.Uint8ClampedArray + typename: 'Uint8ClampedArray' }], ['int16Array', { is: is.int16Array, @@ -365,7 +365,7 @@ const types = new Map([ fixtures: [ new Int16Array() ], - typename: TypeName.Int16Array + typename: 'Int16Array' }], ['uint16Array', { is: is.uint16Array, @@ -373,7 +373,7 @@ const types = new Map([ fixtures: [ new Uint16Array() ], - typename: TypeName.Uint16Array + typename: 'Uint16Array' }], ['int32Array', { is: is.int32Array, @@ -381,7 +381,7 @@ const types = new Map([ fixtures: [ new Int32Array() ], - typename: TypeName.Int32Array + typename: 'Int32Array' }], ['uint32Array', { is: is.uint32Array, @@ -389,7 +389,7 @@ const types = new Map([ fixtures: [ new Uint32Array() ], - typename: TypeName.Uint32Array + typename: 'Uint32Array' }], ['float32Array', { is: is.float32Array, @@ -397,7 +397,7 @@ const types = new Map([ fixtures: [ new Float32Array() ], - typename: TypeName.Float32Array + typename: 'Float32Array' }], ['float64Array', { is: is.float64Array, @@ -405,7 +405,7 @@ const types = new Map([ fixtures: [ new Float64Array() ], - typename: TypeName.Float64Array + typename: 'Float64Array' }], ['bigInt64Array', { is: is.bigInt64Array, @@ -413,7 +413,7 @@ const types = new Map([ fixtures: [ new BigInt64Array() ], - typename: TypeName.BigInt64Array + typename: 'BigInt64Array' }], ['bigUint64Array', { is: is.bigUint64Array, @@ -421,7 +421,7 @@ const types = new Map([ fixtures: [ new BigUint64Array() ], - typename: TypeName.BigUint64Array + typename: 'BigUint64Array' }], ['arrayBuffer', { is: is.arrayBuffer, @@ -429,7 +429,7 @@ const types = new Map([ fixtures: [ new ArrayBuffer(10) ], - typename: TypeName.ArrayBuffer + typename: 'ArrayBuffer' }], ['dataView', { is: is.dataView, @@ -437,7 +437,7 @@ const types = new Map([ fixtures: [ new DataView(new ArrayBuffer(10)) ], - typename: TypeName.DataView + typename: 'DataView' }], ['nan', { is: is.nan, @@ -446,7 +446,7 @@ const types = new Map([ NaN, Number.NaN ], - typename: TypeName.number, + typename: 'number', typeDescription: AssertionTypeDescription.nan }], ['nullOrUndefined', { @@ -466,7 +466,7 @@ const types = new Map([ Object.create(null), new Object() // eslint-disable-line no-new-object ], - typename: TypeName.Object, + typename: 'Object', typeDescription: AssertionTypeDescription.plainObject }], ['integer', { @@ -475,7 +475,7 @@ const types = new Map([ fixtures: [ 6 ], - typename: TypeName.number, + typename: 'number', typeDescription: AssertionTypeDescription.integer }], ['safeInteger', { @@ -485,7 +485,7 @@ const types = new Map([ (2 ** 53) - 1, -(2 ** 53) + 1 ], - typename: TypeName.number, + typename: 'number', typeDescription: AssertionTypeDescription.safeInteger }], ['domElement', { @@ -521,7 +521,7 @@ const types = new Map([ new Subject(), new ZenObservable(() => {}) ], - typename: TypeName.Observable + typename: 'Observable' }], ['nodeStream', { is: is.nodeStream, @@ -537,7 +537,7 @@ const types = new Map([ new Stream.Stream(), new Stream.Writable() ], - typename: TypeName.Object, + typename: 'Object', typeDescription: AssertionTypeDescription.nodeStream }], ['infinite', { @@ -547,7 +547,7 @@ const types = new Map([ Infinity, -Infinity ], - typename: TypeName.number, + typename: 'number', typeDescription: AssertionTypeDescription.infinite }] ]); @@ -591,7 +591,7 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { } if (isTypeUnderTest && typename) { - t.is(is(fixture), typename); + t.is(is(fixture), typename); } } } @@ -959,7 +959,7 @@ test('is.nullOrUndefined', t => { }); test('is.primitive', t => { - const primitives = [ + const primitives: Primitive[] = [ undefined, null, '🦄', @@ -1101,7 +1101,7 @@ test('is.class', t => { }); test('is.typedArray', t => { - const typedArrays = [ + const typedArrays: TypedArray[] = [ new Int8Array(), new Uint8Array(), new Uint8ClampedArray(), From a96abee1a35cbcf1bfc8031be5edb26c35b84b67 Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Sat, 27 Jun 2020 20:13:23 +0900 Subject: [PATCH 136/254] Use shields.io for number of downloads per week (#118) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 294cfa7..98e29ca 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ For example, `is.string('🦄') //=> true` - [Supports type assertions](#type-assertions) - [Aware of generic type parameters](#generic-type-parameters) (use with caution) - Actively maintained -- 2 million weekly downloads +- ![Millions of downloads per week](https://img.shields.io/npm/dw/@sindresorhus/is) ## Install From 71ca1e5573dc84a360590ac7c5ba31669d1d9ffe Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Sun, 28 Jun 2020 05:20:36 +0900 Subject: [PATCH 137/254] Make the `is()` function type-safe (#117) --- source/index.ts | 15 ++++++++++++--- test/test.ts | 20 ++++++++++---------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/source/index.ts b/source/index.ts index 3d96809..f819d82 100644 --- a/source/index.ts +++ b/source/index.ts @@ -66,6 +66,10 @@ const objectTypeNames = [ type ObjectTypeName = typeof objectTypeNames[number]; +function isObjectTypeName(name: unknown): name is ObjectTypeName { + return objectTypeNames.includes(name as ObjectTypeName); +} + const primitiveTypeNames = [ 'null', 'undefined', @@ -100,9 +104,14 @@ function isOfType(type: PrimitiveTypeName | 'fun const {toString} = Object.prototype; const getObjectType = (value: unknown): ObjectTypeName | undefined => { - const objectName = toString.call(value).slice(8, -1); - if (objectName) { - return objectName as ObjectTypeName; + const objectTypeName = toString.call(value).slice(8, -1); + + if (/HTML\w+Element/.test(objectTypeName) && is.domElement(value)) { + return 'HTMLElement'; + } + + if (isObjectTypeName(objectTypeName)) { + return objectTypeName; } return undefined; diff --git a/test/test.ts b/test/test.ts index bd62afc..31bfa2a 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1275,18 +1275,18 @@ test('is.domElement', t => { assert.domElement({nodeType: 1, nodeName: 'div'}); }); - const htmlTagNameToTypeName = { - div: 'HTMLDivElement', - input: 'HTMLInputElement', - span: 'HTMLSpanElement', - img: 'HTMLImageElement', - canvas: 'HTMLCanvasElement', - script: 'HTMLScriptElement' - }; + const tagNames = [ + 'div', + 'input', + 'span', + 'img', + 'canvas', + 'script' + ]; - for (const [tagName, typeName] of Object.entries(htmlTagNameToTypeName)) { + for (const tagName of tagNames) { const domElement = createDomElement(tagName); - t.is(is(domElement), typeName); + t.is(is(domElement), 'HTMLElement'); } }); From 47fa419e4f9a58eb5b6eb7cfb1718986a7e29ce3 Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Sun, 28 Jun 2020 05:20:49 +0900 Subject: [PATCH 138/254] Update devDependency @types/node from v13.7.4 to v14.0.13 (#116) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 26f7f9c..7834028 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "devDependencies": { "@sindresorhus/tsconfig": "^0.7.0", "@types/jsdom": "^16.1.0", - "@types/node": "^13.7.4", + "@types/node": "^14.0.13", "@types/zen-observable": "^0.8.0", "@typescript-eslint/eslint-plugin": "^2.20.0", "@typescript-eslint/parser": "^2.20.0", From 4b35ad5becbd464cb185ee34a4f5371381d70657 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 28 Jun 2020 04:22:15 +0800 Subject: [PATCH 139/254] 3.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7834028..9e7b3d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "2.1.1", + "version": "3.0.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From e31db97eab467f2c180c857dd7474accf8ac2215 Mon Sep 17 00:00:00 2001 From: Richie Bendall Date: Mon, 20 Jul 2020 11:48:08 +1200 Subject: [PATCH 140/254] Extract necessary functions from `type-fest` (#120) --- source/index.ts | 31 +++---------------------------- source/types.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 source/types.ts diff --git a/source/index.ts b/source/index.ts index f819d82..ba578a4 100644 --- a/source/index.ts +++ b/source/index.ts @@ -2,7 +2,9 @@ /// /// -export type Class = new (...args: any[]) => T; +import {Class, TypedArray, ObservableLike, Primitive} from './types'; + +export {Class, TypedArray, ObservableLike, Primitive}; const typedArrayTypeNames = [ 'Int8Array', @@ -18,19 +20,6 @@ const typedArrayTypeNames = [ 'BigUint64Array' ] as const; -export type TypedArray = - | Int8Array - | Uint8Array - | Uint8ClampedArray - | Int16Array - | Uint16Array - | Int32Array - | Uint32Array - | Float32Array - | Float64Array - | BigInt64Array - | BigUint64Array; - type TypedArrayTypeName = typeof typedArrayTypeNames[number]; function isTypedArrayName(name: unknown): name is TypedArrayTypeName { @@ -80,15 +69,6 @@ const primitiveTypeNames = [ 'symbol' ] as const; -export type Primitive = - | null - | undefined - | string - | number - | bigint - | boolean - | symbol; - type PrimitiveTypeName = typeof primitiveTypeNames[number]; function isPrimitiveTypeName(name: unknown): name is PrimitiveTypeName { @@ -319,11 +299,6 @@ is.domElement = (value: unknown): value is HTMLElement => { DOM_PROPERTIES_TO_CHECK.every(property => property in value); }; -export interface ObservableLike { - subscribe(observer: (value: unknown) => void): void; - [Symbol.observable](): ObservableLike; -} - is.observable = (value: unknown): value is ObservableLike => { if (!value) { return false; diff --git a/source/types.ts b/source/types.ts new file mode 100644 index 0000000..8bdd0f5 --- /dev/null +++ b/source/types.ts @@ -0,0 +1,49 @@ +// Extracted from https://github.com/sindresorhus/type-fest/blob/78019f42ea888b0cdceb41a4a78163868de57555/index.d.ts + +/** +Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive). +*/ +export type Primitive = + | null + | undefined + | string + | number + | boolean + | symbol + | bigint; + +// TODO: Remove the `= unknown` sometime in the future when most users are on TS 3.5 as it's now the default +/** +Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). +*/ +export type Class = new (...arguments_: Arguments) => T; + +/** +Matches any [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), like `Uint8Array` or `Float64Array`. +*/ +export type TypedArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array + | BigInt64Array + | BigUint64Array; + +declare global { + interface SymbolConstructor { + readonly observable: symbol; + } +} + +/** +Matches a value that is like an [Observable](https://github.com/tc39/proposal-observable). +*/ +export interface ObservableLike { + subscribe(observer: (value: unknown) => void): void; + [Symbol.observable](): ObservableLike; +} From 3f93bf200d48aa447ae3b6c5b7271ead4d0f552a Mon Sep 17 00:00:00 2001 From: Arnovsky Date: Sat, 25 Jul 2020 10:11:59 +0300 Subject: [PATCH 141/254] Add `is.array` overload that supports asserting array items (#119) Co-authored-by: Pedro Augusto de Paula Barbosa Co-authored-by: Giora Guttsait Co-authored-by: Sindre Sorhus --- readme.md | 10 +++++++++- source/index.ts | 24 +++++++++++++++++++++--- test/test.ts | 24 ++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 98e29ca..daddc0a 100644 --- a/readme.md +++ b/readme.md @@ -93,7 +93,15 @@ Note: `is.number(NaN)` returns `false`. This intentionally deviates from `typeof #### Built-in types -##### .array(value) +##### .array(value, assertion?) + +Returns true if `value` is an array and all of its items match the assertion (if provided). + +```js +is.array(value); // Validate `value` is an array. +is.array(value, is.number); // Validate `value` is an array and all of its items are numbers. +``` + ##### .function(value) ##### .buffer(value) ##### .object(value) diff --git a/source/index.ts b/source/index.ts index ba578a4..9bed5cc 100644 --- a/source/index.ts +++ b/source/index.ts @@ -165,7 +165,18 @@ is.symbol = isOfType('symbol'); is.numericString = (value: unknown): value is string => is.string(value) && !is.emptyStringOrWhitespace(value) && !Number.isNaN(Number(value)); -is.array = Array.isArray; +is.array = (value: unknown, assertion?: (value: T) => value is T): value is T[] => { + if (!Array.isArray(value)) { + return false; + } + + if (!assertion) { + return true; + } + + return value.every(assertion); +}; + is.buffer = (value: unknown): value is Buffer => (value as any)?.constructor?.isBuffer?.(value) ?? false; is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); @@ -438,7 +449,7 @@ interface Assert { boolean: (value: unknown) => asserts value is boolean; symbol: (value: unknown) => asserts value is symbol; numericString: (value: unknown) => asserts value is string; - array: (value: unknown) => asserts value is T[]; + array: (value: unknown, assertion?: (element: unknown) => asserts element is T) => asserts value is T[]; buffer: (value: unknown) => asserts value is Buffer; nullOrUndefined: (value: unknown) => asserts value is null | undefined; object: (value: unknown) => asserts value is Record; @@ -528,7 +539,14 @@ export const assert: Assert = { boolean: (value: unknown): asserts value is boolean => assertType(is.boolean(value), 'boolean', value), symbol: (value: unknown): asserts value is symbol => assertType(is.symbol(value), 'symbol', value), numericString: (value: unknown): asserts value is string => assertType(is.numericString(value), AssertionTypeDescription.numericString, value), - array: (value: unknown): asserts value is T[] => assertType(is.array(value), 'Array', value), + array: (value: unknown, assertion?: (element: unknown) => asserts element is T): asserts value is T[] => { + const assert: (condition: boolean, description: string, value: unknown) => asserts condition = assertType; + assert(is.array(value), 'Array', value); + + if (assertion) { + value.forEach(assertion); + } + }, buffer: (value: unknown): asserts value is Buffer => assertType(is.buffer(value), 'Buffer', value), nullOrUndefined: (value: unknown): asserts value is null | undefined => assertType(is.nullOrUndefined(value), AssertionTypeDescription.nullOrUndefined, value), object: (value: unknown): asserts value is object => assertType(is.object(value), 'Object', value), diff --git a/test/test.ts b/test/test.ts index 31bfa2a..16e376c 100644 --- a/test/test.ts +++ b/test/test.ts @@ -641,6 +641,30 @@ test('is.numericString', t => { test('is.array', t => { testType(t, 'array', ['emptyArray']); + + t.true(is.array([1, 2, 3], is.number)); + t.false(is.array([1, '2', 3], is.number)); + + t.notThrows(() => { + assert.array([1, 2], assert.number); + }); + + t.throws(() => { + assert.array([1, '2'], assert.number); + }); + + t.notThrows(() => { + const x: unknown[] = [1, 2, 3]; + assert.array(x, assert.number); + x[0].toFixed(0); + }); + + t.notThrows(() => { + const x: unknown[] = [1, 2, 3]; + if (is.array(x, is.number)) { + x[0].toFixed(0); + } + }); }); test('is.function', t => { From 09d31733d3ee82788eb02a6f4ebedf8063bc120f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 25 Jul 2020 09:14:15 +0200 Subject: [PATCH 142/254] 3.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9e7b3d3..3757962 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "3.0.0", + "version": "3.1.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 4c29fb35cb46b64a918745be50455c879847dcd8 Mon Sep 17 00:00:00 2001 From: iyegoroff Date: Sun, 16 Aug 2020 13:29:46 +0300 Subject: [PATCH 143/254] Improve import for VS Code (#122) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3757962..8c57943 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, - "main": "dist", + "main": "dist/index.js", "engines": { "node": ">=10" }, @@ -64,7 +64,7 @@ "xo": "^0.26.1", "zen-observable": "^0.8.8" }, - "types": "dist", + "types": "dist/index.d.ts", "sideEffects": false, "ava": { "extensions": [ From 5feadcb0b8ea7f0fa108c82f6a4c50695ff2664b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 16 Aug 2020 12:30:54 +0200 Subject: [PATCH 144/254] 3.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c57943..e2bfc6a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "3.1.0", + "version": "3.1.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From d8ced89efef1e0c8f6a4bfe7116617bc7d49142f Mon Sep 17 00:00:00 2001 From: Arnovsky Date: Fri, 21 Aug 2020 15:18:34 -0700 Subject: [PATCH 145/254] Fix using `is.array` in `is.all` (#125) --- source/index.ts | 2 +- test/test.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/source/index.ts b/source/index.ts index 9bed5cc..7e68ccb 100644 --- a/source/index.ts +++ b/source/index.ts @@ -170,7 +170,7 @@ is.array = (value: unknown, assertion?: (value: T) => value is T): return false; } - if (!assertion) { + if (!is.function_(assertion)) { return true; } diff --git a/test/test.ts b/test/test.ts index 16e376c..c5c98cd 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1537,6 +1537,8 @@ test('is.all', t => { t.false(is.all(is.string, '🦄', [])); t.false(is.all(is.set, new Map(), {})); + t.true(is.all(is.array, ...[['1'], ['2']])); + t.throws(() => { is.all(null as any, true); }); From bf6bba7af8f3f16e1b0ab4eb390196a5ffa9ef7e Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 22 Aug 2020 00:19:30 +0200 Subject: [PATCH 146/254] 3.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2bfc6a..004956b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "3.1.1", + "version": "3.1.2", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 94749dbb2e5aab6c8207b518a42349b8dbc604c6 Mon Sep 17 00:00:00 2001 From: Rocktim Saikia Date: Tue, 13 Oct 2020 03:30:52 +0530 Subject: [PATCH 147/254] Improve `is.plainObject` TypeScript type (#126) --- source/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/index.ts b/source/index.ts index 7e68ccb..a7281b9 100644 --- a/source/index.ts +++ b/source/index.ts @@ -260,7 +260,8 @@ is.primitive = (value: unknown): value is Primitive => is.null_(value) || isPrim is.integer = (value: unknown): value is number => Number.isInteger(value as number); is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); -is.plainObject = (value: unknown): value is Record => { +type ObjectKey = string | number | symbol; +is.plainObject = (value: unknown): value is Record => { // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js if (toString.call(value) !== '[object Object]') { return false; @@ -494,7 +495,7 @@ interface Assert { primitive: (value: unknown) => asserts value is Primitive; integer: (value: unknown) => asserts value is number; safeInteger: (value: unknown) => asserts value is number; - plainObject: (value: unknown) => asserts value is Record; + plainObject: (value: unknown) => asserts value is Record; typedArray: (value: unknown) => asserts value is TypedArray; arrayLike: (value: unknown) => asserts value is ArrayLike; domElement: (value: unknown) => asserts value is HTMLElement; @@ -591,7 +592,7 @@ export const assert: Assert = { primitive: (value: unknown): asserts value is Primitive => assertType(is.primitive(value), AssertionTypeDescription.primitive, value), integer: (value: unknown): asserts value is number => assertType(is.integer(value), AssertionTypeDescription.integer, value), safeInteger: (value: unknown): asserts value is number => assertType(is.safeInteger(value), AssertionTypeDescription.safeInteger, value), - plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), + plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value), arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value), domElement: (value: unknown): asserts value is HTMLElement => assertType(is.domElement(value), AssertionTypeDescription.domElement, value), From d528545e02de3396ea900cd93af478292f0697ee Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 13 Oct 2020 00:08:06 +0200 Subject: [PATCH 148/254] 4.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 004956b..2141cd5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "3.1.2", + "version": "4.0.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From da6bb531af964f6609903443d47fece0bbc4c53d Mon Sep 17 00:00:00 2001 From: Richie Bendall Date: Sat, 2 Jan 2021 17:25:04 +1300 Subject: [PATCH 149/254] Move to GitHub Actions (#129) --- .github/workflows/main.yml | 22 ++++++++++++++++++++++ .travis.yml | 5 ----- readme.md | 2 +- 3 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..c1870cf --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,22 @@ +name: CI +on: + - push + - pull_request +jobs: + test: + name: Node.js ${{ matrix.node-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: + - 14 + - 12 + - 10 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9d7745e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: - - '14' - - '12' - - '10' diff --git a/readme.md b/readme.md index daddc0a..adb9910 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -# is [![Build Status](https://travis-ci.org/sindresorhus/is.svg?branch=master)](https://travis-ci.org/sindresorhus/is) +# is > Type check values From 4f8b01f2dc1e0e5fd6d6a72c21ad9b6a96b40846 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 24 Jan 2021 14:20:06 +0700 Subject: [PATCH 150/254] Rename `master` branch to `main` --- source/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.ts b/source/index.ts index a7281b9..37b536f 100644 --- a/source/index.ts +++ b/source/index.ts @@ -262,7 +262,7 @@ is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value type ObjectKey = string | number | symbol; is.plainObject = (value: unknown): value is Record => { - // From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js + // From: https://github.com/sindresorhus/is-plain-obj/blob/main/index.js if (toString.call(value) !== '[object Object]') { return false; } From 5ed7e9bb400d3ced16edad53a5d882fb100049ae Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 31 Mar 2021 01:04:39 +0700 Subject: [PATCH 151/254] Meta tweaks --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2141cd5..b77ddab 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "scripts": { "build": "del dist && tsc", "test": "xo && ava", - "prepublishOnly": "npm run build" + "prepare": "npm run build" }, "files": [ "dist" From b748ab72b643e09f45b3a74576916875a1e5ad75 Mon Sep 17 00:00:00 2001 From: Dave Cohen Date: Thu, 22 Apr 2021 04:00:08 -0500 Subject: [PATCH 152/254] Fix assertion message for `.all` and `.any` (#132) --- source/index.ts | 19 +++++++++++++++---- test/test.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/source/index.ts b/source/index.ts index 37b536f..ffb2ad2 100644 --- a/source/index.ts +++ b/source/index.ts @@ -388,9 +388,18 @@ is.any = (predicate: Predicate | Predicate[], ...values: unknown[]): boolean => is.all = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.every, predicate, values); -const assertType = (condition: boolean, description: string, value: unknown): asserts condition => { +const assertType = (condition: boolean, description: string, value: unknown, options: {multipleValues?: boolean} = {}): asserts condition => { if (!condition) { - throw new TypeError(`Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`); + const {multipleValues} = options; + const valuesMessage = multipleValues ? + `received values of types ${[ + ...new Set( + (value as any[]).map(singleValue => `\`${is(singleValue)}\``) + ) + ].join(', ')}` : + `received value of type \`${is(value)}\``; + + throw new TypeError(`Expected value which is \`${description}\`, ${valuesMessage}.`); } }; @@ -620,8 +629,10 @@ export const assert: Assert = { inRange: (value: number, range: number | number[]): asserts value is number => assertType(is.inRange(value, range), AssertionTypeDescription.inRange, value), // Variadic functions. - any: (predicate: Predicate | Predicate[], ...values: unknown[]): void | never => assertType(is.any(predicate, ...values), AssertionTypeDescription.any, values), - all: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.all(predicate, ...values), AssertionTypeDescription.all, values) + any: (predicate: Predicate | Predicate[], ...values: unknown[]): void | never => { + return assertType(is.any(predicate, ...values), AssertionTypeDescription.any, values, {multipleValues: true}); + }, + all: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.all(predicate, ...values), AssertionTypeDescription.all, values, {multipleValues: true}) }; // Some few keywords are reserved, but we'll populate them for Node.js users diff --git a/test/test.ts b/test/test.ts index c5c98cd..30c1a86 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1529,6 +1529,27 @@ test('is.any', t => { t.throws(() => { assert.any(is.string); }); + + t.throws(() => { + assert.any(is.string, 1, 2, 3); + }, { + // Removes duplicates: + message: /received values of types `number`./ + }); + + t.throws(() => { + assert.any(is.string, 1, [4]); + }, { + // Lists all types: + message: /received values of types `number`, `Array`./ + }); + + t.throws(() => { + assert.any([is.string, is.nullOrUndefined], 1); + }, { + // Handles array as first argument: + message: /received values of types `number`./ + }); }); test('is.all', t => { @@ -1570,6 +1591,20 @@ test('is.all', t => { t.throws(() => { assert.all(is.string); }); + + t.throws(() => { + assert.all(is.string, 1, 2, 3); + }, { + // Removes duplicates: + message: /received values of types `number`./ + }); + + t.throws(() => { + assert.all(is.string, 1, [4]); + }, { + // Lists all types: + message: /received values of types `number`, `Array`./ + }); }); test('assert', t => { From 238e8c80c7de926f62d3d0e903a257bf32667d0d Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 22 Apr 2021 16:01:32 +0700 Subject: [PATCH 153/254] 4.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b77ddab..16ef3a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "4.0.0", + "version": "4.0.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 6f2b24d8225daf88ad7f64b0ed03586cfcc90da7 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 7 Sep 2021 11:41:26 +0700 Subject: [PATCH 154/254] Minor tweaks --- source/index.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/source/index.ts b/source/index.ts index ffb2ad2..d9fb699 100644 --- a/source/index.ts +++ b/source/index.ts @@ -4,8 +4,6 @@ import {Class, TypedArray, ObservableLike, Primitive} from './types'; -export {Class, TypedArray, ObservableLike, Primitive}; - const typedArrayTypeNames = [ 'Int8Array', 'Uint8Array', @@ -260,8 +258,7 @@ is.primitive = (value: unknown): value is Primitive => is.null_(value) || isPrim is.integer = (value: unknown): value is number => Number.isInteger(value as number); is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); -type ObjectKey = string | number | symbol; -is.plainObject = (value: unknown): value is Record => { +is.plainObject = (value: unknown): value is Record => { // From: https://github.com/sindresorhus/is-plain-obj/blob/main/index.js if (toString.call(value) !== '[object Object]') { return false; @@ -504,7 +501,7 @@ interface Assert { primitive: (value: unknown) => asserts value is Primitive; integer: (value: unknown) => asserts value is number; safeInteger: (value: unknown) => asserts value is number; - plainObject: (value: unknown) => asserts value is Record; + plainObject: (value: unknown) => asserts value is Record; typedArray: (value: unknown) => asserts value is TypedArray; arrayLike: (value: unknown) => asserts value is ArrayLike; domElement: (value: unknown) => asserts value is HTMLElement; @@ -601,7 +598,7 @@ export const assert: Assert = { primitive: (value: unknown): asserts value is Primitive => assertType(is.primitive(value), AssertionTypeDescription.primitive, value), integer: (value: unknown): asserts value is number => assertType(is.integer(value), AssertionTypeDescription.integer, value), safeInteger: (value: unknown): asserts value is number => assertType(is.safeInteger(value), AssertionTypeDescription.safeInteger, value), - plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), + plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value), arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value), domElement: (value: unknown): asserts value is HTMLElement => assertType(is.domElement(value), AssertionTypeDescription.domElement, value), @@ -662,6 +659,8 @@ Object.defineProperties(assert, { export default is; +export {Class, TypedArray, ObservableLike, Primitive} from './types'; + // For CommonJS default export support module.exports = is; module.exports.default = is; From d2f98e472de4d63a11714c54280f67cd25c960df Mon Sep 17 00:00:00 2001 From: PopGoesTheWza <32041843+PopGoesTheWza@users.noreply.github.com> Date: Fri, 10 Sep 2021 10:26:13 +0200 Subject: [PATCH 155/254] Add `is.propertyKey` (#138) Co-authored-by: Sindre Sorhus Co-authored-by: Giora Guttsait --- readme.md | 4 ++++ source/index.ts | 5 +++++ test/test.ts | 14 ++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/readme.md b/readme.md index adb9910..76f167d 100644 --- a/readme.md +++ b/readme.md @@ -428,6 +428,10 @@ Returns `true` if `value` is an even integer. Returns `true` if `value` is an odd integer. +##### .propertyKey(value) + +Returns `true` if `value` can be used as an object property key (either `string`, `number`, or `symbol`). + ##### .any(predicate | predicate[], ...values) Using a single `predicate` argument, returns `true` if **any** of the input `values` returns true in the `predicate`: diff --git a/source/index.ts b/source/index.ts index d9fb699..875794a 100644 --- a/source/index.ts +++ b/source/index.ts @@ -360,6 +360,9 @@ is.nonEmptySet = (value: unknown): value is Set => is.set(value) is.emptyMap = (value: unknown): value is Map => is.map(value) && value.size === 0; is.nonEmptyMap = (value: unknown): value is Map => is.map(value) && value.size > 0; +// `PropertyKey` is any value that can be used as an object key (string, number, or symbol) +is.propertyKey = (value: unknown): value is PropertyKey => is.any([is.string, is.number, is.symbol], value); + export type Predicate = (value: unknown) => boolean; type ArrayMethod = (fn: (value: unknown, index: number, array: unknown[]) => boolean, thisArg?: unknown) => boolean; @@ -519,6 +522,7 @@ interface Assert { nonEmptySet: (value: unknown) => asserts value is Set; emptyMap: (value: unknown) => asserts value is Map; nonEmptyMap: (value: unknown) => asserts value is Map; + propertyKey: (value: unknown) => asserts value is PropertyKey; // Numbers. evenInteger: (value: number) => asserts value is number; @@ -616,6 +620,7 @@ export const assert: Assert = { nonEmptySet: (value: unknown): asserts value is Set => assertType(is.nonEmptySet(value), AssertionTypeDescription.nonEmptySet, value), emptyMap: (value: unknown): asserts value is Map => assertType(is.emptyMap(value), AssertionTypeDescription.emptyMap, value), nonEmptyMap: (value: unknown): asserts value is Map => assertType(is.nonEmptyMap(value), AssertionTypeDescription.nonEmptyMap, value), + propertyKey: (value: unknown): asserts value is number => assertType(is.propertyKey(value), 'PropertyKey', value), // Numbers. evenInteger: (value: number): asserts value is number => assertType(is.evenInteger(value), AssertionTypeDescription.evenInteger, value), diff --git a/test/test.ts b/test/test.ts index 30c1a86..6c2ae57 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1490,6 +1490,20 @@ test('is.nonEmptyMap', t => { }); }); +test('is.propertyKey', t => { + t.true(is.propertyKey('key')); + t.true(is.propertyKey(42)); + t.true(is.propertyKey(Symbol(''))); + + t.false(is.propertyKey(null)); + t.false(is.propertyKey(undefined)); + t.false(is.propertyKey(true)); + t.false(is.propertyKey({})); + t.false(is.propertyKey([])); + t.false(is.propertyKey(new Map())); + t.false(is.propertyKey(new Set())); +}); + test('is.any', t => { t.true(is.any(is.string, {}, true, '🦄')); t.true(is.any(is.object, false, {}, 'unicorns')); From b007935b4b436d93af7fbc5a603bf854534b83a8 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 10 Sep 2021 15:30:39 +0700 Subject: [PATCH 156/254] 4.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 16ef3a4..ec3aed0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "4.0.1", + "version": "4.1.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From a8de3d6f3403f18adc861b8aef69172901d327c7 Mon Sep 17 00:00:00 2001 From: Kayson Wu <772663139@qq.com> Date: Mon, 13 Sep 2021 22:25:44 +0800 Subject: [PATCH 157/254] Add `is.formData` and `is.urlSearchParams` (#139) --- readme.md | 22 ++++++++++++++++++++++ source/index.ts | 9 +++++++++ test/test.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/readme.md b/readme.md index 76f167d..c9b5463 100644 --- a/readme.md +++ b/readme.md @@ -432,6 +432,28 @@ Returns `true` if `value` is an odd integer. Returns `true` if `value` can be used as an object property key (either `string`, `number`, or `symbol`). +##### .formData(value) + +Returns `true` if `value` is an instance of the [`FormData` class](https://developer.mozilla.org/en-US/docs/Web/API/FormData). + +```js +const data = new FormData(); + +is.formData(data); +//=> true +``` + +##### .urlSearchParams(value) + +Returns `true` if `value` is an instance of the [`URLSearchParams` class](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams). + +```js +const searchParams = new URLSearchParams(); + +is.urlSearchParams(searchParams); +//=> true +``` + ##### .any(predicate | predicate[], ...values) Using a single `predicate` argument, returns `true` if **any** of the input `values` returns true in the `predicate`: diff --git a/source/index.ts b/source/index.ts index 875794a..86499f1 100644 --- a/source/index.ts +++ b/source/index.ts @@ -47,6 +47,8 @@ const objectTypeNames = [ 'DataView', 'Promise', 'URL', + 'FormData', + 'URLSearchParams', 'HTMLElement', ...typedArrayTypeNames ] as const; @@ -362,6 +364,9 @@ is.nonEmptyMap = (value: unknown): value is Map< // `PropertyKey` is any value that can be used as an object key (string, number, or symbol) is.propertyKey = (value: unknown): value is PropertyKey => is.any([is.string, is.number, is.symbol], value); +is.formData = (value: unknown): value is FormData => isObjectOfType('FormData')(value); + +is.urlSearchParams = (value: unknown): value is URLSearchParams => isObjectOfType('URLSearchParams')(value); export type Predicate = (value: unknown) => boolean; @@ -523,6 +528,8 @@ interface Assert { emptyMap: (value: unknown) => asserts value is Map; nonEmptyMap: (value: unknown) => asserts value is Map; propertyKey: (value: unknown) => asserts value is PropertyKey; + formData: (value: unknown) => asserts value is FormData; + urlSearchParams: (value: unknown) => asserts value is URLSearchParams; // Numbers. evenInteger: (value: number) => asserts value is number; @@ -621,6 +628,8 @@ export const assert: Assert = { emptyMap: (value: unknown): asserts value is Map => assertType(is.emptyMap(value), AssertionTypeDescription.emptyMap, value), nonEmptyMap: (value: unknown): asserts value is Map => assertType(is.nonEmptyMap(value), AssertionTypeDescription.nonEmptyMap, value), propertyKey: (value: unknown): asserts value is number => assertType(is.propertyKey(value), 'PropertyKey', value), + formData: (value: unknown): asserts value is FormData => assertType(is.formData(value), 'FormData', value), + urlSearchParams: (value: unknown): asserts value is URLSearchParams => assertType(is.urlSearchParams(value), 'URLSearchParams', value), // Numbers. evenInteger: (value: number): asserts value is number => assertType(is.evenInteger(value), AssertionTypeDescription.evenInteger, value), diff --git a/test/test.ts b/test/test.ts index 6c2ae57..87455db 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1621,6 +1621,48 @@ test('is.all', t => { }); }); +test('is.formData', t => { + const data = new window.FormData(); + t.true(is.formData(data)); + t.false(is.formData({})); + t.false(is.formData(undefined)); + t.false(is.formData(null)); + + t.notThrows(() => { + assert.formData(data); + }); + t.throws(() => { + assert.formData({}); + }); + t.throws(() => { + assert.formData(undefined); + }); + t.throws(() => { + assert.formData(null); + }); +}); + +test('is.urlSearchParams', t => { + const searchParams = new URLSearchParams(); + t.true(is.urlSearchParams(searchParams)); + t.false(is.urlSearchParams({})); + t.false(is.urlSearchParams(undefined)); + t.false(is.urlSearchParams(null)); + + t.notThrows(() => { + assert.urlSearchParams(searchParams); + }); + t.throws(() => { + assert.urlSearchParams({}); + }); + t.throws(() => { + assert.urlSearchParams(undefined); + }); + t.throws(() => { + assert.urlSearchParams(null); + }); +}); + test('assert', t => { // Contrived test showing that TypeScript acknowledges the type assertion in `assert.number()`. // Real--world usage includes asserting user input, but here we use a random number/string generator. From 13b2343dfc9f43115aa7fafce1e21646cc6b1e67 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 13 Sep 2021 21:27:03 +0700 Subject: [PATCH 158/254] 4.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ec3aed0..9763df0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "4.1.0", + "version": "4.2.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From a82aeeaa5e06cf7cccc5e2d5da468c26c8e57797 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 31 Oct 2021 12:34:45 +0700 Subject: [PATCH 159/254] Add FAQ about `instanceof` --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index c9b5463..f956fcc 100644 --- a/readme.md +++ b/readme.md @@ -602,6 +602,10 @@ 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. +### Why not just use `instanceof` instead of this package? + +`instanceof` does not work correctly for all types and it does not work across [realms](https://stackoverflow.com/a/49832343/64949). Examples of realms are iframes, windows, web workers, and the `vm` module in Node.js. + ## For enterprise Available as part of the Tidelift Subscription. From ad661ebceebb1ba9d39aece4c1ef11f90fa3ca16 Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Fri, 7 Jan 2022 09:32:06 -0500 Subject: [PATCH 160/254] Fix `is.iterable` and `is.asyncIterable` TypeScript types (#149) --- source/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/index.ts b/source/index.ts index 86499f1..be9f990 100644 --- a/source/index.ts +++ b/source/index.ts @@ -181,13 +181,13 @@ is.buffer = (value: unknown): value is Buffer => (value as any)?.constructor?.is is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); is.object = (value: unknown): value is object => !is.null_(value) && (typeof value === 'object' || is.function_(value)); -is.iterable = (value: unknown): value is IterableIterator => is.function_((value as IterableIterator)?.[Symbol.iterator]); +is.iterable = (value: unknown): value is Iterable => is.function_((value as Iterable)?.[Symbol.iterator]); -is.asyncIterable = (value: unknown): value is AsyncIterableIterator => is.function_((value as AsyncIterableIterator)?.[Symbol.asyncIterator]); +is.asyncIterable = (value: unknown): value is AsyncIterable => is.function_((value as AsyncIterable)?.[Symbol.asyncIterator]); -is.generator = (value: unknown): value is Generator => is.iterable(value) && is.function_(value.next) && is.function_(value.throw); +is.generator = (value: unknown): value is Generator => is.iterable(value) && is.function_((value as Generator)?.next) && is.function_((value as Generator)?.throw); -is.asyncGenerator = (value: unknown): value is AsyncGenerator => is.asyncIterable(value) && is.function_(value.next) && is.function_(value.throw); +is.asyncGenerator = (value: unknown): value is AsyncGenerator => is.asyncIterable(value) && is.function_((value as AsyncGenerator).next) && is.function_((value as AsyncGenerator).throw); is.nativePromise = (value: unknown): value is Promise => isObjectOfType>('Promise')(value); From f5cc764e22da69e2d6d820d3e44f00547bb35336 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 7 Jan 2022 21:35:19 +0700 Subject: [PATCH 161/254] 4.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9763df0..7a07400 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "4.2.0", + "version": "4.2.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From a5b4017d5e3baf10eca7844a96e2e133e2ab899e Mon Sep 17 00:00:00 2001 From: Olivier Beaulieu Date: Mon, 17 Jan 2022 00:55:40 -0500 Subject: [PATCH 162/254] Add `is.enumCase` and `assert.enumCase` (#150) Co-authored-by: Sindre Sorhus --- readme.md | 19 +++++++++++++++++++ source/index.ts | 3 +++ test/test.ts | 17 +++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/readme.md b/readme.md index f956fcc..b980b42 100644 --- a/readme.md +++ b/readme.md @@ -212,6 +212,25 @@ is.boundFunction(function () {}); ##### .sharedArrayBuffer(value) ##### .dataView(value) +##### .enumCase(value, enum) + +**TypeScript-only** + +Returns `true` if `value` is a member of `enum`. + +```ts +enum Direction { + Ascending = 'ascending', + Descending = 'descending' +} + +is.enumCase('ascending', Direction); +//=> true + +is.enumCase('other', Direction); +//=> false +``` + #### Emptiness ##### .emptyString(value) diff --git a/source/index.ts b/source/index.ts index be9f990..561cfcf 100644 --- a/source/index.ts +++ b/source/index.ts @@ -230,6 +230,7 @@ is.bigUint64Array = isObjectOfType('BigUint64Array'); is.arrayBuffer = isObjectOfType('ArrayBuffer'); is.sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); is.dataView = isObjectOfType('DataView'); +is.enumCase = (value: unknown, targetEnum: T) => Object.values(targetEnum).includes(value as string); is.directInstanceOf = (instance: unknown, class_: Class): instance is T => Object.getPrototypeOf(instance) === class_.prototype; is.urlInstance = (value: unknown): value is URL => isObjectOfType('URL')(value); @@ -501,6 +502,7 @@ interface Assert { arrayBuffer: (value: unknown) => asserts value is ArrayBuffer; sharedArrayBuffer: (value: unknown) => asserts value is SharedArrayBuffer; dataView: (value: unknown) => asserts value is DataView; + enumCase: (value: unknown, targetEnum: T) => asserts value is T[keyof T]; urlInstance: (value: unknown) => asserts value is URL; urlString: (value: unknown) => asserts value is string; truthy: (value: unknown) => asserts value is unknown; @@ -601,6 +603,7 @@ export const assert: Assert = { arrayBuffer: (value: unknown): asserts value is ArrayBuffer => assertType(is.arrayBuffer(value), 'ArrayBuffer', value), sharedArrayBuffer: (value: unknown): asserts value is SharedArrayBuffer => assertType(is.sharedArrayBuffer(value), 'SharedArrayBuffer', value), dataView: (value: unknown): asserts value is DataView => assertType(is.dataView(value), 'DataView', value), + enumCase: (value: unknown, targetEnum: T): asserts value is T[keyof T] => assertType(is.enumCase(value, targetEnum), 'EnumCase', value), urlInstance: (value: unknown): asserts value is URL => assertType(is.urlInstance(value), 'URL', value), urlString: (value: unknown): asserts value is string => assertType(is.urlString(value), AssertionTypeDescription.urlString, value), truthy: (value: unknown): asserts value is unknown => assertType(is.truthy(value), AssertionTypeDescription.truthy, value), diff --git a/test/test.ts b/test/test.ts index 87455db..734fd27 100644 --- a/test/test.ts +++ b/test/test.ts @@ -832,6 +832,23 @@ test('is.dataView', t => { testType(t, 'dataView'); }); +test('is.enumCase', t => { + enum NonNumericalEnum { + Key1 = 'key1', + Key2 = 'key2', + } + + t.true(is.enumCase('key1', NonNumericalEnum)); + t.notThrows(() => { + assert.enumCase('key1', NonNumericalEnum); + }); + + t.false(is.enumCase('invalid', NonNumericalEnum)); + t.throws(() => { + assert.enumCase('invalid', NonNumericalEnum); + }); +}); + test('is.directInstanceOf', t => { const error = new Error(); const errorSubclass = new ErrorSubclassFixture(); From b1efe7f5cf615786a42690cdf38ecf4c51f0b798 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 17 Jan 2022 13:04:39 +0700 Subject: [PATCH 163/254] 4.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a07400..10f437d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "4.2.1", + "version": "4.3.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 73daee6648924a3591da24b7b7710fb2e5e74cb3 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Tue, 18 Jan 2022 01:48:03 -0300 Subject: [PATCH 164/254] Improve `.enumCase` doc formatting in readme (#154) --- readme.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/readme.md b/readme.md index b980b42..ecd9028 100644 --- a/readme.md +++ b/readme.md @@ -214,9 +214,7 @@ is.boundFunction(function () {}); ##### .enumCase(value, enum) -**TypeScript-only** - -Returns `true` if `value` is a member of `enum`. +TypeScript-only. Returns `true` if `value` is a member of `enum`. ```ts enum Direction { From 63d75d68eec5e904e9f0613132d8cee783e7ce00 Mon Sep 17 00:00:00 2001 From: Zane Shannon Date: Tue, 25 Jan 2022 02:28:33 -0800 Subject: [PATCH 165/254] Add type guard for `is.truthy` and `is.falsy` (#151) --- source/index.ts | 7 +++---- source/types.ts | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/source/index.ts b/source/index.ts index 561cfcf..fcd66a0 100644 --- a/source/index.ts +++ b/source/index.ts @@ -2,7 +2,7 @@ /// /// -import {Class, TypedArray, ObservableLike, Primitive} from './types'; +import {Class, Falsy, TypedArray, ObservableLike, Primitive} from './types'; const typedArrayTypeNames = [ 'Int8Array', @@ -248,11 +248,10 @@ is.urlString = (value: unknown): value is string => { } }; -// TODO: Use the `not` operator with a type guard here when it's available. // Example: `is.truthy = (value: unknown): value is (not false | not 0 | not '' | not undefined | not null) => Boolean(value);` -is.truthy = (value: unknown) => Boolean(value); +is.truthy = (value: T | Falsy): value is T => Boolean(value); // Example: `is.falsy = (value: unknown): value is (not true | 0 | '' | undefined | null) => Boolean(value);` -is.falsy = (value: unknown) => !value; +is.falsy = (value: T | Falsy): value is Falsy => !value; is.nan = (value: unknown) => Number.isNaN(value as number); diff --git a/source/types.ts b/source/types.ts index 8bdd0f5..f6da50c 100644 --- a/source/types.ts +++ b/source/types.ts @@ -47,3 +47,5 @@ export interface ObservableLike { subscribe(observer: (value: unknown) => void): void; [Symbol.observable](): ObservableLike; } + +export type Falsy = false | 0 | 0n | '' | null | undefined; From c3d12667fd60df4f27218aaf8a6b7ac3201dc311 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 25 Jan 2022 17:29:45 +0700 Subject: [PATCH 166/254] 4.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 10f437d..fa9226d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "4.3.0", + "version": "4.4.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 04ccf21dba99e9c07c41a38bcf3e4881c06b1bac Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 1 Feb 2022 11:37:28 +0700 Subject: [PATCH 167/254] Document TypeScript naming limitation Fixes #157 --- readme.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index ecd9028..1450f83 100644 --- a/readme.md +++ b/readme.md @@ -17,8 +17,8 @@ For example, `is.string('🦄') //=> true` ## Install -``` -$ npm install @sindresorhus/is +```sh +npm install @sindresorhus/is ``` ## Usage @@ -82,6 +82,9 @@ All the below methods accept a value and returns a boolean for whether the value ##### .undefined(value) ##### .null(value) + +**Note:** TypeScript users must use `.null_()` because of a TypeScript naming limitation. + ##### .string(value) ##### .number(value) @@ -103,6 +106,9 @@ is.array(value, is.number); // Validate `value` is an array and all of its items ``` ##### .function(value) + +**Note:** TypeScript users must use `.function_()` because of a TypeScript naming limitation. + ##### .buffer(value) ##### .object(value) @@ -372,6 +378,8 @@ An object is plain if it's created by either `{}`, `new Object()`, or `Object.cr Returns `true` for instances created by a class. +**Note:** TypeScript users must use `.class_()` because of a TypeScript naming limitation. + ##### .typedArray(value) ##### .arrayLike(value) From 23cf074a73a9c4bb0d70fe0c8df2e07bae1e21d9 Mon Sep 17 00:00:00 2001 From: Ivan Katliarchuk <5395690+ivankatliarchuk@users.noreply.github.com> Date: Fri, 25 Feb 2022 09:10:57 +0000 Subject: [PATCH 168/254] Add `.nonEmptyStringAndNotWhitespace()` (#161) Co-authored-by: Sindre Sorhus --- readme.md | 11 +++++++++++ source/index.ts | 6 ++++++ test/test.ts | 21 +++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/readme.md b/readme.md index 1450f83..e4e1924 100644 --- a/readme.md +++ b/readme.md @@ -245,6 +245,17 @@ Returns `true` if the value is a `string` and the `.length` is 0. Returns `true` if the value is a `string` and the `.length` is more than 0. +##### .nonEmptyStringAndNotWhitespace(value) + +Returns `true` if the value is a `string` that is not empty and not whitespace. + +```js +const values = ['property1', '', null, 'property2', ' ', undefined]; + +values.filter(is.nonEmptyStringAndNotWhitespace); +//=> ['property1', 'property2'] +``` + ##### .emptyStringOrWhitespace(value) Returns `true` if `is.emptyString(value)` or if it's a `string` that is all whitespace. diff --git a/source/index.ts b/source/index.ts index fcd66a0..6836b18 100644 --- a/source/index.ts +++ b/source/index.ts @@ -350,6 +350,9 @@ is.nonEmptyString = (value: unknown): value is string => is.string(value) && val const isWhiteSpaceString = (value: unknown): value is string => is.string(value) && !/\S/.test(value); is.emptyStringOrWhitespace = (value: unknown): value is string => is.emptyString(value) || isWhiteSpaceString(value); +// TODO: Use `not ''` when the `not` operator is available. +is.nonEmptyStringAndNotWhitespace = (value: unknown): value is string => is.string(value) && !is.emptyStringOrWhitespace(value); + is.emptyObject = (value: unknown): value is Record => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0; // TODO: Use `not` operator here to remove `Map` and `Set` from type guard: @@ -433,6 +436,7 @@ export const enum AssertionTypeDescription { emptyString = 'empty string', nonEmptyString = 'non-empty string', emptyStringOrWhitespace = 'empty string or whitespace', + nonEmptyStringAndNotWhitespace = 'non-empty string and not whitespace', emptyObject = 'empty object', nonEmptyObject = 'non-empty object', emptySet = 'empty set', @@ -522,6 +526,7 @@ interface Assert { emptyString: (value: unknown) => asserts value is ''; nonEmptyString: (value: unknown) => asserts value is string; emptyStringOrWhitespace: (value: unknown) => asserts value is string; + nonEmptyStringAndNotWhitespace: (value: unknown) => asserts value is string; emptyObject: (value: unknown) => asserts value is Record; nonEmptyObject: (value: unknown) => asserts value is Record; emptySet: (value: unknown) => asserts value is Set; @@ -623,6 +628,7 @@ export const assert: Assert = { emptyString: (value: unknown): asserts value is '' => assertType(is.emptyString(value), AssertionTypeDescription.emptyString, value), nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), AssertionTypeDescription.nonEmptyString, value), emptyStringOrWhitespace: (value: unknown): asserts value is string => assertType(is.emptyStringOrWhitespace(value), AssertionTypeDescription.emptyStringOrWhitespace, value), + nonEmptyStringAndNotWhitespace: (value: unknown): asserts value is string => assertType(is.nonEmptyStringAndNotWhitespace(value), AssertionTypeDescription.nonEmptyStringAndNotWhitespace, value), emptyObject: (value: unknown): asserts value is Record => assertType(is.emptyObject(value), AssertionTypeDescription.emptyObject, value), nonEmptyObject: (value: unknown): asserts value is Record => assertType(is.nonEmptyObject(value), AssertionTypeDescription.nonEmptyObject, value), emptySet: (value: unknown): asserts value is Set => assertType(is.emptySet(value), AssertionTypeDescription.emptySet, value), diff --git a/test/test.ts b/test/test.ts index 734fd27..7fb01f8 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1436,6 +1436,27 @@ test('is.emptyStringOrWhitespace', t => { }); }); +test('is.nonEmptyStringAndNotWhitespace', t => { + t.false(is.nonEmptyStringAndNotWhitespace(' ')); + t.true(is.nonEmptyStringAndNotWhitespace('🦄')); + + for (const value of [null, undefined, 5, NaN, {}, []]) { + t.false(is.nonEmptyStringAndNotWhitespace(value)); + + t.throws(() => { + assert.nonEmptyStringAndNotWhitespace(value); + }); + } + + t.throws(() => { + assert.nonEmptyStringAndNotWhitespace(''); + }); + + t.notThrows(() => { + assert.nonEmptyStringAndNotWhitespace('🦄'); + }); +}); + test('is.emptyObject', t => { t.true(is.emptyObject({})); t.true(is.emptyObject(new Object())); // eslint-disable-line no-new-object From dc2dc9a4380d888c7db37eeac809537205480978 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 25 Feb 2022 16:16:30 +0700 Subject: [PATCH 169/254] 4.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fa9226d..cc86fc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "4.4.0", + "version": "4.5.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 6cbefb9af755c8baf66d5d29a128702a04f09ec9 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 25 Feb 2022 16:19:29 +0700 Subject: [PATCH 170/254] Meta tweaks --- readme.md | 8 ++++---- source/index.ts | 12 ++++++------ test/test.ts | 32 ++++++++++++++++---------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/readme.md b/readme.md index e4e1924..12a56fd 100644 --- a/readme.md +++ b/readme.md @@ -241,6 +241,10 @@ is.enumCase('other', Direction); Returns `true` if the value is a `string` and the `.length` is 0. +##### .emptyStringOrWhitespace(value) + +Returns `true` if `is.emptyString(value)` or if it's a `string` that is all whitespace. + ##### .nonEmptyString(value) Returns `true` if the value is a `string` and the `.length` is more than 0. @@ -256,10 +260,6 @@ values.filter(is.nonEmptyStringAndNotWhitespace); //=> ['property1', 'property2'] ``` -##### .emptyStringOrWhitespace(value) - -Returns `true` if `is.emptyString(value)` or if it's a `string` that is all whitespace. - ##### .emptyArray(value) Returns `true` if the value is an `Array` and the `.length` is 0. diff --git a/source/index.ts b/source/index.ts index 6836b18..504e5e1 100644 --- a/source/index.ts +++ b/source/index.ts @@ -344,12 +344,12 @@ is.nonEmptyArray = (value: unknown): value is unknown[] => is.array(value) && va is.emptyString = (value: unknown): value is '' => is.string(value) && value.length === 0; -// TODO: Use `not ''` when the `not` operator is available. -is.nonEmptyString = (value: unknown): value is string => is.string(value) && value.length > 0; - const isWhiteSpaceString = (value: unknown): value is string => is.string(value) && !/\S/.test(value); is.emptyStringOrWhitespace = (value: unknown): value is string => is.emptyString(value) || isWhiteSpaceString(value); +// TODO: Use `not ''` when the `not` operator is available. +is.nonEmptyString = (value: unknown): value is string => is.string(value) && value.length > 0; + // TODO: Use `not ''` when the `not` operator is available. is.nonEmptyStringAndNotWhitespace = (value: unknown): value is string => is.string(value) && !is.emptyStringOrWhitespace(value); @@ -434,8 +434,8 @@ export const enum AssertionTypeDescription { emptyArray = 'empty array', nonEmptyArray = 'non-empty array', emptyString = 'empty string', - nonEmptyString = 'non-empty string', emptyStringOrWhitespace = 'empty string or whitespace', + nonEmptyString = 'non-empty string', nonEmptyStringAndNotWhitespace = 'non-empty string and not whitespace', emptyObject = 'empty object', nonEmptyObject = 'non-empty object', @@ -524,8 +524,8 @@ interface Assert { emptyArray: (value: unknown) => asserts value is never[]; nonEmptyArray: (value: unknown) => asserts value is unknown[]; emptyString: (value: unknown) => asserts value is ''; - nonEmptyString: (value: unknown) => asserts value is string; emptyStringOrWhitespace: (value: unknown) => asserts value is string; + nonEmptyString: (value: unknown) => asserts value is string; nonEmptyStringAndNotWhitespace: (value: unknown) => asserts value is string; emptyObject: (value: unknown) => asserts value is Record; nonEmptyObject: (value: unknown) => asserts value is Record; @@ -626,8 +626,8 @@ export const assert: Assert = { emptyArray: (value: unknown): asserts value is never[] => assertType(is.emptyArray(value), AssertionTypeDescription.emptyArray, value), nonEmptyArray: (value: unknown): asserts value is unknown[] => assertType(is.nonEmptyArray(value), AssertionTypeDescription.nonEmptyArray, value), emptyString: (value: unknown): asserts value is '' => assertType(is.emptyString(value), AssertionTypeDescription.emptyString, value), - nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), AssertionTypeDescription.nonEmptyString, value), emptyStringOrWhitespace: (value: unknown): asserts value is string => assertType(is.emptyStringOrWhitespace(value), AssertionTypeDescription.emptyStringOrWhitespace, value), + nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), AssertionTypeDescription.nonEmptyString, value), nonEmptyStringAndNotWhitespace: (value: unknown): asserts value is string => assertType(is.nonEmptyStringAndNotWhitespace(value), AssertionTypeDescription.nonEmptyStringAndNotWhitespace, value), emptyObject: (value: unknown): asserts value is Record => assertType(is.emptyObject(value), AssertionTypeDescription.emptyObject, value), nonEmptyObject: (value: unknown): asserts value is Record => assertType(is.nonEmptyObject(value), AssertionTypeDescription.nonEmptyObject, value), diff --git a/test/test.ts b/test/test.ts index 7fb01f8..0556431 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1403,22 +1403,6 @@ test('is.emptyString', t => { }); }); -test('is.nonEmptyString', t => { - t.false(is.nonEmptyString('')); - t.false(is.nonEmptyString(String())); - t.true(is.nonEmptyString('🦄')); - - t.throws(() => { - assert.nonEmptyString(''); - }); - t.throws(() => { - assert.nonEmptyString(String()); - }); - t.notThrows(() => { - assert.nonEmptyString('🦄'); - }); -}); - test('is.emptyStringOrWhitespace', t => { testType(t, 'emptyString', ['string']); t.true(is.emptyStringOrWhitespace(' ')); @@ -1436,6 +1420,22 @@ test('is.emptyStringOrWhitespace', t => { }); }); +test('is.nonEmptyString', t => { + t.false(is.nonEmptyString('')); + t.false(is.nonEmptyString(String())); + t.true(is.nonEmptyString('🦄')); + + t.throws(() => { + assert.nonEmptyString(''); + }); + t.throws(() => { + assert.nonEmptyString(String()); + }); + t.notThrows(() => { + assert.nonEmptyString('🦄'); + }); +}); + test('is.nonEmptyStringAndNotWhitespace', t => { t.false(is.nonEmptyStringAndNotWhitespace(' ')); t.true(is.nonEmptyStringAndNotWhitespace('🦄')); From 5b7ea154e649f269d128af61e7c47fbd5b5e9443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=EF=BC=88=E4=B9=A6=E7=94=9F=EF=BC=89?= Date: Sun, 27 Feb 2022 15:16:25 +0800 Subject: [PATCH 171/254] Add `.isBlob()` (#162) Co-authored-by: Sindre Sorhus --- readme.md | 1 + source/index.ts | 4 ++++ test/test.ts | 12 ++++++++++++ 3 files changed, 17 insertions(+) diff --git a/readme.md b/readme.md index 12a56fd..05a291a 100644 --- a/readme.md +++ b/readme.md @@ -110,6 +110,7 @@ is.array(value, is.number); // Validate `value` is an array and all of its items **Note:** TypeScript users must use `.function_()` because of a TypeScript naming limitation. ##### .buffer(value) +##### .blob(value) ##### .object(value) Keep in mind that [functions are objects too](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions). diff --git a/source/index.ts b/source/index.ts index 504e5e1..5ce333d 100644 --- a/source/index.ts +++ b/source/index.ts @@ -34,6 +34,7 @@ const objectTypeNames = [ 'Observable', 'Array', 'Buffer', + 'Blob', 'Object', 'RegExp', 'Date', @@ -178,6 +179,7 @@ is.array = (value: unknown, assertion?: (value: T) => value is T): }; is.buffer = (value: unknown): value is Buffer => (value as any)?.constructor?.isBuffer?.(value) ?? false; +is.blob = (value: unknown): value is Blob => isObjectOfType('Blob')(value); is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); is.object = (value: unknown): value is object => !is.null_(value) && (typeof value === 'object' || is.function_(value)); @@ -470,6 +472,7 @@ interface Assert { numericString: (value: unknown) => asserts value is string; array: (value: unknown, assertion?: (element: unknown) => asserts element is T) => asserts value is T[]; buffer: (value: unknown) => asserts value is Buffer; + blob: (value: unknown) => asserts value is Blob; nullOrUndefined: (value: unknown) => asserts value is null | undefined; object: (value: unknown) => asserts value is Record; iterable: (value: unknown) => asserts value is Iterable; @@ -572,6 +575,7 @@ export const assert: Assert = { } }, buffer: (value: unknown): asserts value is Buffer => assertType(is.buffer(value), 'Buffer', value), + blob: (value: unknown): asserts value is Blob => assertType(is.blob(value), 'Blob', value), nullOrUndefined: (value: unknown): asserts value is null | undefined => assertType(is.nullOrUndefined(value), AssertionTypeDescription.nullOrUndefined, value), object: (value: unknown): asserts value is object => assertType(is.object(value), 'Object', value), iterable: (value: unknown): asserts value is Iterable => assertType(is.iterable(value), AssertionTypeDescription.iterable, value), diff --git a/test/test.ts b/test/test.ts index 0556431..71a4a9d 100644 --- a/test/test.ts +++ b/test/test.ts @@ -170,6 +170,14 @@ const types = new Map([ ], typename: 'Buffer' }], + ['blob', { + is: is.blob, + assert: assert.blob, + fixtures: [ + new window.Blob() + ], + typename: 'Blob' + }], ['object', { is: is.object, assert: assert.object, @@ -683,6 +691,10 @@ test('is.buffer', t => { testType(t, 'buffer'); }); +test('is.blob', t => { + testType(t, 'blob'); +}); + test('is.object', t => { const testData = types.get('object'); From 65ea91297ede74046b19a65c434411a39c16dacd Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 27 Feb 2022 14:21:30 +0700 Subject: [PATCH 172/254] 4.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cc86fc3..8052a87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "4.5.0", + "version": "4.6.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From c408f5a2680b750c8a000289954dacc2296acc00 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 9 Jun 2022 19:05:31 +0200 Subject: [PATCH 173/254] Fix `NaN` detection in `.is()` (#159) --- source/index.ts | 3 ++- test/test.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/index.ts b/source/index.ts index 5ce333d..39e5883 100644 --- a/source/index.ts +++ b/source/index.ts @@ -51,6 +51,7 @@ const objectTypeNames = [ 'FormData', 'URLSearchParams', 'HTMLElement', + 'NaN', ...typedArrayTypeNames ] as const; @@ -111,7 +112,7 @@ function is(value: unknown): TypeName { case 'string': return 'string'; case 'number': - return 'number'; + return Number.isNaN(value) ? 'NaN' : 'number'; case 'boolean': return 'boolean'; case 'function': diff --git a/test/test.ts b/test/test.ts index 71a4a9d..f4fe213 100644 --- a/test/test.ts +++ b/test/test.ts @@ -454,7 +454,7 @@ const types = new Map([ NaN, Number.NaN ], - typename: 'number', + typename: 'NaN', typeDescription: AssertionTypeDescription.nan }], ['nullOrUndefined', { From d6fc1ce0fe742f97025a305816791a4ab071637f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 11 Jun 2022 17:44:01 +0700 Subject: [PATCH 174/254] Require Node.js 14, TS 4.7, and move to ESM (#167) --- .github/workflows/main.yml | 8 +- package.json | 62 +++--- readme.md | 8 +- source/index.ts | 155 ++++++++------ source/types.ts | 4 +- test/test.ts | 400 +++++++++++++++++++------------------ tsconfig.json | 14 +- tsconfig.xo.json | 6 - 8 files changed, 341 insertions(+), 316 deletions(-) delete mode 100644 tsconfig.xo.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c1870cf..d50ada6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,12 +10,12 @@ jobs: fail-fast: false matrix: node-version: + - 18 + - 16 - 14 - - 12 - - 10 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/package.json b/package.json index 8052a87..34f31f6 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, - "main": "dist/index.js", + "type": "module", + "exports": "./index.js", + "types": "./index.d.ts", "engines": { - "node": ">=10" + "node": ">=14.16" }, "scripts": { "build": "del dist && tsc", @@ -47,50 +49,34 @@ "types" ], "devDependencies": { - "@sindresorhus/tsconfig": "^0.7.0", - "@types/jsdom": "^16.1.0", - "@types/node": "^14.0.13", - "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.20.0", - "@typescript-eslint/parser": "^2.20.0", - "ava": "^3.3.0", - "del-cli": "^2.0.0", - "eslint-config-xo-typescript": "^0.26.0", - "jsdom": "^16.0.1", - "rxjs": "^6.4.0", - "tempy": "^0.4.0", - "ts-node": "^8.3.0", - "typescript": "~3.8.2", - "xo": "^0.26.1", - "zen-observable": "^0.8.8" + "@sindresorhus/tsconfig": "^3.0.1", + "@types/jsdom": "^16.2.14", + "@types/node": "^17.0.42", + "@types/zen-observable": "^0.8.3", + "ava": "^4.3.0", + "del-cli": "^4.0.1", + "jsdom": "^19.0.0", + "rxjs": "^7.5.5", + "tempy": "^3.0.0", + "ts-node": "^10.8.1", + "typescript": "~4.7.3", + "xo": "^0.50.0", + "zen-observable": "^0.8.15" }, - "types": "dist/index.d.ts", "sideEffects": false, "ava": { - "extensions": [ - "ts" - ], - "require": [ - "ts-node/register" + "extensions": { + "ts": "module" + }, + "nodeArguments": [ + "--loader=ts-node/esm" ] }, "xo": { - "extends": "xo-typescript", - "extensions": [ - "ts" - ], - "parserOptions": { - "project": "./tsconfig.xo.json" - }, - "globals": [ - "BigInt", - "BigInt64Array", - "BigUint64Array" - ], "rules": { - "@typescript-eslint/promise-function-async": "off", "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/explicit-function-return-type": "off" + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/triple-slash-reference": "off" } } } diff --git a/readme.md b/readme.md index 05a291a..90ac932 100644 --- a/readme.md +++ b/readme.md @@ -24,7 +24,7 @@ npm install @sindresorhus/is ## Usage ```js -const is = require('@sindresorhus/is'); +import is from '@sindresorhus/is'; is('🦄'); //=> 'string' @@ -39,7 +39,7 @@ is.number(6); [Assertions](#type-assertions) perform the same type checks, but throw an error if the type does not match. ```js -const {assert} = require('@sindresorhus/is'); +import {assert} from '@sindresorhus/is'; assert.string(2); //=> Error: Expected value which is `string`, received value of type `number`. @@ -436,7 +436,7 @@ Returns `true` if `value` is a DOM Element. Returns `true` if `value` is a Node.js [stream](https://nodejs.org/api/stream.html). ```js -const fs = require('fs'); +import fs from 'node:fs'; is.nodeStream(fs.createReadStream('unicorn.png')); //=> true @@ -447,7 +447,7 @@ is.nodeStream(fs.createReadStream('unicorn.png')); Returns `true` if `value` is an `Observable`. ```js -const {Observable} = require('rxjs'); +import {Observable} from 'rxjs'; is.observable(new Observable()); //=> true diff --git a/source/index.ts b/source/index.ts index 39e5883..6457799 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,8 +1,5 @@ -/// -/// -/// - -import {Class, Falsy, TypedArray, ObservableLike, Primitive} from './types'; +import type {Buffer} from 'node:buffer'; +import type {Class, Falsy, TypedArray, ObservableLike, Primitive} from './types.js'; const typedArrayTypeNames = [ 'Int8Array', @@ -15,7 +12,7 @@ const typedArrayTypeNames = [ 'Float32Array', 'Float64Array', 'BigInt64Array', - 'BigUint64Array' + 'BigUint64Array', ] as const; type TypedArrayTypeName = typeof typedArrayTypeNames[number]; @@ -52,7 +49,7 @@ const objectTypeNames = [ 'URLSearchParams', 'HTMLElement', 'NaN', - ...typedArrayTypeNames + ...typedArrayTypeNames, ] as const; type ObjectTypeName = typeof objectTypeNames[number]; @@ -68,7 +65,7 @@ const primitiveTypeNames = [ 'number', 'bigint', 'boolean', - 'symbol' + 'symbol', ] as const; type PrimitiveTypeName = typeof primitiveTypeNames[number]; @@ -149,6 +146,7 @@ function is(value: unknown): TypeName { } is.undefined = isOfType('undefined'); + is.string = isOfType('string'); const isNumberType = isOfType('number'); @@ -159,9 +157,13 @@ is.bigint = isOfType('bigint'); // eslint-disable-next-line @typescript-eslint/ban-types is.function_ = isOfType('function'); +// eslint-disable-next-line @typescript-eslint/ban-types is.null_ = (value: unknown): value is null => value === null; + is.class_ = (value: unknown): value is Class => is.function_(value) && value.toString().startsWith('class '); + is.boolean = (value: unknown): value is boolean => value === true || value === false; + is.symbol = isOfType('symbol'); is.numericString = (value: unknown): value is string => @@ -176,14 +178,18 @@ is.array = (value: unknown, assertion?: (value: T) => value is T): return true; } - return value.every(assertion); + return value.every(element => assertion(element)); }; +// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call is.buffer = (value: unknown): value is Buffer => (value as any)?.constructor?.isBuffer?.(value) ?? false; + is.blob = (value: unknown): value is Blob => isObjectOfType('Blob')(value); -is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); -is.object = (value: unknown): value is object => !is.null_(value) && (typeof value === 'object' || is.function_(value)); +is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); // eslint-disable-line @typescript-eslint/ban-types + +is.object = (value: unknown): value is object => !is.null_(value) && (typeof value === 'object' || is.function_(value)); // eslint-disable-line @typescript-eslint/ban-types + is.iterable = (value: unknown): value is Iterable => is.function_((value as Iterable)?.[Symbol.iterator]); is.asyncIterable = (value: unknown): value is AsyncIterable => is.function_((value as AsyncIterable)?.[Symbol.asyncIterator]); @@ -195,11 +201,11 @@ is.asyncGenerator = (value: unknown): value is AsyncGenerator => is.asyncIterabl is.nativePromise = (value: unknown): value is Promise => isObjectOfType>('Promise')(value); -const hasPromiseAPI = (value: unknown): value is Promise => - is.function_((value as Promise)?.then) && - is.function_((value as Promise)?.catch); +const hasPromiseApi = (value: unknown): value is Promise => + is.function_((value as Promise)?.then) + && is.function_((value as Promise)?.catch); -is.promise = (value: unknown): value is Promise => is.nativePromise(value) || hasPromiseAPI(value); +is.promise = (value: unknown): value is Promise => is.nativePromise(value) || hasPromiseApi(value); is.generatorFunction = isObjectOfType('GeneratorFunction'); @@ -211,12 +217,18 @@ is.asyncFunction = (value: unknown): value is ((...args: any[]) => is.boundFunction = (value: unknown): value is Function => is.function_(value) && !value.hasOwnProperty('prototype'); is.regExp = isObjectOfType('RegExp'); + is.date = isObjectOfType('Date'); + is.error = isObjectOfType('Error'); + is.map = (value: unknown): value is Map => isObjectOfType>('Map')(value); + is.set = (value: unknown): value is Set => isObjectOfType>('Set')(value); -is.weakMap = (value: unknown): value is WeakMap => isObjectOfType>('WeakMap')(value); -is.weakSet = (value: unknown): value is WeakSet => isObjectOfType>('WeakSet')(value); + +is.weakMap = (value: unknown): value is WeakMap => isObjectOfType>('WeakMap')(value); // eslint-disable-line @typescript-eslint/ban-types + +is.weakSet = (value: unknown): value is WeakSet => isObjectOfType>('WeakSet')(value); // eslint-disable-line @typescript-eslint/ban-types is.int8Array = isObjectOfType('Int8Array'); is.uint8Array = isObjectOfType('Uint8Array'); @@ -231,11 +243,15 @@ is.bigInt64Array = isObjectOfType('BigInt64Array'); is.bigUint64Array = isObjectOfType('BigUint64Array'); is.arrayBuffer = isObjectOfType('ArrayBuffer'); + is.sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); + is.dataView = isObjectOfType('DataView'); + is.enumCase = (value: unknown, targetEnum: T) => Object.values(targetEnum).includes(value as string); is.directInstanceOf = (instance: unknown, class_: Class): instance is T => Object.getPrototypeOf(instance) === class_.prototype; + is.urlInstance = (value: unknown): value is URL => isObjectOfType('URL')(value); is.urlString = (value: unknown): value is string => { @@ -252,7 +268,8 @@ is.urlString = (value: unknown): value is string => { }; // Example: `is.truthy = (value: unknown): value is (not false | not 0 | not '' | not undefined | not null) => Boolean(value);` -is.truthy = (value: T | Falsy): value is T => Boolean(value); +is.truthy = (value: T | Falsy): value is T => Boolean(value); // eslint-disable-line unicorn/prefer-native-coercion-functions + // Example: `is.falsy = (value: unknown): value is (not true | 0 | '' | undefined | null) => Boolean(value);` is.falsy = (value: T | Falsy): value is Falsy => !value; @@ -261,6 +278,7 @@ is.nan = (value: unknown) => Number.isNaN(value as number); is.primitive = (value: unknown): value is Primitive => is.null_(value) || isPrimitiveTypeName(typeof value); is.integer = (value: unknown): value is number => Number.isInteger(value as number); + is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); is.plainObject = (value: unknown): value is Record => { @@ -269,6 +287,7 @@ is.plainObject = (value: unknown): value is Record { throw new TypeError(`Invalid range: ${JSON.stringify(range)}`); }; +// eslint-disable-next-line @typescript-eslint/naming-convention const NODE_TYPE_ELEMENT = 1; + +// eslint-disable-next-line @typescript-eslint/naming-convention const DOM_PROPERTIES_TO_CHECK: Array<(keyof HTMLElement)> = [ 'innerHTML', 'ownerDocument', 'style', 'attributes', - 'nodeValue' + 'nodeValue', ]; -is.domElement = (value: unknown): value is HTMLElement => { - return is.object(value) && - (value as HTMLElement).nodeType === NODE_TYPE_ELEMENT && - is.string((value as HTMLElement).nodeName) && - !is.plainObject(value) && - DOM_PROPERTIES_TO_CHECK.every(property => property in value); -}; +is.domElement = (value: unknown): value is HTMLElement => is.object(value) + && (value as HTMLElement).nodeType === NODE_TYPE_ELEMENT + && is.string((value as HTMLElement).nodeName) + && !is.plainObject(value) + && DOM_PROPERTIES_TO_CHECK.every(property => property in value); is.observable = (value: unknown): value is ObservableLike => { if (!value) { return false; } - // eslint-disable-next-line no-use-extend-native/no-use-extend-native + // eslint-disable-next-line no-use-extend-native/no-use-extend-native, @typescript-eslint/no-unsafe-call if (value === (value as any)[Symbol.observable]?.()) { return true; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-call if (value === (value as any)['@@observable']?.()) { return true; } @@ -336,13 +357,14 @@ export interface NodeStream extends NodeJS.EventEmitter { is.nodeStream = (value: unknown): value is NodeStream => is.object(value) && is.function_((value as NodeStream).pipe) && !is.observable(value); -is.infinite = (value: unknown): value is number => value === Infinity || value === -Infinity; +is.infinite = (value: unknown): value is number => value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY; const isAbsoluteMod2 = (remainder: number) => (value: number): value is number => is.integer(value) && Math.abs(value % 2) === remainder; is.evenInteger = isAbsoluteMod2(0); is.oddInteger = isAbsoluteMod2(1); is.emptyArray = (value: unknown): value is never[] => is.array(value) && value.length === 0; + is.nonEmptyArray = (value: unknown): value is unknown[] => is.array(value) && value.length > 0; is.emptyString = (value: unknown): value is '' => is.string(value) && value.length === 0; @@ -356,20 +378,27 @@ is.nonEmptyString = (value: unknown): value is string => is.string(value) && val // TODO: Use `not ''` when the `not` operator is available. is.nonEmptyStringAndNotWhitespace = (value: unknown): value is string => is.string(value) && !is.emptyStringOrWhitespace(value); +// eslint-disable-next-line unicorn/no-array-callback-reference is.emptyObject = (value: unknown): value is Record => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0; // TODO: Use `not` operator here to remove `Map` and `Set` from type guard: // - https://github.com/Microsoft/TypeScript/pull/29317 +// eslint-disable-next-line unicorn/no-array-callback-reference is.nonEmptyObject = (value: unknown): value is Record => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length > 0; is.emptySet = (value: unknown): value is Set => is.set(value) && value.size === 0; + is.nonEmptySet = (value: unknown): value is Set => is.set(value) && value.size > 0; +// eslint-disable-next-line unicorn/no-array-callback-reference is.emptyMap = (value: unknown): value is Map => is.map(value) && value.size === 0; + +// eslint-disable-next-line unicorn/no-array-callback-reference is.nonEmptyMap = (value: unknown): value is Map => is.map(value) && value.size > 0; // `PropertyKey` is any value that can be used as an object key (string, number, or symbol) is.propertyKey = (value: unknown): value is PropertyKey => is.any([is.string, is.number, is.symbol], value); + is.formData = (value: unknown): value is FormData => isObjectOfType('FormData')(value); is.urlSearchParams = (value: unknown): value is URLSearchParams => isObjectOfType('URLSearchParams')(value); @@ -393,7 +422,7 @@ const predicateOnArray = (method: ArrayMethod, predicate: Predicate, values: unk is.any = (predicate: Predicate | Predicate[], ...values: unknown[]): boolean => { const predicates = is.array(predicate) ? predicate : [predicate]; return predicates.some(singlePredicate => - predicateOnArray(Array.prototype.some, singlePredicate, values) + predicateOnArray(Array.prototype.some, singlePredicate, values), ); }; @@ -402,13 +431,13 @@ is.all = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArr const assertType = (condition: boolean, description: string, value: unknown, options: {multipleValues?: boolean} = {}): asserts condition => { if (!condition) { const {multipleValues} = options; - const valuesMessage = multipleValues ? - `received values of types ${[ + const valuesMessage = multipleValues + ? `received values of types ${[ ...new Set( - (value as any[]).map(singleValue => `\`${is(singleValue)}\``) - ) - ].join(', ')}` : - `received value of type \`${is(value)}\``; + (value as any[]).map(singleValue => `\`${is(singleValue)}\``), + ), + ].join(', ')}` + : `received value of type \`${is(value)}\``; throw new TypeError(`Expected value which is \`${description}\`, ${valuesMessage}.`); } @@ -427,7 +456,7 @@ export const enum AssertionTypeDescription { nan = 'NaN', primitive = 'primitive', integer = 'integer', - safeInteger = 'integer', + safeInteger = 'integer', // eslint-disable-line @typescript-eslint/no-duplicate-enum-values plainObject = 'plain object', arrayLike = 'array-like', typedArray = 'TypedArray', @@ -466,7 +495,7 @@ interface Assert { bigint: (value: unknown) => asserts value is bigint; // eslint-disable-next-line @typescript-eslint/ban-types function_: (value: unknown) => asserts value is Function; - null_: (value: unknown) => asserts value is null; + null_: (value: unknown) => asserts value is null; // eslint-disable-line @typescript-eslint/ban-types class_: (value: unknown) => asserts value is Class; boolean: (value: unknown) => asserts value is boolean; symbol: (value: unknown) => asserts value is symbol; @@ -474,7 +503,7 @@ interface Assert { array: (value: unknown, assertion?: (element: unknown) => asserts element is T) => asserts value is T[]; buffer: (value: unknown) => asserts value is Buffer; blob: (value: unknown) => asserts value is Blob; - nullOrUndefined: (value: unknown) => asserts value is null | undefined; + nullOrUndefined: (value: unknown) => asserts value is null | undefined; // eslint-disable-line @typescript-eslint/ban-types object: (value: unknown) => asserts value is Record; iterable: (value: unknown) => asserts value is Iterable; asyncIterable: (value: unknown) => asserts value is AsyncIterable; @@ -493,8 +522,8 @@ interface Assert { error: (value: unknown) => asserts value is Error; map: (value: unknown) => asserts value is Map; set: (value: unknown) => asserts value is Set; - weakMap: (value: unknown) => asserts value is WeakMap; - weakSet: (value: unknown) => asserts value is WeakSet; + weakMap: (value: unknown) => asserts value is WeakMap; // eslint-disable-line @typescript-eslint/ban-types + weakSet: (value: unknown) => asserts value is WeakSet; // eslint-disable-line @typescript-eslint/ban-types int8Array: (value: unknown) => asserts value is Int8Array; uint8Array: (value: unknown) => asserts value is Uint8Array; uint8ClampedArray: (value: unknown) => asserts value is Uint8ClampedArray; @@ -554,6 +583,7 @@ interface Assert { all: (predicate: Predicate, ...values: unknown[]) => void | never; } +/* eslint-disable @typescript-eslint/no-confusing-void-expression */ export const assert: Assert = { // Unknowns. undefined: (value: unknown): asserts value is undefined => assertType(is.undefined(value), 'undefined', value), @@ -562,23 +592,24 @@ export const assert: Assert = { bigint: (value: unknown): asserts value is bigint => assertType(is.bigint(value), 'bigint', value), // eslint-disable-next-line @typescript-eslint/ban-types function_: (value: unknown): asserts value is Function => assertType(is.function_(value), 'Function', value), - null_: (value: unknown): asserts value is null => assertType(is.null_(value), 'null', value), + null_: (value: unknown): asserts value is null => assertType(is.null_(value), 'null', value), // eslint-disable-line @typescript-eslint/ban-types class_: (value: unknown): asserts value is Class => assertType(is.class_(value), AssertionTypeDescription.class_, value), boolean: (value: unknown): asserts value is boolean => assertType(is.boolean(value), 'boolean', value), symbol: (value: unknown): asserts value is symbol => assertType(is.symbol(value), 'symbol', value), numericString: (value: unknown): asserts value is string => assertType(is.numericString(value), AssertionTypeDescription.numericString, value), - array: (value: unknown, assertion?: (element: unknown) => asserts element is T): asserts value is T[] => { + array: (value: unknown, assertion?: (element: unknown) => asserts element is T): asserts value is T[] => { // eslint-disable-line object-shorthand const assert: (condition: boolean, description: string, value: unknown) => asserts condition = assertType; assert(is.array(value), 'Array', value); if (assertion) { + // eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-callback-reference value.forEach(assertion); } }, buffer: (value: unknown): asserts value is Buffer => assertType(is.buffer(value), 'Buffer', value), blob: (value: unknown): asserts value is Blob => assertType(is.blob(value), 'Blob', value), - nullOrUndefined: (value: unknown): asserts value is null | undefined => assertType(is.nullOrUndefined(value), AssertionTypeDescription.nullOrUndefined, value), - object: (value: unknown): asserts value is object => assertType(is.object(value), 'Object', value), + nullOrUndefined: (value: unknown): asserts value is null | undefined => assertType(is.nullOrUndefined(value), AssertionTypeDescription.nullOrUndefined, value), // eslint-disable-line @typescript-eslint/ban-types + object: (value: unknown): asserts value is object => assertType(is.object(value), 'Object', value), // eslint-disable-line @typescript-eslint/ban-types iterable: (value: unknown): asserts value is Iterable => assertType(is.iterable(value), AssertionTypeDescription.iterable, value), asyncIterable: (value: unknown): asserts value is AsyncIterable => assertType(is.asyncIterable(value), AssertionTypeDescription.asyncIterable, value), generator: (value: unknown): asserts value is Generator => assertType(is.generator(value), 'Generator', value), @@ -594,10 +625,10 @@ export const assert: Assert = { regExp: (value: unknown): asserts value is RegExp => assertType(is.regExp(value), 'RegExp', value), date: (value: unknown): asserts value is Date => assertType(is.date(value), 'Date', value), error: (value: unknown): asserts value is Error => assertType(is.error(value), 'Error', value), - map: (value: unknown): asserts value is Map => assertType(is.map(value), 'Map', value), + map: (value: unknown): asserts value is Map => assertType(is.map(value), 'Map', value), // eslint-disable-line unicorn/no-array-callback-reference set: (value: unknown): asserts value is Set => assertType(is.set(value), 'Set', value), - weakMap: (value: unknown): asserts value is WeakMap => assertType(is.weakMap(value), 'WeakMap', value), - weakSet: (value: unknown): asserts value is WeakSet => assertType(is.weakSet(value), 'WeakSet', value), + weakMap: (value: unknown): asserts value is WeakMap => assertType(is.weakMap(value), 'WeakMap', value), // eslint-disable-line @typescript-eslint/ban-types + weakSet: (value: unknown): asserts value is WeakSet => assertType(is.weakSet(value), 'WeakSet', value), // eslint-disable-line @typescript-eslint/ban-types int8Array: (value: unknown): asserts value is Int8Array => assertType(is.int8Array(value), 'Int8Array', value), uint8Array: (value: unknown): asserts value is Uint8Array => assertType(is.uint8Array(value), 'Uint8Array', value), uint8ClampedArray: (value: unknown): asserts value is Uint8ClampedArray => assertType(is.uint8ClampedArray(value), 'Uint8ClampedArray', value), @@ -653,42 +684,36 @@ export const assert: Assert = { inRange: (value: number, range: number | number[]): asserts value is number => assertType(is.inRange(value, range), AssertionTypeDescription.inRange, value), // Variadic functions. - any: (predicate: Predicate | Predicate[], ...values: unknown[]): void | never => { - return assertType(is.any(predicate, ...values), AssertionTypeDescription.any, values, {multipleValues: true}); - }, - all: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.all(predicate, ...values), AssertionTypeDescription.all, values, {multipleValues: true}) + any: (predicate: Predicate | Predicate[], ...values: unknown[]): void | never => assertType(is.any(predicate, ...values), AssertionTypeDescription.any, values, {multipleValues: true}), + all: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.all(predicate, ...values), AssertionTypeDescription.all, values, {multipleValues: true}), }; +/* eslint-enable @typescript-eslint/no-confusing-void-expression */ // Some few keywords are reserved, but we'll populate them for Node.js users // See https://github.com/Microsoft/TypeScript/issues/2536 Object.defineProperties(is, { class: { - value: is.class_ + value: is.class_, }, function: { - value: is.function_ + value: is.function_, }, null: { - value: is.null_ - } + value: is.null_, + }, }); Object.defineProperties(assert, { class: { - value: assert.class_ + value: assert.class_, }, function: { - value: assert.function_ + value: assert.function_, }, null: { - value: assert.null_ - } + value: assert.null_, + }, }); export default is; -export {Class, TypedArray, ObservableLike, Primitive} from './types'; - -// For CommonJS default export support -module.exports = is; -module.exports.default = is; -module.exports.assert = assert; +export type {Class, TypedArray, ObservableLike, Primitive} from './types.js'; diff --git a/source/types.ts b/source/types.ts index f6da50c..3940da2 100644 --- a/source/types.ts +++ b/source/types.ts @@ -4,7 +4,7 @@ Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive). */ export type Primitive = - | null + | null // eslint-disable-line @typescript-eslint/ban-types | undefined | string | number @@ -12,7 +12,6 @@ export type Primitive = | symbol | bigint; -// TODO: Remove the `= unknown` sometime in the future when most users are on TS 3.5 as it's now the default /** Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). */ @@ -48,4 +47,5 @@ export interface ObservableLike { [Symbol.observable](): ObservableLike; } +// eslint-disable-next-line @typescript-eslint/ban-types export type Falsy = false | 0 | 0n | '' | null | undefined; diff --git a/test/test.ts b/test/test.ts index f4fe213..c74f76a 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,13 +1,14 @@ -import fs = require('fs'); -import net = require('net'); -import Stream = require('stream'); -import {inspect} from 'util'; +import {Buffer} from 'node:buffer'; +import fs from 'node:fs'; +import net from 'node:net'; +import Stream from 'node:stream'; +import {inspect} from 'node:util'; import test, {ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; -import tempy = require('tempy'); -import ZenObservable = require('zen-observable'); -import is, {assert, AssertionTypeDescription, Primitive, TypedArray, TypeName} from '../source'; +import {temporaryFile} from 'tempy'; +import ZenObservable from 'zen-observable'; +import is, {assert, AssertionTypeDescription, Primitive, TypedArray, TypeName} from '../source/index.js'; class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} @@ -29,7 +30,7 @@ const invertAssertThrow = (description: string, fn: () => void | never, value: u try { fn(); - } catch (error) { + } catch (error: unknown) { if (error instanceof TypeError && error.message.includes(expectedAssertErrorMessage)) { return; } @@ -45,17 +46,17 @@ const types = new Map([ is: is.undefined, assert: assert.undefined, fixtures: [ - undefined + undefined, ], - typename: 'undefined' + typename: 'undefined', }], ['null', { is: is.null_, assert: assert.null_, fixtures: [ - null + null, ], - typename: 'null' + typename: 'null', }], ['string', { is: is.string, @@ -63,19 +64,19 @@ const types = new Map([ fixtures: [ '🦄', 'hello world', - '' + '', ], - typename: 'string' + typename: 'string', }], ['emptyString', { is: is.emptyString, assert: assert.emptyString, fixtures: [ '', - String() + String(), ], typename: 'string', - typeDescription: AssertionTypeDescription.emptyString + typeDescription: AssertionTypeDescription.emptyString, }], ['number', { is: is.number, @@ -85,10 +86,10 @@ const types = new Map([ 1.4, 0, -0, - Infinity, - -Infinity + Number.POSITIVE_INFINITY, + Number.NEGATIVE_INFINITY, ], - typename: 'number' + typename: 'number', }], ['bigint', { is: is.bigint, @@ -98,25 +99,25 @@ const types = new Map([ // 1n, // 0n, // -0n, - BigInt('1234') + BigInt('1234'), ], - typename: 'bigint' + typename: 'bigint', }], ['boolean', { is: is.boolean, assert: assert.boolean, fixtures: [ - true, false + true, false, ], - typename: 'boolean' + typename: 'boolean', }], ['symbol', { is: is.symbol, assert: assert.symbol, fixtures: [ - Symbol('🦄') + Symbol('🦄'), ], - typename: 'symbol' + typename: 'symbol', }], ['numericString', { is: is.numericString, @@ -125,29 +126,29 @@ const types = new Map([ '5', '-3.2', 'Infinity', - '0x56' + '0x56', ], typename: 'string', - typeDescription: AssertionTypeDescription.numericString + typeDescription: AssertionTypeDescription.numericString, }], ['array', { is: is.array, assert: assert.array, fixtures: [ [1, 2], - new Array(2) + Array.from({length: 2}), ], - typename: 'Array' + typename: 'Array', }], ['emptyArray', { is: is.emptyArray, assert: assert.emptyArray, fixtures: [ [], - new Array() // eslint-disable-line @typescript-eslint/no-array-constructor + new Array(), // eslint-disable-line @typescript-eslint/no-array-constructor ], typename: 'Array', - typeDescription: AssertionTypeDescription.emptyArray + typeDescription: AssertionTypeDescription.emptyArray, }], ['function', { is: is.function_, @@ -158,79 +159,79 @@ const types = new Map([ () => {}, async function () {}, function * (): unknown {}, - async function * (): unknown {} + async function * (): unknown {}, ], - typename: 'Function' + typename: 'Function', }], ['buffer', { is: is.buffer, assert: assert.buffer, fixtures: [ - Buffer.from('🦄') + Buffer.from('🦄'), ], - typename: 'Buffer' + typename: 'Buffer', }], ['blob', { is: is.blob, assert: assert.blob, fixtures: [ - new window.Blob() + new window.Blob(), ], - typename: 'Blob' + typename: 'Blob', }], ['object', { is: is.object, assert: assert.object, fixtures: [ {x: 1}, - Object.create({x: 1}) + Object.create({x: 1}), ], - typename: 'Object' + typename: 'Object', }], ['regExp', { is: is.regExp, assert: assert.regExp, fixtures: [ /\w/, - new RegExp('\\w') // eslint-disable-line prefer-regex-literals + new RegExp('\\w'), // eslint-disable-line prefer-regex-literals ], - typename: 'RegExp' + typename: 'RegExp', }], ['date', { is: is.date, assert: assert.date, fixtures: [ - new Date() + new Date(), ], - typename: 'Date' + typename: 'Date', }], ['error', { is: is.error, assert: assert.error, fixtures: [ new Error('🦄'), - new ErrorSubclassFixture() + new ErrorSubclassFixture(), ], - typename: 'Error' + typename: 'Error', }], ['nativePromise', { is: is.nativePromise, assert: assert.nativePromise, fixtures: [ Promise.resolve(), - PromiseSubclassFixture.resolve() + PromiseSubclassFixture.resolve(), ], typename: 'Promise', - typeDescription: AssertionTypeDescription.nativePromise + typeDescription: AssertionTypeDescription.nativePromise, }], ['promise', { is: is.promise, assert: assert.promise, fixtures: [ - {then() {}, catch() {}} + {then() {}, catch() {}}, // eslint-disable-line unicorn/no-thenable ], typename: 'Object', - typeDescription: 'Promise' + typeDescription: 'Promise', }], ['generator', { is: is.generator, @@ -238,9 +239,9 @@ const types = new Map([ fixtures: [ (function * () { yield 4; - })() + })(), ], - typename: 'Generator' + typename: 'Generator', }], ['asyncGenerator', { is: is.asyncGenerator, @@ -248,9 +249,9 @@ const types = new Map([ fixtures: [ (async function * () { yield 4; - })() + })(), ], - typename: 'AsyncGenerator' + typename: 'AsyncGenerator', }], ['generatorFunction', { is: is.generatorFunction, @@ -258,10 +259,10 @@ const types = new Map([ fixtures: [ function * () { yield 4; - } + }, ], typename: 'Function', - typeDescription: 'GeneratorFunction' + typeDescription: 'GeneratorFunction', }], ['asyncGeneratorFunction', { is: is.asyncGeneratorFunction, @@ -269,202 +270,202 @@ const types = new Map([ fixtures: [ async function * () { yield 4; - } + }, ], typename: 'Function', - typeDescription: 'AsyncGeneratorFunction' + typeDescription: 'AsyncGeneratorFunction', }], ['asyncFunction', { is: is.asyncFunction, assert: assert.asyncFunction, fixtures: [ async function () {}, - async () => {} + async () => {}, ], typename: 'Function', - typeDescription: 'AsyncFunction' + typeDescription: 'AsyncFunction', }], ['boundFunction', { is: is.boundFunction, assert: assert.boundFunction, fixtures: [ () => {}, - function () {}.bind(null) // eslint-disable-line no-extra-bind + function () {}.bind(null), // eslint-disable-line no-extra-bind ], - typename: 'Function' + typename: 'Function', }], ['map', { is: is.map, assert: assert.map, fixtures: [ - new Map([['one', '1']]) + new Map([['one', '1']]), ], - typename: 'Map' + typename: 'Map', }], ['emptyMap', { is: is.emptyMap, assert: assert.emptyMap, fixtures: [ - new Map() + new Map(), ], typename: 'Map', - typeDescription: AssertionTypeDescription.emptyMap + typeDescription: AssertionTypeDescription.emptyMap, }], ['set', { is: is.set, assert: assert.set, fixtures: [ - new Set(['one']) + new Set(['one']), ], - typename: 'Set' + typename: 'Set', }], ['emptySet', { is: is.emptySet, assert: assert.emptySet, fixtures: [ - new Set() + new Set(), ], typename: 'Set', - typeDescription: AssertionTypeDescription.emptySet + typeDescription: AssertionTypeDescription.emptySet, }], ['weakSet', { is: is.weakSet, assert: assert.weakSet, fixtures: [ - new WeakSet() + new WeakSet(), ], - typename: 'WeakSet' + typename: 'WeakSet', }], ['weakMap', { is: is.weakMap, assert: assert.weakMap, fixtures: [ - new WeakMap() + new WeakMap(), ], - typename: 'WeakMap' + typename: 'WeakMap', }], ['int8Array', { is: is.int8Array, assert: assert.int8Array, fixtures: [ - new Int8Array() + new Int8Array(), ], - typename: 'Int8Array' + typename: 'Int8Array', }], ['uint8Array', { is: is.uint8Array, assert: assert.uint8Array, fixtures: [ - new Uint8Array() + new Uint8Array(), ], - typename: 'Uint8Array' + typename: 'Uint8Array', }], ['uint8ClampedArray', { is: is.uint8ClampedArray, assert: assert.uint8ClampedArray, fixtures: [ - new Uint8ClampedArray() + new Uint8ClampedArray(), ], - typename: 'Uint8ClampedArray' + typename: 'Uint8ClampedArray', }], ['int16Array', { is: is.int16Array, assert: assert.int16Array, fixtures: [ - new Int16Array() + new Int16Array(), ], - typename: 'Int16Array' + typename: 'Int16Array', }], ['uint16Array', { is: is.uint16Array, assert: assert.uint16Array, fixtures: [ - new Uint16Array() + new Uint16Array(), ], - typename: 'Uint16Array' + typename: 'Uint16Array', }], ['int32Array', { is: is.int32Array, assert: assert.int32Array, fixtures: [ - new Int32Array() + new Int32Array(), ], - typename: 'Int32Array' + typename: 'Int32Array', }], ['uint32Array', { is: is.uint32Array, assert: assert.uint32Array, fixtures: [ - new Uint32Array() + new Uint32Array(), ], - typename: 'Uint32Array' + typename: 'Uint32Array', }], ['float32Array', { is: is.float32Array, assert: assert.float32Array, fixtures: [ - new Float32Array() + new Float32Array(), ], - typename: 'Float32Array' + typename: 'Float32Array', }], ['float64Array', { is: is.float64Array, assert: assert.float64Array, fixtures: [ - new Float64Array() + new Float64Array(), ], - typename: 'Float64Array' + typename: 'Float64Array', }], ['bigInt64Array', { is: is.bigInt64Array, assert: assert.bigInt64Array, fixtures: [ - new BigInt64Array() + new BigInt64Array(), ], - typename: 'BigInt64Array' + typename: 'BigInt64Array', }], ['bigUint64Array', { is: is.bigUint64Array, assert: assert.bigUint64Array, fixtures: [ - new BigUint64Array() + new BigUint64Array(), ], - typename: 'BigUint64Array' + typename: 'BigUint64Array', }], ['arrayBuffer', { is: is.arrayBuffer, assert: assert.arrayBuffer, fixtures: [ - new ArrayBuffer(10) + new ArrayBuffer(10), ], - typename: 'ArrayBuffer' + typename: 'ArrayBuffer', }], ['dataView', { is: is.dataView, assert: assert.dataView, fixtures: [ - new DataView(new ArrayBuffer(10)) + new DataView(new ArrayBuffer(10)), ], - typename: 'DataView' + typename: 'DataView', }], ['nan', { is: is.nan, assert: assert.nan, fixtures: [ - NaN, - Number.NaN + NaN, // eslint-disable-line unicorn/prefer-number-properties + Number.NaN, ], typename: 'NaN', - typeDescription: AssertionTypeDescription.nan + typeDescription: AssertionTypeDescription.nan, }], ['nullOrUndefined', { is: is.nullOrUndefined, assert: assert.nullOrUndefined, fixtures: [ null, - undefined + undefined, ], - typeDescription: AssertionTypeDescription.nullOrUndefined + typeDescription: AssertionTypeDescription.nullOrUndefined, }], ['plainObject', { is: is.plainObject, @@ -472,29 +473,29 @@ const types = new Map([ fixtures: [ {x: 1}, Object.create(null), - new Object() // eslint-disable-line no-new-object + new Object(), // eslint-disable-line no-new-object ], typename: 'Object', - typeDescription: AssertionTypeDescription.plainObject + typeDescription: AssertionTypeDescription.plainObject, }], ['integer', { is: is.integer, assert: assert.integer, fixtures: [ - 6 + 6, ], typename: 'number', - typeDescription: AssertionTypeDescription.integer + typeDescription: AssertionTypeDescription.integer, }], ['safeInteger', { is: is.safeInteger, assert: assert.safeInteger, fixtures: [ (2 ** 53) - 1, - -(2 ** 53) + 1 + -(2 ** 53) + 1, ], typename: 'number', - typeDescription: AssertionTypeDescription.safeInteger + typeDescription: AssertionTypeDescription.safeInteger, }], ['domElement', { is: is.domElement, @@ -505,21 +506,26 @@ const types = new Map([ 'span', 'img', 'canvas', - 'script' - ].map(createDomElement), - typeDescription: AssertionTypeDescription.domElement + 'script', + ] + .map(fixture => createDomElement(fixture)), + typeDescription: AssertionTypeDescription.domElement, }], ['non-domElements', { is: value => !is.domElement(value), - assert: (value: unknown) => invertAssertThrow(AssertionTypeDescription.domElement, () => assert.domElement(value), value), + assert(value: unknown) { + invertAssertThrow(AssertionTypeDescription.domElement, () => { + assert.domElement(value); + }, value); + }, fixtures: [ document.createTextNode('data'), document.createProcessingInstruction('xml-stylesheet', 'href="mycss.css" type="text/css"'), document.createComment('This is a comment'), document, document.implementation.createDocumentType('svg:svg', '-//W3C//DTD SVG 1.1//EN', 'https://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'), - document.createDocumentFragment() - ] + document.createDocumentFragment(), + ], }], ['observable', { is: is.observable, @@ -527,37 +533,37 @@ const types = new Map([ fixtures: [ new Observable(), new Subject(), - new ZenObservable(() => {}) + new ZenObservable(() => {}), ], - typename: 'Observable' + typename: 'Observable', }], ['nodeStream', { is: is.nodeStream, assert: assert.nodeStream, fixtures: [ fs.createReadStream('readme.md'), - fs.createWriteStream(tempy.file()), + fs.createWriteStream(temporaryFile()), new net.Socket(), new Stream.Duplex(), new Stream.PassThrough(), new Stream.Readable(), new Stream.Transform(), new Stream.Stream(), - new Stream.Writable() + new Stream.Writable(), ], typename: 'Object', - typeDescription: AssertionTypeDescription.nodeStream + typeDescription: AssertionTypeDescription.nodeStream, }], ['infinite', { is: is.infinite, assert: assert.infinite, fixtures: [ - Infinity, - -Infinity + Number.POSITIVE_INFINITY, + Number.NEGATIVE_INFINITY, ], typename: 'number', - typeDescription: AssertionTypeDescription.infinite - }] + typeDescription: AssertionTypeDescription.infinite, + }], ]); // This ensures a certain method matches only the types it's supposed to and none of the other methods' types @@ -594,7 +600,7 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { t.throws(() => { testAssert(fixture); }, { - message: `Expected value which is \`${valueType!}\`, received value of type \`${is(fixture)}\`.` + message: `Expected value which is \`${valueType as string}\`, received value of type \`${is(fixture)}\`.`, }); } @@ -704,9 +710,11 @@ test('is.object', t => { return; } - for (const el of testData.fixtures) { - t.true(is.object(el)); - t.notThrows(() => assert.object(el)); + for (const element of testData.fixtures) { + t.true(is.object(element)); + t.notThrows(() => { + assert.object(element); + }); } }); @@ -735,11 +743,9 @@ test('is.asyncFunction', t => { const fixture = async () => {}; if (is.asyncFunction(fixture)) { - // eslint-disable-next-line promise/prefer-await-to-then t.true(is.function_(fixture().then)); t.notThrows(() => { - // eslint-disable-next-line promise/prefer-await-to-then assert.function_(fixture().then); }); } @@ -862,7 +868,7 @@ test('is.enumCase', t => { }); test('is.directInstanceOf', t => { - const error = new Error(); + const error = new Error('fixture'); const errorSubclass = new ErrorSubclassFixture(); t.true(is.directInstanceOf(error, Error)); @@ -944,24 +950,31 @@ test('is.truthy', t => { t.notThrows(() => { assert.truthy('unicorn'); }); + t.notThrows(() => { assert.truthy('🦄'); }); + t.notThrows(() => { assert.truthy(new Set()); }); + t.notThrows(() => { assert.truthy(Symbol('🦄')); }); + t.notThrows(() => { assert.truthy(true); }); + t.notThrows(() => { assert.truthy(1); }); - // TODO: Disabled until TS supports it for an ESnnnn target. - // t.notThrows(() => assert.truthy(1n)); + t.notThrows(() => { + assert.truthy(1n); + }); + t.notThrows(() => { assert.truthy(BigInt(1)); }); @@ -973,31 +986,38 @@ test('is.falsy', t => { t.true(is.falsy('')); t.true(is.falsy(null)); t.true(is.falsy(undefined)); - t.true(is.falsy(NaN)); - // TODO: Disabled until TS supports it for an ESnnnn target. - // t.true(is.falsy(0n)); + t.true(is.falsy(Number.NaN)); + t.true(is.falsy(0n)); t.true(is.falsy(BigInt(0))); t.notThrows(() => { assert.falsy(false); }); + t.notThrows(() => { assert.falsy(0); }); + t.notThrows(() => { assert.falsy(''); }); + t.notThrows(() => { assert.falsy(null); }); + t.notThrows(() => { assert.falsy(undefined); }); + t.notThrows(() => { - assert.falsy(NaN); + assert.falsy(Number.NaN); }); - // TODO: Disabled until TS supports it for an ESnnnn target. - // t.notThrows(() => assert.falsy(0n)); + + t.notThrows(() => { + assert.falsy(0n); + }); + t.notThrows(() => { assert.falsy(BigInt(0)); }); @@ -1017,11 +1037,11 @@ test('is.primitive', t => { null, '🦄', 6, - Infinity, - -Infinity, + Number.POSITIVE_INFINITY, + Number.NEGATIVE_INFINITY, true, false, - Symbol('🦄') + Symbol('🦄'), // Disabled until TS supports it for an ESnnnn target. // 6n ]; @@ -1065,8 +1085,8 @@ test('is.iterable', t => { t.false(is.iterable(null)); t.false(is.iterable(undefined)); t.false(is.iterable(0)); - t.false(is.iterable(NaN)); - t.false(is.iterable(Infinity)); + t.false(is.iterable(Number.NaN)); + t.false(is.iterable(Number.POSITIVE_INFINITY)); t.false(is.iterable({})); t.notThrows(() => { @@ -1088,10 +1108,10 @@ test('is.iterable', t => { assert.iterable(0); }); t.throws(() => { - assert.iterable(NaN); + assert.iterable(Number.NaN); }); t.throws(() => { - assert.iterable(Infinity); + assert.iterable(Number.POSITIVE_INFINITY); }); t.throws(() => { assert.iterable({}); @@ -1100,19 +1120,19 @@ test('is.iterable', t => { test('is.asyncIterable', t => { t.true(is.asyncIterable({ - [Symbol.asyncIterator]: () => {} + [Symbol.asyncIterator]() {}, })); t.false(is.asyncIterable(null)); t.false(is.asyncIterable(undefined)); t.false(is.asyncIterable(0)); - t.false(is.asyncIterable(NaN)); - t.false(is.asyncIterable(Infinity)); + t.false(is.asyncIterable(Number.NaN)); + t.false(is.asyncIterable(Number.POSITIVE_INFINITY)); t.false(is.asyncIterable({})); t.notThrows(() => { assert.asyncIterable({ - [Symbol.asyncIterator]: () => { } + [Symbol.asyncIterator]() {}, }); }); @@ -1126,10 +1146,10 @@ test('is.asyncIterable', t => { assert.asyncIterable(0); }); t.throws(() => { - assert.asyncIterable(NaN); + assert.asyncIterable(Number.NaN); }); t.throws(() => { - assert.asyncIterable(Infinity); + assert.asyncIterable(Number.POSITIVE_INFINITY); }); t.throws(() => { assert.asyncIterable({}); @@ -1141,7 +1161,7 @@ test('is.class', t => { const classDeclarations = [ Foo, - class Bar extends Foo {} + class Bar extends Foo {}, ]; for (const classDeclaration of classDeclarations) { @@ -1164,7 +1184,7 @@ test('is.typedArray', t => { new Float32Array(), new Float64Array(), new BigInt64Array(), - new BigUint64Array() + new BigUint64Array(), ]; for (const item of typedArrays) { @@ -1334,7 +1354,7 @@ test('is.domElement', t => { 'span', 'img', 'canvas', - 'script' + 'script', ]; for (const tagName of tagNames) { @@ -1356,33 +1376,33 @@ test('is.infinite', t => { }); test('is.evenInteger', t => { - for (const el of [-6, 2, 4]) { - t.true(is.evenInteger(el)); + for (const element of [-6, 2, 4]) { + t.true(is.evenInteger(element)); t.notThrows(() => { - assert.evenInteger(el); + assert.evenInteger(element); }); } - for (const el of [-3, 1, 5]) { - t.false(is.evenInteger(el)); + for (const element of [-3, 1, 5]) { + t.false(is.evenInteger(element)); t.throws(() => { - assert.evenInteger(el); + assert.evenInteger(element); }); } }); test('is.oddInteger', t => { - for (const el of [-5, 7, 13]) { - t.true(is.oddInteger(el)); + for (const element of [-5, 7, 13]) { + t.true(is.oddInteger(element)); t.notThrows(() => { - assert.oddInteger(el); + assert.oddInteger(element); }); } - for (const el of [-8, 8, 10]) { - t.false(is.oddInteger(el)); + for (const element of [-8, 8, 10]) { + t.false(is.oddInteger(element)); t.throws(() => { - assert.oddInteger(el); + assert.oddInteger(element); }); } }); @@ -1452,7 +1472,7 @@ test('is.nonEmptyStringAndNotWhitespace', t => { t.false(is.nonEmptyStringAndNotWhitespace(' ')); t.true(is.nonEmptyStringAndNotWhitespace('🦄')); - for (const value of [null, undefined, 5, NaN, {}, []]) { + for (const value of [null, undefined, 5, Number.NaN, {}, []]) { t.false(is.nonEmptyStringAndNotWhitespace(value)); t.throws(() => { @@ -1509,16 +1529,16 @@ test('is.emptySet', t => { }); test('is.nonEmptySet', t => { - const tempSet = new Set(); - t.false(is.nonEmptySet(tempSet)); + const temporarySet = new Set(); + t.false(is.nonEmptySet(temporarySet)); t.throws(() => { - assert.nonEmptySet(tempSet); + assert.nonEmptySet(temporarySet); }); - tempSet.add(1); - t.true(is.nonEmptySet(tempSet)); + temporarySet.add(1); + t.true(is.nonEmptySet(temporarySet)); t.notThrows(() => { - assert.nonEmptySet(tempSet); + assert.nonEmptySet(temporarySet); }); }); @@ -1527,16 +1547,16 @@ test('is.emptyMap', t => { }); test('is.nonEmptyMap', t => { - const tempMap = new Map(); - t.false(is.nonEmptyMap(tempMap)); + const temporaryMap = new Map(); + t.false(is.nonEmptyMap(temporaryMap)); t.throws(() => { - assert.nonEmptyMap(tempMap); + assert.nonEmptyMap(temporaryMap); }); - tempMap.set('unicorn', '🦄'); - t.true(is.nonEmptyMap(tempMap)); + temporaryMap.set('unicorn', '🦄'); + t.true(is.nonEmptyMap(temporaryMap)); t.notThrows(() => { - assert.nonEmptyMap(tempMap); + assert.nonEmptyMap(temporaryMap); }); }); @@ -1598,21 +1618,21 @@ test('is.any', t => { assert.any(is.string, 1, 2, 3); }, { // Removes duplicates: - message: /received values of types `number`./ + message: /received values of types `number`./, }); t.throws(() => { assert.any(is.string, 1, [4]); }, { // Lists all types: - message: /received values of types `number`, `Array`./ + message: /received values of types `number`, `Array`./, }); t.throws(() => { assert.any([is.string, is.nullOrUndefined], 1); }, { // Handles array as first argument: - message: /received values of types `number`./ + message: /received values of types `number`./, }); }); @@ -1622,7 +1642,7 @@ test('is.all', t => { t.false(is.all(is.string, '🦄', [])); t.false(is.all(is.set, new Map(), {})); - t.true(is.all(is.array, ...[['1'], ['2']])); + t.true(is.all(is.array, ['1'], ['2'])); t.throws(() => { is.all(null as any, true); @@ -1660,14 +1680,14 @@ test('is.all', t => { assert.all(is.string, 1, 2, 3); }, { // Removes duplicates: - message: /received values of types `number`./ + message: /received values of types `number`./, }); t.throws(() => { assert.all(is.string, 1, [4]); }, { // Lists all types: - message: /received values of types `number`, `Array`./ + message: /received values of types `number`, `Array`./, }); }); @@ -1693,14 +1713,14 @@ test('is.formData', t => { }); test('is.urlSearchParams', t => { - const searchParams = new URLSearchParams(); - t.true(is.urlSearchParams(searchParams)); + const searchParameters = new URLSearchParams(); + t.true(is.urlSearchParams(searchParameters)); t.false(is.urlSearchParams({})); t.false(is.urlSearchParams(undefined)); t.false(is.urlSearchParams(null)); t.notThrows(() => { - assert.urlSearchParams(searchParams); + assert.urlSearchParams(searchParameters); }); t.throws(() => { assert.urlSearchParams({}); diff --git a/tsconfig.json b/tsconfig.json index d1f6382..e1c05ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,14 @@ { "extends": "@sindresorhus/tsconfig", "compilerOptions": { - "outDir": "dist", - "target": "es2018", - "lib": [ - "es2018", - "dom" - ] + "outDir": "dist" }, "include": [ "source" - ] + ], + "ts-node": { + "transpileOnly": true, + "files": true, + "experimentalResolver": true + } } diff --git a/tsconfig.xo.json b/tsconfig.xo.json deleted file mode 100644 index b01049f..0000000 --- a/tsconfig.xo.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": [ - "test" - ] -} From 778c5da5b35a9a1402478839badb38cc542bc8fe Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 11 Jun 2022 18:19:55 +0700 Subject: [PATCH 175/254] 5.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34f31f6..66ef6c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "4.6.0", + "version": "5.0.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From ac7c567c2b34ae55243938b0860a6ed1a689d4b1 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 11 Jun 2022 18:27:36 +0700 Subject: [PATCH 176/254] Fix typo --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 66ef6c9..63d3ff2 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "url": "https://sindresorhus.com" }, "type": "module", - "exports": "./index.js", - "types": "./index.d.ts", + "exports": "./dist/index.js", + "types": "./dist/index.d.ts", "engines": { "node": ">=14.16" }, From 06d217f70ce64add95533940be4f3791841d2607 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 11 Jun 2022 18:28:13 +0700 Subject: [PATCH 177/254] 5.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63d3ff2..d4913fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.0.0", + "version": "5.0.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From e503a9ec49bc8ff4fd2cd43ebffde962880c4152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=EF=BC=88=E4=B9=A6=E7=94=9F=EF=BC=89?= Date: Mon, 13 Jun 2022 15:05:26 +0800 Subject: [PATCH 178/254] Add `.weakRef()` (#165) Co-authored-by: Sindre Sorhus --- readme.md | 1 + source/index.ts | 7 ++++++- source/types.ts | 5 +++++ test/test.ts | 12 +++++++++++- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 90ac932..a517379 100644 --- a/readme.md +++ b/readme.md @@ -198,6 +198,7 @@ is.boundFunction(function () {}); ##### .set(value) ##### .weakMap(value) ##### .weakSet(value) +##### .weakRef(value) #### Typed arrays diff --git a/source/index.ts b/source/index.ts index 6457799..4a3df85 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,5 +1,5 @@ import type {Buffer} from 'node:buffer'; -import type {Class, Falsy, TypedArray, ObservableLike, Primitive} from './types.js'; +import type {Class, Falsy, TypedArray, ObservableLike, Primitive, WeakRef} from './types.js'; const typedArrayTypeNames = [ 'Int8Array', @@ -40,6 +40,7 @@ const objectTypeNames = [ 'Set', 'WeakMap', 'WeakSet', + 'WeakRef', 'ArrayBuffer', 'SharedArrayBuffer', 'DataView', @@ -230,6 +231,8 @@ is.weakMap = (value: unknown): val is.weakSet = (value: unknown): value is WeakSet => isObjectOfType>('WeakSet')(value); // eslint-disable-line @typescript-eslint/ban-types +is.weakRef = (value: unknown): value is WeakRef => isObjectOfType>('WeakRef')(value); // eslint-disable-line @typescript-eslint/ban-types + is.int8Array = isObjectOfType('Int8Array'); is.uint8Array = isObjectOfType('Uint8Array'); is.uint8ClampedArray = isObjectOfType('Uint8ClampedArray'); @@ -524,6 +527,7 @@ interface Assert { set: (value: unknown) => asserts value is Set; weakMap: (value: unknown) => asserts value is WeakMap; // eslint-disable-line @typescript-eslint/ban-types weakSet: (value: unknown) => asserts value is WeakSet; // eslint-disable-line @typescript-eslint/ban-types + weakRef: (value: unknown) => asserts value is WeakRef; // eslint-disable-line @typescript-eslint/ban-types int8Array: (value: unknown) => asserts value is Int8Array; uint8Array: (value: unknown) => asserts value is Uint8Array; uint8ClampedArray: (value: unknown) => asserts value is Uint8ClampedArray; @@ -629,6 +633,7 @@ export const assert: Assert = { set: (value: unknown): asserts value is Set => assertType(is.set(value), 'Set', value), weakMap: (value: unknown): asserts value is WeakMap => assertType(is.weakMap(value), 'WeakMap', value), // eslint-disable-line @typescript-eslint/ban-types weakSet: (value: unknown): asserts value is WeakSet => assertType(is.weakSet(value), 'WeakSet', value), // eslint-disable-line @typescript-eslint/ban-types + weakRef: (value: unknown): asserts value is WeakRef => assertType(is.weakRef(value), 'WeakRef', value), // eslint-disable-line @typescript-eslint/ban-types int8Array: (value: unknown): asserts value is Int8Array => assertType(is.int8Array(value), 'Int8Array', value), uint8Array: (value: unknown): asserts value is Uint8Array => assertType(is.uint8Array(value), 'Uint8Array', value), uint8ClampedArray: (value: unknown): asserts value is Uint8ClampedArray => assertType(is.uint8ClampedArray(value), 'Uint8ClampedArray', value), diff --git a/source/types.ts b/source/types.ts index 3940da2..d49b282 100644 --- a/source/types.ts +++ b/source/types.ts @@ -49,3 +49,8 @@ export interface ObservableLike { // eslint-disable-next-line @typescript-eslint/ban-types export type Falsy = false | 0 | 0n | '' | null | undefined; + +export interface WeakRef { // eslint-disable-line @typescript-eslint/ban-types + readonly [Symbol.toStringTag]: 'WeakRef'; + deref(): T | undefined; +} diff --git a/test/test.ts b/test/test.ts index c74f76a..f9334ee 100644 --- a/test/test.ts +++ b/test/test.ts @@ -336,6 +336,12 @@ const types = new Map([ ], typename: 'WeakSet', }], + ['weakRef', { + is: is.weakRef, + assert: assert.weakRef, + fixtures: window.WeakRef ? [new window.WeakRef({})] : [], + typename: 'WeakRef', + }], ['weakMap', { is: is.weakMap, assert: assert.weakMap, @@ -798,6 +804,10 @@ test('is.weakSet', t => { testType(t, 'weakSet'); }); +test('is.weakRef', t => { + testType(t, 'weakRef'); +}); + test('is.int8Array', t => { testType(t, 'int8Array'); }); @@ -1223,7 +1233,7 @@ test('is.arrayLike', t => { t.false(is.arrayLike(new Map())); (function () { - t.notThrows(() => { + t.notThrows(function () { assert.arrayLike(arguments); // eslint-disable-line prefer-rest-params }); })(); From dc99f7cd4a8ad309d4c945b21816132dbc09ef92 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 13 Jun 2022 14:07:07 +0700 Subject: [PATCH 179/254] 5.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d4913fa..3f68a81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.0.1", + "version": "5.1.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 45cae31f4d7311d8ae1250558e710594acff7d01 Mon Sep 17 00:00:00 2001 From: ehmicky Date: Tue, 21 Jun 2022 14:05:40 +0200 Subject: [PATCH 180/254] Improve `plainObject()` (#169) --- source/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/index.ts b/source/index.ts index 4a3df85..3e68b19 100644 --- a/source/index.ts +++ b/source/index.ts @@ -286,14 +286,14 @@ is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value is.plainObject = (value: unknown): value is Record => { // From: https://github.com/sindresorhus/is-plain-obj/blob/main/index.js - if (toString.call(value) !== '[object Object]') { + if (typeof value !== 'object' || value === null) { return false; } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const prototype = Object.getPrototypeOf(value); - return prototype === null || prototype === Object.getPrototypeOf({}); + return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); }; is.typedArray = (value: unknown): value is TypedArray => isTypedArrayName(getObjectType(value)); From d3ff1fdfce3f40aa06aabc2a62b66ff8ccf138ca Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 21 Jun 2022 14:07:38 +0200 Subject: [PATCH 181/254] 5.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f68a81..d63abd7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.1.0", + "version": "5.2.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 592d9093cd634a568018922d6f21c40c7cac939e Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 21 Jul 2022 16:26:15 +0200 Subject: [PATCH 182/254] Make `nonEmptyArray()` more strongly-typed Fixes #171 --- source/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/index.ts b/source/index.ts index 3e68b19..e8e6506 100644 --- a/source/index.ts +++ b/source/index.ts @@ -368,7 +368,7 @@ is.oddInteger = isAbsoluteMod2(1); is.emptyArray = (value: unknown): value is never[] => is.array(value) && value.length === 0; -is.nonEmptyArray = (value: unknown): value is unknown[] => is.array(value) && value.length > 0; +is.nonEmptyArray = (value: unknown): value is [unknown, ...unknown[]] => is.array(value) && value.length > 0; is.emptyString = (value: unknown): value is '' => is.string(value) && value.length === 0; @@ -559,7 +559,7 @@ interface Assert { nodeStream: (value: unknown) => asserts value is NodeStream; infinite: (value: unknown) => asserts value is number; emptyArray: (value: unknown) => asserts value is never[]; - nonEmptyArray: (value: unknown) => asserts value is unknown[]; + nonEmptyArray: (value: unknown) => asserts value is [unknown, ...unknown[]]; emptyString: (value: unknown) => asserts value is ''; emptyStringOrWhitespace: (value: unknown) => asserts value is string; nonEmptyString: (value: unknown) => asserts value is string; @@ -665,7 +665,7 @@ export const assert: Assert = { nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), AssertionTypeDescription.nodeStream, value), infinite: (value: unknown): asserts value is number => assertType(is.infinite(value), AssertionTypeDescription.infinite, value), emptyArray: (value: unknown): asserts value is never[] => assertType(is.emptyArray(value), AssertionTypeDescription.emptyArray, value), - nonEmptyArray: (value: unknown): asserts value is unknown[] => assertType(is.nonEmptyArray(value), AssertionTypeDescription.nonEmptyArray, value), + nonEmptyArray: (value: unknown): asserts value is [unknown, ...unknown[]] => assertType(is.nonEmptyArray(value), AssertionTypeDescription.nonEmptyArray, value), emptyString: (value: unknown): asserts value is '' => assertType(is.emptyString(value), AssertionTypeDescription.emptyString, value), emptyStringOrWhitespace: (value: unknown): asserts value is string => assertType(is.emptyStringOrWhitespace(value), AssertionTypeDescription.emptyStringOrWhitespace, value), nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), AssertionTypeDescription.nonEmptyString, value), From 33cd8155031a72ac1848b5629f26ca3d894768e9 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 21 Jul 2022 16:27:57 +0200 Subject: [PATCH 183/254] Meta tweaks --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index d63abd7..19e48e7 100644 --- a/package.json +++ b/package.json @@ -50,17 +50,17 @@ ], "devDependencies": { "@sindresorhus/tsconfig": "^3.0.1", - "@types/jsdom": "^16.2.14", - "@types/node": "^17.0.42", + "@types/jsdom": "^16.2.15", + "@types/node": "^18.0.6", "@types/zen-observable": "^0.8.3", - "ava": "^4.3.0", - "del-cli": "^4.0.1", - "jsdom": "^19.0.0", - "rxjs": "^7.5.5", + "ava": "^4.3.1", + "del-cli": "^5.0.0", + "jsdom": "^20.0.0", + "rxjs": "^7.5.6", "tempy": "^3.0.0", - "ts-node": "^10.8.1", - "typescript": "~4.7.3", - "xo": "^0.50.0", + "ts-node": "^10.9.1", + "typescript": "~4.7.4", + "xo": "^0.51.0", "zen-observable": "^0.8.15" }, "sideEffects": false, From 911f44dc36b1fecae135644fc356b82ff834cef6 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 21 Jul 2022 16:30:26 +0200 Subject: [PATCH 184/254] 5.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19e48e7..3c029c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.2.0", + "version": "5.3.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From f3693674f691f08d2b01c73de26992a1f9c7f1ff Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 17 Oct 2022 17:51:49 +0700 Subject: [PATCH 185/254] Add failing test for #174 --- test/test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/test.ts b/test/test.ts index f9334ee..4343fcf 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1435,6 +1435,17 @@ test('is.nonEmptyArray', t => { t.throws(() => { assert.nonEmptyArray(new Array()); // eslint-disable-line @typescript-eslint/no-array-constructor }); + + // https://github.com/sindresorhus/is/issues/174 + // { + // const strings = ['foo', 'bar'] + // const function_ = (value: string) => value; + + // if (is.nonEmptyArray(strings)) { + // const value = strings[0] + // function_(value); + // } + // } }); test('is.emptyString', t => { From e559b37b72e89c8bc1e7bfff6e313d17bc9f775d Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 17 Oct 2022 18:02:01 +0700 Subject: [PATCH 186/254] Update dev dependencies --- package.json | 29 ++++++++++++----------------- source/index.ts | 14 +++++++------- source/types.ts | 16 ++++++++++++---- test/test.ts | 15 +++++++++++---- 4 files changed, 42 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 3c029c5..0739ed8 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,16 @@ "url": "https://sindresorhus.com" }, "type": "module", - "exports": "./dist/index.js", - "types": "./dist/index.d.ts", + "exports": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, "engines": { "node": ">=14.16" }, "scripts": { "build": "del dist && tsc", - "test": "xo && ava", + "test": "tsc --noEmit && xo && ava", "prepare": "npm run build" }, "files": [ @@ -50,17 +52,17 @@ ], "devDependencies": { "@sindresorhus/tsconfig": "^3.0.1", - "@types/jsdom": "^16.2.15", - "@types/node": "^18.0.6", + "@types/jsdom": "^20.0.0", + "@types/node": "^18.11.0", "@types/zen-observable": "^0.8.3", - "ava": "^4.3.1", + "ava": "^4.3.3", "del-cli": "^5.0.0", - "jsdom": "^20.0.0", - "rxjs": "^7.5.6", + "jsdom": "^20.0.1", + "rxjs": "^7.5.7", "tempy": "^3.0.0", "ts-node": "^10.9.1", - "typescript": "~4.7.4", - "xo": "^0.51.0", + "typescript": "~4.8.4", + "xo": "^0.52.4", "zen-observable": "^0.8.15" }, "sideEffects": false, @@ -71,12 +73,5 @@ "nodeArguments": [ "--loader=ts-node/esm" ] - }, - "xo": { - "rules": { - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/triple-slash-reference": "off" - } } } diff --git a/source/index.ts b/source/index.ts index e8e6506..1ca92ab 100644 --- a/source/index.ts +++ b/source/index.ts @@ -251,7 +251,7 @@ is.sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); is.dataView = isObjectOfType('DataView'); -is.enumCase = (value: unknown, targetEnum: T) => Object.values(targetEnum).includes(value as string); +is.enumCase = (value: unknown, targetEnum: T): boolean => Object.values(targetEnum as any).includes(value as string); is.directInstanceOf = (instance: unknown, class_: Class): instance is T => Object.getPrototypeOf(instance) === class_.prototype; @@ -298,10 +298,10 @@ is.plainObject = (value: unknown): value is Record isTypedArrayName(getObjectType(value)); -export interface ArrayLike { +export type ArrayLike = { readonly [index: number]: T; readonly length: number; -} +}; const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0; is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); @@ -354,9 +354,9 @@ is.observable = (value: unknown): value is ObservableLike => { return false; }; -export interface NodeStream extends NodeJS.EventEmitter { +export type NodeStream = { pipe(destination: T, options?: {end?: boolean}): T; -} +} & NodeJS.EventEmitter; is.nodeStream = (value: unknown): value is NodeStream => is.object(value) && is.function_((value as NodeStream).pipe) && !is.observable(value); @@ -490,7 +490,7 @@ export const enum AssertionTypeDescription { } // Type assertions have to be declared with an explicit type. -interface Assert { +type Assert = { // Unknowns. undefined: (value: unknown) => asserts value is undefined; string: (value: unknown) => asserts value is string; @@ -585,7 +585,7 @@ interface Assert { // Variadic functions. any: (predicate: Predicate | Predicate[], ...values: unknown[]) => void | never; all: (predicate: Predicate, ...values: unknown[]) => void | never; -} +}; /* eslint-disable @typescript-eslint/no-confusing-void-expression */ export const assert: Assert = { diff --git a/source/types.ts b/source/types.ts index d49b282..60411b2 100644 --- a/source/types.ts +++ b/source/types.ts @@ -15,6 +15,13 @@ export type Primitive = /** Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). */ +/// type Constructor = new(...arguments_: Arguments) => T; + +/** +Matches a [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). +*/ +// TODO: Use the below in the next major version. +// export type Class = Constructor & {prototype: T}; export type Class = new (...arguments_: Arguments) => T; /** @@ -34,6 +41,7 @@ export type TypedArray = | BigUint64Array; declare global { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- This must be an `interface` so it can be merged. interface SymbolConstructor { readonly observable: symbol; } @@ -42,15 +50,15 @@ declare global { /** Matches a value that is like an [Observable](https://github.com/tc39/proposal-observable). */ -export interface ObservableLike { +export type ObservableLike = { subscribe(observer: (value: unknown) => void): void; [Symbol.observable](): ObservableLike; -} +}; // eslint-disable-next-line @typescript-eslint/ban-types export type Falsy = false | 0 | 0n | '' | null | undefined; -export interface WeakRef { // eslint-disable-line @typescript-eslint/ban-types +export type WeakRef = { // eslint-disable-line @typescript-eslint/ban-types readonly [Symbol.toStringTag]: 'WeakRef'; deref(): T | undefined; -} +}; diff --git a/test/test.ts b/test/test.ts index 4343fcf..5f57ab9 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,14 +1,21 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ import {Buffer} from 'node:buffer'; import fs from 'node:fs'; import net from 'node:net'; import Stream from 'node:stream'; import {inspect} from 'node:util'; -import test, {ExecutionContext} from 'ava'; +import test, {type ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; import {temporaryFile} from 'tempy'; import ZenObservable from 'zen-observable'; -import is, {assert, AssertionTypeDescription, Primitive, TypedArray, TypeName} from '../source/index.js'; +import is, { + assert, + AssertionTypeDescription, + type Primitive, + type TypedArray, + type TypeName, +} from '../source/index.js'; class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} @@ -17,13 +24,13 @@ const {window} = new JSDOM(); const {document} = window; const createDomElement = (element: string) => document.createElement(element); -interface Test { +type Test = { assert: (...args: any[]) => void | never; fixtures: unknown[]; typename?: TypeName; typeDescription?: AssertionTypeDescription | TypeName; is(value: unknown): boolean; -} +}; const invertAssertThrow = (description: string, fn: () => void | never, value: unknown): void | never => { const expectedAssertErrorMessage = `Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`; From 888e145c5a83308aca1afdff7b3443d680ff5b32 Mon Sep 17 00:00:00 2001 From: Xananax Date: Tue, 30 May 2023 13:43:51 +0200 Subject: [PATCH 187/254] Improve type guard/assertion of `numericString()` (#178) --- source/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/index.ts b/source/index.ts index 1ca92ab..65b579b 100644 --- a/source/index.ts +++ b/source/index.ts @@ -167,7 +167,7 @@ is.boolean = (value: unknown): value is boolean => value === true || value === f is.symbol = isOfType('symbol'); -is.numericString = (value: unknown): value is string => +is.numericString = (value: unknown): value is `${number}` => is.string(value) && !is.emptyStringOrWhitespace(value) && !Number.isNaN(Number(value)); is.array = (value: unknown, assertion?: (value: T) => value is T): value is T[] => { @@ -502,7 +502,7 @@ type Assert = { class_: (value: unknown) => asserts value is Class; boolean: (value: unknown) => asserts value is boolean; symbol: (value: unknown) => asserts value is symbol; - numericString: (value: unknown) => asserts value is string; + numericString: (value: unknown) => asserts value is `${number}`; array: (value: unknown, assertion?: (element: unknown) => asserts element is T) => asserts value is T[]; buffer: (value: unknown) => asserts value is Buffer; blob: (value: unknown) => asserts value is Blob; @@ -600,7 +600,7 @@ export const assert: Assert = { class_: (value: unknown): asserts value is Class => assertType(is.class_(value), AssertionTypeDescription.class_, value), boolean: (value: unknown): asserts value is boolean => assertType(is.boolean(value), 'boolean', value), symbol: (value: unknown): asserts value is symbol => assertType(is.symbol(value), 'symbol', value), - numericString: (value: unknown): asserts value is string => assertType(is.numericString(value), AssertionTypeDescription.numericString, value), + numericString: (value: unknown): asserts value is `${number}` => assertType(is.numericString(value), AssertionTypeDescription.numericString, value), array: (value: unknown, assertion?: (element: unknown) => asserts element is T): asserts value is T[] => { // eslint-disable-line object-shorthand const assert: (condition: boolean, description: string, value: unknown) => asserts condition = assertType; assert(is.array(value), 'Array', value); From 65404fbd8e2baf9e4964639526bad6078b0b5fe9 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 30 May 2023 21:13:40 +0700 Subject: [PATCH 188/254] Meta tweaks --- package.json | 14 +++++++------- source/index.ts | 30 +++++++++++++++++++++++------- test/test.ts | 6 +++++- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 0739ed8..7675ef8 100644 --- a/package.json +++ b/package.json @@ -52,18 +52,18 @@ ], "devDependencies": { "@sindresorhus/tsconfig": "^3.0.1", - "@types/jsdom": "^20.0.0", - "@types/node": "^18.11.0", + "@types/jsdom": "^21.1.1", + "@types/node": "^20.2.5", "@types/zen-observable": "^0.8.3", - "ava": "^4.3.3", + "ava": "^5.3.0", "del-cli": "^5.0.0", "jsdom": "^20.0.1", - "rxjs": "^7.5.7", + "rxjs": "^7.8.1", "tempy": "^3.0.0", "ts-node": "^10.9.1", - "typescript": "~4.8.4", - "xo": "^0.52.4", - "zen-observable": "^0.8.15" + "typescript": "^5.0.4", + "xo": "^0.54.2", + "zen-observable": "^0.10.0" }, "sideEffects": false, "ava": { diff --git a/source/index.ts b/source/index.ts index 65b579b..07528b2 100644 --- a/source/index.ts +++ b/source/index.ts @@ -105,20 +105,34 @@ function is(value: unknown): TypeName { } switch (typeof value) { - case 'undefined': + case 'undefined': { return 'undefined'; - case 'string': + } + + case 'string': { return 'string'; - case 'number': + } + + case 'number': { return Number.isNaN(value) ? 'NaN' : 'number'; - case 'boolean': + } + + case 'boolean': { return 'boolean'; - case 'function': + } + + case 'function': { return 'Function'; - case 'bigint': + } + + case 'bigint': { return 'bigint'; - case 'symbol': + } + + case 'symbol': { return 'symbol'; + } + default: } @@ -179,6 +193,7 @@ is.array = (value: unknown, assertion?: (value: T) => value is T): return true; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return value.every(element => assertion(element)); }; @@ -251,6 +266,7 @@ is.sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); is.dataView = isObjectOfType('DataView'); +// eslint-disable-next-line @typescript-eslint/no-unsafe-argument is.enumCase = (value: unknown, targetEnum: T): boolean => Object.values(targetEnum as any).includes(value as string); is.directInstanceOf = (instance: unknown, class_: Class): instance is T => Object.getPrototypeOf(instance) === class_.prototype; diff --git a/test/test.ts b/test/test.ts index 5f57ab9..d686439 100644 --- a/test/test.ts +++ b/test/test.ts @@ -603,7 +603,7 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { for (const fixture of fixtures) { assertIs(testIs(fixture), `Value: ${inspect(fixture)}`); - const valueType = typeDescription ? typeDescription : typename; + const valueType = typeDescription ?? typename; if (isTypeUnderTest) { t.notThrows(() => { @@ -1611,6 +1611,7 @@ test('is.any', t => { t.false(is.any([is.boolean, is.number], 'unicorns', [], new Map())); t.throws(() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument is.any(null as any, true); }); @@ -1635,6 +1636,7 @@ test('is.any', t => { }); t.throws(() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument assert.any(null as any, true); }); @@ -1673,6 +1675,7 @@ test('is.all', t => { t.true(is.all(is.array, ['1'], ['2'])); t.throws(() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument is.all(null as any, true); }); @@ -1697,6 +1700,7 @@ test('is.all', t => { }); t.throws(() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument assert.all(null as any, true); }); From 61a437eba39b13b5fb546638565b0694c6362233 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 30 May 2023 21:16:59 +0700 Subject: [PATCH 189/254] 5.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7675ef8..b840995 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.3.0", + "version": "5.4.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From d1574d358da59645a7311aa05d253bdb9940737e Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Sun, 4 Jun 2023 16:01:35 +0800 Subject: [PATCH 190/254] Revert `exports` change in package.json (#180) --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b840995..0ca926a 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,8 @@ "url": "https://sindresorhus.com" }, "type": "module", - "exports": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, + "exports": "./dist/index.js", + "types": "./dist/index.d.ts", "engines": { "node": ">=14.16" }, From 7d468191f427cebe41bd3284c95338c13942b553 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 4 Jun 2023 11:04:07 +0300 Subject: [PATCH 191/254] 5.4.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ca926a..4d9d594 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.4.0", + "version": "5.4.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 9265e9072d512ab1188c1d6347bc46c287775a93 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 10 Jun 2023 11:43:41 +0300 Subject: [PATCH 192/254] Add test for #183 --- test/test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test.ts b/test/test.ts index d686439..11f7f9c 100644 --- a/test/test.ts +++ b/test/test.ts @@ -24,6 +24,8 @@ const {window} = new JSDOM(); const {document} = window; const createDomElement = (element: string) => document.createElement(element); +const structuredClone = globalThis.structuredClone ?? (x => x); + type Test = { assert: (...args: any[]) => void | never; fixtures: unknown[]; @@ -487,6 +489,9 @@ const types = new Map([ {x: 1}, Object.create(null), new Object(), // eslint-disable-line no-new-object + structuredClone({x: 1}), + structuredClone(Object.create(null)), + structuredClone(new Object()), // eslint-disable-line no-new-object ], typename: 'Object', typeDescription: AssertionTypeDescription.plainObject, From 3177d11801f75f23653625276574409671b1eaaa Mon Sep 17 00:00:00 2001 From: Eugene <63408919+eugene-mohc@users.noreply.github.com> Date: Fri, 14 Jul 2023 23:24:56 +0100 Subject: [PATCH 193/254] Fix type narrowing for `nonEmptyArray` (#185) --- source/index.ts | 6 ++--- test/test.ts | 67 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/source/index.ts b/source/index.ts index 07528b2..7312deb 100644 --- a/source/index.ts +++ b/source/index.ts @@ -384,7 +384,7 @@ is.oddInteger = isAbsoluteMod2(1); is.emptyArray = (value: unknown): value is never[] => is.array(value) && value.length === 0; -is.nonEmptyArray = (value: unknown): value is [unknown, ...unknown[]] => is.array(value) && value.length > 0; +is.nonEmptyArray = (value: T | Item[]): value is (T extends Item[] ? [Item, ...Item[]] : T) => is.array(value) && value.length > 0; is.emptyString = (value: unknown): value is '' => is.string(value) && value.length === 0; @@ -575,7 +575,7 @@ type Assert = { nodeStream: (value: unknown) => asserts value is NodeStream; infinite: (value: unknown) => asserts value is number; emptyArray: (value: unknown) => asserts value is never[]; - nonEmptyArray: (value: unknown) => asserts value is [unknown, ...unknown[]]; + nonEmptyArray: (value: T | Item[]) => asserts value is (T extends Item[] ? [Item, ...Item[]] : T); emptyString: (value: unknown) => asserts value is ''; emptyStringOrWhitespace: (value: unknown) => asserts value is string; nonEmptyString: (value: unknown) => asserts value is string; @@ -681,7 +681,7 @@ export const assert: Assert = { nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), AssertionTypeDescription.nodeStream, value), infinite: (value: unknown): asserts value is number => assertType(is.infinite(value), AssertionTypeDescription.infinite, value), emptyArray: (value: unknown): asserts value is never[] => assertType(is.emptyArray(value), AssertionTypeDescription.emptyArray, value), - nonEmptyArray: (value: unknown): asserts value is [unknown, ...unknown[]] => assertType(is.nonEmptyArray(value), AssertionTypeDescription.nonEmptyArray, value), + nonEmptyArray: (value: T | Item[]): asserts value is (T extends Item[] ? [Item, ...Item[]] : T) => assertType(is.nonEmptyArray(value), AssertionTypeDescription.nonEmptyArray, value), emptyString: (value: unknown): asserts value is '' => assertType(is.emptyString(value), AssertionTypeDescription.emptyString, value), emptyStringOrWhitespace: (value: unknown): asserts value is string => assertType(is.emptyStringOrWhitespace(value), AssertionTypeDescription.emptyStringOrWhitespace, value), nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), AssertionTypeDescription.nonEmptyString, value), diff --git a/test/test.ts b/test/test.ts index 11f7f9c..e405325 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1448,16 +1448,65 @@ test('is.nonEmptyArray', t => { assert.nonEmptyArray(new Array()); // eslint-disable-line @typescript-eslint/no-array-constructor }); - // https://github.com/sindresorhus/is/issues/174 - // { - // const strings = ['foo', 'bar'] - // const function_ = (value: string) => value; + { + const strings = ['🦄', 'unicorn']; + const function_ = (value: string) => value; - // if (is.nonEmptyArray(strings)) { - // const value = strings[0] - // function_(value); - // } - // } + if (is.nonEmptyArray(strings)) { + const value = strings[0]; + function_(value); + } + } + + { + const mixed = ['🦄', 'unicorn', 1, 2]; + const function_ = (value: string | number) => value; + + if (is.nonEmptyArray(mixed)) { + const value = mixed[0]; + function_(value); + } + } + + { + const arrays = [['🦄'], ['unicorn']]; + const function_ = (value: string[]) => value; + + if (is.nonEmptyArray(arrays)) { + const value = arrays[0]; + function_(value); + } + } + + { + const strings = ['🦄', 'unicorn']; + const function_ = (value: string) => value; + + assert.nonEmptyArray(strings); + + const value = strings[0]; + function_(value); + } + + { + const mixed = ['🦄', 'unicorn', 1, 2]; + const function_ = (value: string | number) => value; + + assert.nonEmptyArray(mixed); + + const value = mixed[0]; + function_(value); + } + + { + const arrays = [['🦄'], ['unicorn']]; + const function_ = (value: string[]) => value; + + assert.nonEmptyArray(arrays); + + const value = arrays[0]; + function_(value); + } }); test('is.emptyString', t => { From 1284da085f3cad6e1d02fe0ac1bb835293d581dd Mon Sep 17 00:00:00 2001 From: Eugene <63408919+eugene-mohc@users.noreply.github.com> Date: Fri, 14 Jul 2023 23:26:30 +0100 Subject: [PATCH 194/254] Add `is.positiveNumber` and `is.negativeNumber` (#184) --- readme.md | 8 ++++++ source/index.ts | 11 +++++++ test/test.ts | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/readme.md b/readme.md index a517379..fc451a3 100644 --- a/readme.md +++ b/readme.md @@ -410,6 +410,14 @@ function foo() { foo(); ``` +#### .positiveNumber(value) + +Check if `value` is a number and is more than 0. + +#### .negativeNumber(value) + +Check if `value` is a number and is less than 0. + ##### .inRange(value, range) Check if `value` (number) is in the given `range`. The range is an array of two values, lower bound and upper bound, in no specific order. diff --git a/source/index.ts b/source/index.ts index 7312deb..4612504 100644 --- a/source/index.ts +++ b/source/index.ts @@ -167,6 +167,10 @@ is.string = isOfType('string'); const isNumberType = isOfType('number'); is.number = (value: unknown): value is number => isNumberType(value) && !is.nan(value); +is.positiveNumber = (value: unknown): value is number => is.number(value) && value > 0; + +is.negativeNumber = (value: unknown): value is number => is.number(value) && value < 0; + is.bigint = isOfType('bigint'); // eslint-disable-next-line @typescript-eslint/ban-types @@ -498,6 +502,9 @@ export const enum AssertionTypeDescription { evenInteger = 'even integer', oddInteger = 'odd integer', + positiveNumber = 'positive number', + negativeNumber = 'negative number', + directInstanceOf = 'T', inRange = 'in range', @@ -511,6 +518,8 @@ type Assert = { undefined: (value: unknown) => asserts value is undefined; string: (value: unknown) => asserts value is string; number: (value: unknown) => asserts value is number; + positiveNumber: (value: unknown) => asserts value is number; + negativeNumber: (value: unknown) => asserts value is number; bigint: (value: unknown) => asserts value is bigint; // eslint-disable-next-line @typescript-eslint/ban-types function_: (value: unknown) => asserts value is Function; @@ -609,6 +618,8 @@ export const assert: Assert = { undefined: (value: unknown): asserts value is undefined => assertType(is.undefined(value), 'undefined', value), string: (value: unknown): asserts value is string => assertType(is.string(value), 'string', value), number: (value: unknown): asserts value is number => assertType(is.number(value), 'number', value), + positiveNumber: (value: unknown): asserts value is number => assertType(is.positiveNumber(value), AssertionTypeDescription.positiveNumber, value), + negativeNumber: (value: unknown): asserts value is number => assertType(is.negativeNumber(value), AssertionTypeDescription.negativeNumber, value), bigint: (value: unknown): asserts value is bigint => assertType(is.bigint(value), 'bigint', value), // eslint-disable-next-line @typescript-eslint/ban-types function_: (value: unknown): asserts value is Function => assertType(is.function_(value), 'Function', value), diff --git a/test/test.ts b/test/test.ts index e405325..d94fe39 100644 --- a/test/test.ts +++ b/test/test.ts @@ -645,6 +645,82 @@ test('is.number', t => { testType(t, 'number', ['integer', 'safeInteger', 'infinite']); }); +test('is.positiveNumber', t => { + t.true(is.positiveNumber(6)); + t.true(is.positiveNumber(1.4)); + t.true(is.positiveNumber(Number.POSITIVE_INFINITY)); + + t.notThrows(() => { + assert.positiveNumber(6); + }); + t.notThrows(() => { + assert.positiveNumber(1.4); + }); + t.notThrows(() => { + assert.positiveNumber(Number.POSITIVE_INFINITY); + }); + + t.false(is.positiveNumber(0)); + t.false(is.positiveNumber(-0)); + t.false(is.positiveNumber(-6)); + t.false(is.positiveNumber(-1.4)); + t.false(is.positiveNumber(Number.NEGATIVE_INFINITY)); + + t.throws(() => { + assert.positiveNumber(0); + }); + t.throws(() => { + assert.positiveNumber(-0); + }); + t.throws(() => { + assert.positiveNumber(-6); + }); + t.throws(() => { + assert.positiveNumber(-1.4); + }); + t.throws(() => { + assert.positiveNumber(Number.NEGATIVE_INFINITY); + }); +}); + +test('is.negativeNumber', t => { + t.true(is.negativeNumber(-6)); + t.true(is.negativeNumber(-1.4)); + t.true(is.negativeNumber(Number.NEGATIVE_INFINITY)); + + t.notThrows(() => { + assert.negativeNumber(-6); + }); + t.notThrows(() => { + assert.negativeNumber(-1.4); + }); + t.notThrows(() => { + assert.negativeNumber(Number.NEGATIVE_INFINITY); + }); + + t.false(is.negativeNumber(0)); + t.false(is.negativeNumber(-0)); + t.false(is.negativeNumber(6)); + t.false(is.negativeNumber(1.4)); + t.false(is.negativeNumber(Number.POSITIVE_INFINITY)); + + t.throws(() => { + assert.negativeNumber(0); + }); + t.throws(() => { + assert.negativeNumber(-0); + }); + t.throws(() => { + assert.negativeNumber(6); + }); + t.throws(() => { + assert.negativeNumber(1.4); + }); + t.throws(() => { + assert.negativeNumber(Number.POSITIVE_INFINITY); + }); +}); + test('is.bigint', t => { testType(t, 'bigint'); }); From 278e0e9696b9862381a6e3cae34f756c67ef735b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 15 Jul 2023 00:28:14 +0200 Subject: [PATCH 195/254] 5.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4d9d594..dca521a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.4.1", + "version": "5.5.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 9d26c020eeff50d4b2a14d6b86ec51204a6f8157 Mon Sep 17 00:00:00 2001 From: hyperbola Date: Sun, 16 Jul 2023 22:19:31 +0800 Subject: [PATCH 196/254] Fix type guards for `assert.{truthy,falsy,nan}` (#187) --- source/index.ts | 14 +++---- test/test.ts | 101 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 7 deletions(-) diff --git a/source/index.ts b/source/index.ts index 4612504..5862ad9 100644 --- a/source/index.ts +++ b/source/index.ts @@ -294,7 +294,7 @@ is.urlString = (value: unknown): value is string => { is.truthy = (value: T | Falsy): value is T => Boolean(value); // eslint-disable-line unicorn/prefer-native-coercion-functions // Example: `is.falsy = (value: unknown): value is (not true | 0 | '' | undefined | null) => Boolean(value);` -is.falsy = (value: T | Falsy): value is Falsy => !value; +is.falsy = (value: unknown): value is Falsy => !value; is.nan = (value: unknown) => Number.isNaN(value as number); @@ -570,9 +570,9 @@ type Assert = { enumCase: (value: unknown, targetEnum: T) => asserts value is T[keyof T]; urlInstance: (value: unknown) => asserts value is URL; urlString: (value: unknown) => asserts value is string; - truthy: (value: unknown) => asserts value is unknown; - falsy: (value: unknown) => asserts value is unknown; - nan: (value: unknown) => asserts value is unknown; + truthy: (value: T | Falsy) => asserts value is T; + falsy: (value: unknown) => asserts value is Falsy; + nan: (value: unknown) => asserts value is number; primitive: (value: unknown) => asserts value is Primitive; integer: (value: unknown) => asserts value is number; safeInteger: (value: unknown) => asserts value is number; @@ -678,9 +678,9 @@ export const assert: Assert = { enumCase: (value: unknown, targetEnum: T): asserts value is T[keyof T] => assertType(is.enumCase(value, targetEnum), 'EnumCase', value), urlInstance: (value: unknown): asserts value is URL => assertType(is.urlInstance(value), 'URL', value), urlString: (value: unknown): asserts value is string => assertType(is.urlString(value), AssertionTypeDescription.urlString, value), - truthy: (value: unknown): asserts value is unknown => assertType(is.truthy(value), AssertionTypeDescription.truthy, value), - falsy: (value: unknown): asserts value is unknown => assertType(is.falsy(value), AssertionTypeDescription.falsy, value), - nan: (value: unknown): asserts value is unknown => assertType(is.nan(value), AssertionTypeDescription.nan, value), + truthy: (value: T | Falsy): asserts value is T => assertType(is.truthy(value), AssertionTypeDescription.truthy, value), + falsy: (value: unknown): asserts value is Falsy => assertType(is.falsy(value), AssertionTypeDescription.falsy, value), + nan: (value: unknown): asserts value is number => assertType(is.nan(value), AssertionTypeDescription.nan, value), primitive: (value: unknown): asserts value is Primitive => assertType(is.primitive(value), AssertionTypeDescription.primitive, value), integer: (value: unknown): asserts value is number => assertType(is.integer(value), AssertionTypeDescription.integer, value), safeInteger: (value: unknown): asserts value is number => assertType(is.safeInteger(value), AssertionTypeDescription.safeInteger, value), diff --git a/test/test.ts b/test/test.ts index d94fe39..e4734c1 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1076,6 +1076,54 @@ test('is.truthy', t => { t.notThrows(() => { assert.truthy(BigInt(1)); }); + + // Checks that `assert.truthy` narrow downs boolean type to `true`. + { + const booleans = [true, false]; + const function_ = (value: true) => value; + assert.truthy(booleans[0]); + function_(booleans[0]); + } + + // Checks that `assert.truthy` excludes zero value from number type. + { + const bits: Array<0 | 1> = [1, 0, -0]; + const function_ = (value: 1) => value; + assert.truthy(bits[0]); + function_(bits[0]); + } + + // Checks that `assert.truthy` excludes zero value from bigint type. + { + const bits: Array<0n | 1n> = [1n, 0n, -0n]; + const function_ = (value: 1n) => value; + assert.truthy(bits[0]); + function_(bits[0]); + } + + // Checks that `assert.truthy` excludes empty string from string type. + { + const strings: Array<'nonEmpty' | ''> = ['nonEmpty', '']; + const function_ = (value: 'nonEmpty') => value; + assert.truthy(strings[0]); + function_(strings[0]); + } + + // Checks that `assert.truthy` excludes undefined from mixed type. + { + const maybeUndefineds = ['🦄', undefined]; + const function_ = (value: string) => value; + assert.truthy(maybeUndefineds[0]); + function_(maybeUndefineds[0]); + } + + // Checks that `assert.truthy` excludes null from mixed type. + { + const maybeNulls = ['🦄', null]; + const function_ = (value: string) => value; + assert.truthy(maybeNulls[0]); + function_(maybeNulls[0]); + } }); test('is.falsy', t => { @@ -1119,6 +1167,59 @@ test('is.falsy', t => { t.notThrows(() => { assert.falsy(BigInt(0)); }); + + // Checks that `assert.falsy` narrow downs boolean type to `false`. + { + const booleans = [false, true]; + const function_ = (value: false) => value; + assert.falsy(booleans[0]); + function_(booleans[0]); + } + + // Checks that `assert.falsy` narrow downs number type to `0`. + { + const bits = [0, -0, 1]; + const function_ = (value: 0) => value; + assert.falsy(bits[0]); + function_(bits[0]); + assert.falsy(bits[1]); + function_(bits[1]); + } + + // Checks that `assert.falsy` narrow downs bigint type to `0n`. + { + const bits = [0n, -0n, 1n]; + const function_ = (value: 0n) => value; + assert.falsy(bits[0]); + function_(bits[0]); + assert.falsy(bits[1]); + function_(bits[1]); + } + + // Checks that `assert.falsy` narrow downs string type to empty string. + { + const strings = ['', 'nonEmpty']; + const function_ = (value: '') => value; + assert.falsy(strings[0]); + function_(strings[0]); + } + + // Checks that `assert.falsy` can narrow down mixed type to undefined. + { + const maybeUndefineds = [undefined, Symbol('🦄')]; + const function_ = (value: undefined) => value; + assert.falsy(maybeUndefineds[0]); + function_(maybeUndefineds[0]); + } + + // Checks that `assert.falsy` can narrow down mixed type to null. + { + const maybeNulls = [null, Symbol('🦄')]; + // eslint-disable-next-line @typescript-eslint/ban-types + const function_ = (value: null) => value; + assert.falsy(maybeNulls[0]); + function_(maybeNulls[0]); + } }); test('is.nan', t => { From ad0c3b1429c3015d8992785bf5db0c988ae3bd70 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 16 Jul 2023 16:20:29 +0200 Subject: [PATCH 197/254] 5.5.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dca521a..f31e1f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.5.0", + "version": "5.5.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 20ad8231c22c875c8afc096a85a936a2906435c4 Mon Sep 17 00:00:00 2001 From: Eugene <63408919+eugene-mohc@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:42:04 +0100 Subject: [PATCH 198/254] Fix `is.nonEmptyArray()` type narrowing with `undefined` (#188) --- source/index.ts | 6 +++--- test/test.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/index.ts b/source/index.ts index 5862ad9..d69b9ce 100644 --- a/source/index.ts +++ b/source/index.ts @@ -388,7 +388,7 @@ is.oddInteger = isAbsoluteMod2(1); is.emptyArray = (value: unknown): value is never[] => is.array(value) && value.length === 0; -is.nonEmptyArray = (value: T | Item[]): value is (T extends Item[] ? [Item, ...Item[]] : T) => is.array(value) && value.length > 0; +is.nonEmptyArray = (value: T | Item[]): value is [Item, ...Item[]] => is.array(value) && value.length > 0; is.emptyString = (value: unknown): value is '' => is.string(value) && value.length === 0; @@ -584,7 +584,7 @@ type Assert = { nodeStream: (value: unknown) => asserts value is NodeStream; infinite: (value: unknown) => asserts value is number; emptyArray: (value: unknown) => asserts value is never[]; - nonEmptyArray: (value: T | Item[]) => asserts value is (T extends Item[] ? [Item, ...Item[]] : T); + nonEmptyArray: (value: T | Item[]) => asserts value is [Item, ...Item[]]; emptyString: (value: unknown) => asserts value is ''; emptyStringOrWhitespace: (value: unknown) => asserts value is string; nonEmptyString: (value: unknown) => asserts value is string; @@ -692,7 +692,7 @@ export const assert: Assert = { nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), AssertionTypeDescription.nodeStream, value), infinite: (value: unknown): asserts value is number => assertType(is.infinite(value), AssertionTypeDescription.infinite, value), emptyArray: (value: unknown): asserts value is never[] => assertType(is.emptyArray(value), AssertionTypeDescription.emptyArray, value), - nonEmptyArray: (value: T | Item[]): asserts value is (T extends Item[] ? [Item, ...Item[]] : T) => assertType(is.nonEmptyArray(value), AssertionTypeDescription.nonEmptyArray, value), + nonEmptyArray: (value: T | Item[]): asserts value is [Item, ...Item[]] => assertType(is.nonEmptyArray(value), AssertionTypeDescription.nonEmptyArray, value), emptyString: (value: unknown): asserts value is '' => assertType(is.emptyString(value), AssertionTypeDescription.emptyString, value), emptyStringOrWhitespace: (value: unknown): asserts value is string => assertType(is.emptyStringOrWhitespace(value), AssertionTypeDescription.emptyStringOrWhitespace, value), nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), AssertionTypeDescription.nonEmptyString, value), diff --git a/test/test.ts b/test/test.ts index e4734c1..a593332 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1626,7 +1626,7 @@ test('is.nonEmptyArray', t => { }); { - const strings = ['🦄', 'unicorn']; + const strings = ['🦄', 'unicorn'] as string[] | undefined; const function_ = (value: string) => value; if (is.nonEmptyArray(strings)) { @@ -1656,7 +1656,7 @@ test('is.nonEmptyArray', t => { } { - const strings = ['🦄', 'unicorn']; + const strings = ['🦄', 'unicorn'] as string[] | undefined; const function_ = (value: string) => value; assert.nonEmptyArray(strings); From 94dc71557750a96b1ffc0c27ec9a065bc18a05de Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 17 Jul 2023 11:42:55 +0200 Subject: [PATCH 199/254] 5.5.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f31e1f7..8e13525 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.5.1", + "version": "5.5.2", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 3868f47783636086b37aae56a5a715b6639e6714 Mon Sep 17 00:00:00 2001 From: Tal Michel Date: Sun, 23 Jul 2023 15:35:23 +0300 Subject: [PATCH 200/254] Add `.tupleLike()` (#189) Co-authored-by: Sindre Sorhus --- package.json | 3 +- readme.md | 20 +++++++++++++ source/index.ts | 21 ++++++++++++++ test/test.ts | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e13525..5a41f33 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,8 @@ "ts-node": "^10.9.1", "typescript": "^5.0.4", "xo": "^0.54.2", - "zen-observable": "^0.10.0" + "zen-observable": "^0.10.0", + "expect-type": "^0.16.0" }, "sideEffects": false, "ava": { diff --git a/readme.md b/readme.md index fc451a3..40726c5 100644 --- a/readme.md +++ b/readme.md @@ -410,6 +410,26 @@ function foo() { foo(); ``` +##### .tupleLike(value, guards) + +A `value` is tuple-like if it matches the provided `guards` array both in `.length` and in types. + +```js +is.tupleLike([1], [is.number]); +//=> true +``` + +```js +function foo() { + const tuple = [1, '2', true]; + if (is.tupleLike(tuple, [is.number, is.string, is.boolean])) { + tuple // [number, string, boolean] + } +} + +foo(); +``` + #### .positiveNumber(value) Check if `value` is a number and is more than 0. diff --git a/source/index.ts b/source/index.ts index d69b9ce..122917b 100644 --- a/source/index.ts +++ b/source/index.ts @@ -326,6 +326,24 @@ export type ArrayLike = { const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0; is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); +type TypeGuard = (value: unknown) => value is T; + +// eslint-disable-next-line @typescript-eslint/ban-types +type ResolveTypesOfTypeGuardsTuple = + TypeGuardsOfT extends [TypeGuard, ...infer TOthers] + ? ResolveTypesOfTypeGuardsTuple + : TypeGuardsOfT extends undefined[] + ? ResultOfT + : never; + +is.tupleLike = >>(value: unknown, guards: [...T]): value is ResolveTypesOfTypeGuardsTuple => { + if (is.array(guards) && is.array(value) && guards.length === value.length) { + return guards.every((guard, index) => guard(value[index])); + } + + return false; +}; + is.inRange = (value: number, range: number | number[]): value is number => { if (is.number(range)) { return value >= Math.min(0, range) && value <= Math.max(range, 0); @@ -482,6 +500,7 @@ export const enum AssertionTypeDescription { safeInteger = 'integer', // eslint-disable-line @typescript-eslint/no-duplicate-enum-values plainObject = 'plain object', arrayLike = 'array-like', + tupleLike = 'tuple-like', typedArray = 'TypedArray', domElement = 'HTMLElement', nodeStream = 'Node.js Stream', @@ -579,6 +598,7 @@ type Assert = { plainObject: (value: unknown) => asserts value is Record; typedArray: (value: unknown) => asserts value is TypedArray; arrayLike: (value: unknown) => asserts value is ArrayLike; + tupleLike: >>(value: unknown, guards: [...T]) => asserts value is ResolveTypesOfTypeGuardsTuple; domElement: (value: unknown) => asserts value is HTMLElement; observable: (value: unknown) => asserts value is ObservableLike; nodeStream: (value: unknown) => asserts value is NodeStream; @@ -687,6 +707,7 @@ export const assert: Assert = { plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value), arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value), + tupleLike: >>(value: unknown, guards: [...T]): asserts value is ResolveTypesOfTypeGuardsTuple => assertType(is.tupleLike(value, guards), AssertionTypeDescription.tupleLike, value), domElement: (value: unknown): asserts value is HTMLElement => assertType(is.domElement(value), AssertionTypeDescription.domElement, value), observable: (value: unknown): asserts value is ObservableLike => assertType(is.observable(value), 'Observable', value), nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), AssertionTypeDescription.nodeStream, value), diff --git a/test/test.ts b/test/test.ts index a593332..7bd1aed 100644 --- a/test/test.ts +++ b/test/test.ts @@ -8,6 +8,7 @@ import test, {type ExecutionContext} from 'ava'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; import {temporaryFile} from 'tempy'; +import {expectTypeOf} from 'expect-type'; import ZenObservable from 'zen-observable'; import is, { assert, @@ -1445,6 +1446,81 @@ test('is.arrayLike', t => { }); }); +test('is.tupleLike', t => { + (function () { + t.false(is.tupleLike(arguments, [])); // eslint-disable-line prefer-rest-params + })(); + + t.true(is.tupleLike([], [])); + t.true(is.tupleLike([1, '2', true, {}, [], undefined, null], [is.number, is.string, is.boolean, is.object, is.array, is.undefined, is.nullOrUndefined])); + t.false(is.tupleLike('unicorn', [is.string])); + + t.false(is.tupleLike({}, [])); + t.false(is.tupleLike(() => {}, [is.function_])); + t.false(is.tupleLike(new Map(), [is.map])); + + (function () { + t.throws(function () { + assert.tupleLike(arguments, []); // eslint-disable-line prefer-rest-params + }); + })(); + + t.notThrows(() => { + assert.tupleLike([], []); + }); + t.throws(() => { + assert.tupleLike('unicorn', [is.string]); + }); + + t.throws(() => { + assert.tupleLike({}, [is.object]); + }); + t.throws(() => { + assert.tupleLike(() => {}, [is.function_]); + }); + t.throws(() => { + assert.tupleLike(new Map(), [is.map]); + }); + + { + const tuple = [[false, 'unicorn'], 'string', true]; + + if (is.tupleLike(tuple, [is.array, is.string, is.boolean])) { + if (is.tupleLike(tuple[0], [is.boolean, is.string])) { // eslint-disable-line unicorn/no-lonely-if + const value = tuple[0][1]; + expectTypeOf(value).toEqualTypeOf(); + } + } + } + + { + const tuple = [{isTest: true}, '1', true, null]; + + if (is.tupleLike(tuple, [is.nonEmptyObject, is.string, is.boolean, is.null_])) { + const value = tuple[0]; + expectTypeOf(value).toEqualTypeOf>(); + } + } + + { + const tuple = [1, '1', true, null, undefined]; + + if (is.tupleLike(tuple, [is.number, is.string, is.boolean, is.undefined, is.null_])) { + const numericValue = tuple[0]; + const stringValue = tuple[1]; + const booleanValue = tuple[2]; + const undefinedValue = tuple[3]; + const nullValue = tuple[4]; + expectTypeOf(numericValue).toEqualTypeOf(); + expectTypeOf(stringValue).toEqualTypeOf(); + expectTypeOf(booleanValue).toEqualTypeOf(); + expectTypeOf(undefinedValue).toEqualTypeOf(); + // eslint-disable-next-line @typescript-eslint/ban-types + expectTypeOf(nullValue).toEqualTypeOf(); + } + } +}); + test('is.inRange', t => { const x = 3; From 44beb083a3d7aee5dc4136128580bacdbaca9974 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 23 Jul 2023 14:40:00 +0200 Subject: [PATCH 201/254] 5.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a41f33..782a527 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.5.2", + "version": "5.6.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From bd5dfda993fc80d82265eeb4308ee42f043af81b Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Sun, 30 Jul 2023 19:32:34 +0800 Subject: [PATCH 202/254] Replace enums with string literals (#190) --- source/index.ts | 177 ++++++++++++++++++++++++------------------------ test/test.ts | 42 ++++++------ 2 files changed, 109 insertions(+), 110 deletions(-) diff --git a/source/index.ts b/source/index.ts index 122917b..fe78aa7 100644 --- a/source/index.ts +++ b/source/index.ts @@ -77,6 +77,52 @@ function isPrimitiveTypeName(name: unknown): name is PrimitiveTypeName { export type TypeName = ObjectTypeName | PrimitiveTypeName; +const assertionTypeDescriptions = [ + 'positive number', + 'negative number', + 'Class', + 'string with a number', + 'null or undefined', + 'Iterable', + 'AsyncIterable', + 'native Promise', + 'EnumCase', + 'string with a URL', + 'truthy', + 'falsy', + 'primitive', + 'integer', + 'plain object', + 'TypedArray', + 'array-like', + 'tuple-like', + 'Node.js Stream', + 'infinite number', + 'empty array', + 'non-empty array', + 'empty string', + 'empty string or whitespace', + 'non-empty string', + 'non-empty string and not whitespace', + 'empty object', + 'non-empty object', + 'empty set', + 'non-empty set', + 'empty map', + 'non-empty map', + 'PropertyKey', + 'even integer', + 'odd integer', + 'T', + 'in range', + 'predicate returns truthy for any value', + 'predicate returns truthy for all values', + ...objectTypeNames, + ...primitiveTypeNames, +] as const; + +export type AssertionTypeDescription = typeof assertionTypeDescriptions[number]; + // eslint-disable-next-line @typescript-eslint/ban-types function isOfType(type: PrimitiveTypeName | 'function') { return (value: unknown): value is T => typeof value === type; @@ -469,7 +515,7 @@ is.any = (predicate: Predicate | Predicate[], ...values: unknown[]): boolean => is.all = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.every, predicate, values); -const assertType = (condition: boolean, description: string, value: unknown, options: {multipleValues?: boolean} = {}): asserts condition => { +const assertType = (condition: boolean, description: AssertionTypeDescription, value: unknown, options: {multipleValues?: boolean} = {}): asserts condition => { if (!condition) { const {multipleValues} = options; const valuesMessage = multipleValues @@ -484,53 +530,6 @@ const assertType = (condition: boolean, description: string, value: unknown, opt } }; -export const enum AssertionTypeDescription { - class_ = 'Class', - numericString = 'string with a number', - nullOrUndefined = 'null or undefined', - iterable = 'Iterable', - asyncIterable = 'AsyncIterable', - nativePromise = 'native Promise', - urlString = 'string with a URL', - truthy = 'truthy', - falsy = 'falsy', - nan = 'NaN', - primitive = 'primitive', - integer = 'integer', - safeInteger = 'integer', // eslint-disable-line @typescript-eslint/no-duplicate-enum-values - plainObject = 'plain object', - arrayLike = 'array-like', - tupleLike = 'tuple-like', - typedArray = 'TypedArray', - domElement = 'HTMLElement', - nodeStream = 'Node.js Stream', - infinite = 'infinite number', - emptyArray = 'empty array', - nonEmptyArray = 'non-empty array', - emptyString = 'empty string', - emptyStringOrWhitespace = 'empty string or whitespace', - nonEmptyString = 'non-empty string', - nonEmptyStringAndNotWhitespace = 'non-empty string and not whitespace', - emptyObject = 'empty object', - nonEmptyObject = 'non-empty object', - emptySet = 'empty set', - nonEmptySet = 'non-empty set', - emptyMap = 'empty map', - nonEmptyMap = 'non-empty map', - - evenInteger = 'even integer', - oddInteger = 'odd integer', - - positiveNumber = 'positive number', - negativeNumber = 'negative number', - - directInstanceOf = 'T', - inRange = 'in range', - - any = 'predicate returns truthy for any value', - all = 'predicate returns truthy for all values', -} - // Type assertions have to be declared with an explicit type. type Assert = { // Unknowns. @@ -638,18 +637,18 @@ export const assert: Assert = { undefined: (value: unknown): asserts value is undefined => assertType(is.undefined(value), 'undefined', value), string: (value: unknown): asserts value is string => assertType(is.string(value), 'string', value), number: (value: unknown): asserts value is number => assertType(is.number(value), 'number', value), - positiveNumber: (value: unknown): asserts value is number => assertType(is.positiveNumber(value), AssertionTypeDescription.positiveNumber, value), - negativeNumber: (value: unknown): asserts value is number => assertType(is.negativeNumber(value), AssertionTypeDescription.negativeNumber, value), + positiveNumber: (value: unknown): asserts value is number => assertType(is.positiveNumber(value), 'positive number', value), + negativeNumber: (value: unknown): asserts value is number => assertType(is.negativeNumber(value), 'negative number', value), bigint: (value: unknown): asserts value is bigint => assertType(is.bigint(value), 'bigint', value), // eslint-disable-next-line @typescript-eslint/ban-types function_: (value: unknown): asserts value is Function => assertType(is.function_(value), 'Function', value), null_: (value: unknown): asserts value is null => assertType(is.null_(value), 'null', value), // eslint-disable-line @typescript-eslint/ban-types - class_: (value: unknown): asserts value is Class => assertType(is.class_(value), AssertionTypeDescription.class_, value), + class_: (value: unknown): asserts value is Class => assertType(is.class_(value), 'Class', value), boolean: (value: unknown): asserts value is boolean => assertType(is.boolean(value), 'boolean', value), symbol: (value: unknown): asserts value is symbol => assertType(is.symbol(value), 'symbol', value), - numericString: (value: unknown): asserts value is `${number}` => assertType(is.numericString(value), AssertionTypeDescription.numericString, value), + numericString: (value: unknown): asserts value is `${number}` => assertType(is.numericString(value), 'string with a number', value), array: (value: unknown, assertion?: (element: unknown) => asserts element is T): asserts value is T[] => { // eslint-disable-line object-shorthand - const assert: (condition: boolean, description: string, value: unknown) => asserts condition = assertType; + const assert: (condition: boolean, description: AssertionTypeDescription, value: unknown) => asserts condition = assertType; assert(is.array(value), 'Array', value); if (assertion) { @@ -659,13 +658,13 @@ export const assert: Assert = { }, buffer: (value: unknown): asserts value is Buffer => assertType(is.buffer(value), 'Buffer', value), blob: (value: unknown): asserts value is Blob => assertType(is.blob(value), 'Blob', value), - nullOrUndefined: (value: unknown): asserts value is null | undefined => assertType(is.nullOrUndefined(value), AssertionTypeDescription.nullOrUndefined, value), // eslint-disable-line @typescript-eslint/ban-types + nullOrUndefined: (value: unknown): asserts value is null | undefined => assertType(is.nullOrUndefined(value), 'null or undefined', value), // eslint-disable-line @typescript-eslint/ban-types object: (value: unknown): asserts value is object => assertType(is.object(value), 'Object', value), // eslint-disable-line @typescript-eslint/ban-types - iterable: (value: unknown): asserts value is Iterable => assertType(is.iterable(value), AssertionTypeDescription.iterable, value), - asyncIterable: (value: unknown): asserts value is AsyncIterable => assertType(is.asyncIterable(value), AssertionTypeDescription.asyncIterable, value), + iterable: (value: unknown): asserts value is Iterable => assertType(is.iterable(value), 'Iterable', value), + asyncIterable: (value: unknown): asserts value is AsyncIterable => assertType(is.asyncIterable(value), 'AsyncIterable', value), generator: (value: unknown): asserts value is Generator => assertType(is.generator(value), 'Generator', value), asyncGenerator: (value: unknown): asserts value is AsyncGenerator => assertType(is.asyncGenerator(value), 'AsyncGenerator', value), - nativePromise: (value: unknown): asserts value is Promise => assertType(is.nativePromise(value), AssertionTypeDescription.nativePromise, value), + nativePromise: (value: unknown): asserts value is Promise => assertType(is.nativePromise(value), 'native Promise', value), promise: (value: unknown): asserts value is Promise => assertType(is.promise(value), 'Promise', value), generatorFunction: (value: unknown): asserts value is GeneratorFunction => assertType(is.generatorFunction(value), 'GeneratorFunction', value), asyncGeneratorFunction: (value: unknown): asserts value is AsyncGeneratorFunction => assertType(is.asyncGeneratorFunction(value), 'AsyncGeneratorFunction', value), @@ -697,48 +696,48 @@ export const assert: Assert = { dataView: (value: unknown): asserts value is DataView => assertType(is.dataView(value), 'DataView', value), enumCase: (value: unknown, targetEnum: T): asserts value is T[keyof T] => assertType(is.enumCase(value, targetEnum), 'EnumCase', value), urlInstance: (value: unknown): asserts value is URL => assertType(is.urlInstance(value), 'URL', value), - urlString: (value: unknown): asserts value is string => assertType(is.urlString(value), AssertionTypeDescription.urlString, value), - truthy: (value: T | Falsy): asserts value is T => assertType(is.truthy(value), AssertionTypeDescription.truthy, value), - falsy: (value: unknown): asserts value is Falsy => assertType(is.falsy(value), AssertionTypeDescription.falsy, value), - nan: (value: unknown): asserts value is number => assertType(is.nan(value), AssertionTypeDescription.nan, value), - primitive: (value: unknown): asserts value is Primitive => assertType(is.primitive(value), AssertionTypeDescription.primitive, value), - integer: (value: unknown): asserts value is number => assertType(is.integer(value), AssertionTypeDescription.integer, value), - safeInteger: (value: unknown): asserts value is number => assertType(is.safeInteger(value), AssertionTypeDescription.safeInteger, value), - plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), - typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value), - arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value), - tupleLike: >>(value: unknown, guards: [...T]): asserts value is ResolveTypesOfTypeGuardsTuple => assertType(is.tupleLike(value, guards), AssertionTypeDescription.tupleLike, value), - domElement: (value: unknown): asserts value is HTMLElement => assertType(is.domElement(value), AssertionTypeDescription.domElement, value), + urlString: (value: unknown): asserts value is string => assertType(is.urlString(value), 'string with a URL', value), + truthy: (value: T | Falsy): asserts value is T => assertType(is.truthy(value), 'truthy', value), + falsy: (value: unknown): asserts value is Falsy => assertType(is.falsy(value), 'falsy', value), + nan: (value: unknown): asserts value is number => assertType(is.nan(value), 'NaN', value), + primitive: (value: unknown): asserts value is Primitive => assertType(is.primitive(value), 'primitive', value), + integer: (value: unknown): asserts value is number => assertType(is.integer(value), 'integer', value), + safeInteger: (value: unknown): asserts value is number => assertType(is.safeInteger(value), 'integer', value), + plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), 'plain object', value), + typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), 'TypedArray', value), + arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), 'array-like', value), + tupleLike: >>(value: unknown, guards: [...T]): asserts value is ResolveTypesOfTypeGuardsTuple => assertType(is.tupleLike(value, guards), 'tuple-like', value), + domElement: (value: unknown): asserts value is HTMLElement => assertType(is.domElement(value), 'HTMLElement', value), observable: (value: unknown): asserts value is ObservableLike => assertType(is.observable(value), 'Observable', value), - nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), AssertionTypeDescription.nodeStream, value), - infinite: (value: unknown): asserts value is number => assertType(is.infinite(value), AssertionTypeDescription.infinite, value), - emptyArray: (value: unknown): asserts value is never[] => assertType(is.emptyArray(value), AssertionTypeDescription.emptyArray, value), - nonEmptyArray: (value: T | Item[]): asserts value is [Item, ...Item[]] => assertType(is.nonEmptyArray(value), AssertionTypeDescription.nonEmptyArray, value), - emptyString: (value: unknown): asserts value is '' => assertType(is.emptyString(value), AssertionTypeDescription.emptyString, value), - emptyStringOrWhitespace: (value: unknown): asserts value is string => assertType(is.emptyStringOrWhitespace(value), AssertionTypeDescription.emptyStringOrWhitespace, value), - nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), AssertionTypeDescription.nonEmptyString, value), - nonEmptyStringAndNotWhitespace: (value: unknown): asserts value is string => assertType(is.nonEmptyStringAndNotWhitespace(value), AssertionTypeDescription.nonEmptyStringAndNotWhitespace, value), - emptyObject: (value: unknown): asserts value is Record => assertType(is.emptyObject(value), AssertionTypeDescription.emptyObject, value), - nonEmptyObject: (value: unknown): asserts value is Record => assertType(is.nonEmptyObject(value), AssertionTypeDescription.nonEmptyObject, value), - emptySet: (value: unknown): asserts value is Set => assertType(is.emptySet(value), AssertionTypeDescription.emptySet, value), - nonEmptySet: (value: unknown): asserts value is Set => assertType(is.nonEmptySet(value), AssertionTypeDescription.nonEmptySet, value), - emptyMap: (value: unknown): asserts value is Map => assertType(is.emptyMap(value), AssertionTypeDescription.emptyMap, value), - nonEmptyMap: (value: unknown): asserts value is Map => assertType(is.nonEmptyMap(value), AssertionTypeDescription.nonEmptyMap, value), + nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), 'Node.js Stream', value), + infinite: (value: unknown): asserts value is number => assertType(is.infinite(value), 'infinite number', value), + emptyArray: (value: unknown): asserts value is never[] => assertType(is.emptyArray(value), 'empty array', value), + nonEmptyArray: (value: T | Item[]): asserts value is [Item, ...Item[]] => assertType(is.nonEmptyArray(value), 'non-empty array', value), + emptyString: (value: unknown): asserts value is '' => assertType(is.emptyString(value), 'empty string', value), + emptyStringOrWhitespace: (value: unknown): asserts value is string => assertType(is.emptyStringOrWhitespace(value), 'empty string or whitespace', value), + nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), 'non-empty string', value), + nonEmptyStringAndNotWhitespace: (value: unknown): asserts value is string => assertType(is.nonEmptyStringAndNotWhitespace(value), 'non-empty string and not whitespace', value), + emptyObject: (value: unknown): asserts value is Record => assertType(is.emptyObject(value), 'empty object', value), + nonEmptyObject: (value: unknown): asserts value is Record => assertType(is.nonEmptyObject(value), 'non-empty object', value), + emptySet: (value: unknown): asserts value is Set => assertType(is.emptySet(value), 'empty set', value), + nonEmptySet: (value: unknown): asserts value is Set => assertType(is.nonEmptySet(value), 'non-empty set', value), + emptyMap: (value: unknown): asserts value is Map => assertType(is.emptyMap(value), 'empty map', value), + nonEmptyMap: (value: unknown): asserts value is Map => assertType(is.nonEmptyMap(value), 'non-empty map', value), propertyKey: (value: unknown): asserts value is number => assertType(is.propertyKey(value), 'PropertyKey', value), formData: (value: unknown): asserts value is FormData => assertType(is.formData(value), 'FormData', value), urlSearchParams: (value: unknown): asserts value is URLSearchParams => assertType(is.urlSearchParams(value), 'URLSearchParams', value), // Numbers. - evenInteger: (value: number): asserts value is number => assertType(is.evenInteger(value), AssertionTypeDescription.evenInteger, value), - oddInteger: (value: number): asserts value is number => assertType(is.oddInteger(value), AssertionTypeDescription.oddInteger, value), + evenInteger: (value: number): asserts value is number => assertType(is.evenInteger(value), 'even integer', value), + oddInteger: (value: number): asserts value is number => assertType(is.oddInteger(value), 'odd integer', value), // Two arguments. - directInstanceOf: (instance: unknown, class_: Class): asserts instance is T => assertType(is.directInstanceOf(instance, class_), AssertionTypeDescription.directInstanceOf, instance), - inRange: (value: number, range: number | number[]): asserts value is number => assertType(is.inRange(value, range), AssertionTypeDescription.inRange, value), + directInstanceOf: (instance: unknown, class_: Class): asserts instance is T => assertType(is.directInstanceOf(instance, class_), 'T', instance), + inRange: (value: number, range: number | number[]): asserts value is number => assertType(is.inRange(value, range), 'in range', value), // Variadic functions. - any: (predicate: Predicate | Predicate[], ...values: unknown[]): void | never => assertType(is.any(predicate, ...values), AssertionTypeDescription.any, values, {multipleValues: true}), - all: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.all(predicate, ...values), AssertionTypeDescription.all, values, {multipleValues: true}), + any: (predicate: Predicate | Predicate[], ...values: unknown[]): void | never => assertType(is.any(predicate, ...values), 'predicate returns truthy for any value', values, {multipleValues: true}), + all: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.all(predicate, ...values), 'predicate returns truthy for all values', values, {multipleValues: true}), }; /* eslint-enable @typescript-eslint/no-confusing-void-expression */ diff --git a/test/test.ts b/test/test.ts index 7bd1aed..a719c11 100644 --- a/test/test.ts +++ b/test/test.ts @@ -12,7 +12,7 @@ import {expectTypeOf} from 'expect-type'; import ZenObservable from 'zen-observable'; import is, { assert, - AssertionTypeDescription, + type AssertionTypeDescription, type Primitive, type TypedArray, type TypeName, @@ -31,11 +31,11 @@ type Test = { assert: (...args: any[]) => void | never; fixtures: unknown[]; typename?: TypeName; - typeDescription?: AssertionTypeDescription | TypeName; + typeDescription?: AssertionTypeDescription; is(value: unknown): boolean; }; -const invertAssertThrow = (description: string, fn: () => void | never, value: unknown): void | never => { +const invertAssertThrow = (description: AssertionTypeDescription, fn: () => void | never, value: unknown): void | never => { const expectedAssertErrorMessage = `Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`; try { @@ -86,7 +86,7 @@ const types = new Map([ String(), ], typename: 'string', - typeDescription: AssertionTypeDescription.emptyString, + typeDescription: 'empty string', }], ['number', { is: is.number, @@ -139,7 +139,7 @@ const types = new Map([ '0x56', ], typename: 'string', - typeDescription: AssertionTypeDescription.numericString, + typeDescription: 'string with a number', }], ['array', { is: is.array, @@ -158,7 +158,7 @@ const types = new Map([ new Array(), // eslint-disable-line @typescript-eslint/no-array-constructor ], typename: 'Array', - typeDescription: AssertionTypeDescription.emptyArray, + typeDescription: 'empty array', }], ['function', { is: is.function_, @@ -232,7 +232,7 @@ const types = new Map([ PromiseSubclassFixture.resolve(), ], typename: 'Promise', - typeDescription: AssertionTypeDescription.nativePromise, + typeDescription: 'native Promise', }], ['promise', { is: is.promise, @@ -319,7 +319,7 @@ const types = new Map([ new Map(), ], typename: 'Map', - typeDescription: AssertionTypeDescription.emptyMap, + typeDescription: 'empty map', }], ['set', { is: is.set, @@ -336,7 +336,7 @@ const types = new Map([ new Set(), ], typename: 'Set', - typeDescription: AssertionTypeDescription.emptySet, + typeDescription: 'empty set', }], ['weakSet', { is: is.weakSet, @@ -472,7 +472,7 @@ const types = new Map([ Number.NaN, ], typename: 'NaN', - typeDescription: AssertionTypeDescription.nan, + typeDescription: 'NaN', }], ['nullOrUndefined', { is: is.nullOrUndefined, @@ -481,7 +481,7 @@ const types = new Map([ null, undefined, ], - typeDescription: AssertionTypeDescription.nullOrUndefined, + typeDescription: 'null or undefined', }], ['plainObject', { is: is.plainObject, @@ -495,7 +495,7 @@ const types = new Map([ structuredClone(new Object()), // eslint-disable-line no-new-object ], typename: 'Object', - typeDescription: AssertionTypeDescription.plainObject, + typeDescription: 'plain object', }], ['integer', { is: is.integer, @@ -504,7 +504,7 @@ const types = new Map([ 6, ], typename: 'number', - typeDescription: AssertionTypeDescription.integer, + typeDescription: 'integer', }], ['safeInteger', { is: is.safeInteger, @@ -514,7 +514,7 @@ const types = new Map([ -(2 ** 53) + 1, ], typename: 'number', - typeDescription: AssertionTypeDescription.safeInteger, + typeDescription: 'integer', }], ['domElement', { is: is.domElement, @@ -528,12 +528,12 @@ const types = new Map([ 'script', ] .map(fixture => createDomElement(fixture)), - typeDescription: AssertionTypeDescription.domElement, + typeDescription: 'HTMLElement', }], ['non-domElements', { is: value => !is.domElement(value), assert(value: unknown) { - invertAssertThrow(AssertionTypeDescription.domElement, () => { + invertAssertThrow('HTMLElement', () => { assert.domElement(value); }, value); }, @@ -571,7 +571,7 @@ const types = new Map([ new Stream.Writable(), ], typename: 'Object', - typeDescription: AssertionTypeDescription.nodeStream, + typeDescription: 'Node.js Stream', }], ['infinite', { is: is.infinite, @@ -581,7 +581,7 @@ const types = new Map([ Number.NEGATIVE_INFINITY, ], typename: 'number', - typeDescription: AssertionTypeDescription.infinite, + typeDescription: 'infinite number', }], ]); @@ -609,7 +609,7 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { for (const fixture of fixtures) { assertIs(testIs(fixture), `Value: ${inspect(fixture)}`); - const valueType = typeDescription ?? typename; + const valueType = typeDescription ?? typename ?? 'unspecified'; if (isTypeUnderTest) { t.notThrows(() => { @@ -619,12 +619,12 @@ const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { t.throws(() => { testAssert(fixture); }, { - message: `Expected value which is \`${valueType as string}\`, received value of type \`${is(fixture)}\`.`, + message: `Expected value which is \`${valueType}\`, received value of type \`${is(fixture)}\`.`, }); } if (isTypeUnderTest && typename) { - t.is(is(fixture), typename); + t.is(is(fixture), typename); } } } From 5044c912730c7f7480881b197c870b92cabbcaef Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Mon, 7 Aug 2023 08:50:03 +0800 Subject: [PATCH 203/254] Implement named exports (#191) --- readme.md | 48 +- source/index.ts | 1582 +++++++++++++++++++++++++++++++++++------------ source/types.ts | 11 + test/test.ts | 50 +- 4 files changed, 1282 insertions(+), 409 deletions(-) diff --git a/readme.md b/readme.md index 40726c5..05d395b 100644 --- a/readme.md +++ b/readme.md @@ -54,6 +54,18 @@ assert.string(foo); // `foo` is now typed as a `string`. ``` +### Named exports + +Named exports allow tooling to perform tree-shaking, potentially reducing bundle size by including only code from the methods that are used. + +Every method listed below is available as a named export. Each method is prefixed by either `is` or `assert` depending on usage. + +For example: + +```js +import {assertNull, isUndefined} from '@sindresorhus/is'; +``` + ## API ### is(value) @@ -72,19 +84,23 @@ Example: - `'Function'` - `'Object'` +This method is also exported as `detect`. You can import it like this: + +```js +import {detect} from '@sindresorhus/is'; +``` + Note: It will throw an error 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. +All the below methods accept a value and return a boolean for whether the value is of the desired type. #### Primitives ##### .undefined(value) ##### .null(value) -**Note:** TypeScript users must use `.null_()` because of a TypeScript naming limitation. - ##### .string(value) ##### .number(value) @@ -107,8 +123,6 @@ is.array(value, is.number); // Validate `value` is an array and all of its items ##### .function(value) -**Note:** TypeScript users must use `.function_()` because of a TypeScript naming limitation. - ##### .buffer(value) ##### .blob(value) ##### .object(value) @@ -373,7 +387,15 @@ Returns `true` if `value` is one of: `false`, `0`, `''`, `null`, `undefined`, `N ##### .nullOrUndefined(value) ##### .primitive(value) -JavaScript primitives are as follows: `null`, `undefined`, `string`, `number`, `boolean`, `symbol`. +JavaScript primitives are as follows: + +- `null` +- `undefined` +- `string` +- `number` +- `boolean` +- `symbol` +- `bigint` ##### .integer(value) @@ -391,8 +413,6 @@ An object is plain if it's created by either `{}`, `new Object()`, or `Object.cr Returns `true` for instances created by a class. -**Note:** TypeScript users must use `.class_()` because of a TypeScript naming limitation. - ##### .typedArray(value) ##### .arrayLike(value) @@ -458,7 +478,7 @@ is.inRange(3, 10); ##### .domElement(value) -Returns `true` if `value` is a DOM Element. +Returns `true` if `value` is an [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). ##### .nodeStream(value) @@ -554,6 +574,16 @@ is.all(is.string, '🦄', [], 'unicorns'); //=> false ``` +##### .validLength(value) + +Returns `true` if the value is a safe integer that is greater than or equal to zero. + +This can be useful to confirm that a value is a valid count of something, ie. 0 or more. + +##### .whitespaceString(value) + +Returns `true` if the value is a string with only whitespace characters. + ## Type guards When using `is` together with TypeScript, [type guards](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types) are being used extensively to infer the correct type inside if-else statements. diff --git a/source/index.ts b/source/index.ts index fe78aa7..0239440 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,5 +1,15 @@ import type {Buffer} from 'node:buffer'; -import type {Class, Falsy, TypedArray, ObservableLike, Primitive, WeakRef} from './types.js'; +import type { + ArrayLike, + Class, + Falsy, + NodeStream, + ObservableLike, + Predicate, + Primitive, + TypedArray, + WeakRef, +} from './types.js'; const typedArrayTypeNames = [ 'Int8Array', @@ -117,22 +127,18 @@ const assertionTypeDescriptions = [ 'in range', 'predicate returns truthy for any value', 'predicate returns truthy for all values', + 'valid length', + 'whitespace string', ...objectTypeNames, ...primitiveTypeNames, ] as const; export type AssertionTypeDescription = typeof assertionTypeDescriptions[number]; -// eslint-disable-next-line @typescript-eslint/ban-types -function isOfType(type: PrimitiveTypeName | 'function') { - return (value: unknown): value is T => typeof value === type; -} - -const {toString} = Object.prototype; const getObjectType = (value: unknown): ObjectTypeName | undefined => { - const objectTypeName = toString.call(value).slice(8, -1); + const objectTypeName = Object.prototype.toString.call(value).slice(8, -1); - if (/HTML\w+Element/.test(objectTypeName) && is.domElement(value)) { + if (/HTML\w+Element/.test(objectTypeName) && isDomElement(value)) { return 'HTMLElement'; } @@ -143,9 +149,7 @@ const getObjectType = (value: unknown): ObjectTypeName | undefined => { return undefined; }; -const isObjectOfType = (type: ObjectTypeName) => (value: unknown): value is T => getObjectType(value) === type; - -function is(value: unknown): TypeName { +function detect(value: unknown): TypeName { if (value === null) { return 'null'; } @@ -182,15 +186,15 @@ function is(value: unknown): TypeName { default: } - if (is.observable(value)) { + if (isObservable(value)) { return 'Observable'; } - if (is.array(value)) { + if (isArray(value)) { return 'Array'; } - if (is.buffer(value)) { + if (isBuffer(value)) { return 'Buffer'; } @@ -206,201 +210,276 @@ function is(value: unknown): TypeName { return 'Object'; } -is.undefined = isOfType('undefined'); +function hasPromiseApi(value: unknown): value is Promise { + return isFunction((value as Promise)?.then) && isFunction((value as Promise)?.catch); +} -is.string = isOfType('string'); +const is = Object.assign( + detect, + { + all: isAll, + any: isAny, + array: isArray, + arrayBuffer: isArrayBuffer, + arrayLike: isArrayLike, + asyncFunction: isAsyncFunction, + asyncGenerator: isAsyncGenerator, + asyncGeneratorFunction: isAsyncGeneratorFunction, + asyncIterable: isAsyncIterable, + bigint: isBigint, + bigInt64Array: isBigInt64Array, + bigUint64Array: isBigUint64Array, + blob: isBlob, + boolean: isBoolean, + boundFunction: isBoundFunction, + buffer: isBuffer, + class: isClass, + /** @deprecated Renamed to `class`. */ + class_: isClass, + dataView: isDataView, + date: isDate, + detect, + directInstanceOf: isDirectInstanceOf, + domElement: isDomElement, + emptyArray: isEmptyArray, + emptyMap: isEmptyMap, + emptyObject: isEmptyObject, + emptySet: isEmptySet, + emptyString: isEmptyString, + emptyStringOrWhitespace: isEmptyStringOrWhitespace, + enumCase: isEnumCase, + error: isError, + evenInteger: isEvenInteger, + falsy: isFalsy, + float32Array: isFloat32Array, + float64Array: isFloat64Array, + formData: isFormData, + function: isFunction, + /** @deprecated Renamed to `function`. */ + function_: isFunction, + generator: isGenerator, + generatorFunction: isGeneratorFunction, + infinite: isInfinite, + inRange: isInRange, + int16Array: isInt16Array, + int32Array: isInt32Array, + int8Array: isInt8Array, + integer: isInteger, + iterable: isIterable, + map: isMap, + nan: isNan, + nativePromise: isNativePromise, + negativeNumber: isNegativeNumber, + nodeStream: isNodeStream, + nonEmptyArray: isNonEmptyArray, + nonEmptyMap: isNonEmptyMap, + nonEmptyObject: isNonEmptyObject, + nonEmptySet: isNonEmptySet, + nonEmptyString: isNonEmptyString, + nonEmptyStringAndNotWhitespace: isNonEmptyStringAndNotWhitespace, + null: isNull, + /** @deprecated Renamed to `null`. */ + null_: isNull, + nullOrUndefined: isNullOrUndefined, + number: isNumber, + numericString: isNumericString, + object: isObject, + observable: isObservable, + oddInteger: isOddInteger, + plainObject: isPlainObject, + positiveNumber: isPositiveNumber, + primitive: isPrimitive, + promise: isPromise, + propertyKey: isPropertyKey, + regExp: isRegExp, + safeInteger: isSafeInteger, + set: isSet, + sharedArrayBuffer: isSharedArrayBuffer, + string: isString, + symbol: isSymbol, + truthy: isTruthy, + tupleLike: isTupleLike, + typedArray: isTypedArray, + uint16Array: isUint16Array, + uint32Array: isUint32Array, + uint8Array: isUint8Array, + uint8ClampedArray: isUint8ClampedArray, + undefined: isUndefined, + urlInstance: isUrlInstance, + urlSearchParams: isUrlSearchParams, + urlString: isUrlString, + validLength: isValidLength, + weakMap: isWeakMap, + weakRef: isWeakRef, + weakSet: isWeakSet, + whitespaceString: isWhitespaceString, + }, +); -const isNumberType = isOfType('number'); -is.number = (value: unknown): value is number => isNumberType(value) && !is.nan(value); +function isAbsoluteMod2(remainder: 0 | 1) { + return (value: unknown): value is number => isInteger(value) && Math.abs(value % 2) === remainder; +} -is.positiveNumber = (value: unknown): value is number => is.number(value) && value > 0; +export function isAll(predicate: Predicate, ...values: unknown[]): boolean { + return predicateOnArray(Array.prototype.every, predicate, values); +} -is.negativeNumber = (value: unknown): value is number => is.number(value) && value < 0; +export function isAny(predicate: Predicate | Predicate[], ...values: unknown[]): boolean { + const predicates = isArray(predicate) ? predicate : [predicate]; + return predicates.some(singlePredicate => + predicateOnArray(Array.prototype.some, singlePredicate, values), + ); +} -is.bigint = isOfType('bigint'); - -// eslint-disable-next-line @typescript-eslint/ban-types -is.function_ = isOfType('function'); - -// eslint-disable-next-line @typescript-eslint/ban-types -is.null_ = (value: unknown): value is null => value === null; - -is.class_ = (value: unknown): value is Class => is.function_(value) && value.toString().startsWith('class '); - -is.boolean = (value: unknown): value is boolean => value === true || value === false; - -is.symbol = isOfType('symbol'); - -is.numericString = (value: unknown): value is `${number}` => - is.string(value) && !is.emptyStringOrWhitespace(value) && !Number.isNaN(Number(value)); - -is.array = (value: unknown, assertion?: (value: T) => value is T): value is T[] => { +export function isArray(value: unknown, assertion?: (value: T) => value is T): value is T[] { if (!Array.isArray(value)) { return false; } - if (!is.function_(assertion)) { + if (!isFunction(assertion)) { return true; } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return value.every(element => assertion(element)); -}; +} -// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call -is.buffer = (value: unknown): value is Buffer => (value as any)?.constructor?.isBuffer?.(value) ?? false; +export function isArrayBuffer(value: unknown): value is ArrayBuffer { + return getObjectType(value) === 'ArrayBuffer'; +} -is.blob = (value: unknown): value is Blob => isObjectOfType('Blob')(value); +export function isArrayLike(value: unknown): value is ArrayLike { + return !isNullOrUndefined(value) && !isFunction(value) && isValidLength((value as ArrayLike).length); +} -is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value); // eslint-disable-line @typescript-eslint/ban-types +export function isAsyncFunction(value: unknown): value is ((...args: any[]) => Promise) { + return getObjectType(value) === 'AsyncFunction'; +} -is.object = (value: unknown): value is object => !is.null_(value) && (typeof value === 'object' || is.function_(value)); // eslint-disable-line @typescript-eslint/ban-types +export function isAsyncGenerator(value: unknown): value is AsyncGenerator { + return isAsyncIterable(value) && isFunction((value as AsyncGenerator).next) && isFunction((value as AsyncGenerator).throw); +} -is.iterable = (value: unknown): value is Iterable => is.function_((value as Iterable)?.[Symbol.iterator]); +export function isAsyncGeneratorFunction(value: unknown): value is ((...args: any[]) => Promise) { + return getObjectType(value) === 'AsyncGeneratorFunction'; +} -is.asyncIterable = (value: unknown): value is AsyncIterable => is.function_((value as AsyncIterable)?.[Symbol.asyncIterator]); +export function isAsyncIterable(value: unknown): value is AsyncIterable { + return isFunction((value as AsyncIterable)?.[Symbol.asyncIterator]); +} -is.generator = (value: unknown): value is Generator => is.iterable(value) && is.function_((value as Generator)?.next) && is.function_((value as Generator)?.throw); +export function isBigint(value: unknown): value is bigint { + return typeof value === 'bigint'; +} -is.asyncGenerator = (value: unknown): value is AsyncGenerator => is.asyncIterable(value) && is.function_((value as AsyncGenerator).next) && is.function_((value as AsyncGenerator).throw); +export function isBigInt64Array(value: unknown): value is BigInt64Array { + return getObjectType(value) === 'BigInt64Array'; +} -is.nativePromise = (value: unknown): value is Promise => - isObjectOfType>('Promise')(value); +export function isBigUint64Array(value: unknown): value is BigUint64Array { + return getObjectType(value) === 'BigUint64Array'; +} -const hasPromiseApi = (value: unknown): value is Promise => - is.function_((value as Promise)?.then) - && is.function_((value as Promise)?.catch); +export function isBlob(value: unknown): value is Blob { + return getObjectType(value) === 'Blob'; +} -is.promise = (value: unknown): value is Promise => is.nativePromise(value) || hasPromiseApi(value); - -is.generatorFunction = isObjectOfType('GeneratorFunction'); - -is.asyncGeneratorFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === 'AsyncGeneratorFunction'; - -is.asyncFunction = (value: unknown): value is ((...args: any[]) => Promise) => getObjectType(value) === 'AsyncFunction'; - -// eslint-disable-next-line no-prototype-builtins, @typescript-eslint/ban-types -is.boundFunction = (value: unknown): value is Function => is.function_(value) && !value.hasOwnProperty('prototype'); - -is.regExp = isObjectOfType('RegExp'); - -is.date = isObjectOfType('Date'); - -is.error = isObjectOfType('Error'); - -is.map = (value: unknown): value is Map => isObjectOfType>('Map')(value); - -is.set = (value: unknown): value is Set => isObjectOfType>('Set')(value); - -is.weakMap = (value: unknown): value is WeakMap => isObjectOfType>('WeakMap')(value); // eslint-disable-line @typescript-eslint/ban-types - -is.weakSet = (value: unknown): value is WeakSet => isObjectOfType>('WeakSet')(value); // eslint-disable-line @typescript-eslint/ban-types - -is.weakRef = (value: unknown): value is WeakRef => isObjectOfType>('WeakRef')(value); // eslint-disable-line @typescript-eslint/ban-types - -is.int8Array = isObjectOfType('Int8Array'); -is.uint8Array = isObjectOfType('Uint8Array'); -is.uint8ClampedArray = isObjectOfType('Uint8ClampedArray'); -is.int16Array = isObjectOfType('Int16Array'); -is.uint16Array = isObjectOfType('Uint16Array'); -is.int32Array = isObjectOfType('Int32Array'); -is.uint32Array = isObjectOfType('Uint32Array'); -is.float32Array = isObjectOfType('Float32Array'); -is.float64Array = isObjectOfType('Float64Array'); -is.bigInt64Array = isObjectOfType('BigInt64Array'); -is.bigUint64Array = isObjectOfType('BigUint64Array'); - -is.arrayBuffer = isObjectOfType('ArrayBuffer'); - -is.sharedArrayBuffer = isObjectOfType('SharedArrayBuffer'); - -is.dataView = isObjectOfType('DataView'); - -// eslint-disable-next-line @typescript-eslint/no-unsafe-argument -is.enumCase = (value: unknown, targetEnum: T): boolean => Object.values(targetEnum as any).includes(value as string); - -is.directInstanceOf = (instance: unknown, class_: Class): instance is T => Object.getPrototypeOf(instance) === class_.prototype; - -is.urlInstance = (value: unknown): value is URL => isObjectOfType('URL')(value); - -is.urlString = (value: unknown): value is string => { - if (!is.string(value)) { - return false; - } - - try { - new URL(value); // eslint-disable-line no-new - return true; - } catch { - return false; - } -}; - -// Example: `is.truthy = (value: unknown): value is (not false | not 0 | not '' | not undefined | not null) => Boolean(value);` -is.truthy = (value: T | Falsy): value is T => Boolean(value); // eslint-disable-line unicorn/prefer-native-coercion-functions - -// Example: `is.falsy = (value: unknown): value is (not true | 0 | '' | undefined | null) => Boolean(value);` -is.falsy = (value: unknown): value is Falsy => !value; - -is.nan = (value: unknown) => Number.isNaN(value as number); - -is.primitive = (value: unknown): value is Primitive => is.null_(value) || isPrimitiveTypeName(typeof value); - -is.integer = (value: unknown): value is number => Number.isInteger(value as number); - -is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); - -is.plainObject = (value: unknown): value is Record => { - // From: https://github.com/sindresorhus/is-plain-obj/blob/main/index.js - if (typeof value !== 'object' || value === null) { - return false; - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const prototype = Object.getPrototypeOf(value); - - return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); -}; - -is.typedArray = (value: unknown): value is TypedArray => isTypedArrayName(getObjectType(value)); - -export type ArrayLike = { - readonly [index: number]: T; - readonly length: number; -}; - -const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0; -is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); - -type TypeGuard = (value: unknown) => value is T; +export function isBoolean(value: unknown): value is boolean { + return value === true || value === false; +} // eslint-disable-next-line @typescript-eslint/ban-types -type ResolveTypesOfTypeGuardsTuple = - TypeGuardsOfT extends [TypeGuard, ...infer TOthers] - ? ResolveTypesOfTypeGuardsTuple - : TypeGuardsOfT extends undefined[] - ? ResultOfT - : never; +export function isBoundFunction(value: unknown): value is Function { + return isFunction(value) && !Object.prototype.hasOwnProperty.call(value, 'prototype'); +} -is.tupleLike = >>(value: unknown, guards: [...T]): value is ResolveTypesOfTypeGuardsTuple => { - if (is.array(guards) && is.array(value) && guards.length === value.length) { - return guards.every((guard, index) => guard(value[index])); - } +export function isBuffer(value: unknown): value is Buffer { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call + return (value as any)?.constructor?.isBuffer?.(value) ?? false; +} - return false; -}; +export function isClass(value: unknown): value is Class { + return isFunction(value) && value.toString().startsWith('class '); +} -is.inRange = (value: number, range: number | number[]): value is number => { - if (is.number(range)) { - return value >= Math.min(0, range) && value <= Math.max(range, 0); - } +export function isDataView(value: unknown): value is DataView { + return getObjectType(value) === 'DataView'; +} - if (is.array(range) && range.length === 2) { - return value >= Math.min(...range) && value <= Math.max(...range); - } +export function isDate(value: unknown): value is Date { + return getObjectType(value) === 'Date'; +} - throw new TypeError(`Invalid range: ${JSON.stringify(range)}`); -}; +export function isDirectInstanceOf(instance: unknown, class_: Class): instance is T { + return Object.getPrototypeOf(instance) === class_.prototype; +} + +export function isEmptyArray(value: unknown): value is never[] { + return isArray(value) && value.length === 0; +} + +export function isEmptyMap(value: unknown): value is Map { + return isMap(value) && value.size === 0; +} + +export function isEmptyObject(value: unknown): value is Record { + return isObject(value) && !isMap(value) && !isSet(value) && Object.keys(value).length === 0; +} + +export function isEmptySet(value: unknown): value is Set { + return isSet(value) && value.size === 0; +} + +export function isEmptyString(value: unknown): value is '' { + return isString(value) && value.length === 0; +} + +export function isEmptyStringOrWhitespace(value: unknown): value is string { + return isEmptyString(value) || isWhitespaceString(value); +} + +export function isEnumCase(value: unknown, targetEnum: T): boolean { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + return Object.values(targetEnum as any).includes(value as string); +} + +export function isError(value: unknown): value is Error { + return getObjectType(value) === 'Error'; +} + +export function isEvenInteger(value: unknown): value is number { + return isAbsoluteMod2(0)(value); +} + +// Example: `is.falsy = (value: unknown): value is (not true | 0 | '' | undefined | null) => Boolean(value);` +export function isFalsy(value: unknown): value is Falsy { + return !value; +} + +export function isFloat32Array(value: unknown): value is Float32Array { + return getObjectType(value) === 'Float32Array'; +} + +export function isFloat64Array(value: unknown): value is Float64Array { + return getObjectType(value) === 'Float64Array'; +} + +export function isFormData(value: unknown): value is FormData { + return getObjectType(value) === 'FormData'; +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function isFunction(value: unknown): value is Function { + return typeof value === 'function'; +} + +export function isGenerator(value: unknown): value is Generator { + return isIterable(value) && isFunction((value as Generator)?.next) && isFunction((value as Generator)?.throw); +} + +export function isGeneratorFunction(value: unknown): value is GeneratorFunction { + return getObjectType(value) === 'GeneratorFunction'; +} // eslint-disable-next-line @typescript-eslint/naming-convention const NODE_TYPE_ELEMENT = 1; @@ -414,13 +493,122 @@ const DOM_PROPERTIES_TO_CHECK: Array<(keyof HTMLElement)> = [ 'nodeValue', ]; -is.domElement = (value: unknown): value is HTMLElement => is.object(value) - && (value as HTMLElement).nodeType === NODE_TYPE_ELEMENT - && is.string((value as HTMLElement).nodeName) - && !is.plainObject(value) - && DOM_PROPERTIES_TO_CHECK.every(property => property in value); +export function isDomElement(value: unknown): value is HTMLElement { + return isObject(value) + && (value as HTMLElement).nodeType === NODE_TYPE_ELEMENT + && isString((value as HTMLElement).nodeName) + && !isPlainObject(value) + && DOM_PROPERTIES_TO_CHECK.every(property => property in value); +} -is.observable = (value: unknown): value is ObservableLike => { +export function isInfinite(value: unknown): value is number { + return value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY; +} + +export function isInRange(value: number, range: number | [number, number]): value is number { + if (isNumber(range)) { + return value >= Math.min(0, range) && value <= Math.max(range, 0); + } + + if (isArray(range) && range.length === 2) { + return value >= Math.min(...range) && value <= Math.max(...range); + } + + throw new TypeError(`Invalid range: ${JSON.stringify(range)}`); +} + +export function isInt16Array(value: unknown): value is Int16Array { + return getObjectType(value) === 'Int16Array'; +} + +export function isInt32Array(value: unknown): value is Int32Array { + return getObjectType(value) === 'Int32Array'; +} + +export function isInt8Array(value: unknown): value is Int8Array { + return getObjectType(value) === 'Int8Array'; +} + +export function isInteger(value: unknown): value is number { + return Number.isInteger(value); +} + +export function isIterable(value: unknown): value is Iterable { + return isFunction((value as Iterable)?.[Symbol.iterator]); +} + +export function isMap(value: unknown): value is Map { + return getObjectType(value) === 'Map'; +} + +export function isNan(value: unknown) { + return Number.isNaN(value); +} + +export function isNativePromise(value: unknown): value is Promise { + return getObjectType(value) === 'Promise'; +} + +export function isNegativeNumber(value: unknown): value is number { + return isNumber(value) && value < 0; +} + +export function isNodeStream(value: unknown): value is NodeStream { + return isObject(value) && isFunction((value as NodeStream).pipe) && !isObservable(value); +} + +export function isNonEmptyArray(value: T | Item[]): value is [Item, ...Item[]] { + return isArray(value) && value.length > 0; +} + +export function isNonEmptyMap(value: unknown): value is Map { + return isMap(value) && value.size > 0; +} + +// TODO: Use `not` operator here to remove `Map` and `Set` from type guard: +// - https://github.com/Microsoft/TypeScript/pull/29317 +export function isNonEmptyObject(value: unknown): value is Record { + return isObject(value) && !isMap(value) && !isSet(value) && Object.keys(value).length > 0; +} + +export function isNonEmptySet(value: unknown): value is Set { + return isSet(value) && value.size > 0; +} + +// TODO: Use `not ''` when the `not` operator is available. +export function isNonEmptyString(value: unknown): value is string { + return isString(value) && value.length > 0; +} + +// TODO: Use `not ''` when the `not` operator is available. +export function isNonEmptyStringAndNotWhitespace(value: unknown): value is string { + return isString(value) && !isEmptyStringOrWhitespace(value); +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function isNull(value: unknown): value is null { + return value === null; +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function isNullOrUndefined(value: unknown): value is null | undefined { + return isNull(value) || isUndefined(value); +} + +export function isNumber(value: unknown): value is number { + return typeof value === 'number' && !Number.isNaN(value); +} + +export function isNumericString(value: unknown): value is `${number}` { + return isString(value) && !isEmptyStringOrWhitespace(value) && !Number.isNaN(Number(value)); +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function isObject(value: unknown): value is object { + return !isNull(value) && (typeof value === 'object' || isFunction(value)); +} + +export function isObservable(value: unknown): value is ObservableLike { if (!value) { return false; } @@ -436,66 +624,162 @@ is.observable = (value: unknown): value is ObservableLike => { } return false; -}; +} -export type NodeStream = { - pipe(destination: T, options?: {end?: boolean}): T; -} & NodeJS.EventEmitter; +export function isOddInteger(value: unknown): value is number { + return isAbsoluteMod2(1)(value); +} -is.nodeStream = (value: unknown): value is NodeStream => is.object(value) && is.function_((value as NodeStream).pipe) && !is.observable(value); +export function isPlainObject(value: unknown): value is Record { + // From: https://github.com/sindresorhus/is-plain-obj/blob/main/index.js + if (typeof value !== 'object' || value === null) { + return false; + } -is.infinite = (value: unknown): value is number => value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const prototype = Object.getPrototypeOf(value); -const isAbsoluteMod2 = (remainder: number) => (value: number): value is number => is.integer(value) && Math.abs(value % 2) === remainder; -is.evenInteger = isAbsoluteMod2(0); -is.oddInteger = isAbsoluteMod2(1); + return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); +} -is.emptyArray = (value: unknown): value is never[] => is.array(value) && value.length === 0; +export function isPositiveNumber(value: unknown): value is number { + return isNumber(value) && value > 0; +} -is.nonEmptyArray = (value: T | Item[]): value is [Item, ...Item[]] => is.array(value) && value.length > 0; +export function isPrimitive(value: unknown): value is Primitive { + return isNull(value) || isPrimitiveTypeName(typeof value); +} -is.emptyString = (value: unknown): value is '' => is.string(value) && value.length === 0; - -const isWhiteSpaceString = (value: unknown): value is string => is.string(value) && !/\S/.test(value); -is.emptyStringOrWhitespace = (value: unknown): value is string => is.emptyString(value) || isWhiteSpaceString(value); - -// TODO: Use `not ''` when the `not` operator is available. -is.nonEmptyString = (value: unknown): value is string => is.string(value) && value.length > 0; - -// TODO: Use `not ''` when the `not` operator is available. -is.nonEmptyStringAndNotWhitespace = (value: unknown): value is string => is.string(value) && !is.emptyStringOrWhitespace(value); - -// eslint-disable-next-line unicorn/no-array-callback-reference -is.emptyObject = (value: unknown): value is Record => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0; - -// TODO: Use `not` operator here to remove `Map` and `Set` from type guard: -// - https://github.com/Microsoft/TypeScript/pull/29317 -// eslint-disable-next-line unicorn/no-array-callback-reference -is.nonEmptyObject = (value: unknown): value is Record => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length > 0; - -is.emptySet = (value: unknown): value is Set => is.set(value) && value.size === 0; - -is.nonEmptySet = (value: unknown): value is Set => is.set(value) && value.size > 0; - -// eslint-disable-next-line unicorn/no-array-callback-reference -is.emptyMap = (value: unknown): value is Map => is.map(value) && value.size === 0; - -// eslint-disable-next-line unicorn/no-array-callback-reference -is.nonEmptyMap = (value: unknown): value is Map => is.map(value) && value.size > 0; +export function isPromise(value: unknown): value is Promise { + return isNativePromise(value) || hasPromiseApi(value); +} // `PropertyKey` is any value that can be used as an object key (string, number, or symbol) -is.propertyKey = (value: unknown): value is PropertyKey => is.any([is.string, is.number, is.symbol], value); +export function isPropertyKey(value: unknown): value is PropertyKey { + return isAny([isString, isNumber, isSymbol], value); +} -is.formData = (value: unknown): value is FormData => isObjectOfType('FormData')(value); +export function isRegExp(value: unknown): value is RegExp { + return getObjectType(value) === 'RegExp'; +} -is.urlSearchParams = (value: unknown): value is URLSearchParams => isObjectOfType('URLSearchParams')(value); +export function isSafeInteger(value: unknown): value is number { + return Number.isSafeInteger(value); +} -export type Predicate = (value: unknown) => boolean; +export function isSet(value: unknown): value is Set { + return getObjectType(value) === 'Set'; +} + +export function isSharedArrayBuffer(value: unknown): value is SharedArrayBuffer { + return getObjectType(value) === 'SharedArrayBuffer'; +} + +export function isString(value: unknown): value is string { + return typeof value === 'string'; +} + +export function isSymbol(value: unknown): value is symbol { + return typeof value === 'symbol'; +} + +// Example: `is.truthy = (value: unknown): value is (not false | not 0 | not '' | not undefined | not null) => Boolean(value);` +// eslint-disable-next-line unicorn/prefer-native-coercion-functions +export function isTruthy(value: T | Falsy): value is T { + return Boolean(value); +} + +type TypeGuard = (value: unknown) => value is T; + +// eslint-disable-next-line @typescript-eslint/ban-types +type ResolveTypesOfTypeGuardsTuple = + TypeGuardsOfT extends [TypeGuard, ...infer TOthers] + ? ResolveTypesOfTypeGuardsTuple + : TypeGuardsOfT extends undefined[] + ? ResultOfT + : never; + +export function isTupleLike>>(value: unknown, guards: [...T]): value is ResolveTypesOfTypeGuardsTuple { + if (isArray(guards) && isArray(value) && guards.length === value.length) { + return guards.every((guard, index) => guard(value[index])); + } + + return false; +} + +export function isTypedArray(value: unknown): value is TypedArray { + return isTypedArrayName(getObjectType(value)); +} + +export function isUint16Array(value: unknown): value is Uint16Array { + return getObjectType(value) === 'Uint16Array'; +} + +export function isUint32Array(value: unknown): value is Uint32Array { + return getObjectType(value) === 'Uint32Array'; +} + +export function isUint8Array(value: unknown): value is Uint8Array { + return getObjectType(value) === 'Uint8Array'; +} + +export function isUint8ClampedArray(value: unknown): value is Uint8ClampedArray { + return getObjectType(value) === 'Uint8ClampedArray'; +} + +export function isUndefined(value: unknown): value is undefined { + return value === undefined; +} + +export function isUrlInstance(value: unknown): value is URL { + return getObjectType(value) === 'URL'; +} + +// eslint-disable-next-line unicorn/prevent-abbreviations +export function isUrlSearchParams(value: unknown): value is URLSearchParams { + return getObjectType(value) === 'URLSearchParams'; +} + +export function isUrlString(value: unknown): value is string { + if (!isString(value)) { + return false; + } + + try { + new URL(value); // eslint-disable-line no-new + return true; + } catch { + return false; + } +} + +export function isValidLength(value: unknown): value is number { + return isSafeInteger(value) && value >= 0; +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function isWeakMap(value: unknown): value is WeakMap { + return getObjectType(value) === 'WeakMap'; +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function isWeakRef(value: unknown): value is WeakRef { + return getObjectType(value) === 'WeakRef'; +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function isWeakSet(value: unknown): value is WeakSet { + return getObjectType(value) === 'WeakSet'; +} + +export function isWhitespaceString(value: unknown): value is string { + return isString(value) && /^\s+$/.test(value); +} type ArrayMethod = (fn: (value: unknown, index: number, array: unknown[]) => boolean, thisArg?: unknown) => boolean; -const predicateOnArray = (method: ArrayMethod, predicate: Predicate, values: unknown[]) => { - if (!is.function_(predicate)) { +function predicateOnArray(method: ArrayMethod, predicate: Predicate, values: unknown[]) { + if (!isFunction(predicate)) { throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); } @@ -504,31 +788,17 @@ const predicateOnArray = (method: ArrayMethod, predicate: Predicate, values: unk } return method.call(values, predicate); -}; +} -is.any = (predicate: Predicate | Predicate[], ...values: unknown[]): boolean => { - const predicates = is.array(predicate) ? predicate : [predicate]; - return predicates.some(singlePredicate => - predicateOnArray(Array.prototype.some, singlePredicate, values), - ); -}; +function typeErrorMessage(description: AssertionTypeDescription, value: unknown): string { + return `Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`; +} -is.all = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.every, predicate, values); - -const assertType = (condition: boolean, description: AssertionTypeDescription, value: unknown, options: {multipleValues?: boolean} = {}): asserts condition => { - if (!condition) { - const {multipleValues} = options; - const valuesMessage = multipleValues - ? `received values of types ${[ - ...new Set( - (value as any[]).map(singleValue => `\`${is(singleValue)}\``), - ), - ].join(', ')}` - : `received value of type \`${is(value)}\``; - - throw new TypeError(`Expected value which is \`${description}\`, ${valuesMessage}.`); - } -}; +function typeErrorMessageMultipleValue(description: AssertionTypeDescription, values: unknown[]): string { + // eslint-disable-next-line unicorn/prefer-spread + const valueTypes = Array.from(new Set(values.map(singleValue => `\`${is(singleValue)}\``))).join(', '); + return `Expected value which is \`${description}\`, received values of types ${valueTypes}.`; +} // Type assertions have to be declared with an explicit type. type Assert = { @@ -540,8 +810,17 @@ type Assert = { negativeNumber: (value: unknown) => asserts value is number; bigint: (value: unknown) => asserts value is bigint; // eslint-disable-next-line @typescript-eslint/ban-types + function: (value: unknown) => asserts value is Function; + /** @deprecated Renamed to `function`. */ + // eslint-disable-next-line @typescript-eslint/ban-types function_: (value: unknown) => asserts value is Function; - null_: (value: unknown) => asserts value is null; // eslint-disable-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/ban-types + null: (value: unknown) => asserts value is null; + /** @deprecated Renamed to `null`. */ + // eslint-disable-next-line @typescript-eslint/ban-types + null_: (value: unknown) => asserts value is null; + class: (value: unknown) => asserts value is Class; + /** @deprecated Renamed to `class`. */ class_: (value: unknown) => asserts value is Class; boolean: (value: unknown) => asserts value is boolean; symbol: (value: unknown) => asserts value is symbol; @@ -549,7 +828,8 @@ type Assert = { array: (value: unknown, assertion?: (element: unknown) => asserts element is T) => asserts value is T[]; buffer: (value: unknown) => asserts value is Buffer; blob: (value: unknown) => asserts value is Blob; - nullOrUndefined: (value: unknown) => asserts value is null | undefined; // eslint-disable-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/ban-types + nullOrUndefined: (value: unknown) => asserts value is null | undefined; object: (value: unknown) => asserts value is Record; iterable: (value: unknown) => asserts value is Iterable; asyncIterable: (value: unknown) => asserts value is AsyncIterable; @@ -568,9 +848,12 @@ type Assert = { error: (value: unknown) => asserts value is Error; map: (value: unknown) => asserts value is Map; set: (value: unknown) => asserts value is Set; - weakMap: (value: unknown) => asserts value is WeakMap; // eslint-disable-line @typescript-eslint/ban-types - weakSet: (value: unknown) => asserts value is WeakSet; // eslint-disable-line @typescript-eslint/ban-types - weakRef: (value: unknown) => asserts value is WeakRef; // eslint-disable-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/ban-types + weakMap: (value: unknown) => asserts value is WeakMap; + // eslint-disable-next-line @typescript-eslint/ban-types + weakSet: (value: unknown) => asserts value is WeakSet; + // eslint-disable-next-line @typescript-eslint/ban-types + weakRef: (value: unknown) => asserts value is WeakRef; int8Array: (value: unknown) => asserts value is Int8Array; uint8Array: (value: unknown) => asserts value is Uint8Array; uint8ClampedArray: (value: unknown) => asserts value is Uint8ClampedArray; @@ -617,6 +900,8 @@ type Assert = { propertyKey: (value: unknown) => asserts value is PropertyKey; formData: (value: unknown) => asserts value is FormData; urlSearchParams: (value: unknown) => asserts value is URLSearchParams; + validLength: (value: unknown) => asserts value is number; + whitespaceString: (value: unknown) => asserts value is string; // Numbers. evenInteger: (value: number) => asserts value is number; @@ -624,148 +909,665 @@ type Assert = { // Two arguments. directInstanceOf: (instance: unknown, class_: Class) => asserts instance is T; - inRange: (value: number, range: number | number[]) => asserts value is number; + inRange: (value: number, range: number | [number, number]) => asserts value is number; // Variadic functions. any: (predicate: Predicate | Predicate[], ...values: unknown[]) => void | never; all: (predicate: Predicate, ...values: unknown[]) => void | never; }; -/* eslint-disable @typescript-eslint/no-confusing-void-expression */ export const assert: Assert = { - // Unknowns. - undefined: (value: unknown): asserts value is undefined => assertType(is.undefined(value), 'undefined', value), - string: (value: unknown): asserts value is string => assertType(is.string(value), 'string', value), - number: (value: unknown): asserts value is number => assertType(is.number(value), 'number', value), - positiveNumber: (value: unknown): asserts value is number => assertType(is.positiveNumber(value), 'positive number', value), - negativeNumber: (value: unknown): asserts value is number => assertType(is.negativeNumber(value), 'negative number', value), - bigint: (value: unknown): asserts value is bigint => assertType(is.bigint(value), 'bigint', value), - // eslint-disable-next-line @typescript-eslint/ban-types - function_: (value: unknown): asserts value is Function => assertType(is.function_(value), 'Function', value), - null_: (value: unknown): asserts value is null => assertType(is.null_(value), 'null', value), // eslint-disable-line @typescript-eslint/ban-types - class_: (value: unknown): asserts value is Class => assertType(is.class_(value), 'Class', value), - boolean: (value: unknown): asserts value is boolean => assertType(is.boolean(value), 'boolean', value), - symbol: (value: unknown): asserts value is symbol => assertType(is.symbol(value), 'symbol', value), - numericString: (value: unknown): asserts value is `${number}` => assertType(is.numericString(value), 'string with a number', value), - array: (value: unknown, assertion?: (element: unknown) => asserts element is T): asserts value is T[] => { // eslint-disable-line object-shorthand - const assert: (condition: boolean, description: AssertionTypeDescription, value: unknown) => asserts condition = assertType; - assert(is.array(value), 'Array', value); - - if (assertion) { - // eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-callback-reference - value.forEach(assertion); - } - }, - buffer: (value: unknown): asserts value is Buffer => assertType(is.buffer(value), 'Buffer', value), - blob: (value: unknown): asserts value is Blob => assertType(is.blob(value), 'Blob', value), - nullOrUndefined: (value: unknown): asserts value is null | undefined => assertType(is.nullOrUndefined(value), 'null or undefined', value), // eslint-disable-line @typescript-eslint/ban-types - object: (value: unknown): asserts value is object => assertType(is.object(value), 'Object', value), // eslint-disable-line @typescript-eslint/ban-types - iterable: (value: unknown): asserts value is Iterable => assertType(is.iterable(value), 'Iterable', value), - asyncIterable: (value: unknown): asserts value is AsyncIterable => assertType(is.asyncIterable(value), 'AsyncIterable', value), - generator: (value: unknown): asserts value is Generator => assertType(is.generator(value), 'Generator', value), - asyncGenerator: (value: unknown): asserts value is AsyncGenerator => assertType(is.asyncGenerator(value), 'AsyncGenerator', value), - nativePromise: (value: unknown): asserts value is Promise => assertType(is.nativePromise(value), 'native Promise', value), - promise: (value: unknown): asserts value is Promise => assertType(is.promise(value), 'Promise', value), - generatorFunction: (value: unknown): asserts value is GeneratorFunction => assertType(is.generatorFunction(value), 'GeneratorFunction', value), - asyncGeneratorFunction: (value: unknown): asserts value is AsyncGeneratorFunction => assertType(is.asyncGeneratorFunction(value), 'AsyncGeneratorFunction', value), - // eslint-disable-next-line @typescript-eslint/ban-types - asyncFunction: (value: unknown): asserts value is Function => assertType(is.asyncFunction(value), 'AsyncFunction', value), - // eslint-disable-next-line @typescript-eslint/ban-types - boundFunction: (value: unknown): asserts value is Function => assertType(is.boundFunction(value), 'Function', value), - regExp: (value: unknown): asserts value is RegExp => assertType(is.regExp(value), 'RegExp', value), - date: (value: unknown): asserts value is Date => assertType(is.date(value), 'Date', value), - error: (value: unknown): asserts value is Error => assertType(is.error(value), 'Error', value), - map: (value: unknown): asserts value is Map => assertType(is.map(value), 'Map', value), // eslint-disable-line unicorn/no-array-callback-reference - set: (value: unknown): asserts value is Set => assertType(is.set(value), 'Set', value), - weakMap: (value: unknown): asserts value is WeakMap => assertType(is.weakMap(value), 'WeakMap', value), // eslint-disable-line @typescript-eslint/ban-types - weakSet: (value: unknown): asserts value is WeakSet => assertType(is.weakSet(value), 'WeakSet', value), // eslint-disable-line @typescript-eslint/ban-types - weakRef: (value: unknown): asserts value is WeakRef => assertType(is.weakRef(value), 'WeakRef', value), // eslint-disable-line @typescript-eslint/ban-types - int8Array: (value: unknown): asserts value is Int8Array => assertType(is.int8Array(value), 'Int8Array', value), - uint8Array: (value: unknown): asserts value is Uint8Array => assertType(is.uint8Array(value), 'Uint8Array', value), - uint8ClampedArray: (value: unknown): asserts value is Uint8ClampedArray => assertType(is.uint8ClampedArray(value), 'Uint8ClampedArray', value), - int16Array: (value: unknown): asserts value is Int16Array => assertType(is.int16Array(value), 'Int16Array', value), - uint16Array: (value: unknown): asserts value is Uint16Array => assertType(is.uint16Array(value), 'Uint16Array', value), - int32Array: (value: unknown): asserts value is Int32Array => assertType(is.int32Array(value), 'Int32Array', value), - uint32Array: (value: unknown): asserts value is Uint32Array => assertType(is.uint32Array(value), 'Uint32Array', value), - float32Array: (value: unknown): asserts value is Float32Array => assertType(is.float32Array(value), 'Float32Array', value), - float64Array: (value: unknown): asserts value is Float64Array => assertType(is.float64Array(value), 'Float64Array', value), - bigInt64Array: (value: unknown): asserts value is BigInt64Array => assertType(is.bigInt64Array(value), 'BigInt64Array', value), - bigUint64Array: (value: unknown): asserts value is BigUint64Array => assertType(is.bigUint64Array(value), 'BigUint64Array', value), - arrayBuffer: (value: unknown): asserts value is ArrayBuffer => assertType(is.arrayBuffer(value), 'ArrayBuffer', value), - sharedArrayBuffer: (value: unknown): asserts value is SharedArrayBuffer => assertType(is.sharedArrayBuffer(value), 'SharedArrayBuffer', value), - dataView: (value: unknown): asserts value is DataView => assertType(is.dataView(value), 'DataView', value), - enumCase: (value: unknown, targetEnum: T): asserts value is T[keyof T] => assertType(is.enumCase(value, targetEnum), 'EnumCase', value), - urlInstance: (value: unknown): asserts value is URL => assertType(is.urlInstance(value), 'URL', value), - urlString: (value: unknown): asserts value is string => assertType(is.urlString(value), 'string with a URL', value), - truthy: (value: T | Falsy): asserts value is T => assertType(is.truthy(value), 'truthy', value), - falsy: (value: unknown): asserts value is Falsy => assertType(is.falsy(value), 'falsy', value), - nan: (value: unknown): asserts value is number => assertType(is.nan(value), 'NaN', value), - primitive: (value: unknown): asserts value is Primitive => assertType(is.primitive(value), 'primitive', value), - integer: (value: unknown): asserts value is number => assertType(is.integer(value), 'integer', value), - safeInteger: (value: unknown): asserts value is number => assertType(is.safeInteger(value), 'integer', value), - plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), 'plain object', value), - typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), 'TypedArray', value), - arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), 'array-like', value), - tupleLike: >>(value: unknown, guards: [...T]): asserts value is ResolveTypesOfTypeGuardsTuple => assertType(is.tupleLike(value, guards), 'tuple-like', value), - domElement: (value: unknown): asserts value is HTMLElement => assertType(is.domElement(value), 'HTMLElement', value), - observable: (value: unknown): asserts value is ObservableLike => assertType(is.observable(value), 'Observable', value), - nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), 'Node.js Stream', value), - infinite: (value: unknown): asserts value is number => assertType(is.infinite(value), 'infinite number', value), - emptyArray: (value: unknown): asserts value is never[] => assertType(is.emptyArray(value), 'empty array', value), - nonEmptyArray: (value: T | Item[]): asserts value is [Item, ...Item[]] => assertType(is.nonEmptyArray(value), 'non-empty array', value), - emptyString: (value: unknown): asserts value is '' => assertType(is.emptyString(value), 'empty string', value), - emptyStringOrWhitespace: (value: unknown): asserts value is string => assertType(is.emptyStringOrWhitespace(value), 'empty string or whitespace', value), - nonEmptyString: (value: unknown): asserts value is string => assertType(is.nonEmptyString(value), 'non-empty string', value), - nonEmptyStringAndNotWhitespace: (value: unknown): asserts value is string => assertType(is.nonEmptyStringAndNotWhitespace(value), 'non-empty string and not whitespace', value), - emptyObject: (value: unknown): asserts value is Record => assertType(is.emptyObject(value), 'empty object', value), - nonEmptyObject: (value: unknown): asserts value is Record => assertType(is.nonEmptyObject(value), 'non-empty object', value), - emptySet: (value: unknown): asserts value is Set => assertType(is.emptySet(value), 'empty set', value), - nonEmptySet: (value: unknown): asserts value is Set => assertType(is.nonEmptySet(value), 'non-empty set', value), - emptyMap: (value: unknown): asserts value is Map => assertType(is.emptyMap(value), 'empty map', value), - nonEmptyMap: (value: unknown): asserts value is Map => assertType(is.nonEmptyMap(value), 'non-empty map', value), - propertyKey: (value: unknown): asserts value is number => assertType(is.propertyKey(value), 'PropertyKey', value), - formData: (value: unknown): asserts value is FormData => assertType(is.formData(value), 'FormData', value), - urlSearchParams: (value: unknown): asserts value is URLSearchParams => assertType(is.urlSearchParams(value), 'URLSearchParams', value), - - // Numbers. - evenInteger: (value: number): asserts value is number => assertType(is.evenInteger(value), 'even integer', value), - oddInteger: (value: number): asserts value is number => assertType(is.oddInteger(value), 'odd integer', value), - - // Two arguments. - directInstanceOf: (instance: unknown, class_: Class): asserts instance is T => assertType(is.directInstanceOf(instance, class_), 'T', instance), - inRange: (value: number, range: number | number[]): asserts value is number => assertType(is.inRange(value, range), 'in range', value), - - // Variadic functions. - any: (predicate: Predicate | Predicate[], ...values: unknown[]): void | never => assertType(is.any(predicate, ...values), 'predicate returns truthy for any value', values, {multipleValues: true}), - all: (predicate: Predicate, ...values: unknown[]): void | never => assertType(is.all(predicate, ...values), 'predicate returns truthy for all values', values, {multipleValues: true}), + all: assertAll, + any: assertAny, + array: assertArray, + arrayBuffer: assertArrayBuffer, + arrayLike: assertArrayLike, + asyncFunction: assertAsyncFunction, + asyncGenerator: assertAsyncGenerator, + asyncGeneratorFunction: assertAsyncGeneratorFunction, + asyncIterable: assertAsyncIterable, + bigint: assertBigint, + bigInt64Array: assertBigInt64Array, + bigUint64Array: assertBigUint64Array, + blob: assertBlob, + boolean: assertBoolean, + boundFunction: assertBoundFunction, + buffer: assertBuffer, + class: assertClass, + class_: assertClass, + dataView: assertDataView, + date: assertDate, + directInstanceOf: assertDirectInstanceOf, + domElement: assertDomElement, + emptyArray: assertEmptyArray, + emptyMap: assertEmptyMap, + emptyObject: assertEmptyObject, + emptySet: assertEmptySet, + emptyString: assertEmptyString, + emptyStringOrWhitespace: assertEmptyStringOrWhitespace, + enumCase: assertEnumCase, + error: assertError, + evenInteger: assertEvenInteger, + falsy: assertFalsy, + float32Array: assertFloat32Array, + float64Array: assertFloat64Array, + formData: assertFormData, + function: assertFunction, + function_: assertFunction, + generator: assertGenerator, + generatorFunction: assertGeneratorFunction, + infinite: assertInfinite, + inRange: assertInRange, + int16Array: assertInt16Array, + int32Array: assertInt32Array, + int8Array: assertInt8Array, + integer: assertInteger, + iterable: assertIterable, + map: assertMap, + nan: assertNan, + nativePromise: assertNativePromise, + negativeNumber: assertNegativeNumber, + nodeStream: assertNodeStream, + nonEmptyArray: assertNonEmptyArray, + nonEmptyMap: assertNonEmptyMap, + nonEmptyObject: assertNonEmptyObject, + nonEmptySet: assertNonEmptySet, + nonEmptyString: assertNonEmptyString, + nonEmptyStringAndNotWhitespace: assertNonEmptyStringAndNotWhitespace, + null: assertNull, + null_: assertNull, + nullOrUndefined: assertNullOrUndefined, + number: assertNumber, + numericString: assertNumericString, + object: assertObject, + observable: assertObservable, + oddInteger: assertOddInteger, + plainObject: assertPlainObject, + positiveNumber: assertPositiveNumber, + primitive: assertPrimitive, + promise: assertPromise, + propertyKey: assertPropertyKey, + regExp: assertRegExp, + safeInteger: assertSafeInteger, + set: assertSet, + sharedArrayBuffer: assertSharedArrayBuffer, + string: assertString, + symbol: assertSymbol, + truthy: assertTruthy, + tupleLike: assertTupleLike, + typedArray: assertTypedArray, + uint16Array: assertUint16Array, + uint32Array: assertUint32Array, + uint8Array: assertUint8Array, + uint8ClampedArray: assertUint8ClampedArray, + undefined: assertUndefined, + urlInstance: assertUrlInstance, + urlSearchParams: assertUrlSearchParams, + urlString: assertUrlString, + validLength: assertValidLength, + weakMap: assertWeakMap, + weakRef: assertWeakRef, + weakSet: assertWeakSet, + whitespaceString: assertWhitespaceString, }; -/* eslint-enable @typescript-eslint/no-confusing-void-expression */ -// Some few keywords are reserved, but we'll populate them for Node.js users -// See https://github.com/Microsoft/TypeScript/issues/2536 -Object.defineProperties(is, { - class: { - value: is.class_, - }, - function: { - value: is.function_, - }, - null: { - value: is.null_, - }, -}); -Object.defineProperties(assert, { - class: { - value: assert.class_, - }, - function: { - value: assert.function_, - }, - null: { - value: assert.null_, - }, -}); +export function assertAll(predicate: Predicate, ...values: unknown[]): void | never { + if (!isAll(predicate, ...values)) { + throw new TypeError(typeErrorMessageMultipleValue('predicate returns truthy for all values', values)); + } +} + +export function assertAny(predicate: Predicate | Predicate[], ...values: unknown[]): void | never { + if (!isAny(predicate, ...values)) { + throw new TypeError(typeErrorMessageMultipleValue('predicate returns truthy for any value', values)); + } +} + +export function assertArray(value: unknown, assertion?: (element: unknown) => asserts element is T): asserts value is T[] { + if (!isArray(value)) { + throw new TypeError(typeErrorMessage('Array', value)); + } + + if (assertion) { + // eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-callback-reference + value.forEach(assertion); + } +} + +export function assertArrayBuffer(value: unknown): asserts value is ArrayBuffer { + if (!isArrayBuffer(value)) { + throw new TypeError(typeErrorMessage('ArrayBuffer', value)); + } +} + +export function assertArrayLike(value: unknown): asserts value is ArrayLike { + if (!isArrayLike(value)) { + throw new TypeError(typeErrorMessage('array-like', value)); + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function assertAsyncFunction(value: unknown): asserts value is Function { + if (!isAsyncFunction(value)) { + throw new TypeError(typeErrorMessage('AsyncFunction', value)); + } +} + +export function assertAsyncGenerator(value: unknown): asserts value is AsyncGenerator { + if (!isAsyncGenerator(value)) { + throw new TypeError(typeErrorMessage('AsyncGenerator', value)); + } +} + +export function assertAsyncGeneratorFunction(value: unknown): asserts value is AsyncGeneratorFunction { + if (!isAsyncGeneratorFunction(value)) { + throw new TypeError(typeErrorMessage('AsyncGeneratorFunction', value)); + } +} + +export function assertAsyncIterable(value: unknown): asserts value is AsyncIterable { + if (!isAsyncIterable(value)) { + throw new TypeError(typeErrorMessage('AsyncIterable', value)); + } +} + +export function assertBigint(value: unknown): asserts value is bigint { + if (!isBigint(value)) { + throw new TypeError(typeErrorMessage('bigint', value)); + } +} + +export function assertBigInt64Array(value: unknown): asserts value is BigInt64Array { + if (!isBigInt64Array(value)) { + throw new TypeError(typeErrorMessage('BigInt64Array', value)); + } +} + +export function assertBigUint64Array(value: unknown): asserts value is BigUint64Array { + if (!isBigUint64Array(value)) { + throw new TypeError(typeErrorMessage('BigUint64Array', value)); + } +} + +export function assertBlob(value: unknown): asserts value is Blob { + if (!isBlob(value)) { + throw new TypeError(typeErrorMessage('Blob', value)); + } +} + +export function assertBoolean(value: unknown): asserts value is boolean { + if (!isBoolean(value)) { + throw new TypeError(typeErrorMessage('boolean', value)); + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function assertBoundFunction(value: unknown): asserts value is Function { + if (!isBoundFunction(value)) { + throw new TypeError(typeErrorMessage('Function', value)); + } +} + +export function assertBuffer(value: unknown): asserts value is Buffer { + if (!isBuffer(value)) { + throw new TypeError(typeErrorMessage('Buffer', value)); + } +} + +export function assertClass(value: unknown): asserts value is Class { + if (!isClass(value)) { + throw new TypeError(typeErrorMessage('Class', value)); + } +} + +export function assertDataView(value: unknown): asserts value is DataView { + if (!isDataView(value)) { + throw new TypeError(typeErrorMessage('DataView', value)); + } +} + +export function assertDate(value: unknown): asserts value is Date { + if (!isDate(value)) { + throw new TypeError(typeErrorMessage('Date', value)); + } +} + +export function assertDirectInstanceOf(instance: unknown, class_: Class): asserts instance is T { + if (!isDirectInstanceOf(instance, class_)) { + throw new TypeError(typeErrorMessage('T', instance)); + } +} + +export function assertEmptyArray(value: unknown): asserts value is never[] { + if (!isEmptyArray(value)) { + throw new TypeError(typeErrorMessage('empty array', value)); + } +} + +export function assertEmptyMap(value: unknown): asserts value is Map { + if (!isEmptyMap(value)) { + throw new TypeError(typeErrorMessage('empty map', value)); + } +} + +export function assertEmptyObject(value: unknown): asserts value is Record { + if (!isEmptyObject(value)) { + throw new TypeError(typeErrorMessage('empty object', value)); + } +} + +export function assertEmptySet(value: unknown): asserts value is Set { + if (!isEmptySet(value)) { + throw new TypeError(typeErrorMessage('empty set', value)); + } +} + +export function assertEmptyString(value: unknown): asserts value is '' { + if (!isEmptyString(value)) { + throw new TypeError(typeErrorMessage('empty string', value)); + } +} + +export function assertEmptyStringOrWhitespace(value: unknown): asserts value is string { + if (!isEmptyStringOrWhitespace(value)) { + throw new TypeError(typeErrorMessage('empty string or whitespace', value)); + } +} + +export function assertEnumCase(value: unknown, targetEnum: T): asserts value is T[keyof T] { + if (!isEnumCase(value, targetEnum)) { + throw new TypeError(typeErrorMessage('EnumCase', value)); + } +} + +export function assertError(value: unknown): asserts value is Error { + if (!isError(value)) { + throw new TypeError(typeErrorMessage('Error', value)); + } +} + +export function assertEvenInteger(value: number): asserts value is number { + if (!isEvenInteger(value)) { + throw new TypeError(typeErrorMessage('even integer', value)); + } +} + +export function assertFalsy(value: unknown): asserts value is Falsy { + if (!isFalsy(value)) { + throw new TypeError(typeErrorMessage('falsy', value)); + } +} + +export function assertFloat32Array(value: unknown): asserts value is Float32Array { + if (!isFloat32Array(value)) { + throw new TypeError(typeErrorMessage('Float32Array', value)); + } +} + +export function assertFloat64Array(value: unknown): asserts value is Float64Array { + if (!isFloat64Array(value)) { + throw new TypeError(typeErrorMessage('Float64Array', value)); + } +} + +export function assertFormData(value: unknown): asserts value is FormData { + if (!isFormData(value)) { + throw new TypeError(typeErrorMessage('FormData', value)); + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function assertFunction(value: unknown): asserts value is Function { + if (!isFunction(value)) { + throw new TypeError(typeErrorMessage('Function', value)); + } +} + +export function assertGenerator(value: unknown): asserts value is Generator { + if (!isGenerator(value)) { + throw new TypeError(typeErrorMessage('Generator', value)); + } +} + +export function assertGeneratorFunction(value: unknown): asserts value is GeneratorFunction { + if (!isGeneratorFunction(value)) { + throw new TypeError(typeErrorMessage('GeneratorFunction', value)); + } +} + +export function assertDomElement(value: unknown): asserts value is HTMLElement { + if (!isDomElement(value)) { + throw new TypeError(typeErrorMessage('HTMLElement', value)); + } +} + +export function assertInfinite(value: unknown): asserts value is number { + if (!isInfinite(value)) { + throw new TypeError(typeErrorMessage('infinite number', value)); + } +} + +export function assertInRange(value: number, range: number | [number, number]): asserts value is number { + if (!isInRange(value, range)) { + throw new TypeError(typeErrorMessage('in range', value)); + } +} + +export function assertInt16Array(value: unknown): asserts value is Int16Array { + if (!isInt16Array(value)) { + throw new TypeError(typeErrorMessage('Int16Array', value)); + } +} + +export function assertInt32Array(value: unknown): asserts value is Int32Array { + if (!isInt32Array(value)) { + throw new TypeError(typeErrorMessage('Int32Array', value)); + } +} + +export function assertInt8Array(value: unknown): asserts value is Int8Array { + if (!isInt8Array(value)) { + throw new TypeError(typeErrorMessage('Int8Array', value)); + } +} + +export function assertInteger(value: unknown): asserts value is number { + if (!isInteger(value)) { + throw new TypeError(typeErrorMessage('integer', value)); + } +} + +export function assertIterable(value: unknown): asserts value is Iterable { + if (!isIterable(value)) { + throw new TypeError(typeErrorMessage('Iterable', value)); + } +} + +export function assertNativePromise(value: unknown): asserts value is Promise { + if (!isNativePromise(value)) { + throw new TypeError(typeErrorMessage('native Promise', value)); + } +} + +export function assertMap(value: unknown): asserts value is Map { + if (!isMap(value)) { + throw new TypeError(typeErrorMessage('Map', value)); + } +} + +export function assertNan(value: unknown): asserts value is number { + if (!isNan(value)) { + throw new TypeError(typeErrorMessage('NaN', value)); + } +} + +export function assertNegativeNumber(value: unknown): asserts value is number { + if (!isNegativeNumber(value)) { + throw new TypeError(typeErrorMessage('negative number', value)); + } +} + +export function assertNodeStream(value: unknown): asserts value is NodeStream { + if (!isNodeStream(value)) { + throw new TypeError(typeErrorMessage('Node.js Stream', value)); + } +} + +export function assertNonEmptyArray(value: T | Item[]): asserts value is [Item, ...Item[]] { + if (!isNonEmptyArray(value)) { + throw new TypeError(typeErrorMessage('non-empty array', value)); + } +} + +export function assertNonEmptyMap(value: unknown): asserts value is Map { + if (!isNonEmptyMap(value)) { + throw new TypeError(typeErrorMessage('non-empty map', value)); + } +} + +export function assertNonEmptyObject(value: unknown): asserts value is Record { + if (!isNonEmptyObject(value)) { + throw new TypeError(typeErrorMessage('non-empty object', value)); + } +} + +export function assertNonEmptySet(value: unknown): asserts value is Set { + if (!isNonEmptySet(value)) { + throw new TypeError(typeErrorMessage('non-empty set', value)); + } +} + +export function assertNonEmptyString(value: unknown): asserts value is string { + if (!isNonEmptyString(value)) { + throw new TypeError(typeErrorMessage('non-empty string', value)); + } +} + +export function assertNonEmptyStringAndNotWhitespace(value: unknown): asserts value is string { + if (!isNonEmptyStringAndNotWhitespace(value)) { + throw new TypeError(typeErrorMessage('non-empty string and not whitespace', value)); + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function assertNull(value: unknown): asserts value is null { + if (!isNull(value)) { + throw new TypeError(typeErrorMessage('null', value)); + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function assertNullOrUndefined(value: unknown): asserts value is null | undefined { + if (!isNullOrUndefined(value)) { + throw new TypeError(typeErrorMessage('null or undefined', value)); + } +} + +export function assertNumber(value: unknown): asserts value is number { + if (!isNumber(value)) { + throw new TypeError(typeErrorMessage('number', value)); + } +} + +export function assertNumericString(value: unknown): asserts value is `${number}` { + if (!isNumericString(value)) { + throw new TypeError(typeErrorMessage('string with a number', value)); + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function assertObject(value: unknown): asserts value is object { + if (!isObject(value)) { + throw new TypeError(typeErrorMessage('Object', value)); + } +} + +export function assertObservable(value: unknown): asserts value is ObservableLike { + if (!isObservable(value)) { + throw new TypeError(typeErrorMessage('Observable', value)); + } +} + +export function assertOddInteger(value: number): asserts value is number { + if (!isOddInteger(value)) { + throw new TypeError(typeErrorMessage('odd integer', value)); + } +} + +export function assertPlainObject(value: unknown): asserts value is Record { + if (!isPlainObject(value)) { + throw new TypeError(typeErrorMessage('plain object', value)); + } +} + +export function assertPositiveNumber(value: unknown): asserts value is number { + if (!isPositiveNumber(value)) { + throw new TypeError(typeErrorMessage('positive number', value)); + } +} + +export function assertPrimitive(value: unknown): asserts value is Primitive { + if (!isPrimitive(value)) { + throw new TypeError(typeErrorMessage('primitive', value)); + } +} + +export function assertPromise(value: unknown): asserts value is Promise { + if (!isPromise(value)) { + throw new TypeError(typeErrorMessage('Promise', value)); + } +} + +export function assertPropertyKey(value: unknown): asserts value is number { + if (!isPropertyKey(value)) { + throw new TypeError(typeErrorMessage('PropertyKey', value)); + } +} + +export function assertRegExp(value: unknown): asserts value is RegExp { + if (!isRegExp(value)) { + throw new TypeError(typeErrorMessage('RegExp', value)); + } +} + +export function assertSafeInteger(value: unknown): asserts value is number { + if (!isSafeInteger(value)) { + throw new TypeError(typeErrorMessage('integer', value)); + } +} + +export function assertSet(value: unknown): asserts value is Set { + if (!isSet(value)) { + throw new TypeError(typeErrorMessage('Set', value)); + } +} + +export function assertSharedArrayBuffer(value: unknown): asserts value is SharedArrayBuffer { + if (!isSharedArrayBuffer(value)) { + throw new TypeError(typeErrorMessage('SharedArrayBuffer', value)); + } +} + +export function assertString(value: unknown): asserts value is string { + if (!isString(value)) { + throw new TypeError(typeErrorMessage('string', value)); + } +} + +export function assertSymbol(value: unknown): asserts value is symbol { + if (!isSymbol(value)) { + throw new TypeError(typeErrorMessage('symbol', value)); + } +} + +export function assertTruthy(value: T | Falsy): asserts value is T { + if (!isTruthy(value)) { + throw new TypeError(typeErrorMessage('truthy', value)); + } +} + +export function assertTupleLike>>(value: unknown, guards: [...T]): asserts value is ResolveTypesOfTypeGuardsTuple { + if (!isTupleLike(value, guards)) { + throw new TypeError(typeErrorMessage('tuple-like', value)); + } +} + +export function assertTypedArray(value: unknown): asserts value is TypedArray { + if (!isTypedArray(value)) { + throw new TypeError(typeErrorMessage('TypedArray', value)); + } +} + +export function assertUint16Array(value: unknown): asserts value is Uint16Array { + if (!isUint16Array(value)) { + throw new TypeError(typeErrorMessage('Uint16Array', value)); + } +} + +export function assertUint32Array(value: unknown): asserts value is Uint32Array { + if (!isUint32Array(value)) { + throw new TypeError(typeErrorMessage('Uint32Array', value)); + } +} + +export function assertUint8Array(value: unknown): asserts value is Uint8Array { + if (!isUint8Array(value)) { + throw new TypeError(typeErrorMessage('Uint8Array', value)); + } +} + +export function assertUint8ClampedArray(value: unknown): asserts value is Uint8ClampedArray { + if (!isUint8ClampedArray(value)) { + throw new TypeError(typeErrorMessage('Uint8ClampedArray', value)); + } +} + +export function assertUndefined(value: unknown): asserts value is undefined { + if (!isUndefined(value)) { + throw new TypeError(typeErrorMessage('undefined', value)); + } +} + +export function assertUrlInstance(value: unknown): asserts value is URL { + if (!isUrlInstance(value)) { + throw new TypeError(typeErrorMessage('URL', value)); + } +} + +// eslint-disable-next-line unicorn/prevent-abbreviations +export function assertUrlSearchParams(value: unknown): asserts value is URLSearchParams { + if (!isUrlSearchParams(value)) { + throw new TypeError(typeErrorMessage('URLSearchParams', value)); + } +} + +export function assertUrlString(value: unknown): asserts value is string { + if (!isUrlString(value)) { + throw new TypeError(typeErrorMessage('string with a URL', value)); + } +} + +export function assertValidLength(value: unknown): asserts value is number { + if (!isValidLength(value)) { + throw new TypeError(typeErrorMessage('valid length', value)); + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function assertWeakMap(value: unknown): asserts value is WeakMap { + if (!isWeakMap(value)) { + throw new TypeError(typeErrorMessage('WeakMap', value)); + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function assertWeakRef(value: unknown): asserts value is WeakRef { + if (!isWeakRef(value)) { + throw new TypeError(typeErrorMessage('WeakRef', value)); + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function assertWeakSet(value: unknown): asserts value is WeakSet { + if (!isWeakSet(value)) { + throw new TypeError(typeErrorMessage('WeakSet', value)); + } +} + +export function assertWhitespaceString(value: unknown): asserts value is string { + if (!isWhitespaceString(value)) { + throw new TypeError(typeErrorMessage('whitespace string', value)); + } +} export default is; -export type {Class, TypedArray, ObservableLike, Primitive} from './types.js'; +export type { + ArrayLike, + Class, + NodeStream, + ObservableLike, + Predicate, + Primitive, + TypedArray, +} from './types.js'; diff --git a/source/types.ts b/source/types.ts index 60411b2..621c771 100644 --- a/source/types.ts +++ b/source/types.ts @@ -62,3 +62,14 @@ export type WeakRef = { // eslint-disable-line @typescript-esl readonly [Symbol.toStringTag]: 'WeakRef'; deref(): T | undefined; }; + +export type ArrayLike = { + readonly [index: number]: T; + readonly length: number; +}; + +export type NodeStream = { + pipe(destination: T, options?: {end?: boolean}): T; +} & NodeJS.EventEmitter; + +export type Predicate = (value: unknown) => boolean; diff --git a/test/test.ts b/test/test.ts index a719c11..1db8d75 100644 --- a/test/test.ts +++ b/test/test.ts @@ -61,7 +61,7 @@ const types = new Map([ typename: 'undefined', }], ['null', { - is: is.null_, + is: is.null, assert: assert.null_, fixtures: [ null, @@ -161,7 +161,7 @@ const types = new Map([ typeDescription: 'empty array', }], ['function', { - is: is.function_, + is: is.function, assert: assert.function_, fixtures: [ function foo() {}, // eslint-disable-line func-names @@ -838,7 +838,7 @@ test('is.asyncFunction', t => { const fixture = async () => {}; if (is.asyncFunction(fixture)) { - t.true(is.function_(fixture().then)); + t.true(is.function(fixture().then)); t.notThrows(() => { assert.function_(fixture().then); @@ -857,7 +857,7 @@ test('is.asyncGenerator', t => { yield 4; })(); if (is.asyncGenerator(fixture)) { - t.true(is.function_(fixture.next)); + t.true(is.function(fixture.next)); } }); @@ -873,7 +873,7 @@ test('is.asyncGeneratorFunction', t => { }; if (is.asyncGeneratorFunction(fixture)) { - t.true(is.function_(fixture().next)); + t.true(is.function(fixture().next)); } }); @@ -1365,7 +1365,7 @@ test('is.class', t => { ]; for (const classDeclaration of classDeclarations) { - t.true(is.class_(classDeclaration)); + t.true(is.class(classDeclaration)); t.notThrows(() => { assert.class_(classDeclaration); @@ -1456,7 +1456,7 @@ test('is.tupleLike', t => { t.false(is.tupleLike('unicorn', [is.string])); t.false(is.tupleLike({}, [])); - t.false(is.tupleLike(() => {}, [is.function_])); + t.false(is.tupleLike(() => {}, [is.function])); t.false(is.tupleLike(new Map(), [is.map])); (function () { @@ -1476,7 +1476,7 @@ test('is.tupleLike', t => { assert.tupleLike({}, [is.object]); }); t.throws(() => { - assert.tupleLike(() => {}, [is.function_]); + assert.tupleLike(() => {}, [is.function]); }); t.throws(() => { assert.tupleLike(new Map(), [is.map]); @@ -1496,7 +1496,7 @@ test('is.tupleLike', t => { { const tuple = [{isTest: true}, '1', true, null]; - if (is.tupleLike(tuple, [is.nonEmptyObject, is.string, is.boolean, is.null_])) { + if (is.tupleLike(tuple, [is.nonEmptyObject, is.string, is.boolean, is.null])) { const value = tuple[0]; expectTypeOf(value).toEqualTypeOf>(); } @@ -1505,7 +1505,7 @@ test('is.tupleLike', t => { { const tuple = [1, '1', true, null, undefined]; - if (is.tupleLike(tuple, [is.number, is.string, is.boolean, is.undefined, is.null_])) { + if (is.tupleLike(tuple, [is.number, is.string, is.boolean, is.undefined, is.null])) { const numericValue = tuple[0]; const stringValue = tuple[1]; const booleanValue = tuple[2]; @@ -1540,14 +1540,17 @@ test('is.inRange', t => { t.false(is.inRange(-3, -2)); t.throws(() => { + // @ts-expect-error invalid argument is.inRange(0, []); }); t.throws(() => { + // @ts-expect-error invalid argument is.inRange(0, [5]); }); t.throws(() => { + // @ts-expect-error invalid argument is.inRange(0, [1, 2, 3]); }); @@ -1604,14 +1607,17 @@ test('is.inRange', t => { }); t.throws(() => { + // @ts-expect-error invalid argument assert.inRange(0, []); }); t.throws(() => { + // @ts-expect-error invalid argument assert.inRange(0, [5]); }); t.throws(() => { + // @ts-expect-error invalid argument assert.inRange(0, [1, 2, 3]); }); }); @@ -2072,6 +2078,30 @@ test('is.urlSearchParams', t => { }); }); +test('is.validLength', t => { + t.true(is.validLength(1)); + t.true(is.validLength(0)); + t.false(is.validLength(-1)); + t.false(is.validLength(0.1)); + t.notThrows(() => { + assert.validLength(1); + }); + t.throws(() => { + assert.validLength(-1); + }); +}); + +test('is.whitespaceString', t => { + t.true(is.whitespaceString(' ')); + t.true(is.whitespaceString(' ')); + t.true(is.whitespaceString('   ')); + t.true(is.whitespaceString('\u3000')); + t.true(is.whitespaceString(' ')); + t.false(is.whitespaceString('')); + t.false(is.whitespaceString('-')); + t.false(is.whitespaceString(' hi ')); +}); + test('assert', t => { // Contrived test showing that TypeScript acknowledges the type assertion in `assert.number()`. // Real--world usage includes asserting user input, but here we use a random number/string generator. From e03c249d6ccb8fc3c52f20a1e2fa18af06d2b59d Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Mon, 7 Aug 2023 23:18:36 +0800 Subject: [PATCH 204/254] Drop support for Node.js 14 (#192) --- .github/workflows/main.yml | 1 - package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d50ada6..6a82b18 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,6 @@ jobs: node-version: - 18 - 16 - - 14 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/package.json b/package.json index 782a527..a0902d3 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "exports": "./dist/index.js", "types": "./dist/index.d.ts", "engines": { - "node": ">=14.16" + "node": ">=16" }, "scripts": { "build": "del dist && tsc", From 85c89925b6407972121200f91fa122ace937973e Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Wed, 9 Aug 2023 19:49:06 +0800 Subject: [PATCH 205/254] Give better assertion messages for `assert.all` and `assert.any` (#193) --- source/index.ts | 146 ++++++++++++++++++++++++++++++++++++++++++------ test/test.ts | 39 ++++++++++--- tsconfig.json | 8 ++- 3 files changed, 166 insertions(+), 27 deletions(-) diff --git a/source/index.ts b/source/index.ts index 0239440..137dd40 100644 --- a/source/index.ts +++ b/source/index.ts @@ -794,10 +794,18 @@ function typeErrorMessage(description: AssertionTypeDescription, value: unknown) return `Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`; } -function typeErrorMessageMultipleValue(description: AssertionTypeDescription, values: unknown[]): string { +function unique(values: T[]): T[] { // eslint-disable-next-line unicorn/prefer-spread - const valueTypes = Array.from(new Set(values.map(singleValue => `\`${is(singleValue)}\``))).join(', '); - return `Expected value which is \`${description}\`, received values of types ${valueTypes}.`; + return Array.from(new Set(values)); +} + +const andFormatter = new Intl.ListFormat('en', {style: 'long', type: 'conjunction'}); +const orFormatter = new Intl.ListFormat('en', {style: 'long', type: 'disjunction'}); + +function typeErrorMessageMultipleValues(expectedType: AssertionTypeDescription | AssertionTypeDescription[], values: unknown[]): string { + const uniqueExpectedTypes = unique((isArray(expectedType) ? expectedType : [expectedType]).map(value => `\`${value}\``)); + const uniqueValueTypes = unique(values.map(value => `\`${is(value)}\``)); + return `Expected values which are ${orFormatter.format(uniqueExpectedTypes)}. Received values of type${uniqueValueTypes.length > 1 ? 's' : ''} ${andFormatter.format(uniqueValueTypes)}.`; } // Type assertions have to be declared with an explicit type. @@ -1011,15 +1019,119 @@ export const assert: Assert = { whitespaceString: assertWhitespaceString, }; +const methodTypeMap = { + isArray: 'Array', + isArrayBuffer: 'ArrayBuffer', + isArrayLike: 'array-like', + isAsyncFunction: 'AsyncFunction', + isAsyncGenerator: 'AsyncGenerator', + isAsyncGeneratorFunction: 'AsyncGeneratorFunction', + isAsyncIterable: 'AsyncIterable', + isBigint: 'bigint', + isBigInt64Array: 'BigInt64Array', + isBigUint64Array: 'BigUint64Array', + isBlob: 'Blob', + isBoolean: 'boolean', + isBoundFunction: 'Function', + isBuffer: 'Buffer', + isClass: 'Class', + isDataView: 'DataView', + isDate: 'Date', + isDirectInstanceOf: 'T', + isDomElement: 'HTMLElement', + isEmptyArray: 'empty array', + isEmptyMap: 'empty map', + isEmptyObject: 'empty object', + isEmptySet: 'empty set', + isEmptyString: 'empty string', + isEmptyStringOrWhitespace: 'empty string or whitespace', + isEnumCase: 'EnumCase', + isError: 'Error', + isEvenInteger: 'even integer', + isFalsy: 'falsy', + isFloat32Array: 'Float32Array', + isFloat64Array: 'Float64Array', + isFormData: 'FormData', + isFunction: 'Function', + isGenerator: 'Generator', + isGeneratorFunction: 'GeneratorFunction', + isInfinite: 'infinite number', + isInRange: 'in range', + isInt16Array: 'Int16Array', + isInt32Array: 'Int32Array', + isInt8Array: 'Int8Array', + isInteger: 'integer', + isIterable: 'Iterable', + isMap: 'Map', + isNan: 'NaN', + isNativePromise: 'native Promise', + isNegativeNumber: 'negative number', + isNodeStream: 'Node.js Stream', + isNonEmptyArray: 'non-empty array', + isNonEmptyMap: 'non-empty map', + isNonEmptyObject: 'non-empty object', + isNonEmptySet: 'non-empty set', + isNonEmptyString: 'non-empty string', + isNonEmptyStringAndNotWhitespace: 'non-empty string and not whitespace', + isNull: 'null', + isNullOrUndefined: 'null or undefined', + isNumber: 'number', + isNumericString: 'string with a number', + isObject: 'Object', + isObservable: 'Observable', + isOddInteger: 'odd integer', + isPlainObject: 'plain object', + isPositiveNumber: 'positive number', + isPrimitive: 'primitive', + isPromise: 'Promise', + isPropertyKey: 'PropertyKey', + isRegExp: 'RegExp', + isSafeInteger: 'integer', + isSet: 'Set', + isSharedArrayBuffer: 'SharedArrayBuffer', + isString: 'string', + isSymbol: 'symbol', + isTruthy: 'truthy', + isTupleLike: 'tuple-like', + isTypedArray: 'TypedArray', + isUint16Array: 'Uint16Array', + isUint32Array: 'Uint32Array', + isUint8Array: 'Uint8Array', + isUint8ClampedArray: 'Uint8ClampedArray', + isUndefined: 'undefined', + isUrlInstance: 'URL', + isUrlSearchParams: 'URLSearchParams', + isUrlString: 'string with a URL', + isValidLength: 'valid length', + isWeakMap: 'WeakMap', + isWeakRef: 'WeakRef', + isWeakSet: 'WeakSet', + isWhitespaceString: 'whitespace string', +} as const; + +function keysOf>(value: T): Array { + return Object.keys(value) as Array; +} + +type IsMethodName = keyof typeof methodTypeMap; +const isMethodNames: IsMethodName[] = keysOf(methodTypeMap); + +function isIsMethodName(value: unknown): value is IsMethodName { + return isMethodNames.includes(value as IsMethodName); +} + export function assertAll(predicate: Predicate, ...values: unknown[]): void | never { if (!isAll(predicate, ...values)) { - throw new TypeError(typeErrorMessageMultipleValue('predicate returns truthy for all values', values)); + const expectedType = isIsMethodName(predicate.name) ? methodTypeMap[predicate.name] : 'predicate returns truthy for all values'; + throw new TypeError(typeErrorMessageMultipleValues(expectedType, values)); } } export function assertAny(predicate: Predicate | Predicate[], ...values: unknown[]): void | never { if (!isAny(predicate, ...values)) { - throw new TypeError(typeErrorMessageMultipleValue('predicate returns truthy for any value', values)); + const predicates = isArray(predicate) ? predicate : [predicate]; + const expectedTypes = predicates.map(predicate => isIsMethodName(predicate.name) ? methodTypeMap[predicate.name] : 'predicate returns truthy for any value'); + throw new TypeError(typeErrorMessageMultipleValues(expectedTypes, values)); } } @@ -1138,6 +1250,12 @@ export function assertDirectInstanceOf(instance: unknown, class_: Class): } } +export function assertDomElement(value: unknown): asserts value is HTMLElement { + if (!isDomElement(value)) { + throw new TypeError(typeErrorMessage('HTMLElement', value)); + } +} + export function assertEmptyArray(value: unknown): asserts value is never[] { if (!isEmptyArray(value)) { throw new TypeError(typeErrorMessage('empty array', value)); @@ -1235,12 +1353,6 @@ export function assertGeneratorFunction(value: unknown): asserts value is Genera } } -export function assertDomElement(value: unknown): asserts value is HTMLElement { - if (!isDomElement(value)) { - throw new TypeError(typeErrorMessage('HTMLElement', value)); - } -} - export function assertInfinite(value: unknown): asserts value is number { if (!isInfinite(value)) { throw new TypeError(typeErrorMessage('infinite number', value)); @@ -1283,12 +1395,6 @@ export function assertIterable(value: unknown): asserts value is It } } -export function assertNativePromise(value: unknown): asserts value is Promise { - if (!isNativePromise(value)) { - throw new TypeError(typeErrorMessage('native Promise', value)); - } -} - export function assertMap(value: unknown): asserts value is Map { if (!isMap(value)) { throw new TypeError(typeErrorMessage('Map', value)); @@ -1301,6 +1407,12 @@ export function assertNan(value: unknown): asserts value is number { } } +export function assertNativePromise(value: unknown): asserts value is Promise { + if (!isNativePromise(value)) { + throw new TypeError(typeErrorMessage('native Promise', value)); + } +} + export function assertNegativeNumber(value: unknown): asserts value is number { if (!isNegativeNumber(value)) { throw new TypeError(typeErrorMessage('negative number', value)); diff --git a/test/test.ts b/test/test.ts index 1db8d75..d28fb1a 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1960,22 +1960,36 @@ test('is.any', t => { t.throws(() => { assert.any(is.string, 1, 2, 3); }, { - // Removes duplicates: - message: /received values of types `number`./, + // Includes expected type and removes duplicates from received types: + message: /Expected values which are `string`. Received values of type `number`./, }); t.throws(() => { assert.any(is.string, 1, [4]); }, { - // Lists all types: - message: /received values of types `number`, `Array`./, + // Includes expected type and lists all received types: + message: /Expected values which are `string`. Received values of types `number` and `Array`./, }); t.throws(() => { assert.any([is.string, is.nullOrUndefined], 1); }, { // Handles array as first argument: - message: /received values of types `number`./, + message: /Expected values which are `string` or `null or undefined`. Received values of type `number`./, + }); + + t.throws(() => { + assert.any([is.string, is.number, is.boolean], null, undefined, Number.NaN); + }, { + // Handles more than 2 expected and received types: + message: /Expected values which are `string`, `number`, or `boolean`. Received values of types `null`, `undefined`, and `NaN`./, + }); + + t.throws(() => { + assert.any(() => false, 1); + }, { + // Default type assertion message + message: /Expected values which are `predicate returns truthy for any value`./, }); }); @@ -2024,15 +2038,22 @@ test('is.all', t => { t.throws(() => { assert.all(is.string, 1, 2, 3); }, { - // Removes duplicates: - message: /received values of types `number`./, + // Includes expected type and removes duplicates from received types: + message: /Expected values which are `string`. Received values of type `number`./, }); t.throws(() => { assert.all(is.string, 1, [4]); }, { - // Lists all types: - message: /received values of types `number`, `Array`./, + // Includes expected type and lists all received types: + message: /Expected values which are `string`. Received values of types `number` and `Array`./, + }); + + t.throws(() => { + assert.all(() => false, 1); + }, { + // Default type assertion message + message: /Expected values which are `predicate returns truthy for all values`./, }); }); diff --git a/tsconfig.json b/tsconfig.json index e1c05ee..5275116 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,13 @@ { "extends": "@sindresorhus/tsconfig", "compilerOptions": { - "outDir": "dist" + "lib": [ + "DOM", + "DOM.Iterable", + "ES2021" + ], + "outDir": "dist", + "target": "ES2021" }, "include": [ "source" From ee79af32b6e85d71595f32f484feb8d9017e0271 Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Thu, 10 Aug 2023 22:05:22 +0800 Subject: [PATCH 206/254] Update class method description (#195) Co-authored-by: Harminder Virk --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 05d395b..9d2f2f6 100644 --- a/readme.md +++ b/readme.md @@ -411,7 +411,7 @@ An object is plain if it's created by either `{}`, `new Object()`, or `Object.cr ##### .asyncIterable(value) ##### .class(value) -Returns `true` for instances created by a class. +Returns `true` if the value is a class constructor. ##### .typedArray(value) From bcec30d73521e9c7034c9662c479a7230de10485 Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Thu, 10 Aug 2023 22:06:46 +0800 Subject: [PATCH 207/254] Rename `is.domElement()` to `is.htmlElement()` (#196) --- readme.md | 2 +- source/index.ts | 27 +++++++++++++++++---------- test/test.ts | 29 ++++++++++++++--------------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/readme.md b/readme.md index 9d2f2f6..3f6cfba 100644 --- a/readme.md +++ b/readme.md @@ -476,7 +476,7 @@ Check if `value` (number) is in the range of `0` to `upperBound`. is.inRange(3, 10); ``` -##### .domElement(value) +##### .htmlElement(value) Returns `true` if `value` is an [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). diff --git a/source/index.ts b/source/index.ts index 137dd40..782a067 100644 --- a/source/index.ts +++ b/source/index.ts @@ -138,7 +138,7 @@ export type AssertionTypeDescription = typeof assertionTypeDescriptions[number]; const getObjectType = (value: unknown): ObjectTypeName | undefined => { const objectTypeName = Object.prototype.toString.call(value).slice(8, -1); - if (/HTML\w+Element/.test(objectTypeName) && isDomElement(value)) { + if (/HTML\w+Element/.test(objectTypeName) && isHtmlElement(value)) { return 'HTMLElement'; } @@ -240,7 +240,8 @@ const is = Object.assign( date: isDate, detect, directInstanceOf: isDirectInstanceOf, - domElement: isDomElement, + /** @deprecated Renamed to `htmlElement` */ + domElement: isHtmlElement, emptyArray: isEmptyArray, emptyMap: isEmptyMap, emptyObject: isEmptyObject, @@ -259,6 +260,7 @@ const is = Object.assign( function_: isFunction, generator: isGenerator, generatorFunction: isGeneratorFunction, + htmlElement: isHtmlElement, infinite: isInfinite, inRange: isInRange, int16Array: isInt16Array, @@ -493,7 +495,7 @@ const DOM_PROPERTIES_TO_CHECK: Array<(keyof HTMLElement)> = [ 'nodeValue', ]; -export function isDomElement(value: unknown): value is HTMLElement { +export function isHtmlElement(value: unknown): value is HTMLElement { return isObject(value) && (value as HTMLElement).nodeType === NODE_TYPE_ELEMENT && isString((value as HTMLElement).nodeName) @@ -889,7 +891,9 @@ type Assert = { typedArray: (value: unknown) => asserts value is TypedArray; arrayLike: (value: unknown) => asserts value is ArrayLike; tupleLike: >>(value: unknown, guards: [...T]) => asserts value is ResolveTypesOfTypeGuardsTuple; + /** @deprecated Renamed to `htmlElement` */ domElement: (value: unknown) => asserts value is HTMLElement; + htmlElement: (value: unknown) => asserts value is HTMLElement; observable: (value: unknown) => asserts value is ObservableLike; nodeStream: (value: unknown) => asserts value is NodeStream; infinite: (value: unknown) => asserts value is number; @@ -946,7 +950,7 @@ export const assert: Assert = { dataView: assertDataView, date: assertDate, directInstanceOf: assertDirectInstanceOf, - domElement: assertDomElement, + domElement: assertHtmlElement, emptyArray: assertEmptyArray, emptyMap: assertEmptyMap, emptyObject: assertEmptyObject, @@ -964,6 +968,7 @@ export const assert: Assert = { function_: assertFunction, generator: assertGenerator, generatorFunction: assertGeneratorFunction, + htmlElement: assertHtmlElement, infinite: assertInfinite, inRange: assertInRange, int16Array: assertInt16Array, @@ -1038,6 +1043,7 @@ const methodTypeMap = { isDataView: 'DataView', isDate: 'Date', isDirectInstanceOf: 'T', + /** @deprecated */ isDomElement: 'HTMLElement', isEmptyArray: 'empty array', isEmptyMap: 'empty map', @@ -1055,6 +1061,7 @@ const methodTypeMap = { isFunction: 'Function', isGenerator: 'Generator', isGeneratorFunction: 'GeneratorFunction', + isHtmlElement: 'HTMLElement', isInfinite: 'infinite number', isInRange: 'in range', isInt16Array: 'Int16Array', @@ -1250,12 +1257,6 @@ export function assertDirectInstanceOf(instance: unknown, class_: Class): } } -export function assertDomElement(value: unknown): asserts value is HTMLElement { - if (!isDomElement(value)) { - throw new TypeError(typeErrorMessage('HTMLElement', value)); - } -} - export function assertEmptyArray(value: unknown): asserts value is never[] { if (!isEmptyArray(value)) { throw new TypeError(typeErrorMessage('empty array', value)); @@ -1353,6 +1354,12 @@ export function assertGeneratorFunction(value: unknown): asserts value is Genera } } +export function assertHtmlElement(value: unknown): asserts value is HTMLElement { + if (!isHtmlElement(value)) { + throw new TypeError(typeErrorMessage('HTMLElement', value)); + } +} + export function assertInfinite(value: unknown): asserts value is number { if (!isInfinite(value)) { throw new TypeError(typeErrorMessage('infinite number', value)); diff --git a/test/test.ts b/test/test.ts index d28fb1a..8608a25 100644 --- a/test/test.ts +++ b/test/test.ts @@ -23,7 +23,6 @@ class ErrorSubclassFixture extends Error {} const {window} = new JSDOM(); const {document} = window; -const createDomElement = (element: string) => document.createElement(element); const structuredClone = globalThis.structuredClone ?? (x => x); @@ -516,9 +515,9 @@ const types = new Map([ typename: 'number', typeDescription: 'integer', }], - ['domElement', { - is: is.domElement, - assert: assert.domElement, + ['htmlElement', { + is: is.htmlElement, + assert: assert.htmlElement, fixtures: [ 'div', 'input', @@ -527,14 +526,14 @@ const types = new Map([ 'canvas', 'script', ] - .map(fixture => createDomElement(fixture)), + .map(fixture => document.createElement(fixture)), typeDescription: 'HTMLElement', }], - ['non-domElements', { - is: value => !is.domElement(value), + ['non-htmlElement', { + is: value => !is.htmlElement(value), assert(value: unknown) { invertAssertThrow('HTMLElement', () => { - assert.domElement(value); + assert.htmlElement(value); }, value); }, fixtures: [ @@ -1622,11 +1621,11 @@ test('is.inRange', t => { }); }); -test('is.domElement', t => { - testType(t, 'domElement'); - t.false(is.domElement({nodeType: 1, nodeName: 'div'})); +test('is.htmlElement', t => { + testType(t, 'htmlElement'); + t.false(is.htmlElement({nodeType: 1, nodeName: 'div'})); t.throws(() => { - assert.domElement({nodeType: 1, nodeName: 'div'}); + assert.htmlElement({nodeType: 1, nodeName: 'div'}); }); const tagNames = [ @@ -1636,11 +1635,11 @@ test('is.domElement', t => { 'img', 'canvas', 'script', - ]; + ] as const; for (const tagName of tagNames) { - const domElement = createDomElement(tagName); - t.is(is(domElement), 'HTMLElement'); + const element = document.createElement(tagName); + t.is(is(element), 'HTMLElement'); } }); From 68e0c95857e1f990c290926b8d11e649b7d36b7f Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Thu, 10 Aug 2023 22:07:56 +0800 Subject: [PATCH 208/254] Update @sindresorhus/tsconfig to v4 (#194) --- package.json | 2 +- tsconfig.json | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/package.json b/package.json index a0902d3..4dbe54e 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "types" ], "devDependencies": { - "@sindresorhus/tsconfig": "^3.0.1", + "@sindresorhus/tsconfig": "^4.0.0", "@types/jsdom": "^21.1.1", "@types/node": "^20.2.5", "@types/zen-observable": "^0.8.3", diff --git a/tsconfig.json b/tsconfig.json index 5275116..b0821de 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,7 @@ { "extends": "@sindresorhus/tsconfig", "compilerOptions": { - "lib": [ - "DOM", - "DOM.Iterable", - "ES2021" - ], "outDir": "dist", - "target": "ES2021" }, "include": [ "source" From dadca59f6aca57745a44c8a2e50a9ef02363cf3f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 15 Aug 2023 20:50:56 +0200 Subject: [PATCH 209/254] Update dev dependencies --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 4dbe54e..3f9d030 100644 --- a/package.json +++ b/package.json @@ -51,16 +51,16 @@ "devDependencies": { "@sindresorhus/tsconfig": "^4.0.0", "@types/jsdom": "^21.1.1", - "@types/node": "^20.2.5", + "@types/node": "^20.5.0", "@types/zen-observable": "^0.8.3", - "ava": "^5.3.0", + "ava": "^5.3.1", "del-cli": "^5.0.0", - "jsdom": "^20.0.1", + "jsdom": "^22.1.0", "rxjs": "^7.8.1", - "tempy": "^3.0.0", + "tempy": "^3.1.0", "ts-node": "^10.9.1", - "typescript": "^5.0.4", - "xo": "^0.54.2", + "typescript": "^5.1.6", + "xo": "^0.56.0", "zen-observable": "^0.10.0", "expect-type": "^0.16.0" }, From 877ed1cc6aeac5be31186f0359128a5042840c36 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 15 Aug 2023 20:51:44 +0200 Subject: [PATCH 210/254] 6.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f9d030..bb1c164 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "5.6.0", + "version": "6.0.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 9d6c91ee58d14406163861cd95f7d2daceeef035 Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Wed, 16 Aug 2023 18:09:57 +0800 Subject: [PATCH 211/254] Add Node.js 20 to CI (#181) --- .github/workflows/main.yml | 1 + package.json | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6a82b18..1ed55d5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,6 +10,7 @@ jobs: fail-fast: false matrix: node-version: + - 20 - 18 - 16 steps: diff --git a/package.json b/package.json index bb1c164..ce1b9aa 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "scripts": { "build": "del dist && tsc", - "test": "tsc --noEmit && xo && ava", + "test": "tsc --noEmit && xo && NODE_OPTIONS='--loader=ts-node/esm --no-warnings=ExperimentalWarning' ava", "prepare": "npm run build" }, "files": [ @@ -68,9 +68,6 @@ "ava": { "extensions": { "ts": "module" - }, - "nodeArguments": [ - "--loader=ts-node/esm" - ] + } } } From e7e2213e9100a4212caf28c4af49efdaa9c83354 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 15 Oct 2023 16:01:29 +0700 Subject: [PATCH 212/254] `directInstanceOf`: Fix handling of `undefined` and `null` Fixes #199 --- source/index.ts | 4 ++++ test/test.ts | 3 +++ 2 files changed, 7 insertions(+) diff --git a/source/index.ts b/source/index.ts index 782a067..848af80 100644 --- a/source/index.ts +++ b/source/index.ts @@ -413,6 +413,10 @@ export function isDate(value: unknown): value is Date { } export function isDirectInstanceOf(instance: unknown, class_: Class): instance is T { + if (instance === undefined || instance === null) { + return false; + } + return Object.getPrototypeOf(instance) === class_.prototype; } diff --git a/test/test.ts b/test/test.ts index 8608a25..bbf7b57 100644 --- a/test/test.ts +++ b/test/test.ts @@ -986,6 +986,9 @@ test('is.directInstanceOf', t => { t.throws(() => { assert.directInstanceOf(errorSubclass, Error); }); + + t.false(is.directInstanceOf(undefined, Error)); + t.false(is.directInstanceOf(null, Error)); }); test('is.urlInstance', t => { From f10e2caf3d52b342f57f74933d9be54b241119ab Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 15 Oct 2023 19:26:47 +0700 Subject: [PATCH 213/254] 6.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce1b9aa..12b6acf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "6.0.0", + "version": "6.0.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 0d4cf6fcc89a558dd8ea4cf22819a8634b0742b7 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 26 Oct 2023 16:37:39 +0200 Subject: [PATCH 214/254] Improve TypeScript type for `isNonEmptyString()` and `isNonEmptyStringAndNotWhitespace()` (#200) --- source/index.ts | 5 +++-- source/types.ts | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/source/index.ts b/source/index.ts index 848af80..2ee08d3 100644 --- a/source/index.ts +++ b/source/index.ts @@ -4,6 +4,7 @@ import type { Class, Falsy, NodeStream, + NonEmptyString, ObservableLike, Predicate, Primitive, @@ -582,12 +583,12 @@ export function isNonEmptySet(value: unknown): value is Set { } // TODO: Use `not ''` when the `not` operator is available. -export function isNonEmptyString(value: unknown): value is string { +export function isNonEmptyString(value: unknown): value is NonEmptyString { return isString(value) && value.length > 0; } // TODO: Use `not ''` when the `not` operator is available. -export function isNonEmptyStringAndNotWhitespace(value: unknown): value is string { +export function isNonEmptyStringAndNotWhitespace(value: unknown): value is NonEmptyString { return isString(value) && !isEmptyStringOrWhitespace(value); } diff --git a/source/types.ts b/source/types.ts index 621c771..e77d73a 100644 --- a/source/types.ts +++ b/source/types.ts @@ -73,3 +73,5 @@ export type NodeStream = { } & NodeJS.EventEmitter; export type Predicate = (value: unknown) => boolean; + +export type NonEmptyString = string & {0: string}; From 1acbd9e2023f6a8f12deef0788167eaec0fb258f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 26 Oct 2023 21:39:39 +0700 Subject: [PATCH 215/254] 6.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 12b6acf..de6c3e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "6.0.1", + "version": "6.1.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 0e687a23a888bcfa93876b9f9b9e7f5713386542 Mon Sep 17 00:00:00 2001 From: patrik csak Date: Fri, 5 Jan 2024 23:30:28 -0800 Subject: [PATCH 216/254] Fix readme headings (#202) --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 3f6cfba..c59052c 100644 --- a/readme.md +++ b/readme.md @@ -450,11 +450,11 @@ function foo() { foo(); ``` -#### .positiveNumber(value) +##### .positiveNumber(value) Check if `value` is a number and is more than 0. -#### .negativeNumber(value) +##### .negativeNumber(value) Check if `value` is a number and is less than 0. From e9418fe1b9800da960c263ba32ab8549fe6b835b Mon Sep 17 00:00:00 2001 From: Martin Eneqvist Date: Thu, 29 Feb 2024 08:23:30 +0100 Subject: [PATCH 217/254] Add `.validDate()` (#203) --- readme.md | 30 ++++++++++++++++++++++++++++++ source/index.ts | 15 +++++++++++++++ test/test.ts | 11 +++++++++++ 3 files changed, 56 insertions(+) diff --git a/readme.md b/readme.md index c59052c..2c35f9f 100644 --- a/readme.md +++ b/readme.md @@ -574,6 +574,36 @@ is.all(is.string, '🦄', [], 'unicorns'); //=> false ``` +##### .validDate(value) + +Returns `true` if the value is a valid date. + +All [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date) objects have an internal timestamp value which is the number of milliseconds since the [Unix epoch](https://developer.mozilla.org/en-US/docs/Glossary/Unix_time). When a new `Date` is constructed with bad inputs, no error is thrown. Instead, a new `Date` object is returned. But the internal timestamp value is set to `NaN`, which is an `'Invalid Date'`. Bad inputs can be an non-parsable date string, a non-numeric value or a number that is outside of the expected range for a date value. + +```js +const valid = new Date('2000-01-01'); + +is.date(valid); +//=> true +valid.getTime(); +//=> 946684800000 +valid.toUTCString(); +//=> 'Sat, 01 Jan 2000 00:00:00 GMT' +is.validDate(valid); +//=> true + +const invalid = new Date('Not a parsable date string'); + +is.date(invalid); +//=> true +invalid.getTime(); +//=> NaN +invalid.toUTCString(); +//=> 'Invalid Date' +is.validDate(invalid); +//=> false +``` + ##### .validLength(value) Returns `true` if the value is a safe integer that is greater than or equal to zero. diff --git a/source/index.ts b/source/index.ts index 2ee08d3..8c1d00b 100644 --- a/source/index.ts +++ b/source/index.ts @@ -128,6 +128,7 @@ const assertionTypeDescriptions = [ 'in range', 'predicate returns truthy for any value', 'predicate returns truthy for all values', + 'valid Date', 'valid length', 'whitespace string', ...objectTypeNames, @@ -311,6 +312,7 @@ const is = Object.assign( urlInstance: isUrlInstance, urlSearchParams: isUrlSearchParams, urlString: isUrlString, + validDate: isValidDate, validLength: isValidLength, weakMap: isWeakMap, weakRef: isWeakRef, @@ -760,6 +762,10 @@ export function isUrlString(value: unknown): value is string { } } +export function isValidDate(value: unknown): value is Date { + return isDate(value) && !isNan(Number(value)); +} + export function isValidLength(value: unknown): value is number { return isSafeInteger(value) && value >= 0; } @@ -917,6 +923,7 @@ type Assert = { propertyKey: (value: unknown) => asserts value is PropertyKey; formData: (value: unknown) => asserts value is FormData; urlSearchParams: (value: unknown) => asserts value is URLSearchParams; + validDate: (value: unknown) => asserts value is Date; validLength: (value: unknown) => asserts value is number; whitespaceString: (value: unknown) => asserts value is string; @@ -1022,6 +1029,7 @@ export const assert: Assert = { urlInstance: assertUrlInstance, urlSearchParams: assertUrlSearchParams, urlString: assertUrlString, + validDate: assertValidDate, validLength: assertValidLength, weakMap: assertWeakMap, weakRef: assertWeakRef, @@ -1114,6 +1122,7 @@ const methodTypeMap = { isUrlInstance: 'URL', isUrlSearchParams: 'URLSearchParams', isUrlString: 'string with a URL', + isValidDate: 'valid Date', isValidLength: 'valid length', isWeakMap: 'WeakMap', isWeakRef: 'WeakRef', @@ -1651,6 +1660,12 @@ export function assertUrlString(value: unknown): asserts value is string { } } +export function assertValidDate(value: unknown): asserts value is Date { + if (!isValidDate(value)) { + throw new TypeError(typeErrorMessage('valid Date', value)); + } +} + export function assertValidLength(value: unknown): asserts value is number { if (!isValidLength(value)) { throw new TypeError(typeErrorMessage('valid length', value)); diff --git a/test/test.ts b/test/test.ts index bbf7b57..1b94a01 100644 --- a/test/test.ts +++ b/test/test.ts @@ -2101,6 +2101,17 @@ test('is.urlSearchParams', t => { }); }); +test('is.validDate', t => { + t.true(is.validDate(new Date())); + t.false(is.validDate(new Date('x'))); + t.notThrows(() => { + assert.validDate(new Date()); + }); + t.throws(() => { + assert.validDate(new Date('x')); + }); +}); + test('is.validLength', t => { t.true(is.validLength(1)); t.true(is.validLength(0)); From 07ea404e8679fba0bb37a98b08ab337ac318598e Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 6 Feb 2024 14:55:52 +0700 Subject: [PATCH 218/254] Meta tweaks --- .github/funding.yml | 2 -- .github/workflows/main.yml | 6 ++---- package.json | 2 +- readme.md | 6 ------ 4 files changed, 3 insertions(+), 13 deletions(-) delete mode 100644 .github/funding.yml diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 226bf96..0000000 --- a/.github/funding.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: sindresorhus -tidelift: npm/@sindresorhus/is diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1ed55d5..734c8eb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,11 +11,9 @@ jobs: matrix: node-version: - 20 - - 18 - - 16 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/package.json b/package.json index de6c3e4..004bd28 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "type": "module", "exports": "./dist/index.js", "types": "./dist/index.d.ts", + "sideEffects": false, "engines": { "node": ">=16" }, @@ -64,7 +65,6 @@ "zen-observable": "^0.10.0", "expect-type": "^0.16.0" }, - "sideEffects": false, "ava": { "extensions": { "ts": "module" diff --git a/readme.md b/readme.md index 2c35f9f..527745a 100644 --- a/readme.md +++ b/readme.md @@ -732,12 +732,6 @@ The most common mistakes I noticed in these modules was using `instanceof` for t `instanceof` does not work correctly for all types and it does not work across [realms](https://stackoverflow.com/a/49832343/64949). Examples of realms are iframes, windows, web workers, and the `vm` module in Node.js. -## For enterprise - -Available as part of the Tidelift Subscription. - -The maintainers of @sindresorhus/is and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-sindresorhus-is?utm_source=npm-sindresorhus-is&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) - ## Related - [ow](https://github.com/sindresorhus/ow) - Function argument validation for humans From 664b9077e1666531e359118aa5ae1cea5983a2dd Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 29 Feb 2024 14:29:43 +0700 Subject: [PATCH 219/254] 6.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 004bd28..b95cfae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "6.1.0", + "version": "6.2.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From f2e5834421f1f9c00827dbcb14d0d674d84a992f Mon Sep 17 00:00:00 2001 From: Martin Eneqvist Date: Tue, 23 Apr 2024 07:58:28 +0200 Subject: [PATCH 220/254] Support custom assertion messages (#204) Co-authored-by: Sindre Sorhus --- readme.md | 9 + source/index.ts | 536 ++++++++++++++++++++++++------------------------ test/test.ts | 357 ++++++++++++++++++++++++++++++++ 3 files changed, 634 insertions(+), 268 deletions(-) diff --git a/readme.md b/readme.md index 527745a..a1c90cb 100644 --- a/readme.md +++ b/readme.md @@ -45,6 +45,15 @@ assert.string(2); //=> Error: Expected value which is `string`, received value of type `number`. ``` +Assertions (except `assertAll` and `assertAny`) also support an optional custom error message. + +```js +import {assert} from '@sindresorhus/is'; + +assert.nonEmptyString(process.env.API_URL, 'The API_URL environment variable is required.'); +//=> Error: The API_URL environment variable is required. +``` + And with TypeScript: ```ts diff --git a/source/index.ts b/source/index.ts index 8c1d00b..369ce47 100644 --- a/source/index.ts +++ b/source/index.ts @@ -824,116 +824,116 @@ function typeErrorMessageMultipleValues(expectedType: AssertionTypeDescription | // Type assertions have to be declared with an explicit type. type Assert = { // Unknowns. - undefined: (value: unknown) => asserts value is undefined; - string: (value: unknown) => asserts value is string; - number: (value: unknown) => asserts value is number; - positiveNumber: (value: unknown) => asserts value is number; - negativeNumber: (value: unknown) => asserts value is number; - bigint: (value: unknown) => asserts value is bigint; + undefined: (value: unknown, message?: string) => asserts value is undefined; + string: (value: unknown, message?: string) => asserts value is string; + number: (value: unknown, message?: string) => asserts value is number; + positiveNumber: (value: unknown, message?: string) => asserts value is number; + negativeNumber: (value: unknown, message?: string) => asserts value is number; + bigint: (value: unknown, message?: string) => asserts value is bigint; // eslint-disable-next-line @typescript-eslint/ban-types - function: (value: unknown) => asserts value is Function; + function: (value: unknown, message?: string) => asserts value is Function; /** @deprecated Renamed to `function`. */ // eslint-disable-next-line @typescript-eslint/ban-types - function_: (value: unknown) => asserts value is Function; + function_: (value: unknown, message?: string) => asserts value is Function; // eslint-disable-next-line @typescript-eslint/ban-types - null: (value: unknown) => asserts value is null; + null: (value: unknown, message?: string) => asserts value is null; /** @deprecated Renamed to `null`. */ // eslint-disable-next-line @typescript-eslint/ban-types - null_: (value: unknown) => asserts value is null; - class: (value: unknown) => asserts value is Class; + null_: (value: unknown, message?: string) => asserts value is null; + class: (value: unknown, message?: string) => asserts value is Class; /** @deprecated Renamed to `class`. */ - class_: (value: unknown) => asserts value is Class; - boolean: (value: unknown) => asserts value is boolean; - symbol: (value: unknown) => asserts value is symbol; - numericString: (value: unknown) => asserts value is `${number}`; - array: (value: unknown, assertion?: (element: unknown) => asserts element is T) => asserts value is T[]; - buffer: (value: unknown) => asserts value is Buffer; - blob: (value: unknown) => asserts value is Blob; + class_: (value: unknown, message?: string) => asserts value is Class; + boolean: (value: unknown, message?: string) => asserts value is boolean; + symbol: (value: unknown, message?: string) => asserts value is symbol; + numericString: (value: unknown, message?: string) => asserts value is `${number}`; + array: (value: unknown, assertion?: (element: unknown) => asserts element is T, message?: string) => asserts value is T[]; + buffer: (value: unknown, message?: string) => asserts value is Buffer; + blob: (value: unknown, message?: string) => asserts value is Blob; // eslint-disable-next-line @typescript-eslint/ban-types - nullOrUndefined: (value: unknown) => asserts value is null | undefined; - object: (value: unknown) => asserts value is Record; - iterable: (value: unknown) => asserts value is Iterable; - asyncIterable: (value: unknown) => asserts value is AsyncIterable; - generator: (value: unknown) => asserts value is Generator; - asyncGenerator: (value: unknown) => asserts value is AsyncGenerator; - nativePromise: (value: unknown) => asserts value is Promise; - promise: (value: unknown) => asserts value is Promise; - generatorFunction: (value: unknown) => asserts value is GeneratorFunction; - asyncGeneratorFunction: (value: unknown) => asserts value is AsyncGeneratorFunction; + nullOrUndefined: (value: unknown, message?: string) => asserts value is null | undefined; + object: (value: unknown, message?: string) => asserts value is Record; + iterable: (value: unknown, message?: string) => asserts value is Iterable; + asyncIterable: (value: unknown, message?: string) => asserts value is AsyncIterable; + generator: (value: unknown, message?: string) => asserts value is Generator; + asyncGenerator: (value: unknown, message?: string) => asserts value is AsyncGenerator; + nativePromise: (value: unknown, message?: string) => asserts value is Promise; + promise: (value: unknown, message?: string) => asserts value is Promise; + generatorFunction: (value: unknown, message?: string) => asserts value is GeneratorFunction; + asyncGeneratorFunction: (value: unknown, message?: string) => asserts value is AsyncGeneratorFunction; // eslint-disable-next-line @typescript-eslint/ban-types - asyncFunction: (value: unknown) => asserts value is Function; + asyncFunction: (value: unknown, message?: string) => asserts value is Function; // eslint-disable-next-line @typescript-eslint/ban-types - boundFunction: (value: unknown) => asserts value is Function; - regExp: (value: unknown) => asserts value is RegExp; - date: (value: unknown) => asserts value is Date; - error: (value: unknown) => asserts value is Error; - map: (value: unknown) => asserts value is Map; - set: (value: unknown) => asserts value is Set; + boundFunction: (value: unknown, message?: string) => asserts value is Function; + regExp: (value: unknown, message?: string) => asserts value is RegExp; + date: (value: unknown, message?: string) => asserts value is Date; + error: (value: unknown, message?: string) => asserts value is Error; + map: (value: unknown, message?: string) => asserts value is Map; + set: (value: unknown, message?: string) => asserts value is Set; // eslint-disable-next-line @typescript-eslint/ban-types - weakMap: (value: unknown) => asserts value is WeakMap; + weakMap: (value: unknown, message?: string) => asserts value is WeakMap; // eslint-disable-next-line @typescript-eslint/ban-types - weakSet: (value: unknown) => asserts value is WeakSet; + weakSet: (value: unknown, message?: string) => asserts value is WeakSet; // eslint-disable-next-line @typescript-eslint/ban-types - weakRef: (value: unknown) => asserts value is WeakRef; - int8Array: (value: unknown) => asserts value is Int8Array; - uint8Array: (value: unknown) => asserts value is Uint8Array; - uint8ClampedArray: (value: unknown) => asserts value is Uint8ClampedArray; - int16Array: (value: unknown) => asserts value is Int16Array; - uint16Array: (value: unknown) => asserts value is Uint16Array; - int32Array: (value: unknown) => asserts value is Int32Array; - uint32Array: (value: unknown) => asserts value is Uint32Array; - float32Array: (value: unknown) => asserts value is Float32Array; - float64Array: (value: unknown) => asserts value is Float64Array; - bigInt64Array: (value: unknown) => asserts value is BigInt64Array; - bigUint64Array: (value: unknown) => asserts value is BigUint64Array; - arrayBuffer: (value: unknown) => asserts value is ArrayBuffer; - sharedArrayBuffer: (value: unknown) => asserts value is SharedArrayBuffer; - dataView: (value: unknown) => asserts value is DataView; - enumCase: (value: unknown, targetEnum: T) => asserts value is T[keyof T]; - urlInstance: (value: unknown) => asserts value is URL; - urlString: (value: unknown) => asserts value is string; - truthy: (value: T | Falsy) => asserts value is T; - falsy: (value: unknown) => asserts value is Falsy; - nan: (value: unknown) => asserts value is number; - primitive: (value: unknown) => asserts value is Primitive; - integer: (value: unknown) => asserts value is number; - safeInteger: (value: unknown) => asserts value is number; - plainObject: (value: unknown) => asserts value is Record; - typedArray: (value: unknown) => asserts value is TypedArray; - arrayLike: (value: unknown) => asserts value is ArrayLike; - tupleLike: >>(value: unknown, guards: [...T]) => asserts value is ResolveTypesOfTypeGuardsTuple; + weakRef: (value: unknown, message?: string) => asserts value is WeakRef; + int8Array: (value: unknown, message?: string) => asserts value is Int8Array; + uint8Array: (value: unknown, message?: string) => asserts value is Uint8Array; + uint8ClampedArray: (value: unknown, message?: string) => asserts value is Uint8ClampedArray; + int16Array: (value: unknown, message?: string) => asserts value is Int16Array; + uint16Array: (value: unknown, message?: string) => asserts value is Uint16Array; + int32Array: (value: unknown, message?: string) => asserts value is Int32Array; + uint32Array: (value: unknown, message?: string) => asserts value is Uint32Array; + float32Array: (value: unknown, message?: string) => asserts value is Float32Array; + float64Array: (value: unknown, message?: string) => asserts value is Float64Array; + bigInt64Array: (value: unknown, message?: string) => asserts value is BigInt64Array; + bigUint64Array: (value: unknown, message?: string) => asserts value is BigUint64Array; + arrayBuffer: (value: unknown, message?: string) => asserts value is ArrayBuffer; + sharedArrayBuffer: (value: unknown, message?: string) => asserts value is SharedArrayBuffer; + dataView: (value: unknown, message?: string) => asserts value is DataView; + enumCase: (value: unknown, targetEnum: T, message?: string) => asserts value is T[keyof T]; + urlInstance: (value: unknown, message?: string) => asserts value is URL; + urlString: (value: unknown, message?: string) => asserts value is string; + truthy: (value: T | Falsy, message?: string) => asserts value is T; + falsy: (value: unknown, message?: string) => asserts value is Falsy; + nan: (value: unknown, message?: string) => asserts value is number; + primitive: (value: unknown, message?: string) => asserts value is Primitive; + integer: (value: unknown, message?: string) => asserts value is number; + safeInteger: (value: unknown, message?: string) => asserts value is number; + plainObject: (value: unknown, message?: string) => asserts value is Record; + typedArray: (value: unknown, message?: string) => asserts value is TypedArray; + arrayLike: (value: unknown, message?: string) => asserts value is ArrayLike; + tupleLike: >>(value: unknown, guards: [...T], message?: string) => asserts value is ResolveTypesOfTypeGuardsTuple; /** @deprecated Renamed to `htmlElement` */ - domElement: (value: unknown) => asserts value is HTMLElement; - htmlElement: (value: unknown) => asserts value is HTMLElement; - observable: (value: unknown) => asserts value is ObservableLike; - nodeStream: (value: unknown) => asserts value is NodeStream; - infinite: (value: unknown) => asserts value is number; - emptyArray: (value: unknown) => asserts value is never[]; - nonEmptyArray: (value: T | Item[]) => asserts value is [Item, ...Item[]]; - emptyString: (value: unknown) => asserts value is ''; - emptyStringOrWhitespace: (value: unknown) => asserts value is string; - nonEmptyString: (value: unknown) => asserts value is string; - nonEmptyStringAndNotWhitespace: (value: unknown) => asserts value is string; - emptyObject: (value: unknown) => asserts value is Record; - nonEmptyObject: (value: unknown) => asserts value is Record; - emptySet: (value: unknown) => asserts value is Set; - nonEmptySet: (value: unknown) => asserts value is Set; - emptyMap: (value: unknown) => asserts value is Map; - nonEmptyMap: (value: unknown) => asserts value is Map; - propertyKey: (value: unknown) => asserts value is PropertyKey; - formData: (value: unknown) => asserts value is FormData; - urlSearchParams: (value: unknown) => asserts value is URLSearchParams; - validDate: (value: unknown) => asserts value is Date; - validLength: (value: unknown) => asserts value is number; - whitespaceString: (value: unknown) => asserts value is string; + domElement: (value: unknown, message?: string) => asserts value is HTMLElement; + htmlElement: (value: unknown, message?: string) => asserts value is HTMLElement; + observable: (value: unknown, message?: string) => asserts value is ObservableLike; + nodeStream: (value: unknown, message?: string) => asserts value is NodeStream; + infinite: (value: unknown, message?: string) => asserts value is number; + emptyArray: (value: unknown, message?: string) => asserts value is never[]; + nonEmptyArray: (value: T | Item[], message?: string) => asserts value is [Item, ...Item[]]; + emptyString: (value: unknown, message?: string) => asserts value is ''; + emptyStringOrWhitespace: (value: unknown, message?: string) => asserts value is string; + nonEmptyString: (value: unknown, message?: string) => asserts value is string; + nonEmptyStringAndNotWhitespace: (value: unknown, message?: string) => asserts value is string; + emptyObject: (value: unknown, message?: string) => asserts value is Record; + nonEmptyObject: (value: unknown, message?: string) => asserts value is Record; + emptySet: (value: unknown, message?: string) => asserts value is Set; + nonEmptySet: (value: unknown, message?: string) => asserts value is Set; + emptyMap: (value: unknown, message?: string) => asserts value is Map; + nonEmptyMap: (value: unknown, message?: string) => asserts value is Map; + propertyKey: (value: unknown, message?: string) => asserts value is PropertyKey; + formData: (value: unknown, message?: string) => asserts value is FormData; + urlSearchParams: (value: unknown, message?: string) => asserts value is URLSearchParams; + validDate: (value: unknown, message?: string) => asserts value is Date; + validLength: (value: unknown, message?: string) => asserts value is number; + whitespaceString: (value: unknown, message?: string) => asserts value is string; // Numbers. - evenInteger: (value: number) => asserts value is number; - oddInteger: (value: number) => asserts value is number; + evenInteger: (value: number, message?: string) => asserts value is number; + oddInteger: (value: number, message?: string) => asserts value is number; // Two arguments. - directInstanceOf: (instance: unknown, class_: Class) => asserts instance is T; - inRange: (value: number, range: number | [number, number]) => asserts value is number; + directInstanceOf: (instance: unknown, class_: Class, message?: string) => asserts instance is T; + inRange: (value: number, range: number | [number, number], message?: string) => asserts value is number; // Variadic functions. any: (predicate: Predicate | Predicate[], ...values: unknown[]) => void | never; @@ -1156,9 +1156,9 @@ export function assertAny(predicate: Predicate | Predicate[], ...values: unknown } } -export function assertArray(value: unknown, assertion?: (element: unknown) => asserts element is T): asserts value is T[] { +export function assertArray(value: unknown, assertion?: (element: unknown) => asserts element is T, message?: string): asserts value is T[] { if (!isArray(value)) { - throw new TypeError(typeErrorMessage('Array', value)); + throw new TypeError(message ?? typeErrorMessage('Array', value)); } if (assertion) { @@ -1167,535 +1167,535 @@ export function assertArray(value: unknown, assertion?: (element: u } } -export function assertArrayBuffer(value: unknown): asserts value is ArrayBuffer { +export function assertArrayBuffer(value: unknown, message?: string): asserts value is ArrayBuffer { if (!isArrayBuffer(value)) { - throw new TypeError(typeErrorMessage('ArrayBuffer', value)); + throw new TypeError(message ?? typeErrorMessage('ArrayBuffer', value)); } } -export function assertArrayLike(value: unknown): asserts value is ArrayLike { +export function assertArrayLike(value: unknown, message?: string): asserts value is ArrayLike { if (!isArrayLike(value)) { - throw new TypeError(typeErrorMessage('array-like', value)); + throw new TypeError(message ?? typeErrorMessage('array-like', value)); } } // eslint-disable-next-line @typescript-eslint/ban-types -export function assertAsyncFunction(value: unknown): asserts value is Function { +export function assertAsyncFunction(value: unknown, message?: string): asserts value is Function { if (!isAsyncFunction(value)) { - throw new TypeError(typeErrorMessage('AsyncFunction', value)); + throw new TypeError(message ?? typeErrorMessage('AsyncFunction', value)); } } -export function assertAsyncGenerator(value: unknown): asserts value is AsyncGenerator { +export function assertAsyncGenerator(value: unknown, message?: string): asserts value is AsyncGenerator { if (!isAsyncGenerator(value)) { - throw new TypeError(typeErrorMessage('AsyncGenerator', value)); + throw new TypeError(message ?? typeErrorMessage('AsyncGenerator', value)); } } -export function assertAsyncGeneratorFunction(value: unknown): asserts value is AsyncGeneratorFunction { +export function assertAsyncGeneratorFunction(value: unknown, message?: string): asserts value is AsyncGeneratorFunction { if (!isAsyncGeneratorFunction(value)) { - throw new TypeError(typeErrorMessage('AsyncGeneratorFunction', value)); + throw new TypeError(message ?? typeErrorMessage('AsyncGeneratorFunction', value)); } } -export function assertAsyncIterable(value: unknown): asserts value is AsyncIterable { +export function assertAsyncIterable(value: unknown, message?: string): asserts value is AsyncIterable { if (!isAsyncIterable(value)) { - throw new TypeError(typeErrorMessage('AsyncIterable', value)); + throw new TypeError(message ?? typeErrorMessage('AsyncIterable', value)); } } -export function assertBigint(value: unknown): asserts value is bigint { +export function assertBigint(value: unknown, message?: string): asserts value is bigint { if (!isBigint(value)) { - throw new TypeError(typeErrorMessage('bigint', value)); + throw new TypeError(message ?? typeErrorMessage('bigint', value)); } } -export function assertBigInt64Array(value: unknown): asserts value is BigInt64Array { +export function assertBigInt64Array(value: unknown, message?: string): asserts value is BigInt64Array { if (!isBigInt64Array(value)) { - throw new TypeError(typeErrorMessage('BigInt64Array', value)); + throw new TypeError(message ?? typeErrorMessage('BigInt64Array', value)); } } -export function assertBigUint64Array(value: unknown): asserts value is BigUint64Array { +export function assertBigUint64Array(value: unknown, message?: string): asserts value is BigUint64Array { if (!isBigUint64Array(value)) { - throw new TypeError(typeErrorMessage('BigUint64Array', value)); + throw new TypeError(message ?? typeErrorMessage('BigUint64Array', value)); } } -export function assertBlob(value: unknown): asserts value is Blob { +export function assertBlob(value: unknown, message?: string): asserts value is Blob { if (!isBlob(value)) { - throw new TypeError(typeErrorMessage('Blob', value)); + throw new TypeError(message ?? typeErrorMessage('Blob', value)); } } -export function assertBoolean(value: unknown): asserts value is boolean { +export function assertBoolean(value: unknown, message?: string): asserts value is boolean { if (!isBoolean(value)) { - throw new TypeError(typeErrorMessage('boolean', value)); + throw new TypeError(message ?? typeErrorMessage('boolean', value)); } } // eslint-disable-next-line @typescript-eslint/ban-types -export function assertBoundFunction(value: unknown): asserts value is Function { +export function assertBoundFunction(value: unknown, message?: string): asserts value is Function { if (!isBoundFunction(value)) { - throw new TypeError(typeErrorMessage('Function', value)); + throw new TypeError(message ?? typeErrorMessage('Function', value)); } } -export function assertBuffer(value: unknown): asserts value is Buffer { +export function assertBuffer(value: unknown, message?: string): asserts value is Buffer { if (!isBuffer(value)) { - throw new TypeError(typeErrorMessage('Buffer', value)); + throw new TypeError(message ?? typeErrorMessage('Buffer', value)); } } -export function assertClass(value: unknown): asserts value is Class { +export function assertClass(value: unknown, message?: string): asserts value is Class { if (!isClass(value)) { - throw new TypeError(typeErrorMessage('Class', value)); + throw new TypeError(message ?? typeErrorMessage('Class', value)); } } -export function assertDataView(value: unknown): asserts value is DataView { +export function assertDataView(value: unknown, message?: string): asserts value is DataView { if (!isDataView(value)) { - throw new TypeError(typeErrorMessage('DataView', value)); + throw new TypeError(message ?? typeErrorMessage('DataView', value)); } } -export function assertDate(value: unknown): asserts value is Date { +export function assertDate(value: unknown, message?: string): asserts value is Date { if (!isDate(value)) { - throw new TypeError(typeErrorMessage('Date', value)); + throw new TypeError(message ?? typeErrorMessage('Date', value)); } } -export function assertDirectInstanceOf(instance: unknown, class_: Class): asserts instance is T { +export function assertDirectInstanceOf(instance: unknown, class_: Class, message?: string): asserts instance is T { if (!isDirectInstanceOf(instance, class_)) { - throw new TypeError(typeErrorMessage('T', instance)); + throw new TypeError(message ?? typeErrorMessage('T', instance)); } } -export function assertEmptyArray(value: unknown): asserts value is never[] { +export function assertEmptyArray(value: unknown, message?: string): asserts value is never[] { if (!isEmptyArray(value)) { - throw new TypeError(typeErrorMessage('empty array', value)); + throw new TypeError(message ?? typeErrorMessage('empty array', value)); } } -export function assertEmptyMap(value: unknown): asserts value is Map { +export function assertEmptyMap(value: unknown, message?: string): asserts value is Map { if (!isEmptyMap(value)) { - throw new TypeError(typeErrorMessage('empty map', value)); + throw new TypeError(message ?? typeErrorMessage('empty map', value)); } } -export function assertEmptyObject(value: unknown): asserts value is Record { +export function assertEmptyObject(value: unknown, message?: string): asserts value is Record { if (!isEmptyObject(value)) { - throw new TypeError(typeErrorMessage('empty object', value)); + throw new TypeError(message ?? typeErrorMessage('empty object', value)); } } -export function assertEmptySet(value: unknown): asserts value is Set { +export function assertEmptySet(value: unknown, message?: string): asserts value is Set { if (!isEmptySet(value)) { - throw new TypeError(typeErrorMessage('empty set', value)); + throw new TypeError(message ?? typeErrorMessage('empty set', value)); } } -export function assertEmptyString(value: unknown): asserts value is '' { +export function assertEmptyString(value: unknown, message?: string): asserts value is '' { if (!isEmptyString(value)) { - throw new TypeError(typeErrorMessage('empty string', value)); + throw new TypeError(message ?? typeErrorMessage('empty string', value)); } } -export function assertEmptyStringOrWhitespace(value: unknown): asserts value is string { +export function assertEmptyStringOrWhitespace(value: unknown, message?: string): asserts value is string { if (!isEmptyStringOrWhitespace(value)) { - throw new TypeError(typeErrorMessage('empty string or whitespace', value)); + throw new TypeError(message ?? typeErrorMessage('empty string or whitespace', value)); } } -export function assertEnumCase(value: unknown, targetEnum: T): asserts value is T[keyof T] { +export function assertEnumCase(value: unknown, targetEnum: T, message?: string): asserts value is T[keyof T] { if (!isEnumCase(value, targetEnum)) { - throw new TypeError(typeErrorMessage('EnumCase', value)); + throw new TypeError(message ?? typeErrorMessage('EnumCase', value)); } } -export function assertError(value: unknown): asserts value is Error { +export function assertError(value: unknown, message?: string): asserts value is Error { if (!isError(value)) { - throw new TypeError(typeErrorMessage('Error', value)); + throw new TypeError(message ?? typeErrorMessage('Error', value)); } } -export function assertEvenInteger(value: number): asserts value is number { +export function assertEvenInteger(value: number, message?: string): asserts value is number { if (!isEvenInteger(value)) { - throw new TypeError(typeErrorMessage('even integer', value)); + throw new TypeError(message ?? typeErrorMessage('even integer', value)); } } -export function assertFalsy(value: unknown): asserts value is Falsy { +export function assertFalsy(value: unknown, message?: string): asserts value is Falsy { if (!isFalsy(value)) { - throw new TypeError(typeErrorMessage('falsy', value)); + throw new TypeError(message ?? typeErrorMessage('falsy', value)); } } -export function assertFloat32Array(value: unknown): asserts value is Float32Array { +export function assertFloat32Array(value: unknown, message?: string): asserts value is Float32Array { if (!isFloat32Array(value)) { - throw new TypeError(typeErrorMessage('Float32Array', value)); + throw new TypeError(message ?? typeErrorMessage('Float32Array', value)); } } -export function assertFloat64Array(value: unknown): asserts value is Float64Array { +export function assertFloat64Array(value: unknown, message?: string): asserts value is Float64Array { if (!isFloat64Array(value)) { - throw new TypeError(typeErrorMessage('Float64Array', value)); + throw new TypeError(message ?? typeErrorMessage('Float64Array', value)); } } -export function assertFormData(value: unknown): asserts value is FormData { +export function assertFormData(value: unknown, message?: string): asserts value is FormData { if (!isFormData(value)) { - throw new TypeError(typeErrorMessage('FormData', value)); + throw new TypeError(message ?? typeErrorMessage('FormData', value)); } } // eslint-disable-next-line @typescript-eslint/ban-types -export function assertFunction(value: unknown): asserts value is Function { +export function assertFunction(value: unknown, message?: string): asserts value is Function { if (!isFunction(value)) { - throw new TypeError(typeErrorMessage('Function', value)); + throw new TypeError(message ?? typeErrorMessage('Function', value)); } } -export function assertGenerator(value: unknown): asserts value is Generator { +export function assertGenerator(value: unknown, message?: string): asserts value is Generator { if (!isGenerator(value)) { - throw new TypeError(typeErrorMessage('Generator', value)); + throw new TypeError(message ?? typeErrorMessage('Generator', value)); } } -export function assertGeneratorFunction(value: unknown): asserts value is GeneratorFunction { +export function assertGeneratorFunction(value: unknown, message?: string): asserts value is GeneratorFunction { if (!isGeneratorFunction(value)) { - throw new TypeError(typeErrorMessage('GeneratorFunction', value)); + throw new TypeError(message ?? typeErrorMessage('GeneratorFunction', value)); } } -export function assertHtmlElement(value: unknown): asserts value is HTMLElement { +export function assertHtmlElement(value: unknown, message?: string): asserts value is HTMLElement { if (!isHtmlElement(value)) { - throw new TypeError(typeErrorMessage('HTMLElement', value)); + throw new TypeError(message ?? typeErrorMessage('HTMLElement', value)); } } -export function assertInfinite(value: unknown): asserts value is number { +export function assertInfinite(value: unknown, message?: string): asserts value is number { if (!isInfinite(value)) { - throw new TypeError(typeErrorMessage('infinite number', value)); + throw new TypeError(message ?? typeErrorMessage('infinite number', value)); } } -export function assertInRange(value: number, range: number | [number, number]): asserts value is number { +export function assertInRange(value: number, range: number | [number, number], message?: string): asserts value is number { if (!isInRange(value, range)) { - throw new TypeError(typeErrorMessage('in range', value)); + throw new TypeError(message ?? typeErrorMessage('in range', value)); } } -export function assertInt16Array(value: unknown): asserts value is Int16Array { +export function assertInt16Array(value: unknown, message?: string): asserts value is Int16Array { if (!isInt16Array(value)) { - throw new TypeError(typeErrorMessage('Int16Array', value)); + throw new TypeError(message ?? typeErrorMessage('Int16Array', value)); } } -export function assertInt32Array(value: unknown): asserts value is Int32Array { +export function assertInt32Array(value: unknown, message?: string): asserts value is Int32Array { if (!isInt32Array(value)) { - throw new TypeError(typeErrorMessage('Int32Array', value)); + throw new TypeError(message ?? typeErrorMessage('Int32Array', value)); } } -export function assertInt8Array(value: unknown): asserts value is Int8Array { +export function assertInt8Array(value: unknown, message?: string): asserts value is Int8Array { if (!isInt8Array(value)) { - throw new TypeError(typeErrorMessage('Int8Array', value)); + throw new TypeError(message ?? typeErrorMessage('Int8Array', value)); } } -export function assertInteger(value: unknown): asserts value is number { +export function assertInteger(value: unknown, message?: string): asserts value is number { if (!isInteger(value)) { - throw new TypeError(typeErrorMessage('integer', value)); + throw new TypeError(message ?? typeErrorMessage('integer', value)); } } -export function assertIterable(value: unknown): asserts value is Iterable { +export function assertIterable(value: unknown, message?: string): asserts value is Iterable { if (!isIterable(value)) { - throw new TypeError(typeErrorMessage('Iterable', value)); + throw new TypeError(message ?? typeErrorMessage('Iterable', value)); } } -export function assertMap(value: unknown): asserts value is Map { +export function assertMap(value: unknown, message?: string): asserts value is Map { if (!isMap(value)) { - throw new TypeError(typeErrorMessage('Map', value)); + throw new TypeError(message ?? typeErrorMessage('Map', value)); } } -export function assertNan(value: unknown): asserts value is number { +export function assertNan(value: unknown, message?: string): asserts value is number { if (!isNan(value)) { - throw new TypeError(typeErrorMessage('NaN', value)); + throw new TypeError(message ?? typeErrorMessage('NaN', value)); } } -export function assertNativePromise(value: unknown): asserts value is Promise { +export function assertNativePromise(value: unknown, message?: string): asserts value is Promise { if (!isNativePromise(value)) { - throw new TypeError(typeErrorMessage('native Promise', value)); + throw new TypeError(message ?? typeErrorMessage('native Promise', value)); } } -export function assertNegativeNumber(value: unknown): asserts value is number { +export function assertNegativeNumber(value: unknown, message?: string): asserts value is number { if (!isNegativeNumber(value)) { - throw new TypeError(typeErrorMessage('negative number', value)); + throw new TypeError(message ?? typeErrorMessage('negative number', value)); } } -export function assertNodeStream(value: unknown): asserts value is NodeStream { +export function assertNodeStream(value: unknown, message?: string): asserts value is NodeStream { if (!isNodeStream(value)) { - throw new TypeError(typeErrorMessage('Node.js Stream', value)); + throw new TypeError(message ?? typeErrorMessage('Node.js Stream', value)); } } -export function assertNonEmptyArray(value: T | Item[]): asserts value is [Item, ...Item[]] { +export function assertNonEmptyArray(value: T | Item[], message?: string): asserts value is [Item, ...Item[]] { if (!isNonEmptyArray(value)) { - throw new TypeError(typeErrorMessage('non-empty array', value)); + throw new TypeError(message ?? typeErrorMessage('non-empty array', value)); } } -export function assertNonEmptyMap(value: unknown): asserts value is Map { +export function assertNonEmptyMap(value: unknown, message?: string): asserts value is Map { if (!isNonEmptyMap(value)) { - throw new TypeError(typeErrorMessage('non-empty map', value)); + throw new TypeError(message ?? typeErrorMessage('non-empty map', value)); } } -export function assertNonEmptyObject(value: unknown): asserts value is Record { +export function assertNonEmptyObject(value: unknown, message?: string): asserts value is Record { if (!isNonEmptyObject(value)) { - throw new TypeError(typeErrorMessage('non-empty object', value)); + throw new TypeError(message ?? typeErrorMessage('non-empty object', value)); } } -export function assertNonEmptySet(value: unknown): asserts value is Set { +export function assertNonEmptySet(value: unknown, message?: string): asserts value is Set { if (!isNonEmptySet(value)) { - throw new TypeError(typeErrorMessage('non-empty set', value)); + throw new TypeError(message ?? typeErrorMessage('non-empty set', value)); } } -export function assertNonEmptyString(value: unknown): asserts value is string { +export function assertNonEmptyString(value: unknown, message?: string): asserts value is string { if (!isNonEmptyString(value)) { - throw new TypeError(typeErrorMessage('non-empty string', value)); + throw new TypeError(message ?? typeErrorMessage('non-empty string', value)); } } -export function assertNonEmptyStringAndNotWhitespace(value: unknown): asserts value is string { +export function assertNonEmptyStringAndNotWhitespace(value: unknown, message?: string): asserts value is string { if (!isNonEmptyStringAndNotWhitespace(value)) { - throw new TypeError(typeErrorMessage('non-empty string and not whitespace', value)); + throw new TypeError(message ?? typeErrorMessage('non-empty string and not whitespace', value)); } } // eslint-disable-next-line @typescript-eslint/ban-types -export function assertNull(value: unknown): asserts value is null { +export function assertNull(value: unknown, message?: string): asserts value is null { if (!isNull(value)) { - throw new TypeError(typeErrorMessage('null', value)); + throw new TypeError(message ?? typeErrorMessage('null', value)); } } // eslint-disable-next-line @typescript-eslint/ban-types -export function assertNullOrUndefined(value: unknown): asserts value is null | undefined { +export function assertNullOrUndefined(value: unknown, message?: string): asserts value is null | undefined { if (!isNullOrUndefined(value)) { - throw new TypeError(typeErrorMessage('null or undefined', value)); + throw new TypeError(message ?? typeErrorMessage('null or undefined', value)); } } -export function assertNumber(value: unknown): asserts value is number { +export function assertNumber(value: unknown, message?: string): asserts value is number { if (!isNumber(value)) { - throw new TypeError(typeErrorMessage('number', value)); + throw new TypeError(message ?? typeErrorMessage('number', value)); } } -export function assertNumericString(value: unknown): asserts value is `${number}` { +export function assertNumericString(value: unknown, message?: string): asserts value is `${number}` { if (!isNumericString(value)) { - throw new TypeError(typeErrorMessage('string with a number', value)); + throw new TypeError(message ?? typeErrorMessage('string with a number', value)); } } // eslint-disable-next-line @typescript-eslint/ban-types -export function assertObject(value: unknown): asserts value is object { +export function assertObject(value: unknown, message?: string): asserts value is object { if (!isObject(value)) { - throw new TypeError(typeErrorMessage('Object', value)); + throw new TypeError(message ?? typeErrorMessage('Object', value)); } } -export function assertObservable(value: unknown): asserts value is ObservableLike { +export function assertObservable(value: unknown, message?: string): asserts value is ObservableLike { if (!isObservable(value)) { - throw new TypeError(typeErrorMessage('Observable', value)); + throw new TypeError(message ?? typeErrorMessage('Observable', value)); } } -export function assertOddInteger(value: number): asserts value is number { +export function assertOddInteger(value: number, message?: string): asserts value is number { if (!isOddInteger(value)) { - throw new TypeError(typeErrorMessage('odd integer', value)); + throw new TypeError(message ?? typeErrorMessage('odd integer', value)); } } -export function assertPlainObject(value: unknown): asserts value is Record { +export function assertPlainObject(value: unknown, message?: string): asserts value is Record { if (!isPlainObject(value)) { - throw new TypeError(typeErrorMessage('plain object', value)); + throw new TypeError(message ?? typeErrorMessage('plain object', value)); } } -export function assertPositiveNumber(value: unknown): asserts value is number { +export function assertPositiveNumber(value: unknown, message?: string): asserts value is number { if (!isPositiveNumber(value)) { - throw new TypeError(typeErrorMessage('positive number', value)); + throw new TypeError(message ?? typeErrorMessage('positive number', value)); } } -export function assertPrimitive(value: unknown): asserts value is Primitive { +export function assertPrimitive(value: unknown, message?: string): asserts value is Primitive { if (!isPrimitive(value)) { - throw new TypeError(typeErrorMessage('primitive', value)); + throw new TypeError(message ?? typeErrorMessage('primitive', value)); } } -export function assertPromise(value: unknown): asserts value is Promise { +export function assertPromise(value: unknown, message?: string): asserts value is Promise { if (!isPromise(value)) { - throw new TypeError(typeErrorMessage('Promise', value)); + throw new TypeError(message ?? typeErrorMessage('Promise', value)); } } -export function assertPropertyKey(value: unknown): asserts value is number { +export function assertPropertyKey(value: unknown, message?: string): asserts value is number { if (!isPropertyKey(value)) { - throw new TypeError(typeErrorMessage('PropertyKey', value)); + throw new TypeError(message ?? typeErrorMessage('PropertyKey', value)); } } -export function assertRegExp(value: unknown): asserts value is RegExp { +export function assertRegExp(value: unknown, message?: string): asserts value is RegExp { if (!isRegExp(value)) { - throw new TypeError(typeErrorMessage('RegExp', value)); + throw new TypeError(message ?? typeErrorMessage('RegExp', value)); } } -export function assertSafeInteger(value: unknown): asserts value is number { +export function assertSafeInteger(value: unknown, message?: string): asserts value is number { if (!isSafeInteger(value)) { - throw new TypeError(typeErrorMessage('integer', value)); + throw new TypeError(message ?? typeErrorMessage('integer', value)); } } -export function assertSet(value: unknown): asserts value is Set { +export function assertSet(value: unknown, message?: string): asserts value is Set { if (!isSet(value)) { - throw new TypeError(typeErrorMessage('Set', value)); + throw new TypeError(message ?? typeErrorMessage('Set', value)); } } -export function assertSharedArrayBuffer(value: unknown): asserts value is SharedArrayBuffer { +export function assertSharedArrayBuffer(value: unknown, message?: string): asserts value is SharedArrayBuffer { if (!isSharedArrayBuffer(value)) { - throw new TypeError(typeErrorMessage('SharedArrayBuffer', value)); + throw new TypeError(message ?? typeErrorMessage('SharedArrayBuffer', value)); } } -export function assertString(value: unknown): asserts value is string { +export function assertString(value: unknown, message?: string): asserts value is string { if (!isString(value)) { - throw new TypeError(typeErrorMessage('string', value)); + throw new TypeError(message ?? typeErrorMessage('string', value)); } } -export function assertSymbol(value: unknown): asserts value is symbol { +export function assertSymbol(value: unknown, message?: string): asserts value is symbol { if (!isSymbol(value)) { - throw new TypeError(typeErrorMessage('symbol', value)); + throw new TypeError(message ?? typeErrorMessage('symbol', value)); } } -export function assertTruthy(value: T | Falsy): asserts value is T { +export function assertTruthy(value: T | Falsy, message?: string): asserts value is T { if (!isTruthy(value)) { - throw new TypeError(typeErrorMessage('truthy', value)); + throw new TypeError(message ?? typeErrorMessage('truthy', value)); } } -export function assertTupleLike>>(value: unknown, guards: [...T]): asserts value is ResolveTypesOfTypeGuardsTuple { +export function assertTupleLike>>(value: unknown, guards: [...T], message?: string): asserts value is ResolveTypesOfTypeGuardsTuple { if (!isTupleLike(value, guards)) { - throw new TypeError(typeErrorMessage('tuple-like', value)); + throw new TypeError(message ?? typeErrorMessage('tuple-like', value)); } } -export function assertTypedArray(value: unknown): asserts value is TypedArray { +export function assertTypedArray(value: unknown, message?: string): asserts value is TypedArray { if (!isTypedArray(value)) { - throw new TypeError(typeErrorMessage('TypedArray', value)); + throw new TypeError(message ?? typeErrorMessage('TypedArray', value)); } } -export function assertUint16Array(value: unknown): asserts value is Uint16Array { +export function assertUint16Array(value: unknown, message?: string): asserts value is Uint16Array { if (!isUint16Array(value)) { - throw new TypeError(typeErrorMessage('Uint16Array', value)); + throw new TypeError(message ?? typeErrorMessage('Uint16Array', value)); } } -export function assertUint32Array(value: unknown): asserts value is Uint32Array { +export function assertUint32Array(value: unknown, message?: string): asserts value is Uint32Array { if (!isUint32Array(value)) { - throw new TypeError(typeErrorMessage('Uint32Array', value)); + throw new TypeError(message ?? typeErrorMessage('Uint32Array', value)); } } -export function assertUint8Array(value: unknown): asserts value is Uint8Array { +export function assertUint8Array(value: unknown, message?: string): asserts value is Uint8Array { if (!isUint8Array(value)) { - throw new TypeError(typeErrorMessage('Uint8Array', value)); + throw new TypeError(message ?? typeErrorMessage('Uint8Array', value)); } } -export function assertUint8ClampedArray(value: unknown): asserts value is Uint8ClampedArray { +export function assertUint8ClampedArray(value: unknown, message?: string): asserts value is Uint8ClampedArray { if (!isUint8ClampedArray(value)) { - throw new TypeError(typeErrorMessage('Uint8ClampedArray', value)); + throw new TypeError(message ?? typeErrorMessage('Uint8ClampedArray', value)); } } -export function assertUndefined(value: unknown): asserts value is undefined { +export function assertUndefined(value: unknown, message?: string): asserts value is undefined { if (!isUndefined(value)) { - throw new TypeError(typeErrorMessage('undefined', value)); + throw new TypeError(message ?? typeErrorMessage('undefined', value)); } } -export function assertUrlInstance(value: unknown): asserts value is URL { +export function assertUrlInstance(value: unknown, message?: string): asserts value is URL { if (!isUrlInstance(value)) { - throw new TypeError(typeErrorMessage('URL', value)); + throw new TypeError(message ?? typeErrorMessage('URL', value)); } } // eslint-disable-next-line unicorn/prevent-abbreviations -export function assertUrlSearchParams(value: unknown): asserts value is URLSearchParams { +export function assertUrlSearchParams(value: unknown, message?: string): asserts value is URLSearchParams { if (!isUrlSearchParams(value)) { - throw new TypeError(typeErrorMessage('URLSearchParams', value)); + throw new TypeError(message ?? typeErrorMessage('URLSearchParams', value)); } } -export function assertUrlString(value: unknown): asserts value is string { +export function assertUrlString(value: unknown, message?: string): asserts value is string { if (!isUrlString(value)) { - throw new TypeError(typeErrorMessage('string with a URL', value)); + throw new TypeError(message ?? typeErrorMessage('string with a URL', value)); } } -export function assertValidDate(value: unknown): asserts value is Date { +export function assertValidDate(value: unknown, message?: string): asserts value is Date { if (!isValidDate(value)) { - throw new TypeError(typeErrorMessage('valid Date', value)); + throw new TypeError(message ?? typeErrorMessage('valid Date', value)); } } -export function assertValidLength(value: unknown): asserts value is number { +export function assertValidLength(value: unknown, message?: string): asserts value is number { if (!isValidLength(value)) { - throw new TypeError(typeErrorMessage('valid length', value)); + throw new TypeError(message ?? typeErrorMessage('valid length', value)); } } // eslint-disable-next-line @typescript-eslint/ban-types -export function assertWeakMap(value: unknown): asserts value is WeakMap { +export function assertWeakMap(value: unknown, message?: string): asserts value is WeakMap { if (!isWeakMap(value)) { - throw new TypeError(typeErrorMessage('WeakMap', value)); + throw new TypeError(message ?? typeErrorMessage('WeakMap', value)); } } // eslint-disable-next-line @typescript-eslint/ban-types -export function assertWeakRef(value: unknown): asserts value is WeakRef { +export function assertWeakRef(value: unknown, message?: string): asserts value is WeakRef { if (!isWeakRef(value)) { - throw new TypeError(typeErrorMessage('WeakRef', value)); + throw new TypeError(message ?? typeErrorMessage('WeakRef', value)); } } // eslint-disable-next-line @typescript-eslint/ban-types -export function assertWeakSet(value: unknown): asserts value is WeakSet { +export function assertWeakSet(value: unknown, message?: string): asserts value is WeakSet { if (!isWeakSet(value)) { - throw new TypeError(typeErrorMessage('WeakSet', value)); + throw new TypeError(message ?? typeErrorMessage('WeakSet', value)); } } -export function assertWhitespaceString(value: unknown): asserts value is string { +export function assertWhitespaceString(value: unknown, message?: string): asserts value is string { if (!isWhitespaceString(value)) { - throw new TypeError(typeErrorMessage('whitespace string', value)); + throw new TypeError(message ?? typeErrorMessage('whitespace string', value)); } } diff --git a/test/test.ts b/test/test.ts index 1b94a01..1dd5f2a 100644 --- a/test/test.ts +++ b/test/test.ts @@ -2174,3 +2174,360 @@ test('assert', t => { t.true(is.string(badlyTypedVariable)); } }); + +test('custom assertion message', t => { + const message = 'Custom error message'; + + t.throws(() => { + assert.array(undefined, undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.arrayBuffer(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.arrayLike(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.asyncFunction(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.asyncGenerator(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.asyncGeneratorFunction(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.asyncIterable(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.bigInt64Array(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.bigUint64Array(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.bigint(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.blob(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.boolean(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.boundFunction(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.buffer(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.class(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.dataView(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.date(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.directInstanceOf(undefined, Error, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.emptyArray(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.emptyMap(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.emptyObject(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.emptySet(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.emptyString(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.emptyStringOrWhitespace(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + enum Enum {} + assert.enumCase('invalid', Enum, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.error(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.evenInteger(33, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.falsy(true, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.float32Array(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.float64Array(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.formData(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.function(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.generator(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.generatorFunction(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.htmlElement(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.inRange(5, [1, 2], message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.infinite(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.int16Array(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.int32Array(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.int8Array(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.integer(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.iterable(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.map(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.nan(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.nativePromise(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.negativeNumber(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.nodeStream(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.nonEmptyArray(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.nonEmptyMap(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.nonEmptyObject(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.nonEmptySet(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.nonEmptyString(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.nonEmptyStringAndNotWhitespace(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.null(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.nullOrUndefined(false, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.number(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.numericString(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.object(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.observable(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.oddInteger(42, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.plainObject(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.positiveNumber(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.primitive([], message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.promise(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.propertyKey(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.regExp(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.safeInteger(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.set(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.sharedArrayBuffer(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.string(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.symbol(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.truthy(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.tupleLike(undefined, [], message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.typedArray(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.uint16Array(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.uint32Array(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.uint8Array(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.uint8ClampedArray(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.undefined(false, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.urlInstance(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.urlSearchParams(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.urlString(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.validDate(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.validLength(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.weakMap(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.weakRef(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.weakSet(undefined, message); + }, {instanceOf: TypeError, message}); + + t.throws(() => { + assert.whitespaceString(undefined, message); + }, {instanceOf: TypeError, message}); +}); From a1987f8bada5f0279345f17c1b16b8f98291b54c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 23 Apr 2024 12:59:46 +0700 Subject: [PATCH 221/254] 6.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b95cfae..89ae330 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "6.2.0", + "version": "6.3.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From f7148e19dce95de116064e6315f8e63c60c880fc Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 28 Apr 2024 23:54:22 +0700 Subject: [PATCH 222/254] Meta tweaks --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index a1c90cb..7416c87 100644 --- a/readme.md +++ b/readme.md @@ -743,7 +743,7 @@ The most common mistakes I noticed in these modules was using `instanceof` for t ## Related -- [ow](https://github.com/sindresorhus/ow) - Function argument validation for humans +- [environment](https://github.com/sindresorhus/environment) - Check which JavaScript environment your code is running in at runtime - [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 From 0df21e4151931695d283325398d9558c9c6b31df Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 16 May 2024 11:11:44 +0300 Subject: [PATCH 223/254] Add missing type guard for `is.enumCase` Fixes #205 --- source/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.ts b/source/index.ts index 369ce47..a3a7cf3 100644 --- a/source/index.ts +++ b/source/index.ts @@ -447,7 +447,7 @@ export function isEmptyStringOrWhitespace(value: unknown): value is string { return isEmptyString(value) || isWhitespaceString(value); } -export function isEnumCase(value: unknown, targetEnum: T): boolean { +export function isEnumCase(value: unknown, targetEnum: T): value is T[keyof T] { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return Object.values(targetEnum as any).includes(value as string); } From 47f49741eacf0a3678684738159a87c2011bb026 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 16 May 2024 11:15:23 +0300 Subject: [PATCH 224/254] 6.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89ae330..76ef84d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "6.3.0", + "version": "6.3.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 92699e104960d3cd91178169390049f1e24e0215 Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Tue, 25 Jun 2024 08:08:48 +0800 Subject: [PATCH 225/254] Replace ts-node with tsimp (#208) --- .gitignore | 1 + package.json | 24 +++++++++++++++--------- test/test.ts | 14 +++++++------- tsconfig.json | 5 ----- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index c406da7..8fb947a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules yarn.lock dist +.tsimp diff --git a/package.json b/package.json index 76ef84d..0cbbf4a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "scripts": { "build": "del dist && tsc", - "test": "tsc --noEmit && xo && NODE_OPTIONS='--loader=ts-node/esm --no-warnings=ExperimentalWarning' ava", + "test": "tsc --noEmit && xo && ava", "prepare": "npm run build" }, "files": [ @@ -50,24 +50,30 @@ "types" ], "devDependencies": { - "@sindresorhus/tsconfig": "^4.0.0", - "@types/jsdom": "^21.1.1", - "@types/node": "^20.5.0", + "@sindresorhus/tsconfig": "^5.0.0", + "@types/jsdom": "^21.1.7", + "@types/node": "^20.14.8", "@types/zen-observable": "^0.8.3", - "ava": "^5.3.1", + "ava": "^6.1.3", "del-cli": "^5.0.0", + "expect-type": "^0.16.0", "jsdom": "^22.1.0", "rxjs": "^7.8.1", "tempy": "^3.1.0", - "ts-node": "^10.9.1", + "tsimp": "^2.0.11", "typescript": "^5.1.6", "xo": "^0.56.0", - "zen-observable": "^0.10.0", - "expect-type": "^0.16.0" + "zen-observable": "^0.10.0" }, "ava": { + "environmentVariables": { + "TSIMP_DIAG": "error" + }, "extensions": { "ts": "module" - } + }, + "nodeArguments": [ + "--import=tsimp/import" + ] } } diff --git a/test/test.ts b/test/test.ts index 1dd5f2a..a02026e 100644 --- a/test/test.ts +++ b/test/test.ts @@ -764,13 +764,13 @@ test('is.array', t => { t.notThrows(() => { const x: unknown[] = [1, 2, 3]; assert.array(x, assert.number); - x[0].toFixed(0); + x[0]?.toFixed(0); }); t.notThrows(() => { const x: unknown[] = [1, 2, 3]; if (is.array(x, is.number)) { - x[0].toFixed(0); + x[0]?.toFixed(0); } }); }); @@ -1174,7 +1174,7 @@ test('is.falsy', t => { // Checks that `assert.falsy` narrow downs boolean type to `false`. { const booleans = [false, true]; - const function_ = (value: false) => value; + const function_ = (value?: false) => value; assert.falsy(booleans[0]); function_(booleans[0]); } @@ -1182,7 +1182,7 @@ test('is.falsy', t => { // Checks that `assert.falsy` narrow downs number type to `0`. { const bits = [0, -0, 1]; - const function_ = (value: 0) => value; + const function_ = (value?: 0) => value; assert.falsy(bits[0]); function_(bits[0]); assert.falsy(bits[1]); @@ -1192,7 +1192,7 @@ test('is.falsy', t => { // Checks that `assert.falsy` narrow downs bigint type to `0n`. { const bits = [0n, -0n, 1n]; - const function_ = (value: 0n) => value; + const function_ = (value?: 0n) => value; assert.falsy(bits[0]); function_(bits[0]); assert.falsy(bits[1]); @@ -1202,7 +1202,7 @@ test('is.falsy', t => { // Checks that `assert.falsy` narrow downs string type to empty string. { const strings = ['', 'nonEmpty']; - const function_ = (value: '') => value; + const function_ = (value?: '') => value; assert.falsy(strings[0]); function_(strings[0]); } @@ -1219,7 +1219,7 @@ test('is.falsy', t => { { const maybeNulls = [null, Symbol('🦄')]; // eslint-disable-next-line @typescript-eslint/ban-types - const function_ = (value: null) => value; + const function_ = (value?: null) => value; assert.falsy(maybeNulls[0]); function_(maybeNulls[0]); } diff --git a/tsconfig.json b/tsconfig.json index b0821de..b5ebd0e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,9 +6,4 @@ "include": [ "source" ], - "ts-node": { - "transpileOnly": true, - "files": true, - "experimentalResolver": true - } } From 8cbcaee674958b51f78df1977a140349221c2692 Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Wed, 26 Jun 2024 20:30:00 +0800 Subject: [PATCH 226/254] Remove deprecated methods and improve `Class` definition (#209) --- source/index.ts | 30 +++--------------------------- source/types.ts | 6 ++---- test/test.ts | 8 ++++---- 3 files changed, 9 insertions(+), 35 deletions(-) diff --git a/source/index.ts b/source/index.ts index a3a7cf3..fb552f6 100644 --- a/source/index.ts +++ b/source/index.ts @@ -236,14 +236,10 @@ const is = Object.assign( boundFunction: isBoundFunction, buffer: isBuffer, class: isClass, - /** @deprecated Renamed to `class`. */ - class_: isClass, dataView: isDataView, date: isDate, detect, directInstanceOf: isDirectInstanceOf, - /** @deprecated Renamed to `htmlElement` */ - domElement: isHtmlElement, emptyArray: isEmptyArray, emptyMap: isEmptyMap, emptyObject: isEmptyObject, @@ -258,8 +254,6 @@ const is = Object.assign( float64Array: isFloat64Array, formData: isFormData, function: isFunction, - /** @deprecated Renamed to `function`. */ - function_: isFunction, generator: isGenerator, generatorFunction: isGeneratorFunction, htmlElement: isHtmlElement, @@ -282,8 +276,6 @@ const is = Object.assign( nonEmptyString: isNonEmptyString, nonEmptyStringAndNotWhitespace: isNonEmptyStringAndNotWhitespace, null: isNull, - /** @deprecated Renamed to `null`. */ - null_: isNull, nullOrUndefined: isNullOrUndefined, number: isNumber, numericString: isNumericString, @@ -403,7 +395,7 @@ export function isBuffer(value: unknown): value is Buffer { return (value as any)?.constructor?.isBuffer?.(value) ?? false; } -export function isClass(value: unknown): value is Class { +export function isClass(value: unknown): value is Class { return isFunction(value) && value.toString().startsWith('class '); } @@ -832,17 +824,9 @@ type Assert = { bigint: (value: unknown, message?: string) => asserts value is bigint; // eslint-disable-next-line @typescript-eslint/ban-types function: (value: unknown, message?: string) => asserts value is Function; - /** @deprecated Renamed to `function`. */ - // eslint-disable-next-line @typescript-eslint/ban-types - function_: (value: unknown, message?: string) => asserts value is Function; // eslint-disable-next-line @typescript-eslint/ban-types null: (value: unknown, message?: string) => asserts value is null; - /** @deprecated Renamed to `null`. */ - // eslint-disable-next-line @typescript-eslint/ban-types - null_: (value: unknown, message?: string) => asserts value is null; - class: (value: unknown, message?: string) => asserts value is Class; - /** @deprecated Renamed to `class`. */ - class_: (value: unknown, message?: string) => asserts value is Class; + class: (value: unknown, message?: string) => asserts value is Class; boolean: (value: unknown, message?: string) => asserts value is boolean; symbol: (value: unknown, message?: string) => asserts value is symbol; numericString: (value: unknown, message?: string) => asserts value is `${number}`; @@ -902,8 +886,6 @@ type Assert = { typedArray: (value: unknown, message?: string) => asserts value is TypedArray; arrayLike: (value: unknown, message?: string) => asserts value is ArrayLike; tupleLike: >>(value: unknown, guards: [...T], message?: string) => asserts value is ResolveTypesOfTypeGuardsTuple; - /** @deprecated Renamed to `htmlElement` */ - domElement: (value: unknown, message?: string) => asserts value is HTMLElement; htmlElement: (value: unknown, message?: string) => asserts value is HTMLElement; observable: (value: unknown, message?: string) => asserts value is ObservableLike; nodeStream: (value: unknown, message?: string) => asserts value is NodeStream; @@ -958,11 +940,9 @@ export const assert: Assert = { boundFunction: assertBoundFunction, buffer: assertBuffer, class: assertClass, - class_: assertClass, dataView: assertDataView, date: assertDate, directInstanceOf: assertDirectInstanceOf, - domElement: assertHtmlElement, emptyArray: assertEmptyArray, emptyMap: assertEmptyMap, emptyObject: assertEmptyObject, @@ -977,7 +957,6 @@ export const assert: Assert = { float64Array: assertFloat64Array, formData: assertFormData, function: assertFunction, - function_: assertFunction, generator: assertGenerator, generatorFunction: assertGeneratorFunction, htmlElement: assertHtmlElement, @@ -1000,7 +979,6 @@ export const assert: Assert = { nonEmptyString: assertNonEmptyString, nonEmptyStringAndNotWhitespace: assertNonEmptyStringAndNotWhitespace, null: assertNull, - null_: assertNull, nullOrUndefined: assertNullOrUndefined, number: assertNumber, numericString: assertNumericString, @@ -1056,8 +1034,6 @@ const methodTypeMap = { isDataView: 'DataView', isDate: 'Date', isDirectInstanceOf: 'T', - /** @deprecated */ - isDomElement: 'HTMLElement', isEmptyArray: 'empty array', isEmptyMap: 'empty map', isEmptyObject: 'empty object', @@ -1247,7 +1223,7 @@ export function assertBuffer(value: unknown, message?: string): asserts value is } } -export function assertClass(value: unknown, message?: string): asserts value is Class { +export function assertClass(value: unknown, message?: string): asserts value is Class { if (!isClass(value)) { throw new TypeError(message ?? typeErrorMessage('Class', value)); } diff --git a/source/types.ts b/source/types.ts index e77d73a..56f62a7 100644 --- a/source/types.ts +++ b/source/types.ts @@ -15,14 +15,12 @@ export type Primitive = /** Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). */ -/// type Constructor = new(...arguments_: Arguments) => T; +type Constructor = new(...arguments_: Arguments) => T; /** Matches a [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). */ -// TODO: Use the below in the next major version. -// export type Class = Constructor & {prototype: T}; -export type Class = new (...arguments_: Arguments) => T; +export type Class = Constructor & {prototype: T}; /** Matches any [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), like `Uint8Array` or `Float64Array`. diff --git a/test/test.ts b/test/test.ts index a02026e..4086a18 100644 --- a/test/test.ts +++ b/test/test.ts @@ -61,7 +61,7 @@ const types = new Map([ }], ['null', { is: is.null, - assert: assert.null_, + assert: assert.null, fixtures: [ null, ], @@ -161,7 +161,7 @@ const types = new Map([ }], ['function', { is: is.function, - assert: assert.function_, + assert: assert.function, fixtures: [ function foo() {}, // eslint-disable-line func-names function () {}, @@ -840,7 +840,7 @@ test('is.asyncFunction', t => { t.true(is.function(fixture().then)); t.notThrows(() => { - assert.function_(fixture().then); + assert.function(fixture().then); }); } }); @@ -1370,7 +1370,7 @@ test('is.class', t => { t.true(is.class(classDeclaration)); t.notThrows(() => { - assert.class_(classDeclaration); + assert.class(classDeclaration); }); } }); From 25a376875d3c10e3963e2e948960162a221fe583 Mon Sep 17 00:00:00 2001 From: Martin Eneqvist Date: Wed, 26 Jun 2024 14:31:56 +0200 Subject: [PATCH 227/254] Fix type guard for `isWhitespaceString` and `isEmptyStringOrWhitespace` (#207) --- source/index.ts | 9 +++++---- source/types.ts | 2 ++ test/test.ts | 7 +++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/source/index.ts b/source/index.ts index fb552f6..cd7deaf 100644 --- a/source/index.ts +++ b/source/index.ts @@ -10,6 +10,7 @@ import type { Primitive, TypedArray, WeakRef, + Whitespace, } from './types.js'; const typedArrayTypeNames = [ @@ -435,7 +436,7 @@ export function isEmptyString(value: unknown): value is '' { return isString(value) && value.length === 0; } -export function isEmptyStringOrWhitespace(value: unknown): value is string { +export function isEmptyStringOrWhitespace(value: unknown): value is '' | Whitespace { return isEmptyString(value) || isWhitespaceString(value); } @@ -777,7 +778,7 @@ export function isWeakSet(value: unknown): value is WeakSet { return getObjectType(value) === 'WeakSet'; } -export function isWhitespaceString(value: unknown): value is string { +export function isWhitespaceString(value: unknown): value is Whitespace { return isString(value) && /^\s+$/.test(value); } @@ -893,7 +894,7 @@ type Assert = { emptyArray: (value: unknown, message?: string) => asserts value is never[]; nonEmptyArray: (value: T | Item[], message?: string) => asserts value is [Item, ...Item[]]; emptyString: (value: unknown, message?: string) => asserts value is ''; - emptyStringOrWhitespace: (value: unknown, message?: string) => asserts value is string; + emptyStringOrWhitespace: (value: unknown, message?: string) => asserts value is '' | Whitespace; nonEmptyString: (value: unknown, message?: string) => asserts value is string; nonEmptyStringAndNotWhitespace: (value: unknown, message?: string) => asserts value is string; emptyObject: (value: unknown, message?: string) => asserts value is Record; @@ -1277,7 +1278,7 @@ export function assertEmptyString(value: unknown, message?: string): asserts val } } -export function assertEmptyStringOrWhitespace(value: unknown, message?: string): asserts value is string { +export function assertEmptyStringOrWhitespace(value: unknown, message?: string): asserts value is '' | Whitespace { if (!isEmptyStringOrWhitespace(value)) { throw new TypeError(message ?? typeErrorMessage('empty string or whitespace', value)); } diff --git a/source/types.ts b/source/types.ts index 56f62a7..2340d36 100644 --- a/source/types.ts +++ b/source/types.ts @@ -73,3 +73,5 @@ export type NodeStream = { export type Predicate = (value: unknown) => boolean; export type NonEmptyString = string & {0: string}; + +export type Whitespace = ' '; diff --git a/test/test.ts b/test/test.ts index 4086a18..0c9913c 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1793,6 +1793,13 @@ test('is.emptyStringOrWhitespace', t => { t.throws(() => { assert.emptyStringOrWhitespace('unicorn'); }); + + let value = 'test'; // eslint-disable-line prefer-const -- can't use `const` here because then it will be inferred as `never` in the `if` block + if (is.emptyStringOrWhitespace(value)) { + value.charAt(0); // Should be inferred as `'' | Whitespace` and not `never` + } else { + value.charAt(0); // Should be inferred as `string` and not `never` + } }); test('is.nonEmptyString', t => { From 0ff273fee848464472e0cde10ba9c0506b341e3f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 10 Jul 2024 15:41:12 +0200 Subject: [PATCH 228/254] Require Node.js 18 --- .gitignore | 2 +- package.json | 28 +++++++++++++++------------- readme.md | 4 ++++ source/index.ts | 39 ++++++++++++++++++++++++++------------- source/types.ts | 2 +- test/test.ts | 18 +++++++++--------- tsconfig.json | 3 --- 7 files changed, 56 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index 8fb947a..eb99a21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ node_modules yarn.lock -dist +/distribution .tsimp diff --git a/package.json b/package.json index 0cbbf4a..7f01400 100644 --- a/package.json +++ b/package.json @@ -11,19 +11,21 @@ "url": "https://sindresorhus.com" }, "type": "module", - "exports": "./dist/index.js", - "types": "./dist/index.d.ts", + "exports": { + "types": "./distribution/index.d.ts", + "default": "./distribution/index.js" + }, "sideEffects": false, "engines": { - "node": ">=16" + "node": ">=18" }, "scripts": { - "build": "del dist && tsc", + "build": "del distribution && tsc", "test": "tsc --noEmit && xo && ava", "prepare": "npm run build" }, "files": [ - "dist" + "distribution" ], "keywords": [ "type", @@ -50,19 +52,19 @@ "types" ], "devDependencies": { - "@sindresorhus/tsconfig": "^5.0.0", + "@sindresorhus/tsconfig": "^6.0.0", "@types/jsdom": "^21.1.7", - "@types/node": "^20.14.8", - "@types/zen-observable": "^0.8.3", + "@types/node": "^20.14.10", + "@types/zen-observable": "^0.8.7", "ava": "^6.1.3", - "del-cli": "^5.0.0", - "expect-type": "^0.16.0", - "jsdom": "^22.1.0", + "del-cli": "^5.1.0", + "expect-type": "^0.19.0", + "jsdom": "^24.1.0", "rxjs": "^7.8.1", "tempy": "^3.1.0", "tsimp": "^2.0.11", - "typescript": "^5.1.6", - "xo": "^0.56.0", + "typescript": "^5.5.3", + "xo": "^0.58.0", "zen-observable": "^0.10.0" }, "ava": { diff --git a/readme.md b/readme.md index 7416c87..1966a5b 100644 --- a/readme.md +++ b/readme.md @@ -133,6 +133,10 @@ is.array(value, is.number); // Validate `value` is an array and all of its items ##### .function(value) ##### .buffer(value) + +> [!NOTE] +> [Prefer using `Uint8Array` instead of `Buffer`.](https://sindresorhus.com/blog/goodbye-nodejs-buffer) + ##### .blob(value) ##### .object(value) diff --git a/source/index.ts b/source/index.ts index cd7deaf..9fd1923 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,4 +1,3 @@ -import type {Buffer} from 'node:buffer'; import type { ArrayLike, Class, @@ -13,6 +12,14 @@ import type { Whitespace, } from './types.js'; +// From type-fest. +type ExtractFromGlobalConstructors = + Name extends string + ? typeof globalThis extends Record infer T> ? T : never + : never; + +type NodeBuffer = ExtractFromGlobalConstructors<'Buffer'>; + const typedArrayTypeNames = [ 'Int8Array', 'Uint8Array', @@ -314,7 +321,7 @@ const is = Object.assign( }, ); -function isAbsoluteMod2(remainder: 0 | 1) { +function isAbsoluteModule2(remainder: 0 | 1) { return (value: unknown): value is number => isInteger(value) && Math.abs(value % 2) === remainder; } @@ -350,7 +357,7 @@ export function isArrayLike(value: unknown): value is ArrayLike return !isNullOrUndefined(value) && !isFunction(value) && isValidLength((value as ArrayLike).length); } -export function isAsyncFunction(value: unknown): value is ((...args: any[]) => Promise) { +export function isAsyncFunction(value: unknown): value is ((...arguments_: any[]) => Promise) { return getObjectType(value) === 'AsyncFunction'; } @@ -358,7 +365,7 @@ export function isAsyncGenerator(value: unknown): value is AsyncGenerator { return isAsyncIterable(value) && isFunction((value as AsyncGenerator).next) && isFunction((value as AsyncGenerator).throw); } -export function isAsyncGeneratorFunction(value: unknown): value is ((...args: any[]) => Promise) { +export function isAsyncGeneratorFunction(value: unknown): value is ((...arguments_: any[]) => Promise) { return getObjectType(value) === 'AsyncGeneratorFunction'; } @@ -388,10 +395,13 @@ export function isBoolean(value: unknown): value is boolean { // eslint-disable-next-line @typescript-eslint/ban-types export function isBoundFunction(value: unknown): value is Function { - return isFunction(value) && !Object.prototype.hasOwnProperty.call(value, 'prototype'); + return isFunction(value) && !Object.hasOwn(value, 'prototype'); } -export function isBuffer(value: unknown): value is Buffer { +/** +Note: [Prefer using `Uint8Array` instead of `Buffer`.](https://sindresorhus.com/blog/goodbye-nodejs-buffer) +*/ +export function isBuffer(value: unknown): value is NodeBuffer { // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call return (value as any)?.constructor?.isBuffer?.(value) ?? false; } @@ -450,7 +460,7 @@ export function isError(value: unknown): value is Error { } export function isEvenInteger(value: unknown): value is number { - return isAbsoluteMod2(0)(value); + return isAbsoluteModule2(0)(value); } // Example: `is.falsy = (value: unknown): value is (not true | 0 | '' | undefined | null) => Boolean(value);` @@ -629,7 +639,7 @@ export function isObservable(value: unknown): value is ObservableLike { } export function isOddInteger(value: unknown): value is number { - return isAbsoluteMod2(1)(value); + return isAbsoluteModule2(1)(value); } export function isPlainObject(value: unknown): value is Record { @@ -768,7 +778,7 @@ export function isWeakMap(value: u return getObjectType(value) === 'WeakMap'; } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/ban-types, unicorn/prevent-abbreviations export function isWeakRef(value: unknown): value is WeakRef { return getObjectType(value) === 'WeakRef'; } @@ -782,7 +792,7 @@ export function isWhitespaceString(value: unknown): value is Whitespace { return isString(value) && /^\s+$/.test(value); } -type ArrayMethod = (fn: (value: unknown, index: number, array: unknown[]) => boolean, thisArg?: unknown) => boolean; +type ArrayMethod = (function_: (value: unknown, index: number, array: unknown[]) => boolean, thisArgument?: unknown) => boolean; function predicateOnArray(method: ArrayMethod, predicate: Predicate, values: unknown[]) { if (!isFunction(predicate)) { @@ -832,7 +842,7 @@ type Assert = { symbol: (value: unknown, message?: string) => asserts value is symbol; numericString: (value: unknown, message?: string) => asserts value is `${number}`; array: (value: unknown, assertion?: (element: unknown) => asserts element is T, message?: string) => asserts value is T[]; - buffer: (value: unknown, message?: string) => asserts value is Buffer; + buffer: (value: unknown, message?: string) => asserts value is NodeBuffer; blob: (value: unknown, message?: string) => asserts value is Blob; // eslint-disable-next-line @typescript-eslint/ban-types nullOrUndefined: (value: unknown, message?: string) => asserts value is null | undefined; @@ -1218,7 +1228,10 @@ export function assertBoundFunction(value: unknown, message?: string): asserts v } } -export function assertBuffer(value: unknown, message?: string): asserts value is Buffer { +/** +Note: [Prefer using `Uint8Array` instead of `Buffer`.](https://sindresorhus.com/blog/goodbye-nodejs-buffer) +*/ +export function assertBuffer(value: unknown, message?: string): asserts value is NodeBuffer { if (!isBuffer(value)) { throw new TypeError(message ?? typeErrorMessage('Buffer', value)); } @@ -1656,7 +1669,7 @@ export function assertWeakMap(valu } } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/ban-types, unicorn/prevent-abbreviations export function assertWeakRef(value: unknown, message?: string): asserts value is WeakRef { if (!isWeakRef(value)) { throw new TypeError(message ?? typeErrorMessage('WeakRef', value)); diff --git a/source/types.ts b/source/types.ts index 2340d36..b79a603 100644 --- a/source/types.ts +++ b/source/types.ts @@ -56,7 +56,7 @@ export type ObservableLike = { // eslint-disable-next-line @typescript-eslint/ban-types export type Falsy = false | 0 | 0n | '' | null | undefined; -export type WeakRef = { // eslint-disable-line @typescript-eslint/ban-types +export type WeakRef = { // eslint-disable-line @typescript-eslint/ban-types, unicorn/prevent-abbreviations readonly [Symbol.toStringTag]: 'WeakRef'; deref(): T | undefined; }; diff --git a/test/test.ts b/test/test.ts index 0c9913c..4017cc3 100644 --- a/test/test.ts +++ b/test/test.ts @@ -27,18 +27,18 @@ const {document} = window; const structuredClone = globalThis.structuredClone ?? (x => x); type Test = { - assert: (...args: any[]) => void | never; + assert: (...arguments_: any[]) => void | never; fixtures: unknown[]; typename?: TypeName; typeDescription?: AssertionTypeDescription; is(value: unknown): boolean; }; -const invertAssertThrow = (description: AssertionTypeDescription, fn: () => void | never, value: unknown): void | never => { +const invertAssertThrow = (description: AssertionTypeDescription, function_: () => void | never, value: unknown): void | never => { const expectedAssertErrorMessage = `Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`; try { - fn(); + function_(); } catch (error: unknown) { if (error instanceof TypeError && error.message.includes(expectedAssertErrorMessage)) { return; @@ -488,10 +488,10 @@ const types = new Map([ fixtures: [ {x: 1}, Object.create(null), - new Object(), // eslint-disable-line no-new-object + new Object(), // eslint-disable-line no-object-constructor structuredClone({x: 1}), structuredClone(Object.create(null)), - structuredClone(new Object()), // eslint-disable-line no-new-object + structuredClone(new Object()), // eslint-disable-line no-object-constructor ], typename: 'Object', typeDescription: 'plain object', @@ -1841,14 +1841,14 @@ test('is.nonEmptyStringAndNotWhitespace', t => { test('is.emptyObject', t => { t.true(is.emptyObject({})); - t.true(is.emptyObject(new Object())); // eslint-disable-line no-new-object + t.true(is.emptyObject(new Object())); // eslint-disable-line no-object-constructor t.false(is.emptyObject({unicorn: '🦄'})); t.notThrows(() => { assert.emptyObject({}); }); t.notThrows(() => { - assert.emptyObject(new Object()); // eslint-disable-line no-new-object + assert.emptyObject(new Object()); // eslint-disable-line no-object-constructor }); t.throws(() => { assert.emptyObject({unicorn: '🦄'}); @@ -1860,14 +1860,14 @@ test('is.nonEmptyObject', t => { is.nonEmptyObject(foo); t.false(is.nonEmptyObject({})); - t.false(is.nonEmptyObject(new Object())); // eslint-disable-line no-new-object + t.false(is.nonEmptyObject(new Object())); // eslint-disable-line no-object-constructor t.true(is.nonEmptyObject({unicorn: '🦄'})); t.throws(() => { assert.nonEmptyObject({}); }); t.throws(() => { - assert.nonEmptyObject(new Object()); // eslint-disable-line no-new-object + assert.nonEmptyObject(new Object()); // eslint-disable-line no-object-constructor }); t.notThrows(() => { assert.nonEmptyObject({unicorn: '🦄'}); diff --git a/tsconfig.json b/tsconfig.json index b5ebd0e..0aace6f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,5 @@ { "extends": "@sindresorhus/tsconfig", - "compilerOptions": { - "outDir": "dist", - }, "include": [ "source" ], From ab85d9bca9564eb0f867ab45e3cbf45659d9ac4c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 10 Jul 2024 15:43:01 +0200 Subject: [PATCH 229/254] 7.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f01400..70404bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "6.3.1", + "version": "7.0.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 5565c5e3baed0f63bccf358933b5f54bf3b9cfc8 Mon Sep 17 00:00:00 2001 From: Martin Eneqvist Date: Fri, 6 Sep 2024 17:52:56 +0200 Subject: [PATCH 230/254] Fix passing in assertion message to `assertArray` (#210) Co-authored-by: Sindre Sorhus --- source/index.ts | 8 +++++--- test/test.ts | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/source/index.ts b/source/index.ts index 9fd1923..e7835cf 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1143,14 +1143,16 @@ export function assertAny(predicate: Predicate | Predicate[], ...values: unknown } } -export function assertArray(value: unknown, assertion?: (element: unknown) => asserts element is T, message?: string): asserts value is T[] { +export function assertArray(value: unknown, assertion?: (element: unknown, message?: string) => asserts element is T, message?: string): asserts value is T[] { if (!isArray(value)) { throw new TypeError(message ?? typeErrorMessage('Array', value)); } if (assertion) { - // eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-callback-reference - value.forEach(assertion); + for (const element of value) { + // @ts-expect-error: "Assertions require every name in the call target to be declared with an explicit type annotation." + assertion(element, message); + } } } diff --git a/test/test.ts b/test/test.ts index 4017cc3..258fb89 100644 --- a/test/test.ts +++ b/test/test.ts @@ -773,6 +773,10 @@ test('is.array', t => { x[0]?.toFixed(0); } }); + + t.throws(() => { + assert.array([1, '2'], assert.number, 'Expected numbers'); + }, {message: /Expected numbers/}); }); test('is.function', t => { From e0976457e04ba5df210ca0c976844b580b62f741 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 6 Sep 2024 22:54:53 +0700 Subject: [PATCH 231/254] 7.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 70404bf..bcbc532 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "7.0.0", + "version": "7.0.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 47a5099325f959cf69522a50e75bb89c593d554c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 4 Jun 2025 12:12:42 +0300 Subject: [PATCH 232/254] Minor tweaks --- source/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/index.ts b/source/index.ts index e7835cf..5913d7a 100644 --- a/source/index.ts +++ b/source/index.ts @@ -456,6 +456,7 @@ export function isEnumCase(value: unknown, targetEnum: T): value is } export function isError(value: unknown): value is Error { + // TODO: Use `Error.isError` when targeting Node.js 24.` return getObjectType(value) === 'Error'; } @@ -468,6 +469,8 @@ export function isFalsy(value: unknown): value is Falsy { return !value; } +// TODO: Support detecting Float16Array when targeting Node.js 24. + export function isFloat32Array(value: unknown): value is Float32Array { return getObjectType(value) === 'Float32Array'; } From e8e8124ba72e82d13cfb28e7359a4c1e823d4c07 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 4 Jun 2025 12:13:16 +0300 Subject: [PATCH 233/254] FIx observable checking Fixes #214 --- package.json | 4 ++-- source/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index bcbc532..a9f708e 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,8 @@ "jsdom": "^24.1.0", "rxjs": "^7.8.1", "tempy": "^3.1.0", - "tsimp": "^2.0.11", - "typescript": "^5.5.3", + "tsimp": "2.0.11", + "typescript": "5.5.3", "xo": "^0.58.0", "zen-observable": "^0.10.0" }, diff --git a/source/index.ts b/source/index.ts index 5913d7a..bb30c0b 100644 --- a/source/index.ts +++ b/source/index.ts @@ -629,7 +629,7 @@ export function isObservable(value: unknown): value is ObservableLike { } // eslint-disable-next-line no-use-extend-native/no-use-extend-native, @typescript-eslint/no-unsafe-call - if (value === (value as any)[Symbol.observable]?.()) { + if (Symbol.observable !== undefined && value === (value as any)[Symbol.observable]?.()) { return true; } From 882a91c54fb90ce265af9e6f9b9c4e7a65e6bf2f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 4 Jun 2025 12:57:42 +0300 Subject: [PATCH 234/254] 7.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9f708e..c7d1a45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "7.0.1", + "version": "7.0.2", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From ef35cc350af879f5747ccfa9bde6343cc30b144d Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Fri, 6 Jun 2025 19:58:46 +0800 Subject: [PATCH 235/254] Structure fixtures so they can be easily tested for exclusivity (#215) --- source/index.ts | 11 +- source/utilities.ts | 3 + test/test.ts | 1144 ++++++++++++++++--------------------------- 3 files changed, 425 insertions(+), 733 deletions(-) create mode 100644 source/utilities.ts diff --git a/source/index.ts b/source/index.ts index bb30c0b..894318e 100644 --- a/source/index.ts +++ b/source/index.ts @@ -11,6 +11,7 @@ import type { WeakRef, Whitespace, } from './types.js'; +import {keysOf} from './utilities.js'; // From type-fest. type ExtractFromGlobalConstructors = @@ -209,10 +210,14 @@ function detect(value: unknown): TypeName { } const tagType = getObjectType(value); - if (tagType) { + if (tagType && tagType !== 'Object') { return tagType; } + if (hasPromiseApi(value)) { + return 'Promise'; + } + if (value instanceof String || value instanceof Boolean || value instanceof Number) { throw new TypeError('Please don\'t use object wrappers for primitive types'); } @@ -1120,10 +1125,6 @@ const methodTypeMap = { isWhitespaceString: 'whitespace string', } as const; -function keysOf>(value: T): Array { - return Object.keys(value) as Array; -} - type IsMethodName = keyof typeof methodTypeMap; const isMethodNames: IsMethodName[] = keysOf(methodTypeMap); diff --git a/source/utilities.ts b/source/utilities.ts new file mode 100644 index 0000000..102b6db --- /dev/null +++ b/source/utilities.ts @@ -0,0 +1,3 @@ +export function keysOf>(value: T): Array { + return Object.keys(value) as Array; +} diff --git a/test/test.ts b/test/test.ts index 258fb89..bb09f37 100644 --- a/test/test.ts +++ b/test/test.ts @@ -13,10 +13,12 @@ import ZenObservable from 'zen-observable'; import is, { assert, type AssertionTypeDescription, + type Predicate, type Primitive, type TypedArray, type TypeName, } from '../source/index.js'; +import {keysOf} from '../source/utilities.js'; class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} @@ -26,498 +28,385 @@ const {document} = window; const structuredClone = globalThis.structuredClone ?? (x => x); -type Test = { - assert: (...arguments_: any[]) => void | never; +type Test = Readonly<{ fixtures: unknown[]; typename?: TypeName; typeDescription?: AssertionTypeDescription; - is(value: unknown): boolean; -}; +}>; -const invertAssertThrow = (description: AssertionTypeDescription, function_: () => void | never, value: unknown): void | never => { - const expectedAssertErrorMessage = `Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`; +// Every entry should be unique and belongs in the most specific type for that entry +const reusableFixtures = { + asyncFunction: [async function () {}, async () => {}], + asyncGeneratorFunction: [ + async function * () {}, + async function * () { + yield 4; + }, + ], + boundFunction: [() => {}, function () {}.bind(null)], // eslint-disable-line no-extra-bind + buffer: [Buffer.from('🦄')], + emptyArray: [[], new Array()], // eslint-disable-line @typescript-eslint/no-array-constructor + emptyMap: [new Map()], + emptySet: [new Set()], + emptyString: ['', String()], + function: [ + function foo() {}, // eslint-disable-line func-names + function () {}, + ], + generatorFunction: [ + function * () {}, + function * () { + yield 4; + }, + ], + infinite: [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY], + integer: [0, -0, 6], + nativePromise: [Promise.resolve(), PromiseSubclassFixture.resolve()], + number: [1.4], + numericString: ['5', '-3.2', 'Infinity', '0x56'], + plainObject: [ + {x: 1}, + Object.create(null), + new Object(), // eslint-disable-line no-object-constructor + structuredClone({x: 1}), + structuredClone(Object.create(null)), + structuredClone(new Object()), // eslint-disable-line no-object-constructor + ], + promise: [Object.create({then() {}, catch() {}})], // eslint-disable-line unicorn/no-thenable + safeInteger: [(2 ** 53) - 1, -(2 ** 53) + 1], +} as const satisfies Partial<{[K in keyof typeof is]: unknown[]}>; - try { - function_(); - } catch (error: unknown) { - if (error instanceof TypeError && error.message.includes(expectedAssertErrorMessage)) { - return; - } - - throw error; - } - - throw new Error(`Function did not throw any error, expected: ${expectedAssertErrorMessage}`); -}; - -const types = new Map([ - ['undefined', { - is: is.undefined, - assert: assert.undefined, +const primitiveTypes = { + undefined: { fixtures: [ undefined, ], typename: 'undefined', - }], - ['null', { - is: is.null, - assert: assert.null, + }, + null: { fixtures: [ null, ], typename: 'null', - }], - ['string', { - is: is.string, - assert: assert.string, + }, + string: { fixtures: [ '🦄', 'hello world', - '', + ...reusableFixtures.emptyString, + ...reusableFixtures.numericString, ], typename: 'string', - }], - ['emptyString', { - is: is.emptyString, - assert: assert.emptyString, - fixtures: [ - '', - String(), - ], + }, + emptyString: { + fixtures: [...reusableFixtures.emptyString], typename: 'string', typeDescription: 'empty string', - }], - ['number', { - is: is.number, - assert: assert.number, + }, + number: { fixtures: [ - 6, - 1.4, - 0, - -0, - Number.POSITIVE_INFINITY, - Number.NEGATIVE_INFINITY, + ...reusableFixtures.number, + ...reusableFixtures.infinite, + ...reusableFixtures.integer, + ...reusableFixtures.safeInteger, ], typename: 'number', - }], - ['bigint', { - is: is.bigint, - assert: assert.bigint, + }, + bigint: { fixtures: [ - // Disabled until TS supports it for an ESnnnn target. - // 1n, - // 0n, - // -0n, + 1n, + 0n, + -0n, BigInt('1234'), ], typename: 'bigint', - }], - ['boolean', { - is: is.boolean, - assert: assert.boolean, + }, + boolean: { fixtures: [ true, false, ], typename: 'boolean', - }], - ['symbol', { - is: is.symbol, - assert: assert.symbol, - fixtures: [ - Symbol('🦄'), - ], - typename: 'symbol', - }], - ['numericString', { - is: is.numericString, - assert: assert.numericString, - fixtures: [ - '5', - '-3.2', - 'Infinity', - '0x56', - ], + }, + numericString: { + fixtures: [...reusableFixtures.numericString], typename: 'string', typeDescription: 'string with a number', - }], - ['array', { - is: is.array, - assert: assert.array, - fixtures: [ - [1, 2], - Array.from({length: 2}), - ], - typename: 'Array', - }], - ['emptyArray', { - is: is.emptyArray, - assert: assert.emptyArray, - fixtures: [ - [], - new Array(), // eslint-disable-line @typescript-eslint/no-array-constructor - ], - typename: 'Array', - typeDescription: 'empty array', - }], - ['function', { - is: is.function, - assert: assert.function, - fixtures: [ - function foo() {}, // eslint-disable-line func-names - function () {}, - () => {}, - async function () {}, - function * (): unknown {}, - async function * (): unknown {}, - ], - typename: 'Function', - }], - ['buffer', { - is: is.buffer, - assert: assert.buffer, - fixtures: [ - Buffer.from('🦄'), - ], - typename: 'Buffer', - }], - ['blob', { - is: is.blob, - assert: assert.blob, - fixtures: [ - new window.Blob(), - ], - typename: 'Blob', - }], - ['object', { - is: is.object, - assert: assert.object, - fixtures: [ - {x: 1}, - Object.create({x: 1}), - ], - typename: 'Object', - }], - ['regExp', { - is: is.regExp, - assert: assert.regExp, - fixtures: [ - /\w/, - new RegExp('\\w'), // eslint-disable-line prefer-regex-literals - ], - typename: 'RegExp', - }], - ['date', { - is: is.date, - assert: assert.date, - fixtures: [ - new Date(), - ], - typename: 'Date', - }], - ['error', { - is: is.error, - assert: assert.error, - fixtures: [ - new Error('🦄'), - new ErrorSubclassFixture(), - ], - typename: 'Error', - }], - ['nativePromise', { - is: is.nativePromise, - assert: assert.nativePromise, - fixtures: [ - Promise.resolve(), - PromiseSubclassFixture.resolve(), - ], - typename: 'Promise', - typeDescription: 'native Promise', - }], - ['promise', { - is: is.promise, - assert: assert.promise, - fixtures: [ - {then() {}, catch() {}}, // eslint-disable-line unicorn/no-thenable - ], - typename: 'Object', - typeDescription: 'Promise', - }], - ['generator', { - is: is.generator, - assert: assert.generator, - fixtures: [ - (function * () { - yield 4; - })(), - ], - typename: 'Generator', - }], - ['asyncGenerator', { - is: is.asyncGenerator, - assert: assert.asyncGenerator, - fixtures: [ - (async function * () { - yield 4; - })(), - ], - typename: 'AsyncGenerator', - }], - ['generatorFunction', { - is: is.generatorFunction, - assert: assert.generatorFunction, - fixtures: [ - function * () { - yield 4; - }, - ], - typename: 'Function', - typeDescription: 'GeneratorFunction', - }], - ['asyncGeneratorFunction', { - is: is.asyncGeneratorFunction, - assert: assert.asyncGeneratorFunction, - fixtures: [ - async function * () { - yield 4; - }, - ], - typename: 'Function', - typeDescription: 'AsyncGeneratorFunction', - }], - ['asyncFunction', { - is: is.asyncFunction, - assert: assert.asyncFunction, - fixtures: [ - async function () {}, - async () => {}, - ], - typename: 'Function', - typeDescription: 'AsyncFunction', - }], - ['boundFunction', { - is: is.boundFunction, - assert: assert.boundFunction, - fixtures: [ - () => {}, - function () {}.bind(null), // eslint-disable-line no-extra-bind - ], - typename: 'Function', - }], - ['map', { - is: is.map, - assert: assert.map, - fixtures: [ - new Map([['one', '1']]), - ], - typename: 'Map', - }], - ['emptyMap', { - is: is.emptyMap, - assert: assert.emptyMap, - fixtures: [ - new Map(), - ], - typename: 'Map', - typeDescription: 'empty map', - }], - ['set', { - is: is.set, - assert: assert.set, - fixtures: [ - new Set(['one']), - ], - typename: 'Set', - }], - ['emptySet', { - is: is.emptySet, - assert: assert.emptySet, - fixtures: [ - new Set(), - ], - typename: 'Set', - typeDescription: 'empty set', - }], - ['weakSet', { - is: is.weakSet, - assert: assert.weakSet, - fixtures: [ - new WeakSet(), - ], - typename: 'WeakSet', - }], - ['weakRef', { - is: is.weakRef, - assert: assert.weakRef, - fixtures: window.WeakRef ? [new window.WeakRef({})] : [], - typename: 'WeakRef', - }], - ['weakMap', { - is: is.weakMap, - assert: assert.weakMap, - fixtures: [ - new WeakMap(), - ], - typename: 'WeakMap', - }], - ['int8Array', { - is: is.int8Array, - assert: assert.int8Array, - fixtures: [ - new Int8Array(), - ], - typename: 'Int8Array', - }], - ['uint8Array', { - is: is.uint8Array, - assert: assert.uint8Array, - fixtures: [ - new Uint8Array(), - ], - typename: 'Uint8Array', - }], - ['uint8ClampedArray', { - is: is.uint8ClampedArray, - assert: assert.uint8ClampedArray, - fixtures: [ - new Uint8ClampedArray(), - ], - typename: 'Uint8ClampedArray', - }], - ['int16Array', { - is: is.int16Array, - assert: assert.int16Array, - fixtures: [ - new Int16Array(), - ], - typename: 'Int16Array', - }], - ['uint16Array', { - is: is.uint16Array, - assert: assert.uint16Array, - fixtures: [ - new Uint16Array(), - ], - typename: 'Uint16Array', - }], - ['int32Array', { - is: is.int32Array, - assert: assert.int32Array, - fixtures: [ - new Int32Array(), - ], - typename: 'Int32Array', - }], - ['uint32Array', { - is: is.uint32Array, - assert: assert.uint32Array, - fixtures: [ - new Uint32Array(), - ], - typename: 'Uint32Array', - }], - ['float32Array', { - is: is.float32Array, - assert: assert.float32Array, - fixtures: [ - new Float32Array(), - ], - typename: 'Float32Array', - }], - ['float64Array', { - is: is.float64Array, - assert: assert.float64Array, - fixtures: [ - new Float64Array(), - ], - typename: 'Float64Array', - }], - ['bigInt64Array', { - is: is.bigInt64Array, - assert: assert.bigInt64Array, - fixtures: [ - new BigInt64Array(), - ], - typename: 'BigInt64Array', - }], - ['bigUint64Array', { - is: is.bigUint64Array, - assert: assert.bigUint64Array, - fixtures: [ - new BigUint64Array(), - ], - typename: 'BigUint64Array', - }], - ['arrayBuffer', { - is: is.arrayBuffer, - assert: assert.arrayBuffer, - fixtures: [ - new ArrayBuffer(10), - ], - typename: 'ArrayBuffer', - }], - ['dataView', { - is: is.dataView, - assert: assert.dataView, - fixtures: [ - new DataView(new ArrayBuffer(10)), - ], - typename: 'DataView', - }], - ['nan', { - is: is.nan, - assert: assert.nan, + }, + nan: { fixtures: [ NaN, // eslint-disable-line unicorn/prefer-number-properties Number.NaN, ], typename: 'NaN', typeDescription: 'NaN', - }], - ['nullOrUndefined', { - is: is.nullOrUndefined, - assert: assert.nullOrUndefined, + }, + nullOrUndefined: { fixtures: [ null, undefined, ], typeDescription: 'null or undefined', - }], - ['plainObject', { - is: is.plainObject, - assert: assert.plainObject, + }, + integer: { + fixtures: [...reusableFixtures.integer, ...reusableFixtures.safeInteger], + typename: 'number', + typeDescription: 'integer', + }, + safeInteger: { + fixtures: [...reusableFixtures.integer, ...reusableFixtures.safeInteger], + typename: 'number', + typeDescription: 'integer', + }, + infinite: { + fixtures: [...reusableFixtures.infinite], + typename: 'number', + typeDescription: 'infinite number', + }, +} as const satisfies Partial<{[K in keyof typeof is]: Test}>; + +const objectTypes = { + symbol: { fixtures: [ - {x: 1}, - Object.create(null), - new Object(), // eslint-disable-line no-object-constructor - structuredClone({x: 1}), - structuredClone(Object.create(null)), - structuredClone(new Object()), // eslint-disable-line no-object-constructor + Symbol('🦄'), + ], + typename: 'symbol', + }, + array: { + fixtures: [ + [1, 2], + Array.from({length: 2}), + ...reusableFixtures.emptyArray, + ], + typename: 'Array', + }, + emptyArray: { + fixtures: [...reusableFixtures.emptyArray], + typename: 'Array', + typeDescription: 'empty array', + }, + function: { + fixtures: [ + ...reusableFixtures.asyncFunction, + ...reusableFixtures.asyncGeneratorFunction, + ...reusableFixtures.boundFunction, + ...reusableFixtures.function, + ...reusableFixtures.generatorFunction, + ], + typename: 'Function', + }, + buffer: { + fixtures: [...reusableFixtures.buffer], + typename: 'Buffer', + }, + blob: { + fixtures: [ + new window.Blob(), + ], + typename: 'Blob', + }, + object: { + fixtures: [ + Object.create({x: 1}), + ...reusableFixtures.plainObject, + ], + typename: 'Object', + }, + regExp: { + fixtures: [ + /\w/, + new RegExp('\\w'), // eslint-disable-line prefer-regex-literals + ], + typename: 'RegExp', + }, + date: { + fixtures: [ + new Date(), + ], + typename: 'Date', + }, + error: { + fixtures: [ + new Error('🦄'), + new ErrorSubclassFixture(), + ], + typename: 'Error', + }, + nativePromise: { + fixtures: [...reusableFixtures.nativePromise], + typename: 'Promise', + typeDescription: 'native Promise', + }, + promise: { + fixtures: [ + ...reusableFixtures.nativePromise, + ...reusableFixtures.promise, + ], + typename: 'Promise', + typeDescription: 'Promise', + }, + generator: { + fixtures: [ + (function * () { + yield 4; + })(), + ], + typename: 'Generator', + }, + asyncGenerator: { + fixtures: [ + (async function * () { + yield 4; + })(), + ], + typename: 'AsyncGenerator', + }, + generatorFunction: { + fixtures: [...reusableFixtures.generatorFunction], + typename: 'Function', + typeDescription: 'GeneratorFunction', + }, + asyncGeneratorFunction: { + fixtures: [...reusableFixtures.asyncGeneratorFunction], + typename: 'Function', + typeDescription: 'AsyncGeneratorFunction', + }, + asyncFunction: { + fixtures: [...reusableFixtures.asyncFunction], + typename: 'Function', + typeDescription: 'AsyncFunction', + }, + boundFunction: { + fixtures: [...reusableFixtures.boundFunction, ...reusableFixtures.asyncFunction], + typename: 'Function', + }, + map: { + fixtures: [ + new Map([['one', '1']]), + ...reusableFixtures.emptyMap, + ], + typename: 'Map', + }, + emptyMap: { + fixtures: [...reusableFixtures.emptyMap], + typename: 'Map', + typeDescription: 'empty map', + }, + set: { + fixtures: [ + new Set(['one']), + ...reusableFixtures.emptySet, + ], + typename: 'Set', + }, + emptySet: { + fixtures: [...reusableFixtures.emptySet], + typename: 'Set', + typeDescription: 'empty set', + }, + weakSet: { + fixtures: [ + new WeakSet(), + ], + typename: 'WeakSet', + }, + weakRef: { + fixtures: [ + new window.WeakRef({}), + ], + typename: 'WeakRef', + }, + weakMap: { + fixtures: [ + new WeakMap(), + ], + typename: 'WeakMap', + }, + int8Array: { + fixtures: [ + new Int8Array(), + ], + typename: 'Int8Array', + }, + uint8Array: { + fixtures: [ + new Uint8Array(), + ], + typename: 'Uint8Array', + }, + uint8ClampedArray: { + fixtures: [ + new Uint8ClampedArray(), + ], + typename: 'Uint8ClampedArray', + }, + int16Array: { + fixtures: [ + new Int16Array(), + ], + typename: 'Int16Array', + }, + uint16Array: { + fixtures: [ + new Uint16Array(), + ], + typename: 'Uint16Array', + }, + int32Array: { + fixtures: [ + new Int32Array(), + ], + typename: 'Int32Array', + }, + uint32Array: { + fixtures: [ + new Uint32Array(), + ], + typename: 'Uint32Array', + }, + float32Array: { + fixtures: [ + new Float32Array(), + ], + typename: 'Float32Array', + }, + float64Array: { + fixtures: [ + new Float64Array(), + ], + typename: 'Float64Array', + }, + bigInt64Array: { + fixtures: [ + new BigInt64Array(), + ], + typename: 'BigInt64Array', + }, + bigUint64Array: { + fixtures: [ + new BigUint64Array(), + ], + typename: 'BigUint64Array', + }, + arrayBuffer: { + fixtures: [ + new ArrayBuffer(10), + ], + typename: 'ArrayBuffer', + }, + dataView: { + fixtures: [ + new DataView(new ArrayBuffer(10)), + ], + typename: 'DataView', + }, + plainObject: { + fixtures: [ + ...reusableFixtures.plainObject, ], typename: 'Object', typeDescription: 'plain object', - }], - ['integer', { - is: is.integer, - assert: assert.integer, - fixtures: [ - 6, - ], - typename: 'number', - typeDescription: 'integer', - }], - ['safeInteger', { - is: is.safeInteger, - assert: assert.safeInteger, - fixtures: [ - (2 ** 53) - 1, - -(2 ** 53) + 1, - ], - typename: 'number', - typeDescription: 'integer', - }], - ['htmlElement', { - is: is.htmlElement, - assert: assert.htmlElement, + }, + htmlElement: { fixtures: [ 'div', 'input', @@ -528,36 +417,16 @@ const types = new Map([ ] .map(fixture => document.createElement(fixture)), typeDescription: 'HTMLElement', - }], - ['non-htmlElement', { - is: value => !is.htmlElement(value), - assert(value: unknown) { - invertAssertThrow('HTMLElement', () => { - assert.htmlElement(value); - }, value); - }, - fixtures: [ - document.createTextNode('data'), - document.createProcessingInstruction('xml-stylesheet', 'href="mycss.css" type="text/css"'), - document.createComment('This is a comment'), - document, - document.implementation.createDocumentType('svg:svg', '-//W3C//DTD SVG 1.1//EN', 'https://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'), - document.createDocumentFragment(), - ], - }], - ['observable', { - is: is.observable, - assert: assert.observable, + }, + observable: { fixtures: [ new Observable(), new Subject(), new ZenObservable(() => {}), ], typename: 'Observable', - }], - ['nodeStream', { - is: is.nodeStream, - assert: assert.nodeStream, + }, + nodeStream: { fixtures: [ fs.createReadStream('readme.md'), fs.createWriteStream(temporaryFile()), @@ -571,79 +440,76 @@ const types = new Map([ ], typename: 'Object', typeDescription: 'Node.js Stream', - }], - ['infinite', { - is: is.infinite, - assert: assert.infinite, + }, + formData: { fixtures: [ - Number.POSITIVE_INFINITY, - Number.NEGATIVE_INFINITY, + new window.FormData(), ], - typename: 'number', - typeDescription: 'infinite number', - }], + typename: 'FormData', + }, +} as const satisfies Partial<{[K in keyof typeof is]: Test}>; + +const types = { + ...objectTypes, + ...primitiveTypes, +} as const satisfies Partial<{[K in keyof typeof is]: Test}>; + +type TypeNameWithFixture = keyof typeof types; + +const subClasses = new Map([ + ['uint8Array', ['buffer']], // It's too hard to differentiate the two + ['object', keysOf(objectTypes)], ]); // This ensures a certain method matches only the types it's supposed to and none of the other methods' types -const testType = (t: ExecutionContext, type: string, exclude?: string[]) => { - const testData = types.get(type); +const exclusivelyTyped = test.macro({ + exec(t: ExecutionContext, type: TypeNameWithFixture) { + const {fixtures, typeDescription, typename} = types[type] as Test; + const valueType = typeDescription ?? typename ?? 'unspecified'; - if (testData === undefined) { - t.fail(`is.${type} not defined`); - - return; - } - - const {is: testIs, assert: testAssert, typename, typeDescription} = testData; - - for (const [key, {fixtures}] 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?.includes(key)) { - continue; - } - - const isTypeUnderTest = key === type; - const assertIs = isTypeUnderTest ? t.true : t.false; + const testAssert: (value: unknown) => never | void = assert[type]; + const testIs: Predicate = is[type]; for (const fixture of fixtures) { - assertIs(testIs(fixture), `Value: ${inspect(fixture)}`); - const valueType = typeDescription ?? typename ?? 'unspecified'; + t.true(testIs(fixture), `Value: ${inspect(fixture)}`); + t.notThrows(() => { + testAssert(fixture); + }); - if (isTypeUnderTest) { - t.notThrows(() => { - testAssert(fixture); - }); - } else { + if (typename) { + t.is(is(fixture), typename); + } + } + + for (const key of keysOf(types).filter(key => key !== type)) { + if (subClasses.has(type) && subClasses.get(type)?.includes(key)) { + continue; + } + + for (let i = 0; i < types[key].fixtures.length; i += 1) { + const fixture: unknown = types[key].fixtures[i]; + + if (fixtures.includes(fixture)) { + continue; + } + + t.false(testIs(fixture), `${key}.fixture[${i}]: ${inspect(fixture)} should not be ${type}`); t.throws(() => { testAssert(fixture); }, { message: `Expected value which is \`${valueType}\`, received value of type \`${is(fixture)}\`.`, }); } - - if (isTypeUnderTest && typename) { - t.is(is(fixture), typename); - } } - } -}; - -test('is.undefined', t => { - testType(t, 'undefined', ['nullOrUndefined']); + }, + title(_, type: TypeNameWithFixture) { + return `is.${type}`; + }, }); -test('is.null', t => { - testType(t, 'null', ['nullOrUndefined']); -}); - -test('is.string', t => { - testType(t, 'string', ['emptyString', 'numericString']); -}); - -test('is.number', t => { - testType(t, 'number', ['integer', 'safeInteger', 'infinite']); -}); +for (const type of keysOf(types)) { + test(exclusivelyTyped, type); +} test('is.positiveNumber', t => { t.true(is.positiveNumber(6)); @@ -721,20 +587,7 @@ test('is.negativeNumber', t => { }); }); -test('is.bigint', t => { - testType(t, 'bigint'); -}); - -test('is.boolean', t => { - testType(t, 'boolean'); -}); - -test('is.symbol', t => { - testType(t, 'symbol'); -}); - -test('is.numericString', t => { - testType(t, 'numericString'); +test('is.numericString supplemental', t => { t.false(is.numericString('')); t.false(is.numericString(' ')); t.false(is.numericString(' \t\t\n')); @@ -747,9 +600,7 @@ test('is.numericString', t => { }); }); -test('is.array', t => { - testType(t, 'array', ['emptyArray']); - +test('is.array supplemental', t => { t.true(is.array([1, 2, 3], is.number)); t.false(is.array([1, '2', 3], is.number)); @@ -779,11 +630,7 @@ test('is.array', t => { }, {message: /Expected numbers/}); }); -test('is.function', t => { - testType(t, 'function', ['generatorFunction', 'asyncGeneratorFunction', 'asyncFunction', 'boundFunction']); -}); - -test('is.boundFunction', t => { +test('is.boundFunction supplemental', t => { t.false(is.boundFunction(function () {})); // eslint-disable-line prefer-arrow-callback t.throws(() => { @@ -791,54 +638,7 @@ test('is.boundFunction', t => { }); }); -test('is.buffer', t => { - testType(t, 'buffer'); -}); - -test('is.blob', t => { - testType(t, 'blob'); -}); - -test('is.object', t => { - const testData = types.get('object'); - - if (testData === undefined) { - t.fail('is.object not defined'); - - return; - } - - for (const element of testData.fixtures) { - t.true(is.object(element)); - t.notThrows(() => { - assert.object(element); - }); - } -}); - -test('is.regExp', t => { - testType(t, 'regExp'); -}); - -test('is.date', t => { - testType(t, 'date'); -}); - -test('is.error', t => { - testType(t, 'error'); -}); - -test('is.nativePromise', t => { - testType(t, 'nativePromise'); -}); - -test('is.promise', t => { - testType(t, 'promise', ['nativePromise']); -}); - -test('is.asyncFunction', t => { - testType(t, 'asyncFunction', ['function']); - +test('is.asyncFunction supplemental', t => { const fixture = async () => {}; if (is.asyncFunction(fixture)) { t.true(is.function(fixture().then)); @@ -849,13 +649,7 @@ test('is.asyncFunction', t => { } }); -test('is.generator', t => { - testType(t, 'generator'); -}); - -test('is.asyncGenerator', t => { - testType(t, 'asyncGenerator'); - +test('is.asyncGenerator supplemental', t => { const fixture = (async function * () { yield 4; })(); @@ -864,13 +658,7 @@ test('is.asyncGenerator', t => { } }); -test('is.generatorFunction', t => { - testType(t, 'generatorFunction', ['function']); -}); - -test('is.asyncGeneratorFunction', t => { - testType(t, 'asyncGeneratorFunction', ['function']); - +test('is.asyncGeneratorFunction supplemental', t => { const fixture = async function * () { yield 4; }; @@ -880,78 +668,6 @@ test('is.asyncGeneratorFunction', t => { } }); -test('is.map', t => { - testType(t, 'map', ['emptyMap']); -}); - -test('is.set', t => { - testType(t, 'set', ['emptySet']); -}); - -test('is.weakMap', t => { - testType(t, 'weakMap'); -}); - -test('is.weakSet', t => { - testType(t, 'weakSet'); -}); - -test('is.weakRef', t => { - testType(t, 'weakRef'); -}); - -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.bigInt64Array', t => { - testType(t, 'bigInt64Array'); -}); - -test('is.bigUint64Array', t => { - testType(t, 'bigUint64Array'); -}); - -test('is.arrayBuffer', t => { - testType(t, 'arrayBuffer'); -}); - -test('is.dataView', t => { - testType(t, 'dataView'); -}); - test('is.enumCase', t => { enum NonNumericalEnum { Key1 = 'key1', @@ -1048,8 +764,7 @@ test('is.truthy', t => { t.true(is.truthy(Symbol('🦄'))); t.true(is.truthy(true)); t.true(is.truthy(1)); - // Disabled until TS supports it for an ESnnnn target. - // t.true(is.truthy(1n)); + t.true(is.truthy(1n)); t.true(is.truthy(BigInt(1))); t.notThrows(() => { @@ -1229,14 +944,6 @@ test('is.falsy', t => { } }); -test('is.nan', t => { - testType(t, 'nan'); -}); - -test('is.nullOrUndefined', t => { - testType(t, 'nullOrUndefined', ['undefined', 'null']); -}); - test('is.primitive', t => { const primitives: Primitive[] = [ undefined, @@ -1248,8 +955,7 @@ test('is.primitive', t => { true, false, Symbol('🦄'), - // Disabled until TS supports it for an ESnnnn target. - // 6n + 6n, ]; for (const element of primitives) { @@ -1260,16 +966,14 @@ test('is.primitive', t => { } }); -test('is.integer', t => { - testType(t, 'integer', ['number', 'safeInteger']); +test('is.integer supplemental', t => { t.false(is.integer(1.4)); t.throws(() => { assert.integer(1.4); }); }); -test('is.safeInteger', t => { - testType(t, 'safeInteger', ['number', 'integer']); +test('is.safeInteger supplemental', t => { t.false(is.safeInteger(2 ** 53)); t.false(is.safeInteger(-(2 ** 53))); t.throws(() => { @@ -1280,10 +984,6 @@ test('is.safeInteger', t => { }); }); -test('is.plainObject', t => { - testType(t, 'plainObject', ['object', 'promise']); -}); - test('is.iterable', t => { t.true(is.iterable('')); t.true(is.iterable([])); @@ -1628,8 +1328,7 @@ test('is.inRange', t => { }); }); -test('is.htmlElement', t => { - testType(t, 'htmlElement'); +test('is.htmlElement supplemental', t => { t.false(is.htmlElement({nodeType: 1, nodeName: 'div'})); t.throws(() => { assert.htmlElement({nodeType: 1, nodeName: 'div'}); @@ -1648,18 +1347,21 @@ test('is.htmlElement', t => { const element = document.createElement(tagName); t.is(is(element), 'HTMLElement'); } -}); -test('is.observable', t => { - testType(t, 'observable'); -}); + const nonHtmlElements = [ + document.createTextNode('data'), + document.createProcessingInstruction('xml-stylesheet', 'href="mycss.css" type="text/css"'), + document.createComment('This is a comment'), + document, + document.implementation.createDocumentType('svg:svg', '-//W3C//DTD SVG 1.1//EN', 'https://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'), + document.createDocumentFragment(), + ] as const; -test('is.nodeStream', t => { - testType(t, 'nodeStream'); -}); - -test('is.infinite', t => { - testType(t, 'infinite', ['number']); + for (const element of nonHtmlElements) { + t.throws(() => { + assert.htmlElement(element); + }); + } }); test('is.evenInteger', t => { @@ -1694,10 +1396,6 @@ test('is.oddInteger', t => { } }); -test('is.emptyArray', t => { - testType(t, 'emptyArray'); -}); - test('is.nonEmptyArray', t => { t.true(is.nonEmptyArray([1, 2, 3])); t.false(is.nonEmptyArray([])); @@ -1774,16 +1472,14 @@ test('is.nonEmptyArray', t => { } }); -test('is.emptyString', t => { - testType(t, 'emptyString', ['string']); +test('is.emptyString supplemental', t => { t.false(is.emptyString('🦄')); t.throws(() => { assert.emptyString('🦄'); }); }); -test('is.emptyStringOrWhitespace', t => { - testType(t, 'emptyString', ['string']); +test('is.emptyStringOrWhitespace supplemental', t => { t.true(is.emptyStringOrWhitespace(' ')); t.false(is.emptyStringOrWhitespace('🦄')); t.false(is.emptyStringOrWhitespace('unicorn')); @@ -1878,10 +1574,6 @@ test('is.nonEmptyObject', t => { }); }); -test('is.emptySet', t => { - testType(t, 'emptySet'); -}); - test('is.nonEmptySet', t => { const temporarySet = new Set(); t.false(is.nonEmptySet(temporarySet)); @@ -1896,10 +1588,6 @@ test('is.nonEmptySet', t => { }); }); -test('is.emptyMap', t => { - testType(t, 'emptyMap'); -}); - test('is.nonEmptyMap', t => { const temporaryMap = new Map(); t.false(is.nonEmptyMap(temporaryMap)); @@ -2070,7 +1758,7 @@ test('is.all', t => { }); }); -test('is.formData', t => { +test('is.formData supplemental', t => { const data = new window.FormData(); t.true(is.formData(data)); t.false(is.formData({})); From c68ad76062eafbd384badd7e96d09776307478c8 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Fri, 12 Sep 2025 03:58:08 +0700 Subject: [PATCH 236/254] Fix TypeScript type narrowing issue with `isUrlString` Fixes #212 --- source/index.ts | 8 +++++--- source/types.ts | 7 +++++++ test/test.ts | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/source/index.ts b/source/index.ts index 894318e..b89c90e 100644 --- a/source/index.ts +++ b/source/index.ts @@ -8,6 +8,7 @@ import type { Predicate, Primitive, TypedArray, + UrlString, WeakRef, Whitespace, } from './types.js'; @@ -760,7 +761,7 @@ export function isUrlSearchParams(value: unknown): value is URLSearchParams { return getObjectType(value) === 'URLSearchParams'; } -export function isUrlString(value: unknown): value is string { +export function isUrlString(value: unknown): value is UrlString { if (!isString(value)) { return false; } @@ -894,7 +895,7 @@ type Assert = { dataView: (value: unknown, message?: string) => asserts value is DataView; enumCase: (value: unknown, targetEnum: T, message?: string) => asserts value is T[keyof T]; urlInstance: (value: unknown, message?: string) => asserts value is URL; - urlString: (value: unknown, message?: string) => asserts value is string; + urlString: (value: unknown, message?: string) => asserts value is UrlString; truthy: (value: T | Falsy, message?: string) => asserts value is T; falsy: (value: unknown, message?: string) => asserts value is Falsy; nan: (value: unknown, message?: string) => asserts value is number; @@ -1650,7 +1651,7 @@ export function assertUrlSearchParams(value: unknown, message?: string): asserts } } -export function assertUrlString(value: unknown, message?: string): asserts value is string { +export function assertUrlString(value: unknown, message?: string): asserts value is UrlString { if (!isUrlString(value)) { throw new TypeError(message ?? typeErrorMessage('string with a URL', value)); } @@ -1705,4 +1706,5 @@ export type { Predicate, Primitive, TypedArray, + UrlString, } from './types.js'; diff --git a/source/types.ts b/source/types.ts index b79a603..9ad9f2e 100644 --- a/source/types.ts +++ b/source/types.ts @@ -75,3 +75,10 @@ export type Predicate = (value: unknown) => boolean; export type NonEmptyString = string & {0: string}; export type Whitespace = ' '; + +/** +A string that represents a valid URL. + +This is a branded type to prevent incorrect TypeScript type narrowing. +*/ +export type UrlString = string & {readonly __brand: 'UrlString'}; diff --git a/test/test.ts b/test/test.ts index bb09f37..32e6c1a 100644 --- a/test/test.ts +++ b/test/test.ts @@ -17,6 +17,7 @@ import is, { type Primitive, type TypedArray, type TypeName, + type UrlString, } from '../source/index.js'; import {keysOf} from '../source/utilities.js'; @@ -757,6 +758,26 @@ test('is.urlString', t => { }); }); +// Type test for urlString narrowing fix (issue #212) +// This test demonstrates that the fix allows proper type narrowing in both branches +(() => { + const value: unknown = 'test'; + + if (is.urlString(value)) { + // ✅ In true branch: value is narrowed to UrlString + expectTypeOf(value).toEqualTypeOf(); + expectTypeOf(value).toMatchTypeOf(); + } else { + // ✅ In false branch: value remains unknown (not incorrectly narrowed) + expectTypeOf(value).toEqualTypeOf(); + + // ✅ Manual narrowing to string still works + if (typeof value === 'string') { + expectTypeOf(value).toEqualTypeOf(); + } + } +})(); + test('is.truthy', t => { t.true(is.truthy('unicorn')); t.true(is.truthy('🦄')); From 1f2440ae0d964721d1eb87aa2c334a995757dd12 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 13 Sep 2025 02:27:50 +0700 Subject: [PATCH 237/254] Add `is.optional` and `assert.optional` Fixes #111 --- readme.md | 32 ++++++++++++++++++++++++++++++++ source/index.ts | 19 +++++++++++++++++++ test/test.ts | 25 +++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/readme.md b/readme.md index 1966a5b..9a4982c 100644 --- a/readme.md +++ b/readme.md @@ -587,6 +587,21 @@ is.all(is.string, '🦄', [], 'unicorns'); //=> false ``` +##### .optional(value, predicate) + +Returns `true` if `value` is `undefined` or satisfies the given `predicate`. + +```js +is.optional(undefined, is.string); +//=> true + +is.optional('🦄', is.string); +//=> true + +is.optional(123, is.string); +//=> false +``` + ##### .validDate(value) Returns `true` if the value is a valid date. @@ -682,6 +697,23 @@ handleMovieRatingApiResponse({rating: 0.87, title: 'The Matrix'}); handleMovieRatingApiResponse({rating: '🦄'}); ``` +### Optional assertion + +Asserts that `value` is `undefined` or satisfies the provided `assertion`. + +```ts +import {assert} from '@sindresorhus/is'; + +assert.optional(undefined, assert.string); +// Passes without throwing + +assert.optional('🦄', assert.string); +// Passes without throwing + +assert.optional(123, assert.string); +// Throws: Expected value which is `string`, received value of type `number` +``` + ## Generic type parameters The type guards and type assertions are aware of [generic type parameters](https://www.typescriptlang.org/docs/handbook/generics.html), such as `Promise` and `Map`. The default is `unknown` for most cases, since `is` cannot check them at runtime. If the generic type is known at compile-time, either implicitly (inferred) or explicitly (provided), `is` propagates the type so it can be used later. diff --git a/source/index.ts b/source/index.ts index b89c90e..f40fad0 100644 --- a/source/index.ts +++ b/source/index.ts @@ -318,6 +318,7 @@ const is = Object.assign( urlInstance: isUrlInstance, urlSearchParams: isUrlSearchParams, urlString: isUrlString, + optional: isOptional, validDate: isValidDate, validLength: isValidLength, weakMap: isWeakMap, @@ -342,6 +343,10 @@ export function isAny(predicate: Predicate | Predicate[], ...values: unknown[]): ); } +export function isOptional(value: unknown, predicate: (value: unknown) => value is T): value is T | undefined { + return isUndefined(value) || predicate(value); +} + export function isArray(value: unknown, assertion?: (value: T) => value is T): value is T[] { if (!Array.isArray(value)) { return false; @@ -940,11 +945,19 @@ type Assert = { // Variadic functions. any: (predicate: Predicate | Predicate[], ...values: unknown[]) => void | never; all: (predicate: Predicate, ...values: unknown[]) => void | never; + + /** + Asserts that `value` is `undefined` or satisfies the provided `assertion`. + + Useful for optional inputs. + */ + optional: (value: unknown, assertion: (value: unknown, message?: string) => asserts value is T, message?: string) => asserts value is T | undefined; }; export const assert: Assert = { all: assertAll, any: assertAny, + optional: assertOptional, array: assertArray, arrayBuffer: assertArrayBuffer, arrayLike: assertArrayLike, @@ -1148,6 +1161,12 @@ export function assertAny(predicate: Predicate | Predicate[], ...values: unknown } } +export function assertOptional(value: unknown, assertion: (value: unknown, message?: string) => asserts value is T, message?: string): asserts value is T | undefined { + if (!isUndefined(value)) { + assertion(value, message); + } +} + export function assertArray(value: unknown, assertion?: (element: unknown, message?: string) => asserts element is T, message?: string): asserts value is T[] { if (!isArray(value)) { throw new TypeError(message ?? typeErrorMessage('Array', value)); diff --git a/test/test.ts b/test/test.ts index 32e6c1a..523bf11 100644 --- a/test/test.ts +++ b/test/test.ts @@ -2251,3 +2251,28 @@ test('custom assertion message', t => { assert.whitespaceString(undefined, message); }, {instanceOf: TypeError, message}); }); + +test('is.optional', t => { + t.true(is.optional(undefined, is.string)); + t.true(is.optional('🦄', is.string)); + t.false(is.optional(123, is.string)); + t.false(is.optional(null, is.string)); +}); + +test('assert.optional', t => { + t.notThrows(() => { + assert.optional(undefined, assert.string); + }); + + t.notThrows(() => { + assert.optional('🦄', assert.string); + }); + + t.throws(() => { + assert.optional(123, assert.string); + }); + + t.throws(() => { + assert.optional(null, assert.string); + }); +}); From d22ab629916392104a69b34387008ca2a2cc3ddd Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 14 Sep 2025 02:05:33 +0700 Subject: [PATCH 238/254] 7.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7d1a45..feacdd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "7.0.2", + "version": "7.1.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From e7c84fcb797ea9857fa56750688440bf3f347589 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 31 Oct 2025 13:18:25 -0400 Subject: [PATCH 239/254] Fix `is.class` for minified class expression (#217) --- source/index.ts | 2 +- test/test.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/source/index.ts b/source/index.ts index f40fad0..f5c8fdd 100644 --- a/source/index.ts +++ b/source/index.ts @@ -418,7 +418,7 @@ export function isBuffer(value: unknown): value is NodeBuffer { } export function isClass(value: unknown): value is Class { - return isFunction(value) && value.toString().startsWith('class '); + return isFunction(value) && /^class(\s+|{)/.test(value.toString()); } export function isDataView(value: unknown): value is DataView { diff --git a/test/test.ts b/test/test.ts index 523bf11..4da5723 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1086,9 +1086,13 @@ test('is.asyncIterable', t => { test('is.class', t => { class Foo {} // eslint-disable-line @typescript-eslint/no-extraneous-class + // Note: Using new Function to prevent whitespace modifications in tsimp + const minifiedClass = new Function('return class{};'); // eslint-disable-line no-new-func + const classDeclarations = [ Foo, class Bar extends Foo {}, + minifiedClass(), ]; for (const classDeclaration of classDeclarations) { From fbcc68e139627bdc7728ef120607090e10727761 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 1 Nov 2025 00:20:45 +0700 Subject: [PATCH 240/254] 7.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index feacdd1..9e2de0b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "7.1.0", + "version": "7.1.1", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 9bdcd9b57f0ef38f3f495f785aec830c138b0bac Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 20 Dec 2025 02:51:20 +0100 Subject: [PATCH 241/254] Add predicate factory mode to `is.any` and `is.all` Fixes #218 --- readme.md | 47 ++++++++++++++++++++ source/index.ts | 101 ++++++++++++++++++++++++++++++++++++------- test/test.ts | 113 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 245 insertions(+), 16 deletions(-) diff --git a/readme.md b/readme.md index 9a4982c..6965c9b 100644 --- a/readme.md +++ b/readme.md @@ -575,6 +575,31 @@ is.any([is.boolean, is.number], 'unicorns', [], new Map()); //=> false ``` +##### .any(predicate[]) + +Using an array of `predicate[]` without values, returns a combined type guard that checks if a value matches **any** of the predicates: + +```js +const isStringOrNumber = is.any([is.string, is.number]); + +isStringOrNumber('hello'); +//=> true + +isStringOrNumber(123); +//=> true + +isStringOrNumber(true); +//=> false +``` + +This is useful for composing with other methods like `is.optional`: + +```js +is.optional(value, is.any([is.string, is.number])); +``` + +An empty predicate array currently returns a predicate that always returns `false`. This will throw in the next major release. + ##### .all(predicate, ...values) Returns `true` if **all** of the input `values` returns true in the `predicate`: @@ -587,6 +612,28 @@ is.all(is.string, '🦄', [], 'unicorns'); //=> false ``` +##### .all(predicate[]) + +Using an array of `predicate[]` without values, returns a combined type guard that checks if a value matches **all** of the predicates: + +```js +const isArrayAndNonEmpty = is.all([is.array, is.nonEmptyArray]); + +isArrayAndNonEmpty(['hello']); +//=> true + +isArrayAndNonEmpty([]); +//=> false +``` + +This is useful for composing with other methods like `is.optional`: + +```js +is.optional(value, is.all([is.object, is.plainObject])); +``` + +An empty predicate array currently returns a predicate that always returns `true`. This will throw in the next major release. + ##### .optional(value, predicate) Returns `true` if `value` is `undefined` or satisfies the given `predicate`. diff --git a/source/index.ts b/source/index.ts index f5c8fdd..77e9d2f 100644 --- a/source/index.ts +++ b/source/index.ts @@ -332,15 +332,77 @@ function isAbsoluteModule2(remainder: 0 | 1) { return (value: unknown): value is number => isInteger(value) && Math.abs(value % 2) === remainder; } -export function isAll(predicate: Predicate, ...values: unknown[]): boolean { - return predicateOnArray(Array.prototype.every, predicate, values); +type TypeGuard = (value: unknown) => value is T; + +function validatePredicateArray(predicateArray: readonly Predicate[], allowEmpty: boolean) { + if (predicateArray.length === 0) { + if (allowEmpty) { + // Next major release: throw for empty predicate arrays to avoid vacuous results. + // throw new TypeError('Invalid predicate array'); + } else { + throw new TypeError('Invalid predicate array'); + } + + return; + } + + for (const predicate of predicateArray) { + if (!isFunction(predicate)) { + throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); + } + } } -export function isAny(predicate: Predicate | Predicate[], ...values: unknown[]): boolean { - const predicates = isArray(predicate) ? predicate : [predicate]; - return predicates.some(singlePredicate => - predicateOnArray(Array.prototype.some, singlePredicate, values), - ); +// Predicate factory overloads - return a type guard when called with only predicates +export function isAll(predicates: [TypeGuard]): TypeGuard; +export function isAll(predicates: [TypeGuard, TypeGuard]): TypeGuard; +export function isAll(predicates: [TypeGuard, TypeGuard, TypeGuard]): TypeGuard; +export function isAll(predicates: [TypeGuard, TypeGuard, TypeGuard, TypeGuard]): TypeGuard; +export function isAll(predicates: [TypeGuard, TypeGuard, TypeGuard, TypeGuard, TypeGuard]): TypeGuard; +export function isAll(predicates: ReadonlyArray>): TypeGuard; +export function isAll(predicates: readonly Predicate[]): Predicate; +// Evaluator overload - check if all values match the predicate +export function isAll(predicate: Predicate | readonly Predicate[], ...values: unknown[]): boolean; +export function isAll(predicate: Predicate | readonly Predicate[], ...values: unknown[]): boolean | Predicate { + if (Array.isArray(predicate)) { + const predicateArray = predicate as readonly Predicate[]; + validatePredicateArray(predicateArray, values.length === 0); + + const combinedPredicate = (value: unknown) => predicateArray.every(singlePredicate => singlePredicate(value)); + if (values.length === 0) { + return combinedPredicate; + } + + return predicateOnArray(Array.prototype.every, combinedPredicate, values); + } + + return predicateOnArray(Array.prototype.every, predicate as Predicate, values); +} + +// Predicate factory overloads - return a type guard when called with only predicates +export function isAny(predicates: [TypeGuard]): TypeGuard; +export function isAny(predicates: [TypeGuard, TypeGuard]): TypeGuard; +export function isAny(predicates: [TypeGuard, TypeGuard, TypeGuard]): TypeGuard; +export function isAny(predicates: [TypeGuard, TypeGuard, TypeGuard, TypeGuard]): TypeGuard; +export function isAny(predicates: [TypeGuard, TypeGuard, TypeGuard, TypeGuard, TypeGuard]): TypeGuard; +export function isAny(predicates: ReadonlyArray>): TypeGuard; +export function isAny(predicates: readonly Predicate[]): Predicate; +// Evaluator overload - check if any value matches any predicate +export function isAny(predicate: Predicate | readonly Predicate[], ...values: unknown[]): boolean; +export function isAny(predicate: Predicate | readonly Predicate[], ...values: unknown[]): boolean | Predicate { + if (Array.isArray(predicate)) { + const predicateArray = predicate as readonly Predicate[]; + validatePredicateArray(predicateArray, values.length === 0); + + const combinedPredicate = (value: unknown) => predicateArray.some(singlePredicate => singlePredicate(value)); + if (values.length === 0) { + return combinedPredicate; + } + + return predicateOnArray(Array.prototype.some, combinedPredicate, values); + } + + return predicateOnArray(Array.prototype.some, predicate as Predicate, values); } export function isOptional(value: unknown, predicate: (value: unknown) => value is T): value is T | undefined { @@ -715,8 +777,6 @@ export function isTruthy(value: T | Falsy): value is T { return Boolean(value); } -type TypeGuard = (value: unknown) => value is T; - // eslint-disable-next-line @typescript-eslint/ban-types type ResolveTypesOfTypeGuardsTuple = TypeGuardsOfT extends [TypeGuard, ...infer TOthers] @@ -943,8 +1003,8 @@ type Assert = { inRange: (value: number, range: number | [number, number], message?: string) => asserts value is number; // Variadic functions. - any: (predicate: Predicate | Predicate[], ...values: unknown[]) => void | never; - all: (predicate: Predicate, ...values: unknown[]) => void | never; + any: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never; + all: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never; /** Asserts that `value` is `undefined` or satisfies the provided `assertion`. @@ -1146,17 +1206,26 @@ function isIsMethodName(value: unknown): value is IsMethodName { return isMethodNames.includes(value as IsMethodName); } -export function assertAll(predicate: Predicate, ...values: unknown[]): void | never { +export function assertAll(predicate: Predicate | readonly Predicate[], ...values: unknown[]): void | never { + if (values.length === 0) { + throw new TypeError('Invalid number of values'); + } + if (!isAll(predicate, ...values)) { - const expectedType = isIsMethodName(predicate.name) ? methodTypeMap[predicate.name] : 'predicate returns truthy for all values'; + const predicateFunction = predicate as Predicate; + const expectedType = !Array.isArray(predicate) && isIsMethodName(predicateFunction.name) ? methodTypeMap[predicateFunction.name] : 'predicate returns truthy for all values'; throw new TypeError(typeErrorMessageMultipleValues(expectedType, values)); } } -export function assertAny(predicate: Predicate | Predicate[], ...values: unknown[]): void | never { +export function assertAny(predicate: Predicate | readonly Predicate[], ...values: unknown[]): void | never { + if (values.length === 0) { + throw new TypeError('Invalid number of values'); + } + if (!isAny(predicate, ...values)) { - const predicates = isArray(predicate) ? predicate : [predicate]; - const expectedTypes = predicates.map(predicate => isIsMethodName(predicate.name) ? methodTypeMap[predicate.name] : 'predicate returns truthy for any value'); + const predicates = Array.isArray(predicate) ? predicate as readonly Predicate[] : [predicate as Predicate]; + const expectedTypes = predicates.map(singlePredicate => isIsMethodName(singlePredicate.name) ? methodTypeMap[singlePredicate.name] : 'predicate returns truthy for any value'); throw new TypeError(typeErrorMessageMultipleValues(expectedTypes, values)); } } diff --git a/test/test.ts b/test/test.ts index 4da5723..03d535c 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1648,12 +1648,17 @@ test('is.any', t => { t.false(is.any(is.integer, true, 'lol', {})); t.true(is.any([is.string, is.number], {}, true, '🦄')); t.false(is.any([is.boolean, is.number], 'unicorns', [], new Map())); + t.is(typeof is.any([is.string, is.number]), 'function'); t.throws(() => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument is.any(null as any, true); }); + t.throws(() => { + is.any([], 'value'); + }); + t.throws(() => { is.any(is.string); }); @@ -1666,6 +1671,10 @@ test('is.any', t => { assert.any(is.object, false, {}, 'unicorns'); }); + t.throws(() => { + assert.any([is.string, is.number]); + }); + t.throws(() => { assert.any(is.boolean, '🦄', [], 3); }); @@ -1679,6 +1688,10 @@ test('is.any', t => { assert.any(null as any, true); }); + t.throws(() => { + assert.any([], 'value'); + }); + t.throws(() => { assert.any(is.string); }); @@ -1726,12 +1739,18 @@ test('is.all', t => { t.false(is.all(is.set, new Map(), {})); t.true(is.all(is.array, ['1'], ['2'])); + t.true(is.all([is.string, is.nonEmptyString], '🦄', 'unicorns')); + t.false(is.all([is.string, is.number], '🦄')); t.throws(() => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument is.all(null as any, true); }); + t.throws(() => { + is.all([], 'value'); + }); + t.throws(() => { is.all(is.string); }); @@ -1744,10 +1763,22 @@ test('is.all', t => { assert.all(is.boolean, true, false); }); + t.throws(() => { + assert.all([is.string, is.number]); + }); + + t.notThrows(() => { + assert.all([is.string, is.nonEmptyString], '🦄', 'unicorns'); + }); + t.throws(() => { assert.all(is.string, '🦄', []); }); + t.throws(() => { + assert.all([is.string, is.number], '🦄'); + }); + t.throws(() => { assert.all(is.set, new Map(), {}); }); @@ -1757,6 +1788,10 @@ test('is.all', t => { assert.all(null as any, true); }); + t.throws(() => { + assert.all([], 'value'); + }); + t.throws(() => { assert.all(is.string); }); @@ -1783,6 +1818,84 @@ test('is.all', t => { }); }); +test('is.any as predicate factory', t => { + // Returns a type guard function when called with only predicates + const isStringOrNumber = is.any([is.string, is.number]); + t.is(typeof isStringOrNumber, 'function'); + t.true(isStringOrNumber('hello')); + t.true(isStringOrNumber(123)); + t.false(isStringOrNumber(true)); + t.false(isStringOrNumber({})); + + // Type narrowing works correctly + const value: unknown = 'test'; + if (isStringOrNumber(value)) { + // TypeScript should narrow to string | number + const narrowed: string | number = value; + t.pass(`narrowed to: ${typeof narrowed}`); + } + + // Works with is.optional + t.true(is.optional(undefined, is.any([is.string, is.number]))); + t.true(is.optional('test', is.any([is.string, is.number]))); + t.true(is.optional(42, is.any([is.string, is.number]))); + t.false(is.optional(true, is.any([is.string, is.number]))); + + const predicateArray: Predicate[] = [is.string, is.number]; + const isStringOrNumberFromArray = is.any(predicateArray); + t.is(typeof isStringOrNumberFromArray, 'function'); + t.true(isStringOrNumberFromArray('hello')); + t.true(isStringOrNumberFromArray(123)); + t.false(isStringOrNumberFromArray(true)); + + // Type narrowing with is.optional + const optionalValue: unknown = undefined; + if (is.optional(optionalValue, is.any([is.string, is.number]))) { + // TypeScript should narrow to string | number | undefined + const narrowed: string | number | undefined = optionalValue; + t.pass(`optional narrowed to: ${typeof narrowed}`); + } + + // Works with more predicates + const isStringOrNumberOrBoolean = is.any([is.string, is.number, is.boolean]); + t.true(isStringOrNumberOrBoolean('hello')); + t.true(isStringOrNumberOrBoolean(123)); + t.true(isStringOrNumberOrBoolean(true)); + t.false(isStringOrNumberOrBoolean({})); + + t.throws(() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + is.any([is.string, 123 as any]); + }); +}); + +test('is.all as predicate factory', t => { + // Returns a type guard function when called with only predicates + const isArrayAndNonEmpty = is.all([is.array, is.nonEmptyArray]); + t.is(typeof isArrayAndNonEmpty, 'function'); + t.true(isArrayAndNonEmpty(['hello'])); + t.false(isArrayAndNonEmpty([])); + t.false(isArrayAndNonEmpty('hello')); + + // Type narrowing works correctly + const value: unknown = ['test']; + if (isArrayAndNonEmpty(value)) { + // TypeScript should narrow to the intersection type + t.true(Array.isArray(value)); + t.true(value.length > 0); + } + + // Works with is.optional + t.true(is.optional(undefined, is.all([is.object, is.plainObject]))); + t.true(is.optional({foo: 'bar'}, is.all([is.object, is.plainObject]))); + t.false(is.optional([], is.all([is.object, is.plainObject]))); + + t.throws(() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + is.all([is.string, 123 as any]); + }); +}); + test('is.formData supplemental', t => { const data = new window.FormData(); t.true(is.formData(data)); From eff8e6b318d098317d5673b9d391f8e72cb15363 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 27 Dec 2025 11:35:17 +0100 Subject: [PATCH 242/254] 7.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9e2de0b..dffaf75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "7.1.1", + "version": "7.2.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From faf700367e40af945def85224da9ab326c51374c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 7 Apr 2026 17:38:43 +0700 Subject: [PATCH 243/254] Require Node.js 22 --- .github/workflows/main.yml | 7 +- package.json | 49 +- source/index.ts | 84 +- source/types.ts | 8 +- test/test.ts | 1982 ++++++++++++++++++------------------ tsconfig.json | 6 + 6 files changed, 1069 insertions(+), 1067 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 734c8eb..33db234 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,10 +10,11 @@ jobs: fail-fast: false matrix: node-version: - - 20 + - 24 + - 22 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/package.json b/package.json index dffaf75..b5c7463 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,11 @@ }, "sideEffects": false, "engines": { - "node": ">=18" + "node": ">=22" }, "scripts": { "build": "del distribution && tsc", - "test": "tsc --noEmit && xo && ava", + "test": "tsc --noEmit && xo && node --experimental-transform-types --test test/test.ts", "prepare": "npm run build" }, "files": [ @@ -51,31 +51,26 @@ "typeguards", "types" ], - "devDependencies": { - "@sindresorhus/tsconfig": "^6.0.0", - "@types/jsdom": "^21.1.7", - "@types/node": "^20.14.10", - "@types/zen-observable": "^0.8.7", - "ava": "^6.1.3", - "del-cli": "^5.1.0", - "expect-type": "^0.19.0", - "jsdom": "^24.1.0", - "rxjs": "^7.8.1", - "tempy": "^3.1.0", - "tsimp": "2.0.11", - "typescript": "5.5.3", - "xo": "^0.58.0", - "zen-observable": "^0.10.0" + "xo": { + "rules": { + "@typescript-eslint/no-unsafe-enum-comparison": "off", + "@typescript-eslint/no-confusing-void-expression": "off", + "@typescript-eslint/no-unsafe-type-assertion": "off", + "@stylistic/operator-linebreak": "off" + } }, - "ava": { - "environmentVariables": { - "TSIMP_DIAG": "error" - }, - "extensions": { - "ts": "module" - }, - "nodeArguments": [ - "--import=tsimp/import" - ] + "devDependencies": { + "@sindresorhus/tsconfig": "^8.1.0", + "@types/jsdom": "^28.0.1", + "@types/node": "^25.5.2", + "@types/zen-observable": "^0.8.7", + "del-cli": "^7.0.0", + "expect-type": "^1.3.0", + "jsdom": "^29.0.2", + "rxjs": "^7.8.2", + "tempy": "^3.2.0", + "typescript": "6.0.2", + "xo": "^2.0.2", + "zen-observable": "^0.10.0" } } diff --git a/source/index.ts b/source/index.ts index 77e9d2f..ee66861 100644 --- a/source/index.ts +++ b/source/index.ts @@ -11,8 +11,8 @@ import type { UrlString, WeakRef, Whitespace, -} from './types.js'; -import {keysOf} from './utilities.js'; +} from './types.ts'; +import {keysOf} from './utilities.ts'; // From type-fest. type ExtractFromGlobalConstructors = @@ -150,7 +150,7 @@ export type AssertionTypeDescription = typeof assertionTypeDescriptions[number]; const getObjectType = (value: unknown): ObjectTypeName | undefined => { const objectTypeName = Object.prototype.toString.call(value).slice(8, -1); - if (/HTML\w+Element/.test(objectTypeName) && isHtmlElement(value)) { + if (/HTML\w+Element/v.test(objectTypeName) && isHtmlElement(value)) { return 'HTMLElement'; } @@ -166,6 +166,7 @@ function detect(value: unknown): TypeName { return 'null'; } + // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (typeof value) { case 'undefined': { return 'undefined'; @@ -211,7 +212,7 @@ function detect(value: unknown): TypeName { } const tagType = getObjectType(value); - if (tagType && tagType !== 'Object') { + if (tagType !== undefined && tagType !== 'Object') { return tagType; } @@ -219,7 +220,8 @@ function detect(value: unknown): TypeName { return 'Promise'; } - if (value instanceof String || value instanceof Boolean || value instanceof Number) { + const objectTag = Object.prototype.toString.call(value).slice(8, -1); + if (objectTag === 'String' || objectTag === 'Boolean' || objectTag === 'Number') { throw new TypeError('Please don\'t use object wrappers for primitive types'); } @@ -466,7 +468,7 @@ export function isBoolean(value: unknown): value is boolean { return value === true || value === false; } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type export function isBoundFunction(value: unknown): value is Function { return isFunction(value) && !Object.hasOwn(value, 'prototype'); } @@ -475,12 +477,12 @@ export function isBoundFunction(value: unknown): value is Function { Note: [Prefer using `Uint8Array` instead of `Buffer`.](https://sindresorhus.com/blog/goodbye-nodejs-buffer) */ export function isBuffer(value: unknown): value is NodeBuffer { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access return (value as any)?.constructor?.isBuffer?.(value) ?? false; } export function isClass(value: unknown): value is Class { - return isFunction(value) && /^class(\s+|{)/.test(value.toString()); + return isFunction(value) && /^class(?:\s+|\{)/v.test(value.toString()); } export function isDataView(value: unknown): value is DataView { @@ -556,7 +558,7 @@ export function isFormData(value: unknown): value is FormData { return getObjectType(value) === 'FormData'; } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type export function isFunction(value: unknown): value is Function { return typeof value === 'function'; } @@ -569,11 +571,9 @@ export function isGeneratorFunction(value: unknown): value is GeneratorFunction return getObjectType(value) === 'GeneratorFunction'; } -// eslint-disable-next-line @typescript-eslint/naming-convention -const NODE_TYPE_ELEMENT = 1; +const NODE_TYPE_ELEMENT = 1; // eslint-disable-line @typescript-eslint/naming-convention -// eslint-disable-next-line @typescript-eslint/naming-convention -const DOM_PROPERTIES_TO_CHECK: Array<(keyof HTMLElement)> = [ +const DOM_PROPERTIES_TO_CHECK: Array<(keyof HTMLElement)> = [ // eslint-disable-line @typescript-eslint/naming-convention 'innerHTML', 'ownerDocument', 'style', @@ -673,12 +673,12 @@ export function isNonEmptyStringAndNotWhitespace(value: unknown): value is NonEm return isString(value) && !isEmptyStringOrWhitespace(value); } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function isNull(value: unknown): value is null { return value === null; } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function isNullOrUndefined(value: unknown): value is null | undefined { return isNull(value) || isUndefined(value); } @@ -691,7 +691,7 @@ export function isNumericString(value: unknown): value is `${number}` { return isString(value) && !isEmptyStringOrWhitespace(value) && !Number.isNaN(Number(value)); } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function isObject(value: unknown): value is object { return !isNull(value) && (typeof value === 'object' || isFunction(value)); } @@ -701,12 +701,12 @@ export function isObservable(value: unknown): value is ObservableLike { return false; } - // eslint-disable-next-line no-use-extend-native/no-use-extend-native, @typescript-eslint/no-unsafe-call + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access if (Symbol.observable !== undefined && value === (value as any)[Symbol.observable]?.()) { return true; } - // eslint-disable-next-line @typescript-eslint/no-unsafe-call + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access if (value === (value as any)['@@observable']?.()) { return true; } @@ -777,8 +777,8 @@ export function isTruthy(value: T | Falsy): value is T { return Boolean(value); } -// eslint-disable-next-line @typescript-eslint/ban-types -type ResolveTypesOfTypeGuardsTuple = +// eslint-disable-next-line @typescript-eslint/no-restricted-types +type ResolveTypesOfTypeGuardsTuple = TypeGuardsOfT extends [TypeGuard, ...infer TOthers] ? ResolveTypesOfTypeGuardsTuple : TypeGuardsOfT extends undefined[] @@ -847,23 +847,23 @@ export function isValidLength(value: unknown): value is number { return isSafeInteger(value) && value >= 0; } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function isWeakMap(value: unknown): value is WeakMap { return getObjectType(value) === 'WeakMap'; } -// eslint-disable-next-line @typescript-eslint/ban-types, unicorn/prevent-abbreviations +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function isWeakRef(value: unknown): value is WeakRef { return getObjectType(value) === 'WeakRef'; } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function isWeakSet(value: unknown): value is WeakSet { return getObjectType(value) === 'WeakSet'; } export function isWhitespaceString(value: unknown): value is Whitespace { - return isString(value) && /^\s+$/.test(value); + return isString(value) && /^\s+$/v.test(value); } type ArrayMethod = (function_: (value: unknown, index: number, array: unknown[]) => boolean, thisArgument?: unknown) => boolean; @@ -907,9 +907,9 @@ type Assert = { positiveNumber: (value: unknown, message?: string) => asserts value is number; negativeNumber: (value: unknown, message?: string) => asserts value is number; bigint: (value: unknown, message?: string) => asserts value is bigint; - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type function: (value: unknown, message?: string) => asserts value is Function; - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-restricted-types null: (value: unknown, message?: string) => asserts value is null; class: (value: unknown, message?: string) => asserts value is Class; boolean: (value: unknown, message?: string) => asserts value is boolean; @@ -918,7 +918,7 @@ type Assert = { array: (value: unknown, assertion?: (element: unknown) => asserts element is T, message?: string) => asserts value is T[]; buffer: (value: unknown, message?: string) => asserts value is NodeBuffer; blob: (value: unknown, message?: string) => asserts value is Blob; - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-restricted-types nullOrUndefined: (value: unknown, message?: string) => asserts value is null | undefined; object: (value: unknown, message?: string) => asserts value is Record; iterable: (value: unknown, message?: string) => asserts value is Iterable; @@ -929,20 +929,20 @@ type Assert = { promise: (value: unknown, message?: string) => asserts value is Promise; generatorFunction: (value: unknown, message?: string) => asserts value is GeneratorFunction; asyncGeneratorFunction: (value: unknown, message?: string) => asserts value is AsyncGeneratorFunction; - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type asyncFunction: (value: unknown, message?: string) => asserts value is Function; - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type boundFunction: (value: unknown, message?: string) => asserts value is Function; regExp: (value: unknown, message?: string) => asserts value is RegExp; date: (value: unknown, message?: string) => asserts value is Date; error: (value: unknown, message?: string) => asserts value is Error; map: (value: unknown, message?: string) => asserts value is Map; set: (value: unknown, message?: string) => asserts value is Set; - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-restricted-types weakMap: (value: unknown, message?: string) => asserts value is WeakMap; - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-restricted-types weakSet: (value: unknown, message?: string) => asserts value is WeakSet; - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-restricted-types weakRef: (value: unknown, message?: string) => asserts value is WeakRef; int8Array: (value: unknown, message?: string) => asserts value is Int8Array; uint8Array: (value: unknown, message?: string) => asserts value is Uint8Array; @@ -1261,7 +1261,7 @@ export function assertArrayLike(value: unknown, message?: string): } } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type export function assertAsyncFunction(value: unknown, message?: string): asserts value is Function { if (!isAsyncFunction(value)) { throw new TypeError(message ?? typeErrorMessage('AsyncFunction', value)); @@ -1316,7 +1316,7 @@ export function assertBoolean(value: unknown, message?: string): asserts value i } } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type export function assertBoundFunction(value: unknown, message?: string): asserts value is Function { if (!isBoundFunction(value)) { throw new TypeError(message ?? typeErrorMessage('Function', value)); @@ -1434,7 +1434,7 @@ export function assertFormData(value: unknown, message?: string): asserts value } } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type export function assertFunction(value: unknown, message?: string): asserts value is Function { if (!isFunction(value)) { throw new TypeError(message ?? typeErrorMessage('Function', value)); @@ -1567,14 +1567,14 @@ export function assertNonEmptyStringAndNotWhitespace(value: unknown, message?: s } } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function assertNull(value: unknown, message?: string): asserts value is null { if (!isNull(value)) { throw new TypeError(message ?? typeErrorMessage('null', value)); } } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function assertNullOrUndefined(value: unknown, message?: string): asserts value is null | undefined { if (!isNullOrUndefined(value)) { throw new TypeError(message ?? typeErrorMessage('null or undefined', value)); @@ -1593,7 +1593,7 @@ export function assertNumericString(value: unknown, message?: string): asserts v } } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function assertObject(value: unknown, message?: string): asserts value is object { if (!isObject(value)) { throw new TypeError(message ?? typeErrorMessage('Object', value)); @@ -1757,21 +1757,21 @@ export function assertValidLength(value: unknown, message?: string): asserts val } } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function assertWeakMap(value: unknown, message?: string): asserts value is WeakMap { if (!isWeakMap(value)) { throw new TypeError(message ?? typeErrorMessage('WeakMap', value)); } } -// eslint-disable-next-line @typescript-eslint/ban-types, unicorn/prevent-abbreviations +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function assertWeakRef(value: unknown, message?: string): asserts value is WeakRef { if (!isWeakRef(value)) { throw new TypeError(message ?? typeErrorMessage('WeakRef', value)); } } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function assertWeakSet(value: unknown, message?: string): asserts value is WeakSet { if (!isWeakSet(value)) { throw new TypeError(message ?? typeErrorMessage('WeakSet', value)); @@ -1795,4 +1795,4 @@ export type { Primitive, TypedArray, UrlString, -} from './types.js'; +} from './types.ts'; diff --git a/source/types.ts b/source/types.ts index 9ad9f2e..c37e927 100644 --- a/source/types.ts +++ b/source/types.ts @@ -4,7 +4,8 @@ Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive). */ export type Primitive = - | null // eslint-disable-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-restricted-types + | null | undefined | string | number @@ -53,10 +54,11 @@ export type ObservableLike = { [Symbol.observable](): ObservableLike; }; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export type Falsy = false | 0 | 0n | '' | null | undefined; -export type WeakRef = { // eslint-disable-line @typescript-eslint/ban-types, unicorn/prevent-abbreviations +// eslint-disable-next-line @typescript-eslint/no-restricted-types +export type WeakRef = { readonly [Symbol.toStringTag]: 'WeakRef'; deref(): T | undefined; }; diff --git a/test/test.ts b/test/test.ts index 03d535c..662e644 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,25 +1,26 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/no-empty-function, @stylistic/curly-newline, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/no-unsafe-argument */ import {Buffer} from 'node:buffer'; import fs from 'node:fs'; import net from 'node:net'; import Stream from 'node:stream'; import {inspect} from 'node:util'; -import test, {type ExecutionContext} from 'ava'; +import {test} from 'node:test'; +import assert from 'node:assert/strict'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; import {temporaryFile} from 'tempy'; import {expectTypeOf} from 'expect-type'; import ZenObservable from 'zen-observable'; import is, { - assert, + assert as isAssert, type AssertionTypeDescription, type Predicate, type Primitive, type TypedArray, type TypeName, type UrlString, -} from '../source/index.js'; -import {keysOf} from '../source/utilities.js'; +} from '../source/index.ts'; +import {keysOf} from '../source/utilities.ts'; class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} @@ -27,8 +28,6 @@ class ErrorSubclassFixture extends Error {} const {window} = new JSDOM(); const {document} = window; -const structuredClone = globalThis.structuredClone ?? (x => x); - type Test = Readonly<{ fixtures: unknown[]; typename?: TypeName; @@ -118,7 +117,7 @@ const primitiveTypes = { 1n, 0n, -0n, - BigInt('1234'), + 1234n, ], typename: 'bigint', }, @@ -214,8 +213,9 @@ const objectTypes = { }, regExp: { fixtures: [ - /\w/, - new RegExp('\\w'), // eslint-disable-line prefer-regex-literals + /\w/v, + // eslint-disable-next-line prefer-regex-literals + new RegExp(String.raw`\w`, 'v'), ], typename: 'RegExp', }, @@ -463,22 +463,22 @@ const subClasses = new Map([ ]); // This ensures a certain method matches only the types it's supposed to and none of the other methods' types -const exclusivelyTyped = test.macro({ - exec(t: ExecutionContext, type: TypeNameWithFixture) { +for (const type of keysOf(types)) { + test(`is.${type}`, () => { const {fixtures, typeDescription, typename} = types[type] as Test; const valueType = typeDescription ?? typename ?? 'unspecified'; - const testAssert: (value: unknown) => never | void = assert[type]; + const testAssert: (value: unknown) => never | void = isAssert[type]; const testIs: Predicate = is[type]; for (const fixture of fixtures) { - t.true(testIs(fixture), `Value: ${inspect(fixture)}`); - t.notThrows(() => { + assert.ok(testIs(fixture), `Value: ${inspect(fixture)}`); + assert.doesNotThrow(() => { testAssert(fixture); }); - if (typename) { - t.is(is(fixture), typename); + if (typename !== undefined) { + assert.strictEqual(is(fixture), typename); } } @@ -494,267 +494,260 @@ const exclusivelyTyped = test.macro({ continue; } - t.false(testIs(fixture), `${key}.fixture[${i}]: ${inspect(fixture)} should not be ${type}`); - t.throws(() => { + assert.strictEqual(testIs(fixture), false, `${key}.fixture[${i}]: ${inspect(fixture)} should not be ${type}`); + assert.throws(() => { testAssert(fixture); }, { message: `Expected value which is \`${valueType}\`, received value of type \`${is(fixture)}\`.`, }); } } - }, - title(_, type: TypeNameWithFixture) { - return `is.${type}`; - }, -}); - -for (const type of keysOf(types)) { - test(exclusivelyTyped, type); + }); } -test('is.positiveNumber', t => { - t.true(is.positiveNumber(6)); - t.true(is.positiveNumber(1.4)); - t.true(is.positiveNumber(Number.POSITIVE_INFINITY)); +test('is.positiveNumber', () => { + assert.ok(is.positiveNumber(6)); + assert.ok(is.positiveNumber(1.4)); + assert.ok(is.positiveNumber(Number.POSITIVE_INFINITY)); - t.notThrows(() => { - assert.positiveNumber(6); + assert.doesNotThrow(() => { + isAssert.positiveNumber(6); }); - t.notThrows(() => { - assert.positiveNumber(1.4); + assert.doesNotThrow(() => { + isAssert.positiveNumber(1.4); }); - t.notThrows(() => { - assert.positiveNumber(Number.POSITIVE_INFINITY); + assert.doesNotThrow(() => { + isAssert.positiveNumber(Number.POSITIVE_INFINITY); }); - t.false(is.positiveNumber(0)); - t.false(is.positiveNumber(-0)); - t.false(is.positiveNumber(-6)); - t.false(is.positiveNumber(-1.4)); - t.false(is.positiveNumber(Number.NEGATIVE_INFINITY)); + assert.strictEqual(is.positiveNumber(0), false); + assert.strictEqual(is.positiveNumber(-0), false); + assert.strictEqual(is.positiveNumber(-6), false); + assert.strictEqual(is.positiveNumber(-1.4), false); + assert.strictEqual(is.positiveNumber(Number.NEGATIVE_INFINITY), false); - t.throws(() => { - assert.positiveNumber(0); + assert.throws(() => { + isAssert.positiveNumber(0); }); - t.throws(() => { - assert.positiveNumber(-0); + assert.throws(() => { + isAssert.positiveNumber(-0); }); - t.throws(() => { - assert.positiveNumber(-6); + assert.throws(() => { + isAssert.positiveNumber(-6); }); - t.throws(() => { - assert.positiveNumber(-1.4); + assert.throws(() => { + isAssert.positiveNumber(-1.4); }); - t.throws(() => { - assert.positiveNumber(Number.NEGATIVE_INFINITY); + assert.throws(() => { + isAssert.positiveNumber(Number.NEGATIVE_INFINITY); }); }); -test('is.negativeNumber', t => { - t.true(is.negativeNumber(-6)); - t.true(is.negativeNumber(-1.4)); - t.true(is.negativeNumber(Number.NEGATIVE_INFINITY)); +test('is.negativeNumber', () => { + assert.ok(is.negativeNumber(-6)); + assert.ok(is.negativeNumber(-1.4)); + assert.ok(is.negativeNumber(Number.NEGATIVE_INFINITY)); - t.notThrows(() => { - assert.negativeNumber(-6); + assert.doesNotThrow(() => { + isAssert.negativeNumber(-6); }); - t.notThrows(() => { - assert.negativeNumber(-1.4); + assert.doesNotThrow(() => { + isAssert.negativeNumber(-1.4); }); - t.notThrows(() => { - assert.negativeNumber(Number.NEGATIVE_INFINITY); + assert.doesNotThrow(() => { + isAssert.negativeNumber(Number.NEGATIVE_INFINITY); }); - t.false(is.negativeNumber(0)); - t.false(is.negativeNumber(-0)); - t.false(is.negativeNumber(6)); - t.false(is.negativeNumber(1.4)); - t.false(is.negativeNumber(Number.POSITIVE_INFINITY)); + assert.strictEqual(is.negativeNumber(0), false); + assert.strictEqual(is.negativeNumber(-0), false); + assert.strictEqual(is.negativeNumber(6), false); + assert.strictEqual(is.negativeNumber(1.4), false); + assert.strictEqual(is.negativeNumber(Number.POSITIVE_INFINITY), false); - t.throws(() => { - assert.negativeNumber(0); + assert.throws(() => { + isAssert.negativeNumber(0); }); - t.throws(() => { - assert.negativeNumber(-0); + assert.throws(() => { + isAssert.negativeNumber(-0); }); - t.throws(() => { - assert.negativeNumber(6); + assert.throws(() => { + isAssert.negativeNumber(6); }); - t.throws(() => { - assert.negativeNumber(1.4); + assert.throws(() => { + isAssert.negativeNumber(1.4); }); - t.throws(() => { - assert.negativeNumber(Number.POSITIVE_INFINITY); + assert.throws(() => { + isAssert.negativeNumber(Number.POSITIVE_INFINITY); }); }); -test('is.numericString supplemental', t => { - t.false(is.numericString('')); - t.false(is.numericString(' ')); - t.false(is.numericString(' \t\t\n')); - t.false(is.numericString(1)); - t.throws(() => { - assert.numericString(''); +test('is.numericString supplemental', () => { + assert.strictEqual(is.numericString(''), false); + assert.strictEqual(is.numericString(' '), false); + assert.strictEqual(is.numericString(' \t\t\n'), false); + assert.strictEqual(is.numericString(1), false); + assert.throws(() => { + isAssert.numericString(''); }); - t.throws(() => { - assert.numericString(1); + assert.throws(() => { + isAssert.numericString(1); }); }); -test('is.array supplemental', t => { - t.true(is.array([1, 2, 3], is.number)); - t.false(is.array([1, '2', 3], is.number)); +test('is.array supplemental', () => { + assert.ok(is.array([1, 2, 3], is.number)); + assert.strictEqual(is.array([1, '2', 3], is.number), false); - t.notThrows(() => { - assert.array([1, 2], assert.number); + assert.doesNotThrow(() => { + isAssert.array([1, 2], isAssert.number); }); - t.throws(() => { - assert.array([1, '2'], assert.number); + assert.throws(() => { + isAssert.array([1, '2'], isAssert.number); }); - t.notThrows(() => { + assert.doesNotThrow(() => { const x: unknown[] = [1, 2, 3]; - assert.array(x, assert.number); + isAssert.array(x, isAssert.number); x[0]?.toFixed(0); }); - t.notThrows(() => { + assert.doesNotThrow(() => { const x: unknown[] = [1, 2, 3]; if (is.array(x, is.number)) { x[0]?.toFixed(0); } }); - t.throws(() => { - assert.array([1, '2'], assert.number, 'Expected numbers'); - }, {message: /Expected numbers/}); + assert.throws(() => { + isAssert.array([1, '2'], isAssert.number, 'Expected numbers'); + }, /Expected numbers/v); }); -test('is.boundFunction supplemental', t => { - t.false(is.boundFunction(function () {})); // eslint-disable-line prefer-arrow-callback +test('is.boundFunction supplemental', () => { + assert.strictEqual(is.boundFunction(function () {}), false); // eslint-disable-line prefer-arrow-callback - t.throws(() => { - assert.boundFunction(function () {}); // eslint-disable-line prefer-arrow-callback + assert.throws(() => { + isAssert.boundFunction(function () {}); // eslint-disable-line prefer-arrow-callback }); }); -test('is.asyncFunction supplemental', t => { +test('is.asyncFunction supplemental', () => { const fixture = async () => {}; if (is.asyncFunction(fixture)) { - t.true(is.function(fixture().then)); + assert.ok(is.function(fixture().then)); - t.notThrows(() => { - assert.function(fixture().then); + assert.doesNotThrow(() => { + isAssert.function(fixture().then); }); } }); -test('is.asyncGenerator supplemental', t => { +test('is.asyncGenerator supplemental', () => { const fixture = (async function * () { yield 4; })(); if (is.asyncGenerator(fixture)) { - t.true(is.function(fixture.next)); + assert.ok(is.function(fixture.next)); } }); -test('is.asyncGeneratorFunction supplemental', t => { +test('is.asyncGeneratorFunction supplemental', () => { const fixture = async function * () { yield 4; }; if (is.asyncGeneratorFunction(fixture)) { - t.true(is.function(fixture().next)); + assert.ok(is.function(fixture().next)); } }); -test('is.enumCase', t => { +test('is.enumCase', () => { enum NonNumericalEnum { Key1 = 'key1', Key2 = 'key2', } - t.true(is.enumCase('key1', NonNumericalEnum)); - t.notThrows(() => { - assert.enumCase('key1', NonNumericalEnum); + assert.ok(is.enumCase('key1', NonNumericalEnum)); + assert.doesNotThrow(() => { + isAssert.enumCase('key1', NonNumericalEnum); }); - t.false(is.enumCase('invalid', NonNumericalEnum)); - t.throws(() => { - assert.enumCase('invalid', NonNumericalEnum); + assert.strictEqual(is.enumCase('invalid', NonNumericalEnum), false); + assert.throws(() => { + isAssert.enumCase('invalid', NonNumericalEnum); }); }); -test('is.directInstanceOf', t => { +test('is.directInstanceOf', () => { const error = new Error('fixture'); const errorSubclass = new ErrorSubclassFixture(); - t.true(is.directInstanceOf(error, Error)); - t.true(is.directInstanceOf(errorSubclass, ErrorSubclassFixture)); - t.notThrows(() => { - assert.directInstanceOf(error, Error); + assert.ok(is.directInstanceOf(error, Error)); + assert.ok(is.directInstanceOf(errorSubclass, ErrorSubclassFixture)); + assert.doesNotThrow(() => { + isAssert.directInstanceOf(error, Error); }); - t.notThrows(() => { - assert.directInstanceOf(errorSubclass, ErrorSubclassFixture); + assert.doesNotThrow(() => { + isAssert.directInstanceOf(errorSubclass, ErrorSubclassFixture); }); - t.false(is.directInstanceOf(error, ErrorSubclassFixture)); - t.false(is.directInstanceOf(errorSubclass, Error)); - t.throws(() => { - assert.directInstanceOf(error, ErrorSubclassFixture); + assert.strictEqual(is.directInstanceOf(error, ErrorSubclassFixture), false); + assert.strictEqual(is.directInstanceOf(errorSubclass, Error), false); + assert.throws(() => { + isAssert.directInstanceOf(error, ErrorSubclassFixture); }); - t.throws(() => { - assert.directInstanceOf(errorSubclass, Error); + assert.throws(() => { + isAssert.directInstanceOf(errorSubclass, Error); }); - t.false(is.directInstanceOf(undefined, Error)); - t.false(is.directInstanceOf(null, Error)); + assert.strictEqual(is.directInstanceOf(undefined, Error), false); + assert.strictEqual(is.directInstanceOf(null, Error), false); }); -test('is.urlInstance', t => { +test('is.urlInstance', () => { const url = new URL('https://example.com'); - t.true(is.urlInstance(url)); - t.false(is.urlInstance({})); - t.false(is.urlInstance(undefined)); - t.false(is.urlInstance(null)); + assert.ok(is.urlInstance(url)); + assert.strictEqual(is.urlInstance({}), false); + assert.strictEqual(is.urlInstance(undefined), false); + assert.strictEqual(is.urlInstance(null), false); - t.notThrows(() => { - assert.urlInstance(url); + assert.doesNotThrow(() => { + isAssert.urlInstance(url); }); - t.throws(() => { - assert.urlInstance({}); + assert.throws(() => { + isAssert.urlInstance({}); }); - t.throws(() => { - assert.urlInstance(undefined); + assert.throws(() => { + isAssert.urlInstance(undefined); }); - t.throws(() => { - assert.urlInstance(null); + assert.throws(() => { + isAssert.urlInstance(null); }); }); -test('is.urlString', t => { +test('is.urlString', () => { const url = 'https://example.com'; - t.true(is.urlString(url)); - t.false(is.urlString(new URL(url))); - t.false(is.urlString({})); - t.false(is.urlString(undefined)); - t.false(is.urlString(null)); + assert.ok(is.urlString(url)); + assert.strictEqual(is.urlString(new URL(url)), false); + assert.strictEqual(is.urlString({}), false); + assert.strictEqual(is.urlString(undefined), false); + assert.strictEqual(is.urlString(null), false); - t.notThrows(() => { - assert.urlString(url); + assert.doesNotThrow(() => { + isAssert.urlString(url); }); - t.throws(() => { - assert.urlString(new URL(url)); + assert.throws(() => { + isAssert.urlString(new URL(url)); }); - t.throws(() => { - assert.urlString({}); + assert.throws(() => { + isAssert.urlString({}); }); - t.throws(() => { - assert.urlString(undefined); + assert.throws(() => { + isAssert.urlString(undefined); }); - t.throws(() => { - assert.urlString(null); + assert.throws(() => { + isAssert.urlString(null); }); }); @@ -778,194 +771,184 @@ test('is.urlString', t => { } })(); -test('is.truthy', t => { - t.true(is.truthy('unicorn')); - t.true(is.truthy('🦄')); - t.true(is.truthy(new Set())); - t.true(is.truthy(Symbol('🦄'))); - t.true(is.truthy(true)); - t.true(is.truthy(1)); - t.true(is.truthy(1n)); - t.true(is.truthy(BigInt(1))); +test('is.truthy', () => { + assert.ok(is.truthy('unicorn')); + assert.ok(is.truthy('🦄')); + assert.ok(is.truthy(new Set())); + assert.ok(is.truthy(Symbol('🦄'))); + assert.ok(is.truthy(true)); + assert.ok(is.truthy(1)); + assert.ok(is.truthy(1n)); - t.notThrows(() => { - assert.truthy('unicorn'); + assert.doesNotThrow(() => { + isAssert.truthy('unicorn'); }); - t.notThrows(() => { - assert.truthy('🦄'); + assert.doesNotThrow(() => { + isAssert.truthy('🦄'); }); - t.notThrows(() => { - assert.truthy(new Set()); + assert.doesNotThrow(() => { + isAssert.truthy(new Set()); }); - t.notThrows(() => { - assert.truthy(Symbol('🦄')); + assert.doesNotThrow(() => { + isAssert.truthy(Symbol('🦄')); }); - t.notThrows(() => { - assert.truthy(true); + assert.doesNotThrow(() => { + isAssert.truthy(true); }); - t.notThrows(() => { - assert.truthy(1); + assert.doesNotThrow(() => { + isAssert.truthy(1); }); - t.notThrows(() => { - assert.truthy(1n); + assert.doesNotThrow(() => { + isAssert.truthy(1n); }); - t.notThrows(() => { - assert.truthy(BigInt(1)); - }); - - // Checks that `assert.truthy` narrow downs boolean type to `true`. + // Checks that `isAssert.truthy` narrow downs boolean type to `true`. { const booleans = [true, false]; const function_ = (value: true) => value; - assert.truthy(booleans[0]); + isAssert.truthy(booleans[0]); function_(booleans[0]); } - // Checks that `assert.truthy` excludes zero value from number type. + // Checks that `isAssert.truthy` excludes zero value from number type. { const bits: Array<0 | 1> = [1, 0, -0]; const function_ = (value: 1) => value; - assert.truthy(bits[0]); + isAssert.truthy(bits[0]); function_(bits[0]); } - // Checks that `assert.truthy` excludes zero value from bigint type. + // Checks that `isAssert.truthy` excludes zero value from bigint type. { const bits: Array<0n | 1n> = [1n, 0n, -0n]; const function_ = (value: 1n) => value; - assert.truthy(bits[0]); + isAssert.truthy(bits[0]); function_(bits[0]); } - // Checks that `assert.truthy` excludes empty string from string type. + // Checks that `isAssert.truthy` excludes empty string from string type. { const strings: Array<'nonEmpty' | ''> = ['nonEmpty', '']; const function_ = (value: 'nonEmpty') => value; - assert.truthy(strings[0]); + isAssert.truthy(strings[0]); function_(strings[0]); } - // Checks that `assert.truthy` excludes undefined from mixed type. + // Checks that `isAssert.truthy` excludes undefined from mixed type. { const maybeUndefineds = ['🦄', undefined]; const function_ = (value: string) => value; - assert.truthy(maybeUndefineds[0]); + isAssert.truthy(maybeUndefineds[0]); function_(maybeUndefineds[0]); } - // Checks that `assert.truthy` excludes null from mixed type. + // Checks that `isAssert.truthy` excludes null from mixed type. { const maybeNulls = ['🦄', null]; const function_ = (value: string) => value; - assert.truthy(maybeNulls[0]); + isAssert.truthy(maybeNulls[0]); function_(maybeNulls[0]); } }); -test('is.falsy', t => { - t.true(is.falsy(false)); - t.true(is.falsy(0)); - t.true(is.falsy('')); - t.true(is.falsy(null)); - t.true(is.falsy(undefined)); - t.true(is.falsy(Number.NaN)); - t.true(is.falsy(0n)); - t.true(is.falsy(BigInt(0))); +test('is.falsy', () => { + assert.ok(is.falsy(false)); + assert.ok(is.falsy(0)); + assert.ok(is.falsy('')); + assert.ok(is.falsy(null)); + assert.ok(is.falsy(undefined)); + assert.ok(is.falsy(Number.NaN)); + assert.ok(is.falsy(0n)); - t.notThrows(() => { - assert.falsy(false); + assert.doesNotThrow(() => { + isAssert.falsy(false); }); - t.notThrows(() => { - assert.falsy(0); + assert.doesNotThrow(() => { + isAssert.falsy(0); }); - t.notThrows(() => { - assert.falsy(''); + assert.doesNotThrow(() => { + isAssert.falsy(''); }); - t.notThrows(() => { - assert.falsy(null); + assert.doesNotThrow(() => { + isAssert.falsy(null); }); - t.notThrows(() => { - assert.falsy(undefined); + assert.doesNotThrow(() => { + isAssert.falsy(undefined); }); - t.notThrows(() => { - assert.falsy(Number.NaN); + assert.doesNotThrow(() => { + isAssert.falsy(Number.NaN); }); - t.notThrows(() => { - assert.falsy(0n); + assert.doesNotThrow(() => { + isAssert.falsy(0n); }); - t.notThrows(() => { - assert.falsy(BigInt(0)); - }); - - // Checks that `assert.falsy` narrow downs boolean type to `false`. + // Checks that `isAssert.falsy` narrow downs boolean type to `false`. { const booleans = [false, true]; const function_ = (value?: false) => value; - assert.falsy(booleans[0]); + isAssert.falsy(booleans[0]); function_(booleans[0]); } - // Checks that `assert.falsy` narrow downs number type to `0`. + // Checks that `isAssert.falsy` narrow downs number type to `0`. { const bits = [0, -0, 1]; const function_ = (value?: 0) => value; - assert.falsy(bits[0]); + isAssert.falsy(bits[0]); function_(bits[0]); - assert.falsy(bits[1]); + isAssert.falsy(bits[1]); function_(bits[1]); } - // Checks that `assert.falsy` narrow downs bigint type to `0n`. + // Checks that `isAssert.falsy` narrow downs bigint type to `0n`. { const bits = [0n, -0n, 1n]; const function_ = (value?: 0n) => value; - assert.falsy(bits[0]); + isAssert.falsy(bits[0]); function_(bits[0]); - assert.falsy(bits[1]); + isAssert.falsy(bits[1]); function_(bits[1]); } - // Checks that `assert.falsy` narrow downs string type to empty string. + // Checks that `isAssert.falsy` narrow downs string type to empty string. { const strings = ['', 'nonEmpty']; const function_ = (value?: '') => value; - assert.falsy(strings[0]); + isAssert.falsy(strings[0]); function_(strings[0]); } - // Checks that `assert.falsy` can narrow down mixed type to undefined. + // Checks that `isAssert.falsy` can narrow down mixed type to undefined. { const maybeUndefineds = [undefined, Symbol('🦄')]; const function_ = (value: undefined) => value; - assert.falsy(maybeUndefineds[0]); + isAssert.falsy(maybeUndefineds[0]); function_(maybeUndefineds[0]); } - // Checks that `assert.falsy` can narrow down mixed type to null. + // Checks that `isAssert.falsy` can narrow down mixed type to null. { const maybeNulls = [null, Symbol('🦄')]; - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-restricted-types const function_ = (value?: null) => value; - assert.falsy(maybeNulls[0]); + isAssert.falsy(maybeNulls[0]); function_(maybeNulls[0]); } }); -test('is.primitive', t => { +test('is.primitive', () => { const primitives: Primitive[] = [ undefined, null, @@ -980,113 +963,113 @@ test('is.primitive', t => { ]; for (const element of primitives) { - t.true(is.primitive(element)); - t.notThrows(() => { - assert.primitive(element); + assert.ok(is.primitive(element)); + assert.doesNotThrow(() => { + isAssert.primitive(element); }); } }); -test('is.integer supplemental', t => { - t.false(is.integer(1.4)); - t.throws(() => { - assert.integer(1.4); +test('is.integer supplemental', () => { + assert.strictEqual(is.integer(1.4), false); + assert.throws(() => { + isAssert.integer(1.4); }); }); -test('is.safeInteger supplemental', t => { - t.false(is.safeInteger(2 ** 53)); - t.false(is.safeInteger(-(2 ** 53))); - t.throws(() => { - assert.safeInteger(2 ** 53); +test('is.safeInteger supplemental', () => { + assert.strictEqual(is.safeInteger(2 ** 53), false); + assert.strictEqual(is.safeInteger(-(2 ** 53)), false); + assert.throws(() => { + isAssert.safeInteger(2 ** 53); }); - t.throws(() => { - assert.safeInteger(-(2 ** 53)); + assert.throws(() => { + isAssert.safeInteger(-(2 ** 53)); }); }); -test('is.iterable', t => { - t.true(is.iterable('')); - t.true(is.iterable([])); - t.true(is.iterable(new Map())); - t.false(is.iterable(null)); - t.false(is.iterable(undefined)); - t.false(is.iterable(0)); - t.false(is.iterable(Number.NaN)); - t.false(is.iterable(Number.POSITIVE_INFINITY)); - t.false(is.iterable({})); +test('is.iterable', () => { + assert.ok(is.iterable('')); + assert.ok(is.iterable([])); + assert.ok(is.iterable(new Map())); + assert.strictEqual(is.iterable(null), false); + assert.strictEqual(is.iterable(undefined), false); + assert.strictEqual(is.iterable(0), false); + assert.strictEqual(is.iterable(Number.NaN), false); + assert.strictEqual(is.iterable(Number.POSITIVE_INFINITY), false); + assert.strictEqual(is.iterable({}), false); - t.notThrows(() => { - assert.iterable(''); + assert.doesNotThrow(() => { + isAssert.iterable(''); }); - t.notThrows(() => { - assert.iterable([]); + assert.doesNotThrow(() => { + isAssert.iterable([]); }); - t.notThrows(() => { - assert.iterable(new Map()); + assert.doesNotThrow(() => { + isAssert.iterable(new Map()); }); - t.throws(() => { - assert.iterable(null); + assert.throws(() => { + isAssert.iterable(null); }); - t.throws(() => { - assert.iterable(undefined); + assert.throws(() => { + isAssert.iterable(undefined); }); - t.throws(() => { - assert.iterable(0); + assert.throws(() => { + isAssert.iterable(0); }); - t.throws(() => { - assert.iterable(Number.NaN); + assert.throws(() => { + isAssert.iterable(Number.NaN); }); - t.throws(() => { - assert.iterable(Number.POSITIVE_INFINITY); + assert.throws(() => { + isAssert.iterable(Number.POSITIVE_INFINITY); }); - t.throws(() => { - assert.iterable({}); + assert.throws(() => { + isAssert.iterable({}); }); }); -test('is.asyncIterable', t => { - t.true(is.asyncIterable({ +test('is.asyncIterable', () => { + assert.ok(is.asyncIterable({ [Symbol.asyncIterator]() {}, })); - t.false(is.asyncIterable(null)); - t.false(is.asyncIterable(undefined)); - t.false(is.asyncIterable(0)); - t.false(is.asyncIterable(Number.NaN)); - t.false(is.asyncIterable(Number.POSITIVE_INFINITY)); - t.false(is.asyncIterable({})); + assert.strictEqual(is.asyncIterable(null), false); + assert.strictEqual(is.asyncIterable(undefined), false); + assert.strictEqual(is.asyncIterable(0), false); + assert.strictEqual(is.asyncIterable(Number.NaN), false); + assert.strictEqual(is.asyncIterable(Number.POSITIVE_INFINITY), false); + assert.strictEqual(is.asyncIterable({}), false); - t.notThrows(() => { - assert.asyncIterable({ + assert.doesNotThrow(() => { + isAssert.asyncIterable({ [Symbol.asyncIterator]() {}, }); }); - t.throws(() => { - assert.asyncIterable(null); + assert.throws(() => { + isAssert.asyncIterable(null); }); - t.throws(() => { - assert.asyncIterable(undefined); + assert.throws(() => { + isAssert.asyncIterable(undefined); }); - t.throws(() => { - assert.asyncIterable(0); + assert.throws(() => { + isAssert.asyncIterable(0); }); - t.throws(() => { - assert.asyncIterable(Number.NaN); + assert.throws(() => { + isAssert.asyncIterable(Number.NaN); }); - t.throws(() => { - assert.asyncIterable(Number.POSITIVE_INFINITY); + assert.throws(() => { + isAssert.asyncIterable(Number.POSITIVE_INFINITY); }); - t.throws(() => { - assert.asyncIterable({}); + assert.throws(() => { + isAssert.asyncIterable({}); }); }); -test('is.class', t => { +test('is.class', () => { class Foo {} // eslint-disable-line @typescript-eslint/no-extraneous-class - // Note: Using new Function to prevent whitespace modifications in tsimp + // Note: Using new Function to test a minified class (no whitespace in source) const minifiedClass = new Function('return class{};'); // eslint-disable-line no-new-func const classDeclarations = [ @@ -1096,19 +1079,20 @@ test('is.class', t => { ]; for (const classDeclaration of classDeclarations) { - t.true(is.class(classDeclaration)); + assert.ok(is.class(classDeclaration)); - t.notThrows(() => { - assert.class(classDeclaration); + assert.doesNotThrow(() => { + isAssert.class(classDeclaration); }); } }); -test('is.typedArray', t => { +test('is.typedArray', () => { const typedArrays: TypedArray[] = [ new Int8Array(), new Uint8Array(), new Uint8ClampedArray(), + new Int16Array(), new Uint16Array(), new Int32Array(), new Uint32Array(), @@ -1119,98 +1103,98 @@ test('is.typedArray', t => { ]; for (const item of typedArrays) { - t.true(is.typedArray(item)); + assert.ok(is.typedArray(item)); - t.notThrows(() => { - assert.typedArray(item); + assert.doesNotThrow(() => { + isAssert.typedArray(item); }); } - t.false(is.typedArray(new ArrayBuffer(1))); - t.false(is.typedArray([])); - t.false(is.typedArray({})); + assert.strictEqual(is.typedArray(new ArrayBuffer(1)), false); + assert.strictEqual(is.typedArray([]), false); + assert.strictEqual(is.typedArray({}), false); - t.throws(() => { - assert.typedArray(new ArrayBuffer(1)); + assert.throws(() => { + isAssert.typedArray(new ArrayBuffer(1)); }); - t.throws(() => { - assert.typedArray([]); + assert.throws(() => { + isAssert.typedArray([]); }); - t.throws(() => { - assert.typedArray({}); + assert.throws(() => { + isAssert.typedArray({}); }); }); -test('is.arrayLike', t => { +test('is.arrayLike', () => { (function () { - t.true(is.arrayLike(arguments)); // eslint-disable-line prefer-rest-params + assert.ok(is.arrayLike(arguments)); // eslint-disable-line prefer-rest-params })(); - t.true(is.arrayLike([])); - t.true(is.arrayLike('unicorn')); + assert.ok(is.arrayLike([])); + assert.ok(is.arrayLike('unicorn')); - t.false(is.arrayLike({})); - t.false(is.arrayLike(() => {})); - t.false(is.arrayLike(new Map())); + assert.strictEqual(is.arrayLike({}), false); + assert.strictEqual(is.arrayLike(() => {}), false); + assert.strictEqual(is.arrayLike(new Map()), false); (function () { - t.notThrows(function () { - assert.arrayLike(arguments); // eslint-disable-line prefer-rest-params + assert.doesNotThrow(function () { + isAssert.arrayLike(arguments); // eslint-disable-line prefer-rest-params }); })(); - t.notThrows(() => { - assert.arrayLike([]); + assert.doesNotThrow(() => { + isAssert.arrayLike([]); }); - t.notThrows(() => { - assert.arrayLike('unicorn'); + assert.doesNotThrow(() => { + isAssert.arrayLike('unicorn'); }); - t.throws(() => { - assert.arrayLike({}); + assert.throws(() => { + isAssert.arrayLike({}); }); - t.throws(() => { - assert.arrayLike(() => {}); + assert.throws(() => { + isAssert.arrayLike(() => {}); }); - t.throws(() => { - assert.arrayLike(new Map()); + assert.throws(() => { + isAssert.arrayLike(new Map()); }); }); -test('is.tupleLike', t => { +test('is.tupleLike', () => { (function () { - t.false(is.tupleLike(arguments, [])); // eslint-disable-line prefer-rest-params + assert.strictEqual(is.tupleLike(arguments, []), false); // eslint-disable-line prefer-rest-params })(); - t.true(is.tupleLike([], [])); - t.true(is.tupleLike([1, '2', true, {}, [], undefined, null], [is.number, is.string, is.boolean, is.object, is.array, is.undefined, is.nullOrUndefined])); - t.false(is.tupleLike('unicorn', [is.string])); + assert.ok(is.tupleLike([], [])); + assert.ok(is.tupleLike([1, '2', true, {}, [], undefined, null], [is.number, is.string, is.boolean, is.object, is.array, is.undefined, is.nullOrUndefined])); + assert.strictEqual(is.tupleLike('unicorn', [is.string]), false); - t.false(is.tupleLike({}, [])); - t.false(is.tupleLike(() => {}, [is.function])); - t.false(is.tupleLike(new Map(), [is.map])); + assert.strictEqual(is.tupleLike({}, []), false); + assert.strictEqual(is.tupleLike(() => {}, [is.function]), false); + assert.strictEqual(is.tupleLike(new Map(), [is.map]), false); (function () { - t.throws(function () { - assert.tupleLike(arguments, []); // eslint-disable-line prefer-rest-params + assert.throws(function () { + isAssert.tupleLike(arguments, []); // eslint-disable-line prefer-rest-params }); })(); - t.notThrows(() => { - assert.tupleLike([], []); + assert.doesNotThrow(() => { + isAssert.tupleLike([], []); }); - t.throws(() => { - assert.tupleLike('unicorn', [is.string]); + assert.throws(() => { + isAssert.tupleLike('unicorn', [is.string]); }); - t.throws(() => { - assert.tupleLike({}, [is.object]); + assert.throws(() => { + isAssert.tupleLike({}, [is.object]); }); - t.throws(() => { - assert.tupleLike(() => {}, [is.function]); + assert.throws(() => { + isAssert.tupleLike(() => {}, [is.function]); }); - t.throws(() => { - assert.tupleLike(new Map(), [is.map]); + assert.throws(() => { + isAssert.tupleLike(new Map(), [is.map]); }); { @@ -1236,127 +1220,127 @@ test('is.tupleLike', t => { { const tuple = [1, '1', true, null, undefined]; - if (is.tupleLike(tuple, [is.number, is.string, is.boolean, is.undefined, is.null])) { + if (is.tupleLike(tuple, [is.number, is.string, is.boolean, is.null, is.undefined])) { const numericValue = tuple[0]; const stringValue = tuple[1]; const booleanValue = tuple[2]; - const undefinedValue = tuple[3]; - const nullValue = tuple[4]; + const nullValue = tuple[3]; + const undefinedValue = tuple[4]; expectTypeOf(numericValue).toEqualTypeOf(); expectTypeOf(stringValue).toEqualTypeOf(); expectTypeOf(booleanValue).toEqualTypeOf(); - expectTypeOf(undefinedValue).toEqualTypeOf(); - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-restricted-types expectTypeOf(nullValue).toEqualTypeOf(); + expectTypeOf(undefinedValue).toEqualTypeOf(); } } }); -test('is.inRange', t => { +test('is.inRange', () => { const x = 3; - t.true(is.inRange(x, [0, 5])); - t.true(is.inRange(x, [5, 0])); - t.true(is.inRange(x, [-5, 5])); - t.true(is.inRange(x, [5, -5])); - t.false(is.inRange(x, [4, 8])); - t.true(is.inRange(-7, [-5, -10])); - t.true(is.inRange(-5, [-5, -10])); - t.true(is.inRange(-10, [-5, -10])); + assert.ok(is.inRange(x, [0, 5])); + assert.ok(is.inRange(x, [5, 0])); + assert.ok(is.inRange(x, [-5, 5])); + assert.ok(is.inRange(x, [5, -5])); + assert.strictEqual(is.inRange(x, [4, 8]), false); + assert.ok(is.inRange(-7, [-5, -10])); + assert.ok(is.inRange(-5, [-5, -10])); + assert.ok(is.inRange(-10, [-5, -10])); - t.true(is.inRange(x, 10)); - t.true(is.inRange(0, 0)); - t.true(is.inRange(-2, -3)); - t.false(is.inRange(x, 2)); - t.false(is.inRange(-3, -2)); + assert.ok(is.inRange(x, 10)); + assert.ok(is.inRange(0, 0)); + assert.ok(is.inRange(-2, -3)); + assert.strictEqual(is.inRange(x, 2), false); + assert.strictEqual(is.inRange(-3, -2), false); - t.throws(() => { + assert.throws(() => { // @ts-expect-error invalid argument is.inRange(0, []); }); - t.throws(() => { + assert.throws(() => { // @ts-expect-error invalid argument is.inRange(0, [5]); }); - t.throws(() => { + assert.throws(() => { // @ts-expect-error invalid argument is.inRange(0, [1, 2, 3]); }); - t.notThrows(() => { - assert.inRange(x, [0, 5]); + assert.doesNotThrow(() => { + isAssert.inRange(x, [0, 5]); }); - t.notThrows(() => { - assert.inRange(x, [5, 0]); + assert.doesNotThrow(() => { + isAssert.inRange(x, [5, 0]); }); - t.notThrows(() => { - assert.inRange(x, [-5, 5]); + assert.doesNotThrow(() => { + isAssert.inRange(x, [-5, 5]); }); - t.notThrows(() => { - assert.inRange(x, [5, -5]); + assert.doesNotThrow(() => { + isAssert.inRange(x, [5, -5]); }); - t.throws(() => { - assert.inRange(x, [4, 8]); + assert.throws(() => { + isAssert.inRange(x, [4, 8]); }); - t.notThrows(() => { - assert.inRange(-7, [-5, -10]); + assert.doesNotThrow(() => { + isAssert.inRange(-7, [-5, -10]); }); - t.notThrows(() => { - assert.inRange(-5, [-5, -10]); + assert.doesNotThrow(() => { + isAssert.inRange(-5, [-5, -10]); }); - t.notThrows(() => { - assert.inRange(-10, [-5, -10]); + assert.doesNotThrow(() => { + isAssert.inRange(-10, [-5, -10]); }); - t.notThrows(() => { - assert.inRange(x, 10); + assert.doesNotThrow(() => { + isAssert.inRange(x, 10); }); - t.notThrows(() => { - assert.inRange(0, 0); + assert.doesNotThrow(() => { + isAssert.inRange(0, 0); }); - t.notThrows(() => { - assert.inRange(-2, -3); + assert.doesNotThrow(() => { + isAssert.inRange(-2, -3); }); - t.throws(() => { - assert.inRange(x, 2); + assert.throws(() => { + isAssert.inRange(x, 2); }); - t.throws(() => { - assert.inRange(-3, -2); + assert.throws(() => { + isAssert.inRange(-3, -2); }); - t.throws(() => { + assert.throws(() => { // @ts-expect-error invalid argument - assert.inRange(0, []); + isAssert.inRange(0, []); }); - t.throws(() => { + assert.throws(() => { // @ts-expect-error invalid argument - assert.inRange(0, [5]); + isAssert.inRange(0, [5]); }); - t.throws(() => { + assert.throws(() => { // @ts-expect-error invalid argument - assert.inRange(0, [1, 2, 3]); + isAssert.inRange(0, [1, 2, 3]); }); }); -test('is.htmlElement supplemental', t => { - t.false(is.htmlElement({nodeType: 1, nodeName: 'div'})); - t.throws(() => { - assert.htmlElement({nodeType: 1, nodeName: 'div'}); +test('is.htmlElement supplemental', () => { + assert.strictEqual(is.htmlElement({nodeType: 1, nodeName: 'div'}), false); + assert.throws(() => { + isAssert.htmlElement({nodeType: 1, nodeName: 'div'}); }); const tagNames = [ @@ -1370,7 +1354,7 @@ test('is.htmlElement supplemental', t => { for (const tagName of tagNames) { const element = document.createElement(tagName); - t.is(is(element), 'HTMLElement'); + assert.strictEqual(is(element), 'HTMLElement'); } const nonHtmlElements = [ @@ -1383,57 +1367,57 @@ test('is.htmlElement supplemental', t => { ] as const; for (const element of nonHtmlElements) { - t.throws(() => { - assert.htmlElement(element); + assert.throws(() => { + isAssert.htmlElement(element); }); } }); -test('is.evenInteger', t => { +test('is.evenInteger', () => { for (const element of [-6, 2, 4]) { - t.true(is.evenInteger(element)); - t.notThrows(() => { - assert.evenInteger(element); + assert.ok(is.evenInteger(element)); + assert.doesNotThrow(() => { + isAssert.evenInteger(element); }); } for (const element of [-3, 1, 5]) { - t.false(is.evenInteger(element)); - t.throws(() => { - assert.evenInteger(element); + assert.strictEqual(is.evenInteger(element), false); + assert.throws(() => { + isAssert.evenInteger(element); }); } }); -test('is.oddInteger', t => { +test('is.oddInteger', () => { for (const element of [-5, 7, 13]) { - t.true(is.oddInteger(element)); - t.notThrows(() => { - assert.oddInteger(element); + assert.ok(is.oddInteger(element)); + assert.doesNotThrow(() => { + isAssert.oddInteger(element); }); } for (const element of [-8, 8, 10]) { - t.false(is.oddInteger(element)); - t.throws(() => { - assert.oddInteger(element); + assert.strictEqual(is.oddInteger(element), false); + assert.throws(() => { + isAssert.oddInteger(element); }); } }); -test('is.nonEmptyArray', t => { - t.true(is.nonEmptyArray([1, 2, 3])); - t.false(is.nonEmptyArray([])); - t.false(is.nonEmptyArray(new Array())); // eslint-disable-line @typescript-eslint/no-array-constructor +test('is.nonEmptyArray', () => { + assert.ok(is.nonEmptyArray([1, 2, 3])); + assert.strictEqual(is.nonEmptyArray([]), false); + assert.strictEqual(is.nonEmptyArray(new Array()), false); // eslint-disable-line @typescript-eslint/no-array-constructor - t.notThrows(() => { - assert.nonEmptyArray([1, 2, 3]); + assert.doesNotThrow(() => { + isAssert.nonEmptyArray([1, 2, 3]); }); - t.throws(() => { - assert.nonEmptyArray([]); + assert.throws(() => { + isAssert.nonEmptyArray([]); }); - t.throws(() => { - assert.nonEmptyArray(new Array()); // eslint-disable-line @typescript-eslint/no-array-constructor + assert.throws(() => { + isAssert.nonEmptyArray(new Array()); // eslint-disable-line @typescript-eslint/no-array-constructor }); { @@ -1470,7 +1454,7 @@ test('is.nonEmptyArray', t => { const strings = ['🦄', 'unicorn'] as string[] | undefined; const function_ = (value: string) => value; - assert.nonEmptyArray(strings); + isAssert.nonEmptyArray(strings); const value = strings[0]; function_(value); @@ -1480,7 +1464,7 @@ test('is.nonEmptyArray', t => { const mixed = ['🦄', 'unicorn', 1, 2]; const function_ = (value: string | number) => value; - assert.nonEmptyArray(mixed); + isAssert.nonEmptyArray(mixed); const value = mixed[0]; function_(value); @@ -1490,33 +1474,33 @@ test('is.nonEmptyArray', t => { const arrays = [['🦄'], ['unicorn']]; const function_ = (value: string[]) => value; - assert.nonEmptyArray(arrays); + isAssert.nonEmptyArray(arrays); const value = arrays[0]; function_(value); } }); -test('is.emptyString supplemental', t => { - t.false(is.emptyString('🦄')); - t.throws(() => { - assert.emptyString('🦄'); +test('is.emptyString supplemental', () => { + assert.strictEqual(is.emptyString('🦄'), false); + assert.throws(() => { + isAssert.emptyString('🦄'); }); }); -test('is.emptyStringOrWhitespace supplemental', t => { - t.true(is.emptyStringOrWhitespace(' ')); - t.false(is.emptyStringOrWhitespace('🦄')); - t.false(is.emptyStringOrWhitespace('unicorn')); +test('is.emptyStringOrWhitespace supplemental', () => { + assert.ok(is.emptyStringOrWhitespace(' ')); + assert.strictEqual(is.emptyStringOrWhitespace('🦄'), false); + assert.strictEqual(is.emptyStringOrWhitespace('unicorn'), false); - t.notThrows(() => { - assert.emptyStringOrWhitespace(' '); + assert.doesNotThrow(() => { + isAssert.emptyStringOrWhitespace(' '); }); - t.throws(() => { - assert.emptyStringOrWhitespace('🦄'); + assert.throws(() => { + isAssert.emptyStringOrWhitespace('🦄'); }); - t.throws(() => { - assert.emptyStringOrWhitespace('unicorn'); + assert.throws(() => { + isAssert.emptyStringOrWhitespace('unicorn'); }); let value = 'test'; // eslint-disable-line prefer-const -- can't use `const` here because then it will be inferred as `never` in the `if` block @@ -1527,456 +1511,470 @@ test('is.emptyStringOrWhitespace supplemental', t => { } }); -test('is.nonEmptyString', t => { - t.false(is.nonEmptyString('')); - t.false(is.nonEmptyString(String())); - t.true(is.nonEmptyString('🦄')); +test('is.nonEmptyString', () => { + assert.strictEqual(is.nonEmptyString(''), false); + assert.strictEqual(is.nonEmptyString(String()), false); + assert.ok(is.nonEmptyString('🦄')); - t.throws(() => { - assert.nonEmptyString(''); + assert.throws(() => { + isAssert.nonEmptyString(''); }); - t.throws(() => { - assert.nonEmptyString(String()); + assert.throws(() => { + isAssert.nonEmptyString(String()); }); - t.notThrows(() => { - assert.nonEmptyString('🦄'); + assert.doesNotThrow(() => { + isAssert.nonEmptyString('🦄'); }); }); -test('is.nonEmptyStringAndNotWhitespace', t => { - t.false(is.nonEmptyStringAndNotWhitespace(' ')); - t.true(is.nonEmptyStringAndNotWhitespace('🦄')); +test('is.nonEmptyStringAndNotWhitespace', () => { + assert.strictEqual(is.nonEmptyStringAndNotWhitespace(' '), false); + assert.ok(is.nonEmptyStringAndNotWhitespace('🦄')); for (const value of [null, undefined, 5, Number.NaN, {}, []]) { - t.false(is.nonEmptyStringAndNotWhitespace(value)); + assert.strictEqual(is.nonEmptyStringAndNotWhitespace(value), false); - t.throws(() => { - assert.nonEmptyStringAndNotWhitespace(value); + assert.throws(() => { + isAssert.nonEmptyStringAndNotWhitespace(value); }); } - t.throws(() => { - assert.nonEmptyStringAndNotWhitespace(''); + assert.throws(() => { + isAssert.nonEmptyStringAndNotWhitespace(''); }); - t.notThrows(() => { - assert.nonEmptyStringAndNotWhitespace('🦄'); + assert.doesNotThrow(() => { + isAssert.nonEmptyStringAndNotWhitespace('🦄'); }); }); -test('is.emptyObject', t => { - t.true(is.emptyObject({})); - t.true(is.emptyObject(new Object())); // eslint-disable-line no-object-constructor - t.false(is.emptyObject({unicorn: '🦄'})); +test('is.emptyObject', () => { + assert.ok(is.emptyObject({})); + assert.ok(is.emptyObject(new Object())); // eslint-disable-line no-object-constructor + assert.strictEqual(is.emptyObject({unicorn: '🦄'}), false); - t.notThrows(() => { - assert.emptyObject({}); + assert.doesNotThrow(() => { + isAssert.emptyObject({}); }); - t.notThrows(() => { - assert.emptyObject(new Object()); // eslint-disable-line no-object-constructor + assert.doesNotThrow(() => { + isAssert.emptyObject(new Object()); // eslint-disable-line no-object-constructor }); - t.throws(() => { - assert.emptyObject({unicorn: '🦄'}); + assert.throws(() => { + isAssert.emptyObject({unicorn: '🦄'}); }); }); -test('is.nonEmptyObject', t => { +test('is.nonEmptyObject', () => { const foo = {}; is.nonEmptyObject(foo); - t.false(is.nonEmptyObject({})); - t.false(is.nonEmptyObject(new Object())); // eslint-disable-line no-object-constructor - t.true(is.nonEmptyObject({unicorn: '🦄'})); + assert.strictEqual(is.nonEmptyObject({}), false); + assert.strictEqual(is.nonEmptyObject(new Object()), false); // eslint-disable-line no-object-constructor + assert.ok(is.nonEmptyObject({unicorn: '🦄'})); - t.throws(() => { - assert.nonEmptyObject({}); + assert.throws(() => { + isAssert.nonEmptyObject({}); }); - t.throws(() => { - assert.nonEmptyObject(new Object()); // eslint-disable-line no-object-constructor + assert.throws(() => { + isAssert.nonEmptyObject(new Object()); // eslint-disable-line no-object-constructor }); - t.notThrows(() => { - assert.nonEmptyObject({unicorn: '🦄'}); + assert.doesNotThrow(() => { + isAssert.nonEmptyObject({unicorn: '🦄'}); }); }); -test('is.nonEmptySet', t => { +test('is.nonEmptySet', () => { const temporarySet = new Set(); - t.false(is.nonEmptySet(temporarySet)); - t.throws(() => { - assert.nonEmptySet(temporarySet); + assert.strictEqual(is.nonEmptySet(temporarySet), false); + assert.throws(() => { + isAssert.nonEmptySet(temporarySet); }); temporarySet.add(1); - t.true(is.nonEmptySet(temporarySet)); - t.notThrows(() => { - assert.nonEmptySet(temporarySet); + assert.ok(is.nonEmptySet(temporarySet)); + assert.doesNotThrow(() => { + isAssert.nonEmptySet(temporarySet); }); }); -test('is.nonEmptyMap', t => { +test('is.nonEmptyMap', () => { const temporaryMap = new Map(); - t.false(is.nonEmptyMap(temporaryMap)); - t.throws(() => { - assert.nonEmptyMap(temporaryMap); + assert.strictEqual(is.nonEmptyMap(temporaryMap), false); + assert.throws(() => { + isAssert.nonEmptyMap(temporaryMap); }); temporaryMap.set('unicorn', '🦄'); - t.true(is.nonEmptyMap(temporaryMap)); - t.notThrows(() => { - assert.nonEmptyMap(temporaryMap); + assert.ok(is.nonEmptyMap(temporaryMap)); + assert.doesNotThrow(() => { + isAssert.nonEmptyMap(temporaryMap); }); }); -test('is.propertyKey', t => { - t.true(is.propertyKey('key')); - t.true(is.propertyKey(42)); - t.true(is.propertyKey(Symbol(''))); +test('is.propertyKey', () => { + assert.ok(is.propertyKey('key')); + assert.ok(is.propertyKey(42)); + assert.ok(is.propertyKey(Symbol(''))); - t.false(is.propertyKey(null)); - t.false(is.propertyKey(undefined)); - t.false(is.propertyKey(true)); - t.false(is.propertyKey({})); - t.false(is.propertyKey([])); - t.false(is.propertyKey(new Map())); - t.false(is.propertyKey(new Set())); + assert.strictEqual(is.propertyKey(null), false); + assert.strictEqual(is.propertyKey(undefined), false); + assert.strictEqual(is.propertyKey(true), false); + assert.strictEqual(is.propertyKey({}), false); + assert.strictEqual(is.propertyKey([]), false); + assert.strictEqual(is.propertyKey(new Map()), false); + assert.strictEqual(is.propertyKey(new Set()), false); }); -test('is.any', t => { - t.true(is.any(is.string, {}, true, '🦄')); - t.true(is.any(is.object, false, {}, 'unicorns')); - t.false(is.any(is.boolean, '🦄', [], 3)); - t.false(is.any(is.integer, true, 'lol', {})); - t.true(is.any([is.string, is.number], {}, true, '🦄')); - t.false(is.any([is.boolean, is.number], 'unicorns', [], new Map())); - t.is(typeof is.any([is.string, is.number]), 'function'); +test('is.any', () => { + assert.ok(is.any(is.string, {}, true, '🦄')); + assert.ok(is.any(is.object, false, {}, 'unicorns')); + assert.strictEqual(is.any(is.boolean, '🦄', [], 3), false); + assert.strictEqual(is.any(is.integer, true, 'lol', {}), false); + assert.ok(is.any([is.string, is.number], {}, true, '🦄')); + assert.strictEqual(is.any([is.boolean, is.number], 'unicorns', [], new Map()), false); + assert.strictEqual(typeof is.any([is.string, is.number]), 'function'); - t.throws(() => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + assert.throws(() => { is.any(null as any, true); }); - t.throws(() => { + assert.throws(() => { is.any([], 'value'); }); - t.throws(() => { + assert.throws(() => { is.any(is.string); }); - t.notThrows(() => { - assert.any(is.string, {}, true, '🦄'); + assert.doesNotThrow(() => { + isAssert.any(is.string, {}, true, '🦄'); }); - t.notThrows(() => { - assert.any(is.object, false, {}, 'unicorns'); + assert.doesNotThrow(() => { + isAssert.any(is.object, false, {}, 'unicorns'); }); - t.throws(() => { - assert.any([is.string, is.number]); + assert.throws(() => { + isAssert.any([is.string, is.number]); }); - t.throws(() => { - assert.any(is.boolean, '🦄', [], 3); + assert.throws(() => { + isAssert.any(is.boolean, '🦄', [], 3); }); - t.throws(() => { - assert.any(is.integer, true, 'lol', {}); + assert.throws(() => { + isAssert.any(is.integer, true, 'lol', {}); }); - t.throws(() => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - assert.any(null as any, true); + assert.throws(() => { + isAssert.any(null as any, true); }); - t.throws(() => { - assert.any([], 'value'); + assert.throws(() => { + isAssert.any([], 'value'); }); - t.throws(() => { - assert.any(is.string); + assert.throws(() => { + isAssert.any(is.string); }); - t.throws(() => { - assert.any(is.string, 1, 2, 3); + assert.throws(() => { + isAssert.any(is.string, 1, 2, 3); }, { // Includes expected type and removes duplicates from received types: - message: /Expected values which are `string`. Received values of type `number`./, + // eslint-disable-next-line prefer-regex-literals + message: new RegExp('Expected values which are `string`. Received values of type `number`.', 'v'), }); - t.throws(() => { - assert.any(is.string, 1, [4]); + assert.throws(() => { + isAssert.any(is.string, 1, [4]); }, { // Includes expected type and lists all received types: - message: /Expected values which are `string`. Received values of types `number` and `Array`./, + // eslint-disable-next-line prefer-regex-literals + message: new RegExp('Expected values which are `string`. Received values of types `number` and `Array`.', 'v'), }); - t.throws(() => { - assert.any([is.string, is.nullOrUndefined], 1); + assert.throws(() => { + isAssert.any([is.string, is.nullOrUndefined], 1); }, { // Handles array as first argument: - message: /Expected values which are `string` or `null or undefined`. Received values of type `number`./, + // eslint-disable-next-line prefer-regex-literals + message: new RegExp('Expected values which are `string` or `null or undefined`. Received values of type `number`.', 'v'), }); - t.throws(() => { - assert.any([is.string, is.number, is.boolean], null, undefined, Number.NaN); + assert.throws(() => { + isAssert.any([is.string, is.number, is.boolean], null, undefined, Number.NaN); }, { // Handles more than 2 expected and received types: - message: /Expected values which are `string`, `number`, or `boolean`. Received values of types `null`, `undefined`, and `NaN`./, + // eslint-disable-next-line prefer-regex-literals + message: new RegExp('Expected values which are `string`, `number`, or `boolean`. Received values of types `null`, `undefined`, and `NaN`.', 'v'), }); - t.throws(() => { - assert.any(() => false, 1); + assert.throws(() => { + isAssert.any(() => false, 1); }, { // Default type assertion message - message: /Expected values which are `predicate returns truthy for any value`./, + // eslint-disable-next-line prefer-regex-literals + message: new RegExp('Expected values which are `predicate returns truthy for any value`.', 'v'), }); }); -test('is.all', t => { - t.true(is.all(is.object, {}, new Set(), new Map())); - t.true(is.all(is.boolean, true, false)); - t.false(is.all(is.string, '🦄', [])); - t.false(is.all(is.set, new Map(), {})); +test('is.all', () => { + assert.ok(is.all(is.object, {}, new Set(), new Map())); + assert.ok(is.all(is.boolean, true, false)); + assert.strictEqual(is.all(is.string, '🦄', []), false); + assert.strictEqual(is.all(is.set, new Map(), {}), false); - t.true(is.all(is.array, ['1'], ['2'])); - t.true(is.all([is.string, is.nonEmptyString], '🦄', 'unicorns')); - t.false(is.all([is.string, is.number], '🦄')); + assert.ok(is.all(is.array, ['1'], ['2'])); + assert.ok(is.all([is.string, is.nonEmptyString], '🦄', 'unicorns')); + assert.strictEqual(is.all([is.string, is.number], '🦄'), false); - t.throws(() => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + assert.throws(() => { is.all(null as any, true); }); - t.throws(() => { + assert.throws(() => { is.all([], 'value'); }); - t.throws(() => { + assert.throws(() => { is.all(is.string); }); - t.notThrows(() => { - assert.all(is.object, {}, new Set(), new Map()); + assert.doesNotThrow(() => { + isAssert.all(is.object, {}, new Set(), new Map()); }); - t.notThrows(() => { - assert.all(is.boolean, true, false); + assert.doesNotThrow(() => { + isAssert.all(is.boolean, true, false); }); - t.throws(() => { - assert.all([is.string, is.number]); + assert.throws(() => { + isAssert.all([is.string, is.number]); }); - t.notThrows(() => { - assert.all([is.string, is.nonEmptyString], '🦄', 'unicorns'); + assert.doesNotThrow(() => { + isAssert.all([is.string, is.nonEmptyString], '🦄', 'unicorns'); }); - t.throws(() => { - assert.all(is.string, '🦄', []); + assert.throws(() => { + isAssert.all(is.string, '🦄', []); }); - t.throws(() => { - assert.all([is.string, is.number], '🦄'); + assert.throws(() => { + isAssert.all([is.string, is.number], '🦄'); }); - t.throws(() => { - assert.all(is.set, new Map(), {}); + assert.throws(() => { + isAssert.all(is.set, new Map(), {}); }); - t.throws(() => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - assert.all(null as any, true); + assert.throws(() => { + isAssert.all(null as any, true); }); - t.throws(() => { - assert.all([], 'value'); + assert.throws(() => { + isAssert.all([], 'value'); }); - t.throws(() => { - assert.all(is.string); + assert.throws(() => { + isAssert.all(is.string); }); - t.throws(() => { - assert.all(is.string, 1, 2, 3); + assert.throws(() => { + isAssert.all(is.string, 1, 2, 3); }, { // Includes expected type and removes duplicates from received types: - message: /Expected values which are `string`. Received values of type `number`./, + // eslint-disable-next-line prefer-regex-literals + message: new RegExp('Expected values which are `string`. Received values of type `number`.', 'v'), }); - t.throws(() => { - assert.all(is.string, 1, [4]); + assert.throws(() => { + isAssert.all(is.string, 1, [4]); }, { // Includes expected type and lists all received types: - message: /Expected values which are `string`. Received values of types `number` and `Array`./, + // eslint-disable-next-line prefer-regex-literals + message: new RegExp('Expected values which are `string`. Received values of types `number` and `Array`.', 'v'), }); - t.throws(() => { - assert.all(() => false, 1); + assert.throws(() => { + isAssert.all(() => false, 1); }, { // Default type assertion message - message: /Expected values which are `predicate returns truthy for all values`./, + // eslint-disable-next-line prefer-regex-literals + message: new RegExp('Expected values which are `predicate returns truthy for all values`.', 'v'), }); }); -test('is.any as predicate factory', t => { +test('is.any as predicate factory', () => { // Returns a type guard function when called with only predicates const isStringOrNumber = is.any([is.string, is.number]); - t.is(typeof isStringOrNumber, 'function'); - t.true(isStringOrNumber('hello')); - t.true(isStringOrNumber(123)); - t.false(isStringOrNumber(true)); - t.false(isStringOrNumber({})); + assert.strictEqual(typeof isStringOrNumber, 'function'); + assert.ok(isStringOrNumber('hello')); + assert.ok(isStringOrNumber(123)); + assert.strictEqual(isStringOrNumber(true), false); + assert.strictEqual(isStringOrNumber({}), false); - // Type narrowing works correctly + // Type narrowing works correctly (compile-time check) const value: unknown = 'test'; if (isStringOrNumber(value)) { // TypeScript should narrow to string | number const narrowed: string | number = value; - t.pass(`narrowed to: ${typeof narrowed}`); + assert.ok(typeof narrowed === 'string' || typeof narrowed === 'number'); } // Works with is.optional - t.true(is.optional(undefined, is.any([is.string, is.number]))); - t.true(is.optional('test', is.any([is.string, is.number]))); - t.true(is.optional(42, is.any([is.string, is.number]))); - t.false(is.optional(true, is.any([is.string, is.number]))); + assert.ok(is.optional(undefined, is.any([is.string, is.number]))); + assert.ok(is.optional('test', is.any([is.string, is.number]))); + assert.ok(is.optional(42, is.any([is.string, is.number]))); + assert.strictEqual(is.optional(true, is.any([is.string, is.number])), false); const predicateArray: Predicate[] = [is.string, is.number]; const isStringOrNumberFromArray = is.any(predicateArray); - t.is(typeof isStringOrNumberFromArray, 'function'); - t.true(isStringOrNumberFromArray('hello')); - t.true(isStringOrNumberFromArray(123)); - t.false(isStringOrNumberFromArray(true)); + assert.strictEqual(typeof isStringOrNumberFromArray, 'function'); + assert.ok(isStringOrNumberFromArray('hello')); + assert.ok(isStringOrNumberFromArray(123)); + assert.strictEqual(isStringOrNumberFromArray(true), false); - // Type narrowing with is.optional + // Type narrowing with is.optional (compile-time check) const optionalValue: unknown = undefined; if (is.optional(optionalValue, is.any([is.string, is.number]))) { // TypeScript should narrow to string | number | undefined const narrowed: string | number | undefined = optionalValue; - t.pass(`optional narrowed to: ${typeof narrowed}`); + assert.ok(narrowed === undefined || typeof narrowed === 'string' || typeof narrowed === 'number'); } // Works with more predicates const isStringOrNumberOrBoolean = is.any([is.string, is.number, is.boolean]); - t.true(isStringOrNumberOrBoolean('hello')); - t.true(isStringOrNumberOrBoolean(123)); - t.true(isStringOrNumberOrBoolean(true)); - t.false(isStringOrNumberOrBoolean({})); + assert.ok(isStringOrNumberOrBoolean('hello')); + assert.ok(isStringOrNumberOrBoolean(123)); + assert.ok(isStringOrNumberOrBoolean(true)); + assert.strictEqual(isStringOrNumberOrBoolean({}), false); - t.throws(() => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + assert.throws(() => { is.any([is.string, 123 as any]); }); }); -test('is.all as predicate factory', t => { +test('is.all as predicate factory', () => { // Returns a type guard function when called with only predicates const isArrayAndNonEmpty = is.all([is.array, is.nonEmptyArray]); - t.is(typeof isArrayAndNonEmpty, 'function'); - t.true(isArrayAndNonEmpty(['hello'])); - t.false(isArrayAndNonEmpty([])); - t.false(isArrayAndNonEmpty('hello')); + assert.strictEqual(typeof isArrayAndNonEmpty, 'function'); + assert.ok(isArrayAndNonEmpty(['hello'])); + assert.strictEqual(isArrayAndNonEmpty([]), false); + assert.strictEqual(isArrayAndNonEmpty('hello'), false); // Type narrowing works correctly const value: unknown = ['test']; if (isArrayAndNonEmpty(value)) { // TypeScript should narrow to the intersection type - t.true(Array.isArray(value)); - t.true(value.length > 0); + assert.ok(Array.isArray(value)); + assert.ok(value.length > 0); } // Works with is.optional - t.true(is.optional(undefined, is.all([is.object, is.plainObject]))); - t.true(is.optional({foo: 'bar'}, is.all([is.object, is.plainObject]))); - t.false(is.optional([], is.all([is.object, is.plainObject]))); + assert.ok(is.optional(undefined, is.all([is.object, is.plainObject]))); + assert.ok(is.optional({foo: 'bar'}, is.all([is.object, is.plainObject]))); + assert.strictEqual(is.optional([], is.all([is.object, is.plainObject])), false); - t.throws(() => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + assert.throws(() => { is.all([is.string, 123 as any]); }); }); -test('is.formData supplemental', t => { +test('is.formData supplemental', () => { const data = new window.FormData(); - t.true(is.formData(data)); - t.false(is.formData({})); - t.false(is.formData(undefined)); - t.false(is.formData(null)); + assert.ok(is.formData(data)); + assert.strictEqual(is.formData({}), false); + assert.strictEqual(is.formData(undefined), false); + assert.strictEqual(is.formData(null), false); - t.notThrows(() => { - assert.formData(data); + assert.doesNotThrow(() => { + isAssert.formData(data); }); - t.throws(() => { - assert.formData({}); + assert.throws(() => { + isAssert.formData({}); }); - t.throws(() => { - assert.formData(undefined); + assert.throws(() => { + isAssert.formData(undefined); }); - t.throws(() => { - assert.formData(null); + assert.throws(() => { + isAssert.formData(null); }); }); -test('is.urlSearchParams', t => { +test('is.urlSearchParams', () => { const searchParameters = new URLSearchParams(); - t.true(is.urlSearchParams(searchParameters)); - t.false(is.urlSearchParams({})); - t.false(is.urlSearchParams(undefined)); - t.false(is.urlSearchParams(null)); + assert.ok(is.urlSearchParams(searchParameters)); + assert.strictEqual(is.urlSearchParams({}), false); + assert.strictEqual(is.urlSearchParams(undefined), false); + assert.strictEqual(is.urlSearchParams(null), false); - t.notThrows(() => { - assert.urlSearchParams(searchParameters); + assert.doesNotThrow(() => { + isAssert.urlSearchParams(searchParameters); }); - t.throws(() => { - assert.urlSearchParams({}); + assert.throws(() => { + isAssert.urlSearchParams({}); }); - t.throws(() => { - assert.urlSearchParams(undefined); + assert.throws(() => { + isAssert.urlSearchParams(undefined); }); - t.throws(() => { - assert.urlSearchParams(null); + assert.throws(() => { + isAssert.urlSearchParams(null); }); }); -test('is.validDate', t => { - t.true(is.validDate(new Date())); - t.false(is.validDate(new Date('x'))); - t.notThrows(() => { - assert.validDate(new Date()); +test('is.validDate', () => { + assert.ok(is.validDate(new Date())); + assert.strictEqual(is.validDate(new Date('x')), false); + assert.doesNotThrow(() => { + isAssert.validDate(new Date()); }); - t.throws(() => { - assert.validDate(new Date('x')); + assert.throws(() => { + isAssert.validDate(new Date('x')); }); }); -test('is.validLength', t => { - t.true(is.validLength(1)); - t.true(is.validLength(0)); - t.false(is.validLength(-1)); - t.false(is.validLength(0.1)); - t.notThrows(() => { - assert.validLength(1); +test('is.validLength', () => { + assert.ok(is.validLength(1)); + assert.ok(is.validLength(0)); + assert.strictEqual(is.validLength(-1), false); + assert.strictEqual(is.validLength(0.1), false); + assert.doesNotThrow(() => { + isAssert.validLength(1); }); - t.throws(() => { - assert.validLength(-1); + assert.throws(() => { + isAssert.validLength(-1); + }); + assert.throws(() => { + isAssert.validLength(0.1); }); }); -test('is.whitespaceString', t => { - t.true(is.whitespaceString(' ')); - t.true(is.whitespaceString(' ')); - t.true(is.whitespaceString('   ')); - t.true(is.whitespaceString('\u3000')); - t.true(is.whitespaceString(' ')); - t.false(is.whitespaceString('')); - t.false(is.whitespaceString('-')); - t.false(is.whitespaceString(' hi ')); +test('is.whitespaceString', () => { + assert.ok(is.whitespaceString(' ')); + assert.ok(is.whitespaceString(' ')); + assert.ok(is.whitespaceString('   ')); + assert.ok(is.whitespaceString('\u3000')); + assert.ok(is.whitespaceString(' ')); + assert.strictEqual(is.whitespaceString(''), false); + assert.strictEqual(is.whitespaceString('-'), false); + assert.strictEqual(is.whitespaceString(' hi '), false); + + assert.doesNotThrow(() => { + isAssert.whitespaceString(' '); + }); + assert.throws(() => { + isAssert.whitespaceString(''); + }); + assert.throws(() => { + isAssert.whitespaceString(' hi '); + }); }); -test('assert', t => { - // Contrived test showing that TypeScript acknowledges the type assertion in `assert.number()`. - // Real--world usage includes asserting user input, but here we use a random number/string generator. - t.plan(2); +test('assert', () => { + // Contrived test showing that TypeScript acknowledges the type assertion in `isAssert.number()`. + // Real-world usage includes asserting user input, but here we use a random number/string generator. const getNumberOrStringRandomly = (): number | string => { const random = Math.random(); @@ -1990,7 +1988,7 @@ test('assert', t => { const canUseOnlyNumber = (badlyTypedArgument: any): number => { // Narrow the type to number, or throw an error at runtime for non-numbers. - assert.number(badlyTypedArgument); + isAssert.number(badlyTypedArgument); // Both the type and runtime value is number. return 1000 * badlyTypedArgument; @@ -1998,398 +1996,398 @@ test('assert', t => { const badlyTypedVariable: any = getNumberOrStringRandomly(); - t.true(is.number(badlyTypedVariable) || is.string(badlyTypedVariable)); + assert.ok(is.number(badlyTypedVariable) || is.string(badlyTypedVariable)); // Using try/catch for test purposes only. try { const result = canUseOnlyNumber(badlyTypedVariable); // Got lucky, the input was a number yielding a good result. - t.true(is.number(result)); + assert.ok(is.number(result)); } catch { // Assertion was tripped. - t.true(is.string(badlyTypedVariable)); + assert.ok(is.string(badlyTypedVariable)); } }); -test('custom assertion message', t => { +test('custom assertion message', () => { const message = 'Custom error message'; - t.throws(() => { - assert.array(undefined, undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.array(undefined, undefined, message); + }, {message}); - t.throws(() => { - assert.arrayBuffer(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.arrayBuffer(undefined, message); + }, {message}); - t.throws(() => { - assert.arrayLike(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.arrayLike(undefined, message); + }, {message}); - t.throws(() => { - assert.asyncFunction(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.asyncFunction(undefined, message); + }, {message}); - t.throws(() => { - assert.asyncGenerator(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.asyncGenerator(undefined, message); + }, {message}); - t.throws(() => { - assert.asyncGeneratorFunction(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.asyncGeneratorFunction(undefined, message); + }, {message}); - t.throws(() => { - assert.asyncIterable(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.asyncIterable(undefined, message); + }, {message}); - t.throws(() => { - assert.bigInt64Array(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.bigInt64Array(undefined, message); + }, {message}); - t.throws(() => { - assert.bigUint64Array(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.bigUint64Array(undefined, message); + }, {message}); - t.throws(() => { - assert.bigint(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.bigint(undefined, message); + }, {message}); - t.throws(() => { - assert.blob(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.blob(undefined, message); + }, {message}); - t.throws(() => { - assert.boolean(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.boolean(undefined, message); + }, {message}); - t.throws(() => { - assert.boundFunction(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.boundFunction(undefined, message); + }, {message}); - t.throws(() => { - assert.buffer(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.buffer(undefined, message); + }, {message}); - t.throws(() => { - assert.class(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.class(undefined, message); + }, {message}); - t.throws(() => { - assert.dataView(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.dataView(undefined, message); + }, {message}); - t.throws(() => { - assert.date(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.date(undefined, message); + }, {message}); - t.throws(() => { - assert.directInstanceOf(undefined, Error, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.directInstanceOf(undefined, Error, message); + }, {message}); - t.throws(() => { - assert.emptyArray(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.emptyArray(undefined, message); + }, {message}); - t.throws(() => { - assert.emptyMap(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.emptyMap(undefined, message); + }, {message}); - t.throws(() => { - assert.emptyObject(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.emptyObject(undefined, message); + }, {message}); - t.throws(() => { - assert.emptySet(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.emptySet(undefined, message); + }, {message}); - t.throws(() => { - assert.emptyString(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.emptyString(undefined, message); + }, {message}); - t.throws(() => { - assert.emptyStringOrWhitespace(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.emptyStringOrWhitespace(undefined, message); + }, {message}); - t.throws(() => { + assert.throws(() => { enum Enum {} - assert.enumCase('invalid', Enum, message); - }, {instanceOf: TypeError, message}); + isAssert.enumCase('invalid', Enum, message); + }, {message}); - t.throws(() => { - assert.error(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.error(undefined, message); + }, {message}); - t.throws(() => { - assert.evenInteger(33, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.evenInteger(33, message); + }, {message}); - t.throws(() => { - assert.falsy(true, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.falsy(true, message); + }, {message}); - t.throws(() => { - assert.float32Array(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.float32Array(undefined, message); + }, {message}); - t.throws(() => { - assert.float64Array(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.float64Array(undefined, message); + }, {message}); - t.throws(() => { - assert.formData(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.formData(undefined, message); + }, {message}); - t.throws(() => { - assert.function(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.function(undefined, message); + }, {message}); - t.throws(() => { - assert.generator(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.generator(undefined, message); + }, {message}); - t.throws(() => { - assert.generatorFunction(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.generatorFunction(undefined, message); + }, {message}); - t.throws(() => { - assert.htmlElement(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.htmlElement(undefined, message); + }, {message}); - t.throws(() => { - assert.inRange(5, [1, 2], message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.inRange(5, [1, 2], message); + }, {message}); - t.throws(() => { - assert.infinite(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.infinite(undefined, message); + }, {message}); - t.throws(() => { - assert.int16Array(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.int16Array(undefined, message); + }, {message}); - t.throws(() => { - assert.int32Array(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.int32Array(undefined, message); + }, {message}); - t.throws(() => { - assert.int8Array(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.int8Array(undefined, message); + }, {message}); - t.throws(() => { - assert.integer(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.integer(undefined, message); + }, {message}); - t.throws(() => { - assert.iterable(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.iterable(undefined, message); + }, {message}); - t.throws(() => { - assert.map(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.map(undefined, message); + }, {message}); - t.throws(() => { - assert.nan(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.nan(undefined, message); + }, {message}); - t.throws(() => { - assert.nativePromise(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.nativePromise(undefined, message); + }, {message}); - t.throws(() => { - assert.negativeNumber(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.negativeNumber(undefined, message); + }, {message}); - t.throws(() => { - assert.nodeStream(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.nodeStream(undefined, message); + }, {message}); - t.throws(() => { - assert.nonEmptyArray(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.nonEmptyArray(undefined, message); + }, {message}); - t.throws(() => { - assert.nonEmptyMap(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.nonEmptyMap(undefined, message); + }, {message}); - t.throws(() => { - assert.nonEmptyObject(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.nonEmptyObject(undefined, message); + }, {message}); - t.throws(() => { - assert.nonEmptySet(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.nonEmptySet(undefined, message); + }, {message}); - t.throws(() => { - assert.nonEmptyString(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.nonEmptyString(undefined, message); + }, {message}); - t.throws(() => { - assert.nonEmptyStringAndNotWhitespace(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.nonEmptyStringAndNotWhitespace(undefined, message); + }, {message}); - t.throws(() => { - assert.null(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.null(undefined, message); + }, {message}); - t.throws(() => { - assert.nullOrUndefined(false, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.nullOrUndefined(false, message); + }, {message}); - t.throws(() => { - assert.number(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.number(undefined, message); + }, {message}); - t.throws(() => { - assert.numericString(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.numericString(undefined, message); + }, {message}); - t.throws(() => { - assert.object(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.object(undefined, message); + }, {message}); - t.throws(() => { - assert.observable(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.observable(undefined, message); + }, {message}); - t.throws(() => { - assert.oddInteger(42, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.oddInteger(42, message); + }, {message}); - t.throws(() => { - assert.plainObject(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.plainObject(undefined, message); + }, {message}); - t.throws(() => { - assert.positiveNumber(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.positiveNumber(undefined, message); + }, {message}); - t.throws(() => { - assert.primitive([], message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.primitive([], message); + }, {message}); - t.throws(() => { - assert.promise(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.promise(undefined, message); + }, {message}); - t.throws(() => { - assert.propertyKey(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.propertyKey(undefined, message); + }, {message}); - t.throws(() => { - assert.regExp(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.regExp(undefined, message); + }, {message}); - t.throws(() => { - assert.safeInteger(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.safeInteger(undefined, message); + }, {message}); - t.throws(() => { - assert.set(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.set(undefined, message); + }, {message}); - t.throws(() => { - assert.sharedArrayBuffer(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.sharedArrayBuffer(undefined, message); + }, {message}); - t.throws(() => { - assert.string(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.string(undefined, message); + }, {message}); - t.throws(() => { - assert.symbol(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.symbol(undefined, message); + }, {message}); - t.throws(() => { - assert.truthy(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.truthy(undefined, message); + }, {message}); - t.throws(() => { - assert.tupleLike(undefined, [], message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.tupleLike(undefined, [], message); + }, {message}); - t.throws(() => { - assert.typedArray(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.typedArray(undefined, message); + }, {message}); - t.throws(() => { - assert.uint16Array(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.uint16Array(undefined, message); + }, {message}); - t.throws(() => { - assert.uint32Array(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.uint32Array(undefined, message); + }, {message}); - t.throws(() => { - assert.uint8Array(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.uint8Array(undefined, message); + }, {message}); - t.throws(() => { - assert.uint8ClampedArray(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.uint8ClampedArray(undefined, message); + }, {message}); - t.throws(() => { - assert.undefined(false, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.undefined(false, message); + }, {message}); - t.throws(() => { - assert.urlInstance(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.urlInstance(undefined, message); + }, {message}); - t.throws(() => { - assert.urlSearchParams(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.urlSearchParams(undefined, message); + }, {message}); - t.throws(() => { - assert.urlString(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.urlString(undefined, message); + }, {message}); - t.throws(() => { - assert.validDate(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.validDate(undefined, message); + }, {message}); - t.throws(() => { - assert.validLength(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.validLength(undefined, message); + }, {message}); - t.throws(() => { - assert.weakMap(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.weakMap(undefined, message); + }, {message}); - t.throws(() => { - assert.weakRef(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.weakRef(undefined, message); + }, {message}); - t.throws(() => { - assert.weakSet(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.weakSet(undefined, message); + }, {message}); - t.throws(() => { - assert.whitespaceString(undefined, message); - }, {instanceOf: TypeError, message}); + assert.throws(() => { + isAssert.whitespaceString(undefined, message); + }, {message}); }); -test('is.optional', t => { - t.true(is.optional(undefined, is.string)); - t.true(is.optional('🦄', is.string)); - t.false(is.optional(123, is.string)); - t.false(is.optional(null, is.string)); +test('is.optional', () => { + assert.ok(is.optional(undefined, is.string)); + assert.ok(is.optional('🦄', is.string)); + assert.strictEqual(is.optional(123, is.string), false); + assert.strictEqual(is.optional(null, is.string), false); }); -test('assert.optional', t => { - t.notThrows(() => { - assert.optional(undefined, assert.string); +test('isAssert.optional', () => { + assert.doesNotThrow(() => { + isAssert.optional(undefined, isAssert.string); }); - t.notThrows(() => { - assert.optional('🦄', assert.string); + assert.doesNotThrow(() => { + isAssert.optional('🦄', isAssert.string); }); - t.throws(() => { - assert.optional(123, assert.string); + assert.throws(() => { + isAssert.optional(123, isAssert.string); }); - t.throws(() => { - assert.optional(null, assert.string); + assert.throws(() => { + isAssert.optional(null, isAssert.string); }); }); diff --git a/tsconfig.json b/tsconfig.json index 0aace6f..46013a6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,11 @@ { "extends": "@sindresorhus/tsconfig", + "compilerOptions": { + "types": ["node"], + "rootDir": "source", + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true + }, "include": [ "source" ], From 3b40955b02fc9a55e55904e3e9bb82ef07c72442 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 8 Apr 2026 04:56:05 +0700 Subject: [PATCH 244/254] Fix handling of functions and arrays in `isEmptyObject` and `isNonEmptyObject` --- source/index.ts | 6 +++--- test/test.ts | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/source/index.ts b/source/index.ts index ee66861..8a510dd 100644 --- a/source/index.ts +++ b/source/index.ts @@ -510,7 +510,7 @@ export function isEmptyMap(value: unknown): value is Map { } export function isEmptyObject(value: unknown): value is Record { - return isObject(value) && !isMap(value) && !isSet(value) && Object.keys(value).length === 0; + return isObject(value) && !isFunction(value) && !isArray(value) && !isMap(value) && !isSet(value) && Object.keys(value).length === 0; } export function isEmptySet(value: unknown): value is Set { @@ -656,7 +656,7 @@ export function isNonEmptyMap(value: unknown): v // TODO: Use `not` operator here to remove `Map` and `Set` from type guard: // - https://github.com/Microsoft/TypeScript/pull/29317 export function isNonEmptyObject(value: unknown): value is Record { - return isObject(value) && !isMap(value) && !isSet(value) && Object.keys(value).length > 0; + return isObject(value) && !isFunction(value) && !isArray(value) && !isMap(value) && !isSet(value) && Object.keys(value).length > 0; } export function isNonEmptySet(value: unknown): value is Set { @@ -1636,7 +1636,7 @@ export function assertPromise(value: unknown, message?: string): as } } -export function assertPropertyKey(value: unknown, message?: string): asserts value is number { +export function assertPropertyKey(value: unknown, message?: string): asserts value is PropertyKey { if (!isPropertyKey(value)) { throw new TypeError(message ?? typeErrorMessage('PropertyKey', value)); } diff --git a/test/test.ts b/test/test.ts index 662e644..c789fdb 100644 --- a/test/test.ts +++ b/test/test.ts @@ -13,6 +13,7 @@ import {expectTypeOf} from 'expect-type'; import ZenObservable from 'zen-observable'; import is, { assert as isAssert, + assertPropertyKey, type AssertionTypeDescription, type Predicate, type Primitive, @@ -1552,6 +1553,11 @@ test('is.emptyObject', () => { assert.ok(is.emptyObject({})); assert.ok(is.emptyObject(new Object())); // eslint-disable-line no-object-constructor assert.strictEqual(is.emptyObject({unicorn: '🦄'}), false); + assert.strictEqual(is.emptyObject(function () {}), false); // eslint-disable-line prefer-arrow-callback + assert.strictEqual(is.emptyObject(() => {}), false); + assert.strictEqual(is.emptyObject(class Foo {}), false); // eslint-disable-line @typescript-eslint/no-extraneous-class + assert.strictEqual(is.emptyObject([]), false); + assert.strictEqual(is.emptyObject(['unicorn']), false); assert.doesNotThrow(() => { isAssert.emptyObject({}); @@ -1562,6 +1568,9 @@ test('is.emptyObject', () => { assert.throws(() => { isAssert.emptyObject({unicorn: '🦄'}); }); + assert.throws(() => { + isAssert.emptyObject(function () {}); // eslint-disable-line prefer-arrow-callback + }); }); test('is.nonEmptyObject', () => { @@ -1572,6 +1581,13 @@ test('is.nonEmptyObject', () => { assert.strictEqual(is.nonEmptyObject(new Object()), false); // eslint-disable-line no-object-constructor assert.ok(is.nonEmptyObject({unicorn: '🦄'})); + assert.strictEqual(is.nonEmptyObject([]), false); + assert.strictEqual(is.nonEmptyObject(['unicorn']), false); + + const functionWithProperty = function () {}; + (functionWithProperty as any).custom = 'value'; + assert.strictEqual(is.nonEmptyObject(functionWithProperty), false); + assert.throws(() => { isAssert.nonEmptyObject({}); }); @@ -1623,6 +1639,11 @@ test('is.propertyKey', () => { assert.strictEqual(is.propertyKey([]), false); assert.strictEqual(is.propertyKey(new Map()), false); assert.strictEqual(is.propertyKey(new Set()), false); + + // AssertPropertyKey should narrow to PropertyKey (string | number | symbol), not just number + const symbolValue: unknown = Symbol('test'); + assertPropertyKey(symbolValue); + expectTypeOf(symbolValue).toEqualTypeOf(); }); test('is.any', () => { From 47415dc46aaa27ad491393af87978e873cb9fbb1 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 8 Apr 2026 05:21:23 +0700 Subject: [PATCH 245/254] Fix `isNumericString` incorrectly accepting strings with surrounding whitespace --- source/index.ts | 2 +- test/test.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/source/index.ts b/source/index.ts index 8a510dd..3131d02 100644 --- a/source/index.ts +++ b/source/index.ts @@ -688,7 +688,7 @@ export function isNumber(value: unknown): value is number { } export function isNumericString(value: unknown): value is `${number}` { - return isString(value) && !isEmptyStringOrWhitespace(value) && !Number.isNaN(Number(value)); + return isString(value) && !isEmptyStringOrWhitespace(value) && value === value.trim() && !Number.isNaN(Number(value)); } // eslint-disable-next-line @typescript-eslint/no-restricted-types diff --git a/test/test.ts b/test/test.ts index c789fdb..31e661b 100644 --- a/test/test.ts +++ b/test/test.ts @@ -587,6 +587,10 @@ test('is.numericString supplemental', () => { assert.strictEqual(is.numericString(' '), false); assert.strictEqual(is.numericString(' \t\t\n'), false); assert.strictEqual(is.numericString(1), false); + assert.strictEqual(is.numericString(' 5'), false); + assert.strictEqual(is.numericString('5 '), false); + assert.strictEqual(is.numericString(' 5 '), false); + assert.strictEqual(is.numericString('\t3'), false); assert.throws(() => { isAssert.numericString(''); }); From ac46b5400d7ae00fcc001467ddfda5f9624c4ce8 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 8 Apr 2026 14:54:37 +0700 Subject: [PATCH 246/254] Fix `isInRange` silently returning false when range contains `NaN` --- source/index.ts | 4 ++++ test/test.ts | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/source/index.ts b/source/index.ts index 3131d02..9bfe2e6 100644 --- a/source/index.ts +++ b/source/index.ts @@ -599,6 +599,10 @@ export function isInRange(value: number, range: number | [number, number]): valu } if (isArray(range) && range.length === 2) { + if (Number.isNaN(range[0]) || Number.isNaN(range[1])) { + throw new TypeError(`Invalid range: ${JSON.stringify(range)}`); + } + return value >= Math.min(...range) && value <= Math.max(...range); } diff --git a/test/test.ts b/test/test.ts index 31e661b..750858e 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1274,6 +1274,14 @@ test('is.inRange', () => { is.inRange(0, [1, 2, 3]); }); + assert.throws(() => { + is.inRange(5, [NaN, 10]); + }, TypeError); + + assert.throws(() => { + is.inRange(5, [0, NaN]); + }, TypeError); + assert.doesNotThrow(() => { isAssert.inRange(x, [0, 5]); }); From 63be5c0c1945c863c49341507ef52c29d40cbef9 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 8 Apr 2026 16:12:51 +0700 Subject: [PATCH 247/254] Add `finiteNumber`, `nonNegativeNumber`, and `positiveInteger` predicates --- readme.md | 12 +++++++ source/index.ts | 42 ++++++++++++++++++++++ test/test.ts | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+) diff --git a/readme.md b/readme.md index 6965c9b..5f82bbf 100644 --- a/readme.md +++ b/readme.md @@ -463,6 +463,10 @@ function foo() { foo(); ``` +##### .finiteNumber(value) + +Check if `value` is a number and is finite. Excludes `Infinity` and `-Infinity`. + ##### .positiveNumber(value) Check if `value` is a number and is more than 0. @@ -471,6 +475,14 @@ Check if `value` is a number and is more than 0. Check if `value` is a number and is less than 0. +##### .nonNegativeNumber(value) + +Check if `value` is a number and is 0 or more. + +##### .positiveInteger(value) + +Check if `value` is an integer and is more than 0. + ##### .inRange(value, range) Check if `value` (number) is in the given `range`. The range is an array of two values, lower bound and upper bound, in no specific order. diff --git a/source/index.ts b/source/index.ts index 9bfe2e6..5f563a5 100644 --- a/source/index.ts +++ b/source/index.ts @@ -266,6 +266,7 @@ const is = Object.assign( error: isError, evenInteger: isEvenInteger, falsy: isFalsy, + finiteNumber: isFiniteNumber, float32Array: isFloat32Array, float64Array: isFloat64Array, formData: isFormData, @@ -291,6 +292,7 @@ const is = Object.assign( nonEmptySet: isNonEmptySet, nonEmptyString: isNonEmptyString, nonEmptyStringAndNotWhitespace: isNonEmptyStringAndNotWhitespace, + nonNegativeNumber: isNonNegativeNumber, null: isNull, nullOrUndefined: isNullOrUndefined, number: isNumber, @@ -299,6 +301,7 @@ const is = Object.assign( observable: isObservable, oddInteger: isOddInteger, plainObject: isPlainObject, + positiveInteger: isPositiveInteger, positiveNumber: isPositiveNumber, primitive: isPrimitive, promise: isPromise, @@ -544,6 +547,10 @@ export function isFalsy(value: unknown): value is Falsy { return !value; } +export function isFiniteNumber(value: unknown): value is number { + return Number.isFinite(value); +} + // TODO: Support detecting Float16Array when targeting Node.js 24. export function isFloat32Array(value: unknown): value is Float32Array { @@ -677,6 +684,10 @@ export function isNonEmptyStringAndNotWhitespace(value: unknown): value is NonEm return isString(value) && !isEmptyStringOrWhitespace(value); } +export function isNonNegativeNumber(value: unknown): value is number { + return isNumber(value) && value >= 0; +} + // eslint-disable-next-line @typescript-eslint/no-restricted-types export function isNull(value: unknown): value is null { return value === null; @@ -734,6 +745,10 @@ export function isPlainObject(value: unknown): value is Record< return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); } +export function isPositiveInteger(value: unknown): value is number { + return isInteger(value) && value > 0; +} + export function isPositiveNumber(value: unknown): value is number { return isNumber(value) && value > 0; } @@ -908,8 +923,11 @@ type Assert = { undefined: (value: unknown, message?: string) => asserts value is undefined; string: (value: unknown, message?: string) => asserts value is string; number: (value: unknown, message?: string) => asserts value is number; + finiteNumber: (value: unknown, message?: string) => asserts value is number; positiveNumber: (value: unknown, message?: string) => asserts value is number; negativeNumber: (value: unknown, message?: string) => asserts value is number; + nonNegativeNumber: (value: unknown, message?: string) => asserts value is number; + positiveInteger: (value: unknown, message?: string) => asserts value is number; bigint: (value: unknown, message?: string) => asserts value is bigint; // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type function: (value: unknown, message?: string) => asserts value is Function; @@ -1050,6 +1068,7 @@ export const assert: Assert = { error: assertError, evenInteger: assertEvenInteger, falsy: assertFalsy, + finiteNumber: assertFiniteNumber, float32Array: assertFloat32Array, float64Array: assertFloat64Array, formData: assertFormData, @@ -1075,6 +1094,7 @@ export const assert: Assert = { nonEmptySet: assertNonEmptySet, nonEmptyString: assertNonEmptyString, nonEmptyStringAndNotWhitespace: assertNonEmptyStringAndNotWhitespace, + nonNegativeNumber: assertNonNegativeNumber, null: assertNull, nullOrUndefined: assertNullOrUndefined, number: assertNumber, @@ -1083,6 +1103,7 @@ export const assert: Assert = { observable: assertObservable, oddInteger: assertOddInteger, plainObject: assertPlainObject, + positiveInteger: assertPositiveInteger, positiveNumber: assertPositiveNumber, primitive: assertPrimitive, promise: assertPromise, @@ -1141,6 +1162,7 @@ const methodTypeMap = { isError: 'Error', isEvenInteger: 'even integer', isFalsy: 'falsy', + isFiniteNumber: 'finite number', isFloat32Array: 'Float32Array', isFloat64Array: 'Float64Array', isFormData: 'FormData', @@ -1166,6 +1188,7 @@ const methodTypeMap = { isNonEmptySet: 'non-empty set', isNonEmptyString: 'non-empty string', isNonEmptyStringAndNotWhitespace: 'non-empty string and not whitespace', + isNonNegativeNumber: 'non-negative number', isNull: 'null', isNullOrUndefined: 'null or undefined', isNumber: 'number', @@ -1174,6 +1197,7 @@ const methodTypeMap = { isObservable: 'Observable', isOddInteger: 'odd integer', isPlainObject: 'plain object', + isPositiveInteger: 'positive integer', isPositiveNumber: 'positive number', isPrimitive: 'primitive', isPromise: 'Promise', @@ -1420,6 +1444,12 @@ export function assertFalsy(value: unknown, message?: string): asserts value is } } +export function assertFiniteNumber(value: unknown, message?: string): asserts value is number { + if (!isFiniteNumber(value)) { + throw new TypeError(message ?? typeErrorMessage('finite number', value)); + } +} + export function assertFloat32Array(value: unknown, message?: string): asserts value is Float32Array { if (!isFloat32Array(value)) { throw new TypeError(message ?? typeErrorMessage('Float32Array', value)); @@ -1571,6 +1601,12 @@ export function assertNonEmptyStringAndNotWhitespace(value: unknown, message?: s } } +export function assertNonNegativeNumber(value: unknown, message?: string): asserts value is number { + if (!isNonNegativeNumber(value)) { + throw new TypeError(message ?? typeErrorMessage('non-negative number', value)); + } +} + // eslint-disable-next-line @typescript-eslint/no-restricted-types export function assertNull(value: unknown, message?: string): asserts value is null { if (!isNull(value)) { @@ -1622,6 +1658,12 @@ export function assertPlainObject(value: unknown, message?: str } } +export function assertPositiveInteger(value: unknown, message?: string): asserts value is number { + if (!isPositiveInteger(value)) { + throw new TypeError(message ?? typeErrorMessage('positive integer', value)); + } +} + export function assertPositiveNumber(value: unknown, message?: string): asserts value is number { if (!isPositiveNumber(value)) { throw new TypeError(message ?? typeErrorMessage('positive number', value)); diff --git a/test/test.ts b/test/test.ts index 750858e..fce8b4d 100644 --- a/test/test.ts +++ b/test/test.ts @@ -544,6 +544,34 @@ test('is.positiveNumber', () => { }); }); +test('is.finiteNumber', () => { + assert.ok(is.finiteNumber(6)); + assert.ok(is.finiteNumber(-6)); + assert.ok(is.finiteNumber(0)); + assert.ok(is.finiteNumber(1.4)); + + assert.doesNotThrow(() => { + isAssert.finiteNumber(6); + }); + assert.doesNotThrow(() => { + isAssert.finiteNumber(0); + }); + + assert.strictEqual(is.finiteNumber(Number.POSITIVE_INFINITY), false); + assert.strictEqual(is.finiteNumber(Number.NEGATIVE_INFINITY), false); + assert.strictEqual(is.finiteNumber(Number.NaN), false); + + assert.throws(() => { + isAssert.finiteNumber(Number.POSITIVE_INFINITY); + }); + assert.throws(() => { + isAssert.finiteNumber(Number.NEGATIVE_INFINITY); + }); + assert.throws(() => { + isAssert.finiteNumber(Number.NaN); + }); +}); + test('is.negativeNumber', () => { assert.ok(is.negativeNumber(-6)); assert.ok(is.negativeNumber(-1.4)); @@ -582,6 +610,62 @@ test('is.negativeNumber', () => { }); }); +test('is.nonNegativeNumber', () => { + assert.ok(is.nonNegativeNumber(0)); + assert.ok(is.nonNegativeNumber(6)); + assert.ok(is.nonNegativeNumber(1.4)); + assert.ok(is.nonNegativeNumber(Number.POSITIVE_INFINITY)); + + assert.doesNotThrow(() => { + isAssert.nonNegativeNumber(0); + }); + assert.doesNotThrow(() => { + isAssert.nonNegativeNumber(6); + }); + + assert.ok(is.nonNegativeNumber(-0)); // -0 >= 0 is true in JavaScript + assert.strictEqual(is.nonNegativeNumber(-6), false); + assert.strictEqual(is.nonNegativeNumber(-1.4), false); + assert.strictEqual(is.nonNegativeNumber(Number.NEGATIVE_INFINITY), false); + assert.strictEqual(is.nonNegativeNumber(Number.NaN), false); + + assert.throws(() => { + isAssert.nonNegativeNumber(-6); + }); + assert.throws(() => { + isAssert.nonNegativeNumber(Number.NEGATIVE_INFINITY); + }); +}); + +test('is.positiveInteger', () => { + assert.ok(is.positiveInteger(1)); + assert.ok(is.positiveInteger(6)); + assert.ok(is.positiveInteger(100)); + + assert.doesNotThrow(() => { + isAssert.positiveInteger(1); + }); + assert.doesNotThrow(() => { + isAssert.positiveInteger(6); + }); + + assert.strictEqual(is.positiveInteger(0), false); + assert.strictEqual(is.positiveInteger(-1), false); + assert.strictEqual(is.positiveInteger(1.5), false); + assert.strictEqual(is.positiveInteger(Number.POSITIVE_INFINITY), false); + assert.strictEqual(is.positiveInteger(Number.NaN), false); + + assert.throws(() => { + isAssert.positiveInteger(0); + }); + assert.throws(() => { + isAssert.positiveInteger(-1); + }); + assert.throws(() => { + isAssert.positiveInteger(1.5); + }); +}); + test('is.numericString supplemental', () => { assert.strictEqual(is.numericString(''), false); assert.strictEqual(is.numericString(' '), false); @@ -2159,6 +2243,10 @@ test('custom assertion message', () => { isAssert.falsy(true, message); }, {message}); + assert.throws(() => { + isAssert.finiteNumber(Number.POSITIVE_INFINITY, message); + }, {message}); + assert.throws(() => { isAssert.float32Array(undefined, message); }, {message}); @@ -2259,6 +2347,10 @@ test('custom assertion message', () => { isAssert.nonEmptyStringAndNotWhitespace(undefined, message); }, {message}); + assert.throws(() => { + isAssert.nonNegativeNumber(-1, message); + }, {message}); + assert.throws(() => { isAssert.null(undefined, message); }, {message}); @@ -2291,6 +2383,10 @@ test('custom assertion message', () => { isAssert.plainObject(undefined, message); }, {message}); + assert.throws(() => { + isAssert.positiveInteger(0, message); + }, {message}); + assert.throws(() => { isAssert.positiveNumber(undefined, message); }, {message}); From 54fc09406a5e8edf44726fdea02ed4658d6b3db2 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 8 Apr 2026 18:58:23 +0700 Subject: [PATCH 248/254] Add `negativeInteger`, `nonNegativeInteger`, `arrayOf`, and `oneOf` predicates --- readme.md | 30 ++++++++++++++ source/index.ts | 43 ++++++++++++++++++++ test/test.ts | 102 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 173 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 5f82bbf..b2fb284 100644 --- a/readme.md +++ b/readme.md @@ -130,6 +130,17 @@ is.array(value); // Validate `value` is an array. is.array(value, is.number); // Validate `value` is an array and all of its items are numbers. ``` +##### .arrayOf(predicate) + +Returns a type guard that checks if `value` is an array where every item matches the predicate. Useful for composing with other methods. + +```js +const isStringArray = is.arrayOf(is.string); + +isStringArray(['a', 'b']); //=> true +isStringArray(['a', 1]); //=> false +``` + ##### .function(value) ##### .buffer(value) @@ -483,6 +494,14 @@ Check if `value` is a number and is 0 or more. Check if `value` is an integer and is more than 0. +##### .negativeInteger(value) + +Check if `value` is an integer and is less than 0. + +##### .nonNegativeInteger(value) + +Check if `value` is an integer and is 0 or more. + ##### .inRange(value, range) Check if `value` (number) is in the given `range`. The range is an array of two values, lower bound and upper bound, in no specific order. @@ -661,6 +680,17 @@ is.optional(123, is.string); //=> false ``` +##### .oneOf(values) + +Returns a type guard that checks if `value` is one of the given `values`. Best used with `as const` for precise type narrowing. + +```ts +const isDirection = is.oneOf(['north', 'south', 'east', 'west'] as const); + +isDirection('north'); //=> true +isDirection('up'); //=> false +``` + ##### .validDate(value) Returns `true` if the value is a valid date. diff --git a/source/index.ts b/source/index.ts index 5f563a5..1a9308d 100644 --- a/source/index.ts +++ b/source/index.ts @@ -133,7 +133,12 @@ const assertionTypeDescriptions = [ 'non-empty map', 'PropertyKey', 'even integer', + 'finite number', + 'negative integer', + 'non-negative integer', + 'non-negative number', 'odd integer', + 'positive integer', 'T', 'in range', 'predicate returns truthy for any value', @@ -240,6 +245,7 @@ const is = Object.assign( array: isArray, arrayBuffer: isArrayBuffer, arrayLike: isArrayLike, + arrayOf: isArrayOf, asyncFunction: isAsyncFunction, asyncGenerator: isAsyncGenerator, asyncGeneratorFunction: isAsyncGeneratorFunction, @@ -284,6 +290,7 @@ const is = Object.assign( map: isMap, nan: isNan, nativePromise: isNativePromise, + negativeInteger: isNegativeInteger, negativeNumber: isNegativeNumber, nodeStream: isNodeStream, nonEmptyArray: isNonEmptyArray, @@ -292,6 +299,7 @@ const is = Object.assign( nonEmptySet: isNonEmptySet, nonEmptyString: isNonEmptyString, nonEmptyStringAndNotWhitespace: isNonEmptyStringAndNotWhitespace, + nonNegativeInteger: isNonNegativeInteger, nonNegativeNumber: isNonNegativeNumber, null: isNull, nullOrUndefined: isNullOrUndefined, @@ -300,6 +308,7 @@ const is = Object.assign( object: isObject, observable: isObservable, oddInteger: isOddInteger, + oneOf: isOneOf, plainObject: isPlainObject, positiveInteger: isPositiveInteger, positiveNumber: isPositiveNumber, @@ -435,6 +444,10 @@ export function isArrayLike(value: unknown): value is ArrayLike return !isNullOrUndefined(value) && !isFunction(value) && isValidLength((value as ArrayLike).length); } +export function isArrayOf(predicate: (value: unknown) => value is T): (value: unknown) => value is T[] { + return (value: unknown): value is T[] => isArray(value) && value.every(element => predicate(element)); +} + export function isAsyncFunction(value: unknown): value is ((...arguments_: any[]) => Promise) { return getObjectType(value) === 'AsyncFunction'; } @@ -648,6 +661,10 @@ export function isNativePromise(value: unknown): value is Promise= 0; +} + export function isNonNegativeNumber(value: unknown): value is number { return isNumber(value) && value >= 0; } @@ -733,6 +754,10 @@ export function isOddInteger(value: unknown): value is number { return isAbsoluteModule2(1)(value); } +export function isOneOf(values: T): (value: unknown) => value is T[number] { + return (value: unknown): value is T[number] => values.includes(value as T[number]); +} + export function isPlainObject(value: unknown): value is Record { // From: https://github.com/sindresorhus/is-plain-obj/blob/main/index.js if (typeof value !== 'object' || value === null) { @@ -925,7 +950,9 @@ type Assert = { number: (value: unknown, message?: string) => asserts value is number; finiteNumber: (value: unknown, message?: string) => asserts value is number; positiveNumber: (value: unknown, message?: string) => asserts value is number; + negativeInteger: (value: unknown, message?: string) => asserts value is number; negativeNumber: (value: unknown, message?: string) => asserts value is number; + nonNegativeInteger: (value: unknown, message?: string) => asserts value is number; nonNegativeNumber: (value: unknown, message?: string) => asserts value is number; positiveInteger: (value: unknown, message?: string) => asserts value is number; bigint: (value: unknown, message?: string) => asserts value is bigint; @@ -1086,6 +1113,7 @@ export const assert: Assert = { map: assertMap, nan: assertNan, nativePromise: assertNativePromise, + negativeInteger: assertNegativeInteger, negativeNumber: assertNegativeNumber, nodeStream: assertNodeStream, nonEmptyArray: assertNonEmptyArray, @@ -1094,6 +1122,7 @@ export const assert: Assert = { nonEmptySet: assertNonEmptySet, nonEmptyString: assertNonEmptyString, nonEmptyStringAndNotWhitespace: assertNonEmptyStringAndNotWhitespace, + nonNegativeInteger: assertNonNegativeInteger, nonNegativeNumber: assertNonNegativeNumber, null: assertNull, nullOrUndefined: assertNullOrUndefined, @@ -1180,6 +1209,7 @@ const methodTypeMap = { isMap: 'Map', isNan: 'NaN', isNativePromise: 'native Promise', + isNegativeInteger: 'negative integer', isNegativeNumber: 'negative number', isNodeStream: 'Node.js Stream', isNonEmptyArray: 'non-empty array', @@ -1188,6 +1218,7 @@ const methodTypeMap = { isNonEmptySet: 'non-empty set', isNonEmptyString: 'non-empty string', isNonEmptyStringAndNotWhitespace: 'non-empty string and not whitespace', + isNonNegativeInteger: 'non-negative integer', isNonNegativeNumber: 'non-negative number', isNull: 'null', isNullOrUndefined: 'null or undefined', @@ -1553,6 +1584,12 @@ export function assertNativePromise(value: unknown, message?: strin } } +export function assertNegativeInteger(value: unknown, message?: string): asserts value is number { + if (!isNegativeInteger(value)) { + throw new TypeError(message ?? typeErrorMessage('negative integer', value)); + } +} + export function assertNegativeNumber(value: unknown, message?: string): asserts value is number { if (!isNegativeNumber(value)) { throw new TypeError(message ?? typeErrorMessage('negative number', value)); @@ -1601,6 +1638,12 @@ export function assertNonEmptyStringAndNotWhitespace(value: unknown, message?: s } } +export function assertNonNegativeInteger(value: unknown, message?: string): asserts value is number { + if (!isNonNegativeInteger(value)) { + throw new TypeError(message ?? typeErrorMessage('non-negative integer', value)); + } +} + export function assertNonNegativeNumber(value: unknown, message?: string): asserts value is number { if (!isNonNegativeNumber(value)) { throw new TypeError(message ?? typeErrorMessage('non-negative number', value)); diff --git a/test/test.ts b/test/test.ts index fce8b4d..11ab0df 100644 --- a/test/test.ts +++ b/test/test.ts @@ -666,6 +666,69 @@ test('is.positiveInteger', () => { }); }); +test('is.negativeInteger', () => { + assert.ok(is.negativeInteger(-1)); + assert.ok(is.negativeInteger(-6)); + assert.ok(is.negativeInteger(-100)); + + assert.doesNotThrow(() => { + isAssert.negativeInteger(-1); + }); + assert.doesNotThrow(() => { + isAssert.negativeInteger(-6); + }); + + assert.strictEqual(is.negativeInteger(0), false); + assert.strictEqual(is.negativeInteger(-0), false); // -0 < 0 is false in JavaScript + assert.strictEqual(is.negativeInteger(1), false); + assert.strictEqual(is.negativeInteger(-1.5), false); + assert.strictEqual(is.negativeInteger(Number.NEGATIVE_INFINITY), false); + assert.strictEqual(is.negativeInteger(Number.NaN), false); + + assert.throws(() => { + isAssert.negativeInteger(0); + }); + assert.throws(() => { + isAssert.negativeInteger(1); + }); + assert.throws(() => { + isAssert.negativeInteger(-1.5); + }); + assert.throws(() => { + isAssert.negativeInteger(Number.NEGATIVE_INFINITY); + }); +}); + +test('is.nonNegativeInteger', () => { + assert.ok(is.nonNegativeInteger(0)); + assert.ok(is.nonNegativeInteger(1)); + assert.ok(is.nonNegativeInteger(100)); + + assert.doesNotThrow(() => { + isAssert.nonNegativeInteger(0); + }); + assert.doesNotThrow(() => { + isAssert.nonNegativeInteger(1); + }); + + assert.ok(is.nonNegativeInteger(-0)); // -0 >= 0 is true in JavaScript + + assert.strictEqual(is.nonNegativeInteger(-1), false); + assert.strictEqual(is.nonNegativeInteger(1.5), false); + assert.strictEqual(is.nonNegativeInteger(Number.POSITIVE_INFINITY), false); + assert.strictEqual(is.nonNegativeInteger(Number.NaN), false); + + assert.throws(() => { + isAssert.nonNegativeInteger(-1); + }); + assert.throws(() => { + isAssert.nonNegativeInteger(1.5); + }); + assert.throws(() => { + isAssert.nonNegativeInteger(Number.POSITIVE_INFINITY); + }); +}); + test('is.numericString supplemental', () => { assert.strictEqual(is.numericString(''), false); assert.strictEqual(is.numericString(' '), false); @@ -713,6 +776,41 @@ test('is.array supplemental', () => { }, /Expected numbers/v); }); +test('is.arrayOf', () => { + const isStringArray = is.arrayOf(is.string); + assert.ok(isStringArray(['a', 'b', 'c'])); + assert.ok(isStringArray([])); + assert.strictEqual(isStringArray([1, 2, 3]), false); + assert.strictEqual(isStringArray(['a', 1]), false); + assert.strictEqual(isStringArray('not an array'), false); + assert.strictEqual(isStringArray(undefined), false); + + const isNumberArray = is.arrayOf(is.number); + assert.ok(isNumberArray([1, 2, 3])); + assert.strictEqual(isNumberArray([1, '2']), false); +}); + +test('is.oneOf', () => { + const isDirection = is.oneOf(['north', 'south', 'east', 'west'] as const); + assert.ok(isDirection('north')); + assert.ok(isDirection('west')); + assert.strictEqual(isDirection('up'), false); + assert.strictEqual(isDirection(1), false); + assert.strictEqual(isDirection(undefined), false); + + const isSmallNumber = is.oneOf([1, 2, 3] as const); + assert.ok(isSmallNumber(1)); + assert.strictEqual(isSmallNumber(4), false); + + // Empty values array always returns false + const isNever = is.oneOf([] as const); + assert.strictEqual(isNever('anything'), false); + + // Array.includes uses SameValueZero, so NaN matches NaN (unlike ===) + const isNanValue = is.oneOf([Number.NaN] as const); + assert.ok(isNanValue(Number.NaN)); +}); + test('is.boundFunction supplemental', () => { assert.strictEqual(is.boundFunction(function () {}), false); // eslint-disable-line prefer-arrow-callback @@ -1359,11 +1457,11 @@ test('is.inRange', () => { }); assert.throws(() => { - is.inRange(5, [NaN, 10]); + is.inRange(5, [Number.NaN, 10]); }, TypeError); assert.throws(() => { - is.inRange(5, [0, NaN]); + is.inRange(5, [0, Number.NaN]); }, TypeError); assert.doesNotThrow(() => { From cb4ee0e92cd3bff8353bb6738bd1f54db87dc293 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 8 Apr 2026 19:53:46 +0700 Subject: [PATCH 249/254] Fix `isEnumCase` incorrectly accepting numeric enum key names --- source/index.ts | 19 ++++++++++++++++--- test/test.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/source/index.ts b/source/index.ts index 1a9308d..6104501 100644 --- a/source/index.ts +++ b/source/index.ts @@ -542,8 +542,21 @@ export function isEmptyStringOrWhitespace(value: unknown): value is '' | Whitesp } export function isEnumCase(value: unknown, targetEnum: T): value is T[keyof T] { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - return Object.values(targetEnum as any).includes(value as string); + // Numeric enums have reverse mappings (e.g. `Direction[0] = "Up"`), so their runtime object contains both `{ Up: 0 }` and `{ "0": "Up" }`. Filtering out entries that round-trip like a canonical number and point back to an own property leaves only actual enum member values. + const enumObject = targetEnum as Record; + + return Object.entries(enumObject).some(([key, enumValue]) => { + if (!isString(enumValue)) { + return enumValue === value; + } + + const numericKey = Number(key); + if (Number.isNaN(numericKey) || String(numericKey) !== key) { + return enumValue === value; + } + + return enumValue === value && !(Object.hasOwn(enumObject, enumValue) && enumObject[enumValue] === numericKey); + }); } export function isError(value: unknown): value is Error { @@ -786,7 +799,7 @@ export function isPromise(value: unknown): value is Promise { return isNativePromise(value) || hasPromiseApi(value); } -// `PropertyKey` is any value that can be used as an object key (string, number, or symbol) +// `PropertyKey` is any value that can be used as an object key (string, number, or symbol). Note: NaN is technically `typeof 'number'` and thus fits TypeScript's `PropertyKey`, but we intentionally exclude it here because using NaN as a property key is almost always a mistake. export function isPropertyKey(value: unknown): value is PropertyKey { return isAny([isString, isNumber, isSymbol], value); } diff --git a/test/test.ts b/test/test.ts index 11ab0df..ce26b50 100644 --- a/test/test.ts +++ b/test/test.ts @@ -855,6 +855,12 @@ test('is.enumCase', () => { Key2 = 'key2', } + enum NumericKeyStringEnum { + // eslint-disable-next-line @stylistic/quote-props + '0' = 'zero', + '01' = 'padded', + } + assert.ok(is.enumCase('key1', NonNumericalEnum)); assert.doesNotThrow(() => { isAssert.enumCase('key1', NonNumericalEnum); @@ -864,6 +870,40 @@ test('is.enumCase', () => { assert.throws(() => { isAssert.enumCase('invalid', NonNumericalEnum); }); + + assert.ok(is.enumCase('zero', NumericKeyStringEnum)); + assert.ok(is.enumCase('padded', NumericKeyStringEnum)); + assert.doesNotThrow(() => { + isAssert.enumCase('zero', NumericKeyStringEnum); + }); + assert.doesNotThrow(() => { + isAssert.enumCase('padded', NumericKeyStringEnum); + }); + + enum NumericalEnum { + Key1 = 0, + Key2 = 1, + } + + assert.ok(is.enumCase(0, NumericalEnum)); + assert.ok(is.enumCase(1, NumericalEnum)); + assert.strictEqual(is.enumCase('Key1', NumericalEnum), false); + assert.strictEqual(is.enumCase('Key2', NumericalEnum), false); + assert.doesNotThrow(() => { + isAssert.enumCase(0, NumericalEnum); + }); + assert.throws(() => { + isAssert.enumCase('Key1', NumericalEnum); + }); + + enum HeterogeneousEnum { + A = 1, + B = 'hello', + } + + assert.ok(is.enumCase(1, HeterogeneousEnum)); + assert.ok(is.enumCase('hello', HeterogeneousEnum)); + assert.strictEqual(is.enumCase('A', HeterogeneousEnum), false); }); test('is.directInstanceOf', () => { From 13febb6b01e24863ced3847a7ee112a48c154e0e Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 9 Apr 2026 00:31:33 +0700 Subject: [PATCH 250/254] Fix some type guards --- AGENTS.md | 9 + CLAUDE.md | 1 + package.json | 2 +- source/index.ts | 116 +++++++++--- source/types.ts | 113 ++++++++++++ test/test.ts | 447 ++++++++++++++++++++++++++------------------- test/tsconfig.json | 12 ++ test/type-tests.ts | 220 ++++++++++++++++++++++ 8 files changed, 715 insertions(+), 205 deletions(-) create mode 100644 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 test/tsconfig.json create mode 100644 test/type-tests.ts diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2996cf5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,9 @@ +# Notes + +## Branded types for type guards + +TypeScript type guards narrow in both branches. If `is.integer(n)` returns `value is number` and the input is `number`, the false branch computes `Exclude` = `never`. This makes common patterns like `if (!is.integer(n)) throw; use(n)` fail because `n` becomes `never` after the guard. + +To avoid this, type guard predicates use branded types (e.g., `number & {readonly __brand: 'Integer'}`, `string & {readonly __brand: 'UrlString'}`). A branded subtype ensures the false branch stays the original type (e.g., `Exclude` = `number`). + +Assert functions (`asserts value is T`) don't need branded types since they throw on failure and have no false branch. They use plain types like `asserts value is number`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/package.json b/package.json index b5c7463..0222a80 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "scripts": { "build": "del distribution && tsc", - "test": "tsc --noEmit && xo && node --experimental-transform-types --test test/test.ts", + "test": "tsc --noEmit && tsc --project test/tsconfig.json --noEmit && xo && node --experimental-transform-types --test test/test.ts", "prepare": "npm run build" }, "files": [ diff --git a/source/index.ts b/source/index.ts index 6104501..eee1316 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,14 +1,29 @@ import type { ArrayLike, Class, + EvenInteger, Falsy, + FiniteNumber, + Integer, + NaN as NaNType, + NegativeInfinity, + NegativeInteger, + NegativeNumber, NodeStream, NonEmptyString, + NonNegativeInteger, + NonNegativeNumber, ObservableLike, + OddInteger, Predicate, Primitive, + PositiveInfinity, + PositiveInteger, + PositiveNumber, + SafeInteger, TypedArray, UrlString, + ValidLength, WeakRef, Whitespace, } from './types.ts'; @@ -22,6 +37,15 @@ type ExtractFromGlobalConstructors = type NodeBuffer = ExtractFromGlobalConstructors<'Buffer'>; +type NumericGuardResult = + ( + unknown extends Input + ? Branded + : Input extends number + ? Branded & Input + : number + ) & Input; + const typedArrayTypeNames = [ 'Int8Array', 'Uint8Array', @@ -99,6 +123,7 @@ function isPrimitiveTypeName(name: unknown): name is PrimitiveTypeName { export type TypeName = ObjectTypeName | PrimitiveTypeName; const assertionTypeDescriptions = [ + 'bound Function', 'positive number', 'negative number', 'Class', @@ -139,6 +164,7 @@ const assertionTypeDescriptions = [ 'non-negative number', 'odd integer', 'positive integer', + 'safe integer', 'T', 'in range', 'predicate returns truthy for any value', @@ -225,8 +251,7 @@ function detect(value: unknown): TypeName { return 'Promise'; } - const objectTag = Object.prototype.toString.call(value).slice(8, -1); - if (objectTag === 'String' || objectTag === 'Boolean' || objectTag === 'Number') { + if (isBoxedPrimitiveObject(value)) { throw new TypeError('Please don\'t use object wrappers for primitive types'); } @@ -237,6 +262,23 @@ function hasPromiseApi(value: unknown): value is Promise { return isFunction((value as Promise)?.then) && isFunction((value as Promise)?.catch); } +function hasBoxedPrimitiveBrand(value: unknown, valueOf: () => unknown): boolean { + try { + // `Object.prototype.toString` can be spoofed via `Symbol.toStringTag`, but the + // boxed primitive `valueOf` methods still enforce the real internal brand. + Reflect.apply(valueOf, value, []); + return true; + } catch { + return false; + } +} + +function isBoxedPrimitiveObject(value: unknown): boolean { + return hasBoxedPrimitiveBrand(value, String.prototype.valueOf) + || hasBoxedPrimitiveBrand(value, Boolean.prototype.valueOf) + || hasBoxedPrimitiveBrand(value, Number.prototype.valueOf); +} + const is = Object.assign( detect, { @@ -560,11 +602,13 @@ export function isEnumCase(value: unknown, targetEnum: T): value is } export function isError(value: unknown): value is Error { - // TODO: Use `Error.isError` when targeting Node.js 24.` + // TODO: Use `Error.isError` when targeting Node.js 24. return getObjectType(value) === 'Error'; } -export function isEvenInteger(value: unknown): value is number { +// For numeric guards, preserve branded narrowing for `unknown`, keep the false branch usable for plain `number`, and still narrow mixed unions to `number`. +export function isEvenInteger(value: Input): value is NumericGuardResult; +export function isEvenInteger(value: unknown): boolean { return isAbsoluteModule2(0)(value); } @@ -573,7 +617,8 @@ export function isFalsy(value: unknown): value is Falsy { return !value; } -export function isFiniteNumber(value: unknown): value is number { +export function isFiniteNumber(value: Input): value is NumericGuardResult; +export function isFiniteNumber(value: unknown): boolean { return Number.isFinite(value); } @@ -622,7 +667,8 @@ export function isHtmlElement(value: unknown): value is HTMLElement { && DOM_PROPERTIES_TO_CHECK.every(property => property in value); } -export function isInfinite(value: unknown): value is number { +export function isInfinite(value: Input): value is NumericGuardResult; +export function isInfinite(value: unknown): boolean { return value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY; } @@ -654,7 +700,8 @@ export function isInt8Array(value: unknown): value is Int8Array { return getObjectType(value) === 'Int8Array'; } -export function isInteger(value: unknown): value is number { +export function isInteger(value: Input): value is NumericGuardResult; +export function isInteger(value: unknown): boolean { return Number.isInteger(value); } @@ -666,7 +713,8 @@ export function isMap(value: unknown): value is return getObjectType(value) === 'Map'; } -export function isNan(value: unknown) { +export function isNan(value: Input): value is NumericGuardResult; +export function isNan(value: unknown): boolean { return Number.isNaN(value); } @@ -674,11 +722,13 @@ export function isNativePromise(value: unknown): value is Promise(value: Input): value is NumericGuardResult; +export function isNegativeInteger(value: unknown): boolean { return isInteger(value) && value < 0; } -export function isNegativeNumber(value: unknown): value is number { +export function isNegativeNumber(value: Input): value is NumericGuardResult; +export function isNegativeNumber(value: unknown): boolean { return isNumber(value) && value < 0; } @@ -714,11 +764,13 @@ export function isNonEmptyStringAndNotWhitespace(value: unknown): value is NonEm return isString(value) && !isEmptyStringOrWhitespace(value); } -export function isNonNegativeInteger(value: unknown): value is number { +export function isNonNegativeInteger(value: Input): value is NumericGuardResult; +export function isNonNegativeInteger(value: unknown): boolean { return isInteger(value) && value >= 0; } -export function isNonNegativeNumber(value: unknown): value is number { +export function isNonNegativeNumber(value: Input): value is NumericGuardResult; +export function isNonNegativeNumber(value: unknown): boolean { return isNumber(value) && value >= 0; } @@ -763,7 +815,8 @@ export function isObservable(value: unknown): value is ObservableLike { return false; } -export function isOddInteger(value: unknown): value is number { +export function isOddInteger(value: Input): value is NumericGuardResult; +export function isOddInteger(value: unknown): boolean { return isAbsoluteModule2(1)(value); } @@ -783,11 +836,13 @@ export function isPlainObject(value: unknown): value is Record< return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); } -export function isPositiveInteger(value: unknown): value is number { +export function isPositiveInteger(value: Input): value is NumericGuardResult; +export function isPositiveInteger(value: unknown): boolean { return isInteger(value) && value > 0; } -export function isPositiveNumber(value: unknown): value is number { +export function isPositiveNumber(value: Input): value is NumericGuardResult; +export function isPositiveNumber(value: unknown): boolean { return isNumber(value) && value > 0; } @@ -808,7 +863,8 @@ export function isRegExp(value: unknown): value is RegExp { return getObjectType(value) === 'RegExp'; } -export function isSafeInteger(value: unknown): value is number { +export function isSafeInteger(value: Input): value is NumericGuardResult; +export function isSafeInteger(value: unknown): boolean { return Number.isSafeInteger(value); } @@ -900,7 +956,8 @@ export function isValidDate(value: unknown): value is Date { return isDate(value) && !isNan(Number(value)); } -export function isValidLength(value: unknown): value is number { +export function isValidLength(value: Input): value is NumericGuardResult; +export function isValidLength(value: unknown): boolean { return isSafeInteger(value) && value >= 0; } @@ -956,6 +1013,8 @@ function typeErrorMessageMultipleValues(expectedType: AssertionTypeDescription | } // Type assertions have to be declared with an explicit type. +// Keep assertion outputs unbranded even when the corresponding `is.*` guard uses a branded subtype. +// The brands exist to preserve useful false-branch narrowing for type guards on `number` inputs, which does not apply to `asserts`. type Assert = { // Unknowns. undefined: (value: unknown, message?: string) => asserts value is undefined; @@ -1188,7 +1247,7 @@ const methodTypeMap = { isBigUint64Array: 'BigUint64Array', isBlob: 'Blob', isBoolean: 'boolean', - isBoundFunction: 'Function', + isBoundFunction: 'bound Function', isBuffer: 'Buffer', isClass: 'Class', isDataView: 'DataView', @@ -1247,7 +1306,7 @@ const methodTypeMap = { isPromise: 'Promise', isPropertyKey: 'PropertyKey', isRegExp: 'RegExp', - isSafeInteger: 'integer', + isSafeInteger: 'safe integer', isSet: 'Set', isSharedArrayBuffer: 'SharedArrayBuffer', isString: 'string', @@ -1391,7 +1450,7 @@ export function assertBoolean(value: unknown, message?: string): asserts value i // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type export function assertBoundFunction(value: unknown, message?: string): asserts value is Function { if (!isBoundFunction(value)) { - throw new TypeError(message ?? typeErrorMessage('Function', value)); + throw new TypeError(message ?? typeErrorMessage('bound Function', value)); } } @@ -1752,7 +1811,7 @@ export function assertRegExp(value: unknown, message?: string): asserts value is export function assertSafeInteger(value: unknown, message?: string): asserts value is number { if (!isSafeInteger(value)) { - throw new TypeError(message ?? typeErrorMessage('integer', value)); + throw new TypeError(message ?? typeErrorMessage('safe integer', value)); } } @@ -1891,10 +1950,25 @@ export default is; export type { ArrayLike, Class, + EvenInteger, + FiniteNumber, + Integer, + NaN, + NegativeInfinity, + NegativeInteger, + NegativeNumber, NodeStream, + NonNegativeInteger, + NonNegativeNumber, ObservableLike, + OddInteger, + PositiveInfinity, + PositiveInteger, + PositiveNumber, Predicate, Primitive, + SafeInteger, TypedArray, UrlString, + ValidLength, } from './types.ts'; diff --git a/source/types.ts b/source/types.ts index c37e927..9255072 100644 --- a/source/types.ts +++ b/source/types.ts @@ -78,9 +78,122 @@ export type NonEmptyString = string & {0: string}; export type Whitespace = ' '; +type Brand = Readonly>; + /** A string that represents a valid URL. This is a branded type to prevent incorrect TypeScript type narrowing. */ export type UrlString = string & {readonly __brand: 'UrlString'}; + +// Keep numeric guards branded and simple. This intentionally favors correct false-branch narrowing for `number` inputs over perfect success-branch narrowing for numeric literal unions. + +/** +The IEEE 754 "Not-a-Number" value, typed as a subtype of `number`. + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type NaN = number & Brand<'__nanBrand'>; + +/** +A finite number (excludes `NaN`, `Infinity`, and `-Infinity`). + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type FiniteNumber = number & Brand<'__finiteNumberBrand'>; + +/** +A number greater than or equal to zero. + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type NonNegativeNumber = number & Brand<'__nonNegativeNumberBrand'>; + +/** +An integer value (no fractional part). + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type Integer = FiniteNumber & Brand<'__integerBrand'>; + +/** +A number greater than zero. + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type PositiveNumber = NonNegativeNumber & Brand<'__positiveNumberBrand'>; + +/** +A number less than zero. + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type NegativeNumber = number & Brand<'__negativeNumberBrand'>; + +/** +An integer less than zero. + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type NegativeInteger = Integer & NegativeNumber & Brand<'__negativeIntegerBrand'>; + +/** +An integer greater than or equal to zero. + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type NonNegativeInteger = Integer & NonNegativeNumber & Brand<'__nonNegativeIntegerBrand'>; + +/** +An integer greater than zero. + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type PositiveInteger = NonNegativeInteger & PositiveNumber & Brand<'__positiveIntegerBrand'>; + +// Note: type-fest uses the `1e999` overflow trick to represent these types (since TypeScript has +// no built-in Infinity type), but we use branded types here for consistency and to avoid +// relying on numeric overflow behavior. + +/** +A positive infinite number (`Infinity`). + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type PositiveInfinity = PositiveNumber & Brand<'__positiveInfinityBrand'>; + +/** +A negative infinite number (`-Infinity`). + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type NegativeInfinity = NegativeNumber & Brand<'__negativeInfinityBrand'>; + +/** +A safe integer (within the range of `Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`). + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type SafeInteger = Integer & Brand<'__safeIntegerBrand'>; + +/** +An even integer. + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type EvenInteger = Integer & Brand<'__evenIntegerBrand'>; + +/** +An odd integer. + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type OddInteger = Integer & Brand<'__oddIntegerBrand'>; + +/** +A non-negative safe integer, suitable as an array or string length. + +Branded to prevent false-branch narrowing to `never` when the input is `number`. +*/ +export type ValidLength = SafeInteger & NonNegativeInteger & Brand<'__validLengthBrand'>; diff --git a/test/test.ts b/test/test.ts index ce26b50..8e64d69 100644 --- a/test/test.ts +++ b/test/test.ts @@ -15,6 +15,7 @@ import is, { assert as isAssert, assertPropertyKey, type AssertionTypeDescription, + type NaN as NaNType, type Predicate, type Primitive, type TypedArray, @@ -156,7 +157,7 @@ const primitiveTypes = { safeInteger: { fixtures: [...reusableFixtures.integer, ...reusableFixtures.safeInteger], typename: 'number', - typeDescription: 'integer', + typeDescription: 'safe integer', }, infinite: { fixtures: [...reusableFixtures.infinite], @@ -208,6 +209,7 @@ const objectTypes = { object: { fixtures: [ Object.create({x: 1}), + {[Symbol.toStringTag]: 'String'}, ...reusableFixtures.plainObject, ], typename: 'Object', @@ -280,6 +282,7 @@ const objectTypes = { boundFunction: { fixtures: [...reusableFixtures.boundFunction, ...reusableFixtures.asyncFunction], typename: 'Function', + typeDescription: 'bound Function', }, map: { fixtures: [ @@ -526,6 +529,7 @@ test('is.positiveNumber', () => { assert.strictEqual(is.positiveNumber(-6), false); assert.strictEqual(is.positiveNumber(-1.4), false); assert.strictEqual(is.positiveNumber(Number.NEGATIVE_INFINITY), false); + assert.strictEqual(is.positiveNumber(Number.NaN), false); assert.throws(() => { isAssert.positiveNumber(0); @@ -544,6 +548,33 @@ test('is.positiveNumber', () => { }); }); +test('is.nan', () => { + assert.ok(is.nan(Number.NaN)); + assert.ok(is.nan(NaN)); // eslint-disable-line unicorn/prefer-number-properties + + assert.doesNotThrow(() => { + isAssert.nan(Number.NaN); + }); + + assert.strictEqual(is.nan(0), false); + assert.strictEqual(is.nan(-0), false); + assert.strictEqual(is.nan(1), false); + assert.strictEqual(is.nan(Number.POSITIVE_INFINITY), false); + assert.strictEqual(is.nan(Number.NEGATIVE_INFINITY), false); + assert.strictEqual(is.nan('NaN'), false); + assert.strictEqual(is.nan(undefined), false); + + assert.throws(() => { + isAssert.nan(0); + }); + assert.throws(() => { + isAssert.nan(1); + }); + assert.throws(() => { + isAssert.nan('NaN'); + }); +}); + test('is.finiteNumber', () => { assert.ok(is.finiteNumber(6)); assert.ok(is.finiteNumber(-6)); @@ -592,6 +623,7 @@ test('is.negativeNumber', () => { assert.strictEqual(is.negativeNumber(6), false); assert.strictEqual(is.negativeNumber(1.4), false); assert.strictEqual(is.negativeNumber(Number.POSITIVE_INFINITY), false); + assert.strictEqual(is.negativeNumber(Number.NaN), false); assert.throws(() => { isAssert.negativeNumber(0); @@ -729,6 +761,32 @@ test('is.nonNegativeInteger', () => { }); }); +test('is.infinite', () => { + assert.ok(is.infinite(Number.POSITIVE_INFINITY)); + assert.ok(is.infinite(Number.NEGATIVE_INFINITY)); + + assert.doesNotThrow(() => { + isAssert.infinite(Number.POSITIVE_INFINITY); + }); + assert.doesNotThrow(() => { + isAssert.infinite(Number.NEGATIVE_INFINITY); + }); + + assert.strictEqual(is.infinite(0), false); + assert.strictEqual(is.infinite(1), false); + assert.strictEqual(is.infinite(-1), false); + assert.strictEqual(is.infinite(Number.NaN), false); + assert.strictEqual(is.infinite(Number.MAX_VALUE), false); + assert.strictEqual(is.infinite('Infinity'), false); + + assert.throws(() => { + isAssert.infinite(0); + }); + assert.throws(() => { + isAssert.infinite(Number.NaN); + }); +}); + test('is.numericString supplemental', () => { assert.strictEqual(is.numericString(''), false); assert.strictEqual(is.numericString(' '), false); @@ -998,6 +1056,20 @@ test('is.urlString', () => { } })(); +// Type test for is.nan branded-type narrowing +(() => { + const value: unknown = Number.NaN; + + if (is.nan(value)) { + // ✅ In true branch: value is narrowed to the branded NaN type + expectTypeOf(value).toEqualTypeOf(); + expectTypeOf(value).toMatchTypeOf(); + } else { + // ✅ In false branch: value remains unknown (not incorrectly narrowed) + expectTypeOf(value).toEqualTypeOf(); + } +})(); + test('is.truthy', () => { assert.ok(is.truthy('unicorn')); assert.ok(is.truthy('🦄')); @@ -2268,370 +2340,379 @@ test('assert', () => { test('custom assertion message', () => { const message = 'Custom error message'; - assert.throws(() => { + const assertThrowsTypeErrorWithMessage = (assertion: () => void) => { + // `node:assert` does not verify the error class when matching only on `{message}`. + assert.throws(assertion, error => { + assert.ok(error instanceof TypeError); + assert.strictEqual(error.message, message); + return true; + }); + }; + + assertThrowsTypeErrorWithMessage(() => { isAssert.array(undefined, undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.arrayBuffer(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.arrayLike(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.asyncFunction(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.asyncGenerator(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.asyncGeneratorFunction(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.asyncIterable(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.bigInt64Array(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.bigUint64Array(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.bigint(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.blob(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.boolean(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.boundFunction(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.buffer(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.class(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.dataView(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.date(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.directInstanceOf(undefined, Error, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.emptyArray(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.emptyMap(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.emptyObject(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.emptySet(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.emptyString(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.emptyStringOrWhitespace(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { enum Enum {} isAssert.enumCase('invalid', Enum, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.error(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.evenInteger(33, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.falsy(true, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.finiteNumber(Number.POSITIVE_INFINITY, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.float32Array(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.float64Array(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.formData(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.function(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.generator(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.generatorFunction(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.htmlElement(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.inRange(5, [1, 2], message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.infinite(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.int16Array(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.int32Array(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.int8Array(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.integer(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.iterable(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.map(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nan(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nativePromise(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.negativeNumber(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nodeStream(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptyArray(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptyMap(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptyObject(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptySet(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptyString(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptyStringAndNotWhitespace(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nonNegativeNumber(-1, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.null(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.nullOrUndefined(false, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.number(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.numericString(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.object(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.observable(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.oddInteger(42, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.plainObject(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.positiveInteger(0, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.positiveNumber(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.primitive([], message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.promise(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.propertyKey(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.regExp(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.safeInteger(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.set(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.sharedArrayBuffer(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.string(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.symbol(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.truthy(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.tupleLike(undefined, [], message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.typedArray(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.uint16Array(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.uint32Array(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.uint8Array(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.uint8ClampedArray(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.undefined(false, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.urlInstance(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.urlSearchParams(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.urlString(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.validDate(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.validLength(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.weakMap(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.weakRef(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.weakSet(undefined, message); - }, {message}); + }); - assert.throws(() => { + assertThrowsTypeErrorWithMessage(() => { isAssert.whitespaceString(undefined, message); - }, {message}); + }); }); test('is.optional', () => { diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..1f492d3 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": "..", + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "include": [ + "../source", + "type-tests.ts" + ] +} diff --git a/test/type-tests.ts b/test/type-tests.ts new file mode 100644 index 0000000..bb50e68 --- /dev/null +++ b/test/type-tests.ts @@ -0,0 +1,220 @@ +import is, { + type EvenInteger, + type FiniteNumber, + type Integer, + type NaN as NaNType, + type NegativeInfinity, + type NegativeInteger, + type NegativeNumber, + type NonNegativeInteger, + type NonNegativeNumber, + type OddInteger, + type PositiveInfinity, + type PositiveInteger, + type PositiveNumber, + type SafeInteger, + type ValidLength, +} from '../source/index.ts'; + +// For each predicate, verify two things: +// 1. True branch narrows to the branded type. +// 2. False branch on a `number` input stays `number` (not `never`). +// Without the branded types, `Exclude` = `never` would break +// the common validation-guard pattern: if (!is.X(n)) throw; use(n). + +const nanCheck = (value: number) => { + if (is.nan(value)) { + const _: NaNType = value; + } else { + const _: number = value; + } +}; + +const finiteNumberCheck = (value: number) => { + if (is.finiteNumber(value)) { + const _: FiniteNumber = value; + } else { + const _: number = value; + } +}; + +const nonNegativeNumberCheck = (value: number) => { + if (is.nonNegativeNumber(value)) { + const _: NonNegativeNumber = value; + } else { + const _: number = value; + } +}; + +const positiveIntegerCheck = (value: number) => { + if (is.positiveInteger(value)) { + const _: PositiveInteger = value; + const __: Integer = value; + const ___: NonNegativeInteger = value; + } else { + const _: number = value; + } +}; + +const negativeIntegerCheck = (value: number) => { + if (is.negativeInteger(value)) { + const _: NegativeInteger = value; + const __: Integer = value; + } else { + const _: number = value; + } +}; + +const nonNegativeIntegerCheck = (value: number) => { + if (is.nonNegativeInteger(value)) { + const _: NonNegativeInteger = value; + const __: Integer = value; + } else { + const _: number = value; + } +}; + +const infiniteCheck = (value: number) => { + if (is.infinite(value)) { + const _: PositiveInfinity | NegativeInfinity = value; + const __: PositiveNumber | NegativeNumber = value; + } else { + const _: number = value; + } +}; + +const integerCheck = (value: number) => { + if (is.integer(value)) { + const _: Integer = value; + const __: FiniteNumber = value; + } else { + const _: number = value; + } +}; + +const safeIntegerCheck = (value: number) => { + if (is.safeInteger(value)) { + const _: SafeInteger = value; + const __: Integer = value; + } else { + const _: number = value; + } +}; + +const evenIntegerCheck = (value: number) => { + if (is.evenInteger(value)) { + const _: EvenInteger = value; + const __: Integer = value; + } else { + const _: number = value; + } +}; + +const oddIntegerCheck = (value: number) => { + if (is.oddInteger(value)) { + const _: OddInteger = value; + const __: Integer = value; + } else { + const _: number = value; + } +}; + +const positiveNumberCheck = (value: number) => { + if (is.positiveNumber(value)) { + const _: PositiveNumber = value; + const __: NonNegativeNumber = value; + } else { + const _: number = value; + } +}; + +const negativeNumberCheck = (value: number) => { + if (is.negativeNumber(value)) { + const _: NegativeNumber = value; + } else { + const _: number = value; + } +}; + +const validLengthCheck = (value: number) => { + if (is.validLength(value)) { + const _: ValidLength = value; + const __: SafeInteger = value; + const ___: NonNegativeInteger = value; + } else { + const _: number = value; + } +}; + +const integerUnknownCheck = (value: unknown) => { + if (is.integer(value)) { + const _: Integer = value; + const __: FiniteNumber = value; + } +}; + +const positiveIntegerUnknownCheck = (value: unknown) => { + if (is.positiveInteger(value)) { + const _: PositiveInteger = value; + const __: NonNegativeInteger = value; + } +}; + +const integerMixedUnionCheck = (value: string | number) => { + if (is.integer(value)) { + const _: number = value; + } else { + const _: string = value; + } +}; + +const positiveNumberMixedUnionCheck = (value: string | number) => { + if (is.positiveNumber(value)) { + const _: number = value; + } else { + const _: string = value; + } +}; + +const chainedNumericGuardCheck = (value: number) => { + if (is.positiveNumber(value) && is.integer(value)) { + const _: PositiveNumber = value; + const __: Integer = value; + const ___: FiniteNumber = value; + } +}; + +const distinctNumericBrandsStayDistinct = ( + positiveInteger: PositiveInteger, + negativeInteger: NegativeInteger, + validLength: ValidLength, +) => { + // @ts-expect-error -- Distinct numeric refinements must not collapse into each other. + const _: NegativeInteger = positiveInteger; + // @ts-expect-error -- ValidLength is non-negative and must not become a signed integer refinement. + const __: NegativeInteger = validLength; + + return negativeInteger; +}; + +// Suppress unused variable warnings +nanCheck(42); +finiteNumberCheck(42); +nonNegativeNumberCheck(42); +positiveIntegerCheck(42); +negativeIntegerCheck(-1); +nonNegativeIntegerCheck(0); +infiniteCheck(Number.POSITIVE_INFINITY); +integerCheck(1); +safeIntegerCheck(1); +evenIntegerCheck(2); +oddIntegerCheck(1); +positiveNumberCheck(1); +negativeNumberCheck(-1); +validLengthCheck(0); +integerUnknownCheck(1); +positiveIntegerUnknownCheck(1); +integerMixedUnionCheck(1); +positiveNumberMixedUnionCheck(1); +chainedNumericGuardCheck(1); +distinctNumericBrandsStayDistinct(42 as PositiveInteger, -1 as NegativeInteger, 0 as ValidLength); From 48df5c429ced9b65fc788aa17ab8b9004d46a772 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 9 Apr 2026 22:09:01 +0700 Subject: [PATCH 251/254] 8.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0222a80..ac33c30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "7.2.0", + "version": "8.0.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 2d4956e6349182b5c8421e8a4b54033bd964e7b1 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 10 May 2026 15:01:37 +0900 Subject: [PATCH 252/254] Add negative assertion helper Fixes #220 --- readme.md | 38 ++++++++++ source/index.ts | 76 +++++++++++++++++-- test/test.ts | 81 ++++++++++++++++++++ test/type-tests.ts | 185 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 374 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index b2fb284..509f75f 100644 --- a/readme.md +++ b/readme.md @@ -786,6 +786,44 @@ handleMovieRatingApiResponse({rating: 0.87, title: 'The Matrix'}); handleMovieRatingApiResponse({rating: '🦄'}); ``` +### Negative assertion + +Asserts that `value` is not the specified type. Only exact, type-safe negative assertions are exposed. + +Supported assertions: + +- `assert.not.undefined(value)` +- `assert.not.null(value)` +- `assert.not.nullOrUndefined(value)` +- `assert.not.string(value)` +- `assert.not.boolean(value)` +- `assert.not.symbol(value)` +- `assert.not.bigint(value)` +- `assert.not.primitive(value)` + +This intentionally excludes checks that cannot produce a safe TypeScript complement: `number` because `is.number` rejects `NaN`, refinements such as `integer` and `validDate`, and branded structural object checks such as `map` and `date`. Broad object checks such as `object` are also excluded to keep negative assertions limited to primitive and nullish types. + +```ts +import {assert} from '@sindresorhus/is'; + +const value: string | undefined = getValue(); + +assert.not.undefined(value); +// Throws if `value` is `undefined`. Otherwise, `value` is now typed as `string`. +``` + +For `unknown` input, exact negative assertions narrow to the remaining representable type: + +```ts +const value: unknown = getValue(); + +assert.not.nullOrUndefined(value); +// `value` is now typed as non-nullish. + +assert.not.primitive(value); +// `value` is now typed as `object`. +``` + ### Optional assertion Asserts that `value` is `undefined` or satisfies the provided `assertion`. diff --git a/source/index.ts b/source/index.ts index eee1316..fe48452 100644 --- a/source/index.ts +++ b/source/index.ts @@ -403,9 +403,13 @@ function validatePredicateArray(predicateArray: readonly Predicate[], allowEmpty } for (const predicate of predicateArray) { - if (!isFunction(predicate)) { - throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); - } + validatePredicate(predicate); + } +} + +function validatePredicate(predicate: Predicate) { + if (!isFunction(predicate)) { + throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); } } @@ -983,9 +987,7 @@ export function isWhitespaceString(value: unknown): value is Whitespace { type ArrayMethod = (function_: (value: unknown, index: number, array: unknown[]) => boolean, thisArgument?: unknown) => boolean; function predicateOnArray(method: ArrayMethod, predicate: Predicate, values: unknown[]) { - if (!isFunction(predicate)) { - throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); - } + validatePredicate(predicate); if (values.length === 0) { throw new TypeError('Invalid number of values'); @@ -998,6 +1000,17 @@ function typeErrorMessage(description: AssertionTypeDescription, value: unknown) return `Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`; } +function typeErrorMessageNot(description: AssertionTypeDescription, value: unknown): string { + return `Expected value which is not \`${description}\`, received value of type \`${is(value)}\`.`; +} + +type NotAssertionResult = Exclude & ([unknown] extends [Value] ? UnknownResult : unknown); + +type NotAssertion = (value: Value, message?: string) => asserts value is NotAssertionResult; + +// eslint-disable-next-line @typescript-eslint/no-restricted-types +type UnknownNotPrimitive = Exclude | object; + function unique(values: T[]): T[] { // eslint-disable-next-line unicorn/prefer-spread return Array.from(new Set(values)); @@ -1123,6 +1136,8 @@ type Assert = { directInstanceOf: (instance: unknown, class_: Class, message?: string) => asserts instance is T; inRange: (value: number, range: number | [number, number], message?: string) => asserts value is number; + not: NotAssert; + // Variadic functions. any: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never; all: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never; @@ -1135,9 +1150,58 @@ type Assert = { optional: (value: unknown, assertion: (value: unknown, message?: string) => asserts value is T, message?: string) => asserts value is T | undefined; }; +type NotAssert = { + undefined: NotAssertion>; + // eslint-disable-next-line @typescript-eslint/no-restricted-types + null: NotAssertion>; + // eslint-disable-next-line @typescript-eslint/no-restricted-types + nullOrUndefined: NotAssertion>; + string: NotAssertion>; + boolean: NotAssertion>; + symbol: NotAssertion>; + bigint: NotAssertion>; + // eslint-disable-next-line @typescript-eslint/no-restricted-types + primitive: NotAssertion; +}; + +// Negative assertions are limited to types where the assertion rejects every TypeScript value assignable to the forbidden type. Structural object types such as `Map`, `Set`, `Date`, and `Array` are excluded because TypeScript accepts shape-compatible mocks while the runtime checks use object brands, so `Exclude` would narrow values that can pass the negative assertion. +function createAssertNot(predicate: Predicate, description: AssertionTypeDescription): NotAssertion { + return (value: Value, message?: string): asserts value is NotAssertionResult => { + if (predicate(value)) { + throw new TypeError(message ?? typeErrorMessageNot(description, value)); + } + }; +} + +export const assertNotUndefined: NotAssertion> = createAssertNot>(isUndefined, 'undefined'); +// eslint-disable-next-line @typescript-eslint/no-restricted-types +export const assertNotNull: NotAssertion> = createAssertNot>(isNull, 'null'); +// eslint-disable-next-line @typescript-eslint/no-restricted-types +export const assertNotNullOrUndefined: NotAssertion> + // eslint-disable-next-line @typescript-eslint/no-restricted-types + = createAssertNot>(isNullOrUndefined, 'null or undefined'); +export const assertNotString: NotAssertion> = createAssertNot>(isString, 'string'); +export const assertNotBoolean: NotAssertion> = createAssertNot>(isBoolean, 'boolean'); +export const assertNotSymbol: NotAssertion> = createAssertNot>(isSymbol, 'symbol'); +export const assertNotBigint: NotAssertion> = createAssertNot>(isBigint, 'bigint'); +export const assertNotPrimitive: NotAssertion = createAssertNot(isPrimitive, 'primitive'); // eslint-disable-line @typescript-eslint/no-restricted-types + +// We intentionally do not support `assert.not(is.undefined, value)`. TypeScript cannot derive safe complement types from arbitrary predicates, and many predicates here are refinements (for example, `is.number` rejects `NaN`). Explicit methods keep runtime checks and type narrowing aligned. +const notAssertions: NotAssert = { + bigint: assertNotBigint, + boolean: assertNotBoolean, + null: assertNotNull, + nullOrUndefined: assertNotNullOrUndefined, + primitive: assertNotPrimitive, + string: assertNotString, + symbol: assertNotSymbol, + undefined: assertNotUndefined, +}; + export const assert: Assert = { all: assertAll, any: assertAny, + not: notAssertions, optional: assertOptional, array: assertArray, arrayBuffer: assertArrayBuffer, diff --git a/test/test.ts b/test/test.ts index 8e64d69..2f98bfb 100644 --- a/test/test.ts +++ b/test/test.ts @@ -466,6 +466,17 @@ const subClasses = new Map([ ['object', keysOf(objectTypes)], ]); +const notAssertionFixtures = { + bigint: {fixture: 1n, nonFixture: '🦄', typeDescription: 'bigint'}, + boolean: {fixture: false, nonFixture: '🦄', typeDescription: 'boolean'}, + null: {fixture: null, nonFixture: '🦄', typeDescription: 'null'}, + nullOrUndefined: {fixtures: [null, undefined], nonFixture: '🦄', typeDescription: 'null or undefined'}, + primitive: {fixtures: [false, null, undefined], nonFixture: [], typeDescription: 'primitive'}, + string: {fixture: '🦄', nonFixture: 1, typeDescription: 'string'}, + symbol: {fixture: Symbol('🦄'), nonFixture: '🦄', typeDescription: 'symbol'}, + undefined: {fixture: undefined, nonFixture: null, typeDescription: 'undefined'}, +} as const satisfies Record; + // This ensures a certain method matches only the types it's supposed to and none of the other methods' types for (const type of keysOf(types)) { test(`is.${type}`, () => { @@ -2534,6 +2545,14 @@ test('custom assertion message', () => { isAssert.nativePromise(undefined, message); }); + assertThrowsTypeErrorWithMessage(() => { + isAssert.not.undefined(undefined, message); + }); + + assertThrowsTypeErrorWithMessage(() => { + isAssert.not.string('hello', message); + }); + assertThrowsTypeErrorWithMessage(() => { isAssert.negativeNumber(undefined, message); }); @@ -2715,6 +2734,68 @@ test('custom assertion message', () => { }); }); +test('isAssert.not.undefined', () => { + assert.throws(() => { + isAssert.not.undefined(undefined); + }, { + message: 'Expected value which is not `undefined`, received value of type `undefined`.', + }); + + assert.doesNotThrow(() => { + isAssert.not.undefined(null); + }); + + assert.doesNotThrow(() => { + isAssert.not.undefined(false); + }); + + assert.doesNotThrow(() => { + isAssert.not.undefined(0); + }); + + assert.doesNotThrow(() => { + isAssert.not.undefined(''); + }); +}); + +test('isAssert.not', () => { + assert.deepStrictEqual(new Set(keysOf(isAssert.not)), new Set(keysOf(notAssertionFixtures))); + + for (const type of keysOf(notAssertionFixtures)) { + const {nonFixture, typeDescription} = notAssertionFixtures[type]; + const testAssert = isAssert.not[type]; + const fixtures = 'fixtures' in notAssertionFixtures[type] ? notAssertionFixtures[type].fixtures : [notAssertionFixtures[type].fixture]; + + for (const fixture of fixtures) { + assert.throws(() => { + testAssert(fixture); + }, { + message: `Expected value which is not \`${typeDescription}\`, received value of type \`${is(fixture)}\`.`, + }); + } + + assert.doesNotThrow(() => { + testAssert(nonFixture); + }); + } + + assert.strictEqual('number' in isAssert.not, false); + assert.strictEqual('integer' in isAssert.not, false); + assert.strictEqual('object' in isAssert.not, false); + assert.strictEqual('blob' in isAssert.not, false); + assert.strictEqual('array' in isAssert.not, false); + assert.strictEqual('date' in isAssert.not, false); + assert.strictEqual('function' in isAssert.not, false); + assert.strictEqual('map' in isAssert.not, false); + assert.strictEqual('set' in isAssert.not, false); +}); + +test('isAssert.not edge cases', () => { + assert.doesNotThrow(() => { + isAssert.not.null(undefined); + }); +}); + test('is.optional', () => { assert.ok(is.optional(undefined, is.string)); assert.ok(is.optional('🦄', is.string)); diff --git a/test/type-tests.ts b/test/type-tests.ts index bb50e68..f08fa25 100644 --- a/test/type-tests.ts +++ b/test/type-tests.ts @@ -1,4 +1,10 @@ +import {expectTypeOf} from 'expect-type'; import is, { + assert as isAssert, + assertNotNullOrUndefined, + assertNotPrimitive, + assertNotString, + assertNotUndefined, type EvenInteger, type FiniteNumber, type Integer, @@ -12,10 +18,14 @@ import is, { type PositiveInfinity, type PositiveInteger, type PositiveNumber, + type Primitive, type SafeInteger, type ValidLength, } from '../source/index.ts'; +// eslint-disable-next-line @typescript-eslint/no-restricted-types +type UnknownNotPrimitive = Exclude | object; + // For each predicate, verify two things: // 1. True branch narrows to the branded type. // 2. False branch on a `number` input stays `number` (not `never`). @@ -197,6 +207,154 @@ const distinctNumericBrandsStayDistinct = ( return negativeInteger; }; +const assertNotUndefinedCheck = (value: string | undefined) => { + isAssert.not.undefined(value); + expectTypeOf(value).toEqualTypeOf(); +}; + +const assertNotUndefinedUnknownCheck = (value: unknown) => { + isAssert.not.undefined(value); + expectTypeOf(value).toEqualTypeOf>(); +}; + +const assertNotUndefinedGenericCheck = (value: T) => { + isAssert.not.undefined(value); + const _: Exclude = value; +}; + +const nullValue = null; +type Null = typeof nullValue; + +const assertNotNullUnknownCheck = (value: unknown) => { + isAssert.not.null(value); + expectTypeOf(value).toEqualTypeOf>(); +}; + +const assertNotNullOrUndefinedCheck = (value: string | Null | undefined) => { + isAssert.not.nullOrUndefined(value); + expectTypeOf(value).toEqualTypeOf(); +}; + +const assertNotNullOrUndefinedUnknownCheck = (value: unknown) => { + isAssert.not.nullOrUndefined(value); + expectTypeOf(value).toEqualTypeOf>(); +}; + +const assertNotStringCheck = (value: string | number) => { + isAssert.not.string(value); + expectTypeOf(value).toEqualTypeOf(); +}; + +const assertNotStringUnknownCheck = (value: unknown) => { + isAssert.not.string(value); + expectTypeOf(value).toEqualTypeOf>(); +}; + +const assertNotStringGenericCheck = (value: T) => { + isAssert.not.string(value); + const _: Exclude = value; +}; + +const assertNotBooleanUnknownCheck = (value: unknown) => { + isAssert.not.boolean(value); + expectTypeOf(value).toEqualTypeOf>(); +}; + +const assertNotSymbolUnknownCheck = (value: unknown) => { + isAssert.not.symbol(value); + expectTypeOf(value).toEqualTypeOf>(); +}; + +const assertNotBigintUnknownCheck = (value: unknown) => { + isAssert.not.bigint(value); + expectTypeOf(value).toEqualTypeOf>(); +}; + +const assertNotPrimitiveUnknownCheck = (value: unknown) => { + isAssert.not.primitive(value); + // eslint-disable-next-line @typescript-eslint/no-restricted-types + expectTypeOf(value).toEqualTypeOf(); +}; + +const assertNotPrimitiveGenericCheck = (value: T) => { + isAssert.not.primitive(value); + const _: Exclude = value; +}; + +const assertNotNamedUndefinedExportCheck = (value: 0 | false | '' | Null | undefined | 'ok') => { + assertNotUndefined(value); + expectTypeOf(value).toEqualTypeOf<0 | false | '' | Null | 'ok'>(); +}; + +const assertNotNamedNullOrUndefinedUnknownExportCheck = (value: unknown) => { + assertNotNullOrUndefined(value); + expectTypeOf(value).toEqualTypeOf>(); +}; + +const assertNotNamedStringExportCheck = (value: string | number) => { + assertNotString(value); + expectTypeOf(value).toEqualTypeOf(); +}; + +const assertNotNamedStringUnknownExportCheck = (value: unknown) => { + assertNotString(value); + expectTypeOf(value).toEqualTypeOf>(); +}; + +const assertNotNamedPrimitiveUnknownExportCheck = (value: unknown) => { + assertNotPrimitive(value); + // eslint-disable-next-line @typescript-eslint/no-restricted-types + expectTypeOf(value).toEqualTypeOf(); +}; + +const assertNotCallableDoesNotExistCheck = (value: string | undefined) => { + // @ts-expect-error -- Generic negative assertions cannot safely infer complement types from arbitrary predicates. + isAssert.not(is.undefined, value); + const _: string | undefined = value; +}; + +const assertNotNumberDoesNotExistCheck = (value: string | number) => { + // @ts-expect-error -- `is.number` rejects `NaN`, so a narrowing negative assertion would be unsound. + isAssert.not.number(value); // eslint-disable-line @typescript-eslint/no-unsafe-call + const _: string | number = value; +}; + +const assertNotIntegerDoesNotExistCheck = (value: string | number) => { + // @ts-expect-error -- Numeric refinements are intentionally excluded from `assert.not`. + isAssert.not.integer(value); // eslint-disable-line @typescript-eslint/no-unsafe-call + const _: string | number = value; +}; + +const assertNotObjectDoesNotExistCheck = (value: Record | string) => { + // @ts-expect-error -- TypeScript's `{}` type includes primitives, so `not.object` cannot safely narrow every object-like input. + isAssert.not.object(value); // eslint-disable-line @typescript-eslint/no-unsafe-call + const _: Record | string = value; +}; + +const assertNotBlobDoesNotExistCheck = (value: Blob | File | string) => { + // @ts-expect-error -- `File` extends `Blob` in TypeScript but does not match the exact runtime `Blob` check. + isAssert.not.blob(value); // eslint-disable-line @typescript-eslint/no-unsafe-call + const _: Blob | File | string = value; +}; + +const assertNotMapDoesNotExistCheck = (value: Map | string) => { + // @ts-expect-error -- Structural object types such as `Map` can be assignable in TypeScript without matching the runtime brand check. + isAssert.not.map(value); // eslint-disable-line @typescript-eslint/no-unsafe-call, unicorn/no-array-callback-reference + const _: Map | string = value; +}; + +const assertNotSetDoesNotExistCheck = (value: Set | string) => { + // @ts-expect-error -- Structural object types such as `Set` can be assignable in TypeScript without matching the runtime brand check. + isAssert.not.set(value); // eslint-disable-line @typescript-eslint/no-unsafe-call + const _: Set | string = value; +}; + +const assertNotDateDoesNotExistCheck = (value: Date | string) => { + // @ts-expect-error -- Structural object types such as `Date` can be assignable in TypeScript without matching the runtime brand check. + isAssert.not.date(value); // eslint-disable-line @typescript-eslint/no-unsafe-call + const _: Date | string = value; +}; + // Suppress unused variable warnings nanCheck(42); finiteNumberCheck(42); @@ -218,3 +376,30 @@ integerMixedUnionCheck(1); positiveNumberMixedUnionCheck(1); chainedNumericGuardCheck(1); distinctNumericBrandsStayDistinct(42 as PositiveInteger, -1 as NegativeInteger, 0 as ValidLength); +assertNotUndefinedCheck('🦄'); +assertNotUndefinedUnknownCheck('🦄'); +assertNotUndefinedGenericCheck('🦄'); +assertNotNullUnknownCheck('🦄'); +assertNotNullOrUndefinedCheck('🦄'); +assertNotNullOrUndefinedUnknownCheck('🦄'); +assertNotStringCheck(1); +assertNotStringUnknownCheck(1); +assertNotStringGenericCheck(1); +assertNotBooleanUnknownCheck(1); +assertNotSymbolUnknownCheck(1); +assertNotBigintUnknownCheck(1); +assertNotPrimitiveUnknownCheck({}); +assertNotPrimitiveGenericCheck({unicorn: true}); +assertNotNamedUndefinedExportCheck(0); +assertNotNamedNullOrUndefinedUnknownExportCheck('🦄'); +assertNotNamedStringExportCheck(1); +assertNotNamedStringUnknownExportCheck(1); +assertNotNamedPrimitiveUnknownExportCheck({}); +assertNotCallableDoesNotExistCheck('🦄'); +assertNotNumberDoesNotExistCheck(Number.NaN); +assertNotIntegerDoesNotExistCheck(1.5); +assertNotObjectDoesNotExistCheck('🦄'); +assertNotBlobDoesNotExistCheck('🦄'); +assertNotMapDoesNotExistCheck('🦄'); +assertNotSetDoesNotExistCheck('🦄'); +assertNotDateDoesNotExistCheck('🦄'); From a4393055546c2d90aa386dd6f6f9cde80b9e6338 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 11 May 2026 20:22:19 +0900 Subject: [PATCH 253/254] 8.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac33c30..d7a386f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sindresorhus/is", - "version": "8.0.0", + "version": "8.1.0", "description": "Type check values", "license": "MIT", "repository": "sindresorhus/is", From 7821031c66cdeb7256a0feb2d506535f9e84fcaf Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 11 May 2026 22:57:22 +0900 Subject: [PATCH 254/254] Fix CI --- source/index.ts | 2 +- source/utilities.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/index.ts b/source/index.ts index fe48452..6c2f428 100644 --- a/source/index.ts +++ b/source/index.ts @@ -825,7 +825,7 @@ export function isOddInteger(value: unknown): boolean { } export function isOneOf(values: T): (value: unknown) => value is T[number] { - return (value: unknown): value is T[number] => values.includes(value as T[number]); + return (value: unknown): value is T[number] => values.includes(value); } export function isPlainObject(value: unknown): value is Record { diff --git a/source/utilities.ts b/source/utilities.ts index 102b6db..686edb1 100644 --- a/source/utilities.ts +++ b/source/utilities.ts @@ -1,3 +1,3 @@ export function keysOf>(value: T): Array { - return Object.keys(value) as Array; + return Object.keys(value) as Array; // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion }