Add .tupleLike() (#189)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
parent
94dc715577
commit
3868f47783
4 changed files with 119 additions and 1 deletions
|
|
@ -61,7 +61,8 @@
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"xo": "^0.54.2",
|
"xo": "^0.54.2",
|
||||||
"zen-observable": "^0.10.0"
|
"zen-observable": "^0.10.0",
|
||||||
|
"expect-type": "^0.16.0"
|
||||||
},
|
},
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"ava": {
|
"ava": {
|
||||||
|
|
|
||||||
20
readme.md
20
readme.md
|
|
@ -410,6 +410,26 @@ function foo() {
|
||||||
foo();
|
foo();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### .tupleLike(value, guards)
|
||||||
|
|
||||||
|
A `value` is tuple-like if it matches the provided `guards` array both in `.length` and in types.
|
||||||
|
|
||||||
|
```js
|
||||||
|
is.tupleLike([1], [is.number]);
|
||||||
|
//=> true
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
function foo() {
|
||||||
|
const tuple = [1, '2', true];
|
||||||
|
if (is.tupleLike(tuple, [is.number, is.string, is.boolean])) {
|
||||||
|
tuple // [number, string, boolean]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foo();
|
||||||
|
```
|
||||||
|
|
||||||
#### .positiveNumber(value)
|
#### .positiveNumber(value)
|
||||||
|
|
||||||
Check if `value` is a number and is more than 0.
|
Check if `value` is a number and is more than 0.
|
||||||
|
|
|
||||||
|
|
@ -326,6 +326,24 @@ export type ArrayLike<T> = {
|
||||||
const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0;
|
const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0;
|
||||||
is.arrayLike = <T = unknown>(value: unknown): value is ArrayLike<T> => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike<T>).length);
|
is.arrayLike = <T = unknown>(value: unknown): value is ArrayLike<T> => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike<T>).length);
|
||||||
|
|
||||||
|
type TypeGuard<T> = (value: unknown) => value is T;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
type ResolveTypesOfTypeGuardsTuple<TypeGuardsOfT, ResultOfT extends unknown[] = [] > =
|
||||||
|
TypeGuardsOfT extends [TypeGuard<infer U>, ...infer TOthers]
|
||||||
|
? ResolveTypesOfTypeGuardsTuple<TOthers, [...ResultOfT, U]>
|
||||||
|
: TypeGuardsOfT extends undefined[]
|
||||||
|
? ResultOfT
|
||||||
|
: never;
|
||||||
|
|
||||||
|
is.tupleLike = <T extends Array<TypeGuard<unknown>>>(value: unknown, guards: [...T]): value is ResolveTypesOfTypeGuardsTuple<T> => {
|
||||||
|
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 => {
|
is.inRange = (value: number, range: number | number[]): value is number => {
|
||||||
if (is.number(range)) {
|
if (is.number(range)) {
|
||||||
return value >= Math.min(0, range) && value <= Math.max(range, 0);
|
return value >= Math.min(0, range) && value <= Math.max(range, 0);
|
||||||
|
|
@ -482,6 +500,7 @@ export const enum AssertionTypeDescription {
|
||||||
safeInteger = 'integer', // eslint-disable-line @typescript-eslint/no-duplicate-enum-values
|
safeInteger = 'integer', // eslint-disable-line @typescript-eslint/no-duplicate-enum-values
|
||||||
plainObject = 'plain object',
|
plainObject = 'plain object',
|
||||||
arrayLike = 'array-like',
|
arrayLike = 'array-like',
|
||||||
|
tupleLike = 'tuple-like',
|
||||||
typedArray = 'TypedArray',
|
typedArray = 'TypedArray',
|
||||||
domElement = 'HTMLElement',
|
domElement = 'HTMLElement',
|
||||||
nodeStream = 'Node.js Stream',
|
nodeStream = 'Node.js Stream',
|
||||||
|
|
@ -579,6 +598,7 @@ type Assert = {
|
||||||
plainObject: <Value = unknown>(value: unknown) => asserts value is Record<PropertyKey, Value>;
|
plainObject: <Value = unknown>(value: unknown) => asserts value is Record<PropertyKey, Value>;
|
||||||
typedArray: (value: unknown) => asserts value is TypedArray;
|
typedArray: (value: unknown) => asserts value is TypedArray;
|
||||||
arrayLike: <T = unknown>(value: unknown) => asserts value is ArrayLike<T>;
|
arrayLike: <T = unknown>(value: unknown) => asserts value is ArrayLike<T>;
|
||||||
|
tupleLike: <T extends Array<TypeGuard<unknown>>>(value: unknown, guards: [...T]) => asserts value is ResolveTypesOfTypeGuardsTuple<T>;
|
||||||
domElement: (value: unknown) => asserts value is HTMLElement;
|
domElement: (value: unknown) => asserts value is HTMLElement;
|
||||||
observable: (value: unknown) => asserts value is ObservableLike;
|
observable: (value: unknown) => asserts value is ObservableLike;
|
||||||
nodeStream: (value: unknown) => asserts value is NodeStream;
|
nodeStream: (value: unknown) => asserts value is NodeStream;
|
||||||
|
|
@ -687,6 +707,7 @@ export const assert: Assert = {
|
||||||
plainObject: <Value = unknown>(value: unknown): asserts value is Record<PropertyKey, Value> => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value),
|
plainObject: <Value = unknown>(value: unknown): asserts value is Record<PropertyKey, Value> => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value),
|
||||||
typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value),
|
typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value),
|
||||||
arrayLike: <T = unknown>(value: unknown): asserts value is ArrayLike<T> => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value),
|
arrayLike: <T = unknown>(value: unknown): asserts value is ArrayLike<T> => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value),
|
||||||
|
tupleLike: <T extends Array<TypeGuard<unknown>>>(value: unknown, guards: [...T]): asserts value is ResolveTypesOfTypeGuardsTuple<T> => assertType(is.tupleLike(value, guards), AssertionTypeDescription.tupleLike, value),
|
||||||
domElement: (value: unknown): asserts value is HTMLElement => assertType(is.domElement(value), AssertionTypeDescription.domElement, 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),
|
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),
|
nodeStream: (value: unknown): asserts value is NodeStream => assertType(is.nodeStream(value), AssertionTypeDescription.nodeStream, value),
|
||||||
|
|
|
||||||
76
test/test.ts
76
test/test.ts
|
|
@ -8,6 +8,7 @@ import test, {type ExecutionContext} from 'ava';
|
||||||
import {JSDOM} from 'jsdom';
|
import {JSDOM} from 'jsdom';
|
||||||
import {Subject, Observable} from 'rxjs';
|
import {Subject, Observable} from 'rxjs';
|
||||||
import {temporaryFile} from 'tempy';
|
import {temporaryFile} from 'tempy';
|
||||||
|
import {expectTypeOf} from 'expect-type';
|
||||||
import ZenObservable from 'zen-observable';
|
import ZenObservable from 'zen-observable';
|
||||||
import is, {
|
import is, {
|
||||||
assert,
|
assert,
|
||||||
|
|
@ -1445,6 +1446,81 @@ test('is.arrayLike', t => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('is.tupleLike', t => {
|
||||||
|
(function () {
|
||||||
|
t.false(is.tupleLike(arguments, [])); // eslint-disable-line prefer-rest-params
|
||||||
|
})();
|
||||||
|
|
||||||
|
t.true(is.tupleLike([], []));
|
||||||
|
t.true(is.tupleLike([1, '2', true, {}, [], undefined, null], [is.number, is.string, is.boolean, is.object, is.array, is.undefined, is.nullOrUndefined]));
|
||||||
|
t.false(is.tupleLike('unicorn', [is.string]));
|
||||||
|
|
||||||
|
t.false(is.tupleLike({}, []));
|
||||||
|
t.false(is.tupleLike(() => {}, [is.function_]));
|
||||||
|
t.false(is.tupleLike(new Map(), [is.map]));
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
t.throws(function () {
|
||||||
|
assert.tupleLike(arguments, []); // eslint-disable-line prefer-rest-params
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
t.notThrows(() => {
|
||||||
|
assert.tupleLike([], []);
|
||||||
|
});
|
||||||
|
t.throws(() => {
|
||||||
|
assert.tupleLike('unicorn', [is.string]);
|
||||||
|
});
|
||||||
|
|
||||||
|
t.throws(() => {
|
||||||
|
assert.tupleLike({}, [is.object]);
|
||||||
|
});
|
||||||
|
t.throws(() => {
|
||||||
|
assert.tupleLike(() => {}, [is.function_]);
|
||||||
|
});
|
||||||
|
t.throws(() => {
|
||||||
|
assert.tupleLike(new Map(), [is.map]);
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const tuple = [[false, 'unicorn'], 'string', true];
|
||||||
|
|
||||||
|
if (is.tupleLike(tuple, [is.array, is.string, is.boolean])) {
|
||||||
|
if (is.tupleLike(tuple[0], [is.boolean, is.string])) { // eslint-disable-line unicorn/no-lonely-if
|
||||||
|
const value = tuple[0][1];
|
||||||
|
expectTypeOf(value).toEqualTypeOf<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const tuple = [{isTest: true}, '1', true, null];
|
||||||
|
|
||||||
|
if (is.tupleLike(tuple, [is.nonEmptyObject, is.string, is.boolean, is.null_])) {
|
||||||
|
const value = tuple[0];
|
||||||
|
expectTypeOf(value).toEqualTypeOf<Record<string | number | symbol, unknown>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const tuple = [1, '1', true, null, undefined];
|
||||||
|
|
||||||
|
if (is.tupleLike(tuple, [is.number, is.string, is.boolean, is.undefined, is.null_])) {
|
||||||
|
const numericValue = tuple[0];
|
||||||
|
const stringValue = tuple[1];
|
||||||
|
const booleanValue = tuple[2];
|
||||||
|
const undefinedValue = tuple[3];
|
||||||
|
const nullValue = tuple[4];
|
||||||
|
expectTypeOf(numericValue).toEqualTypeOf<number>();
|
||||||
|
expectTypeOf(stringValue).toEqualTypeOf<string>();
|
||||||
|
expectTypeOf(booleanValue).toEqualTypeOf<boolean>();
|
||||||
|
expectTypeOf(undefinedValue).toEqualTypeOf<undefined>();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
expectTypeOf(nullValue).toEqualTypeOf<null>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('is.inRange', t => {
|
test('is.inRange', t => {
|
||||||
const x = 3;
|
const x = 3;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue