From faf700367e40af945def85224da9ab326c51374c Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 7 Apr 2026 17:38:43 +0700 Subject: [PATCH 01/12] 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 02/12] 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 03/12] 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 04/12] 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 05/12] 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 06/12] 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 07/12] 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 08/12] 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 09/12] 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 10/12] 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 11/12] 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 12/12] 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 }