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 01/57] 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 02/57] 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 03/57] 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 04/57] 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 05/57] 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 06/57] 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 07/57] 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 08/57] 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 09/57] 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 10/57] 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 11/57] 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 12/57] 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 13/57] 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 14/57] 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 15/57] `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 16/57] 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 17/57] 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 18/57] 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 19/57] 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 20/57] 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 21/57] 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 22/57] 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 23/57] 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 24/57] 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 25/57] 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 26/57] 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 27/57] 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 28/57] 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 29/57] 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 30/57] 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 31/57] 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 32/57] 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 33/57] 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 34/57] 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 35/57] 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 36/57] 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 37/57] 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 38/57] 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 39/57] 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 40/57] 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 41/57] 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 42/57] 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 43/57] 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 44/57] 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 45/57] 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 46/57] 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 47/57] 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 48/57] 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 49/57] 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 50/57] 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 51/57] 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 52/57] 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 53/57] 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 54/57] 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 55/57] 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 56/57] 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 57/57] 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 }