// We intentionally do not use the `isArray` helper function to avoid circular imports. (The // improved return type is not needed for these assertion functions.) /* eslint-disable complete/prefer-is-array */ /** * Helper functions that have to do with asserting. * * @module */ import type { TranspiledEnum } from "../types/TranspiledEnum.js"; import type { Tuple } from "../types/Tuple.js"; import { isEnumValue } from "./enums.js"; import { isObject } from "./types.js"; /** Helper function to throw an error if the provided value is not an array. */ export function assertArray( value: T, ...[msg]: [T] extends [readonly unknown[]] ? [ "The assertion is useless because the provided value is already an array.", ] : [string] ): asserts value is T & unknown[] { if (!Array.isArray(value)) { throw new TypeError(msg); } } /** * Helper function to throw an error if the provided value is not an array with every element being * a boolean. */ export function assertArrayBoolean( value: T, ...[msg]: [T] extends [readonly boolean[]] ? [ "The assertion is useless because the provided value is already a boolean array.", ] : [string] ): asserts value is T & boolean[] { if (!Array.isArray(value)) { throw new TypeError(msg); } if ((value as unknown[]).some((element) => typeof element !== "boolean")) { throw new TypeError(msg); } } /** * Helper function to throw an error if the provided array does not have the specified length. Will * also type narrow the array into a tuple of the specified length. */ export function assertArrayLength( value: E[], // eslint-disable-line complete/prefer-readonly-parameter-types length: N, msg: string, ): asserts value is Tuple; export function assertArrayLength( value: readonly E[], length: N, msg: string, ): asserts value is Readonly>; export function assertArrayLength( value: readonly unknown[], length: number, msg: string, ): asserts value is unknown[] { if (value.length !== length) { throw new TypeError(msg); } } /** Helper function to throw an error if the provided value is not an array or is an empty array. */ export function assertArrayNonEmpty( value: T, ...[msg]: [T] extends [readonly [unknown, ...unknown[]]] ? [ "The assertion is useless because the provided value is already a non-empty array.", ] : [string] ): asserts value is T & (T extends ReadonlyArray ? [E, ...E[]] : [unknown, ...unknown[]]) { if (!Array.isArray(value)) { throw new TypeError(msg); } if (value.length === 0) { throw new TypeError(msg); } } /** * Helper function to throw an error if the provided value is not an array with every element being * a number. */ export function assertArrayNumber( value: T, ...[msg]: [T] extends [readonly number[]] ? [ "The assertion is useless because the provided value is already a number array.", ] : [string] ): asserts value is T & number[] { if (!Array.isArray(value)) { throw new TypeError(msg); } if ((value as unknown[]).some((element) => typeof element !== "number")) { throw new TypeError(msg); } } /** * Helper function to throw an error if the provided value is not an array with every element being * an object (i.e., a TypeScript record). */ export function assertArrayObject( value: T, ...[msg]: [T] extends [ReadonlyArray>] ? [ "The assertion is useless because the provided value is already an object array.", ] : [string] ): asserts value is T & Array> { if (!Array.isArray(value)) { throw new TypeError(msg); } if ((value as unknown[]).some((element) => !isObject(element))) { throw new TypeError(msg); } } /** * Helper function to throw an error if the provided value is not an array with every element being * a string. */ export function assertArrayString( value: T, ...[msg]: [T] extends [readonly string[]] ? [ "The assertion is useless because the provided value is already a string array.", ] : [string] ): asserts value is T & string[] { if (!Array.isArray(value)) { throw new TypeError(msg); } if ((value as unknown[]).some((element) => typeof element !== "string")) { throw new TypeError(msg); } } /** Helper function to throw an error if the provided value is not a boolean. */ export function assertBoolean( value: T, ...[msg]: [T] extends [boolean] ? [ "The assertion is useless because the provided value is already a boolean.", ] : [string] ): asserts value is T & boolean { if (typeof value !== "boolean") { throw new TypeError(msg); } } /** * Helper function to throw an error if the provided value is equal to `undefined`. * * This is useful to have TypeScript narrow a `T | undefined` value to `T` in a concise way. */ export function assertDefined( value: T, ...[msg]: [undefined] extends [T] ? [string] : [ "The assertion is useless because the provided value does not contain undefined.", ] ): asserts value is Exclude { if (value === undefined) { throw new TypeError(msg); } } /** * Helper function to throw an error if the provided value is not contained within an enum. * * @param value The value to check. * @param transpiledEnum The enum to check against. * @param msg The error message to throw if the check fails. * @param set Optional. A set that contains all of the values of an enum. If provided, this function * will check for existence using the set (instead of the enum itself). Using a set * should be more performant for enums with around 52 or more elements. */ export function assertEnumValue( value: number | string, transpiledEnum: T, msg: string, set?: ReadonlySet, ): asserts value is T[keyof T] { if (!isEnumValue(value, transpiledEnum, set)) { throw new TypeError(msg); } } /** Helper function to throw an error if the provided value is not an integer. */ export function assertInteger( value: unknown, msg: string, ): asserts value is number { // `Number.isSafeInteger` will correctly return false for non-number variables such as strings, // booleans, and so on. if (!Number.isSafeInteger(value)) { throw new TypeError(msg); } } /** * Helper function to throw an error if the provided value is not an instance of the expected class. * * This is useful to have TypeScript narrow a value to a specific type in a concise way. */ export function assertIs< T extends abstract new (...args: unknown[]) => unknown, >( value: unknown, constructor: T, msg: string, ): asserts value is InstanceType { if (!(value instanceof constructor)) { throw new TypeError(msg); } } /** * Helper function to throw an error if the provided value is equal to `null`. * * This is useful to have TypeScript narrow a `T | null` value to `T` in a concise way. */ export function assertNotNull( value: T, ...[msg]: [null] extends [T] ? [string] : [ "The assertion is useless because the provided value does not contain null.", ] ): asserts value is Exclude { if (value === null) { throw new TypeError(msg); } } /** Helper function to throw an error if the provided value is not a number. */ export function assertNumber( value: T, ...[msg]: [T] extends [number] ? [ "The assertion is useless because the provided value is already a number.", ] : [string] ): asserts value is T & number { if (typeof value !== "number") { throw new TypeError(msg); } } /** * Helper function to throw an error if the provided value is not an object (i.e., a TypeScript * record). * * This is useful to have TypeScript narrow a `Record | undefined` value to * `Record` in a concise way. * * Under the hood, this function uses the `isObject` helper function. */ export function assertObject( value: T, ...[msg]: [T] extends [Record] ? [ "The assertion is useless because the provided value is already an object.", ] : [string] ): asserts value is T & Record { if (!isObject(value)) { throw new TypeError(msg); } } /** Helper function to throw an error if the provided value is not a positive integer. */ export function assertPositiveInteger( value: unknown, msg: string, ): asserts value is number { // `Number.isSafeInteger` will correctly return false for non-number variables such as strings, // booleans, and so on. if (!Number.isSafeInteger(value) || (value as number) <= 0) { throw new TypeError(msg); } } /** Helper function to throw an error if the provided value is not a string. */ export function assertString( value: T, ...[msg]: [T] extends [string] ? [ "The assertion is useless because the provided value is already a string.", ] : [string] ): asserts value is T & string { if (typeof value !== "string") { throw new TypeError(msg); } } /** Helper function to throw an error if the provided value is not a string or an empty string. */ export function assertStringNotEmpty( value: unknown, msg: string, ): asserts value is string { assertString(value, msg); if (value === "") { throw new TypeError(msg); } }