diff --git a/source/index.ts b/source/index.ts index d69b9ce..a01439f 100644 --- a/source/index.ts +++ b/source/index.ts @@ -326,6 +326,19 @@ export type ArrayLike = { const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0; is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); +type TypeGuard = (value: unknown) => value is T; + +// eslint-disable-next-line @typescript-eslint/ban-types +type ResolveTypesOfTypeGuardsTuple = TypeGuardsOfT extends [TypeGuard, ...infer TOthers] ? ResolveTypesOfTypeGuardsTuple : TypeGuardsOfT extends undefined[] ? ResultOfT : never; + +is.tupleLike = >>(value: unknown, guards: [...T]): value is ResolveTypesOfTypeGuardsTuple => { + if (is.array(guards) && is.array(value) && guards.length === value.length ) { + return guards.every((guard, index) => guard(value[index])); + } + + return false; +}; + is.inRange = (value: number, range: number | number[]): value is number => { if (is.number(range)) { return value >= Math.min(0, range) && value <= Math.max(range, 0); @@ -482,6 +495,7 @@ export const enum AssertionTypeDescription { safeInteger = 'integer', // eslint-disable-line @typescript-eslint/no-duplicate-enum-values plainObject = 'plain object', arrayLike = 'array-like', + tupleLike = 'tuple-like', typedArray = 'TypedArray', domElement = 'HTMLElement', nodeStream = 'Node.js Stream', @@ -579,6 +593,7 @@ type Assert = { plainObject: (value: unknown) => asserts value is Record; typedArray: (value: unknown) => asserts value is TypedArray; arrayLike: (value: unknown) => asserts value is ArrayLike; + tupleLike: >>(value: unknown, guards: [...T]) => asserts value is ResolveTypesOfTypeGuardsTuple; domElement: (value: unknown) => asserts value is HTMLElement; observable: (value: unknown) => asserts value is ObservableLike; nodeStream: (value: unknown) => asserts value is NodeStream; @@ -687,6 +702,7 @@ export const assert: Assert = { plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value), arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value), + tupleLike: >>(value: unknown, guards: [...T]): asserts value is ResolveTypesOfTypeGuardsTuple => assertType(is.tupleLike(value, guards), AssertionTypeDescription.tupleLike, value), domElement: (value: unknown): asserts value is HTMLElement => assertType(is.domElement(value), AssertionTypeDescription.domElement, value), observable: (value: unknown): asserts value is ObservableLike => assertType(is.observable(value), 'Observable', value), nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), AssertionTypeDescription.nodeStream, value), diff --git a/test/test.ts b/test/test.ts index a593332..7ee9504 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1445,6 +1445,43 @@ test('is.arrayLike', t => { }); }); +test('is.tupleLike', t => { + (function () { + t.false(is.tupleLike(arguments, [])); // eslint-disable-line prefer-rest-params + })(); + + t.true(is.tupleLike([], [])); + t.true(is.tupleLike([1, '2', true, {}, [], undefined, null], [is.number, is.string, is.boolean, is.object, is.array, is.undefined, is.nullOrUndefined])); + t.false(is.tupleLike('unicorn', [is.string])); + + t.false(is.tupleLike({}, [])); + t.false(is.tupleLike(() => {}, [is.function_])); + t.false(is.tupleLike(new Map(), [is.map])); + + (function () { + t.throws(function () { + assert.tupleLike(arguments, []); // eslint-disable-line prefer-rest-params + }); + })(); + + t.notThrows(() => { + assert.tupleLike([], []); + }); + t.throws(() => { + assert.tupleLike('unicorn', [is.string]); + }); + + t.throws(() => { + assert.tupleLike({}, [is.object]); + }); + t.throws(() => { + assert.tupleLike(() => {}, [is.function_]); + }); + t.throws(() => { + assert.tupleLike(new Map(), [is.map]); + }); +}); + test('is.inRange', t => { const x = 3;