parent
fbcc68e139
commit
9bdcd9b57f
3 changed files with 245 additions and 16 deletions
47
readme.md
47
readme.md
|
|
@ -575,6 +575,31 @@ is.any([is.boolean, is.number], 'unicorns', [], new Map());
|
|||
//=> false
|
||||
```
|
||||
|
||||
##### .any(predicate[])
|
||||
|
||||
Using an array of `predicate[]` without values, returns a combined type guard that checks if a value matches **any** of the predicates:
|
||||
|
||||
```js
|
||||
const isStringOrNumber = is.any([is.string, is.number]);
|
||||
|
||||
isStringOrNumber('hello');
|
||||
//=> true
|
||||
|
||||
isStringOrNumber(123);
|
||||
//=> true
|
||||
|
||||
isStringOrNumber(true);
|
||||
//=> false
|
||||
```
|
||||
|
||||
This is useful for composing with other methods like `is.optional`:
|
||||
|
||||
```js
|
||||
is.optional(value, is.any([is.string, is.number]));
|
||||
```
|
||||
|
||||
An empty predicate array currently returns a predicate that always returns `false`. This will throw in the next major release.
|
||||
|
||||
##### .all(predicate, ...values)
|
||||
|
||||
Returns `true` if **all** of the input `values` returns true in the `predicate`:
|
||||
|
|
@ -587,6 +612,28 @@ is.all(is.string, '🦄', [], 'unicorns');
|
|||
//=> false
|
||||
```
|
||||
|
||||
##### .all(predicate[])
|
||||
|
||||
Using an array of `predicate[]` without values, returns a combined type guard that checks if a value matches **all** of the predicates:
|
||||
|
||||
```js
|
||||
const isArrayAndNonEmpty = is.all([is.array, is.nonEmptyArray]);
|
||||
|
||||
isArrayAndNonEmpty(['hello']);
|
||||
//=> true
|
||||
|
||||
isArrayAndNonEmpty([]);
|
||||
//=> false
|
||||
```
|
||||
|
||||
This is useful for composing with other methods like `is.optional`:
|
||||
|
||||
```js
|
||||
is.optional(value, is.all([is.object, is.plainObject]));
|
||||
```
|
||||
|
||||
An empty predicate array currently returns a predicate that always returns `true`. This will throw in the next major release.
|
||||
|
||||
##### .optional(value, predicate)
|
||||
|
||||
Returns `true` if `value` is `undefined` or satisfies the given `predicate`.
|
||||
|
|
|
|||
101
source/index.ts
101
source/index.ts
|
|
@ -332,15 +332,77 @@ function isAbsoluteModule2(remainder: 0 | 1) {
|
|||
return (value: unknown): value is number => isInteger(value) && Math.abs(value % 2) === remainder;
|
||||
}
|
||||
|
||||
export function isAll(predicate: Predicate, ...values: unknown[]): boolean {
|
||||
return predicateOnArray(Array.prototype.every, predicate, values);
|
||||
type TypeGuard<T> = (value: unknown) => value is T;
|
||||
|
||||
function validatePredicateArray(predicateArray: readonly Predicate[], allowEmpty: boolean) {
|
||||
if (predicateArray.length === 0) {
|
||||
if (allowEmpty) {
|
||||
// Next major release: throw for empty predicate arrays to avoid vacuous results.
|
||||
// throw new TypeError('Invalid predicate array');
|
||||
} else {
|
||||
throw new TypeError('Invalid predicate array');
|
||||
}
|
||||
|
||||
export function isAny(predicate: Predicate | Predicate[], ...values: unknown[]): boolean {
|
||||
const predicates = isArray(predicate) ? predicate : [predicate];
|
||||
return predicates.some(singlePredicate =>
|
||||
predicateOnArray(Array.prototype.some, singlePredicate, values),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const predicate of predicateArray) {
|
||||
if (!isFunction(predicate)) {
|
||||
throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Predicate factory overloads - return a type guard when called with only predicates
|
||||
export function isAll<T1>(predicates: [TypeGuard<T1>]): TypeGuard<T1>;
|
||||
export function isAll<T1, T2>(predicates: [TypeGuard<T1>, TypeGuard<T2>]): TypeGuard<T1 & T2>;
|
||||
export function isAll<T1, T2, T3>(predicates: [TypeGuard<T1>, TypeGuard<T2>, TypeGuard<T3>]): TypeGuard<T1 & T2 & T3>;
|
||||
export function isAll<T1, T2, T3, T4>(predicates: [TypeGuard<T1>, TypeGuard<T2>, TypeGuard<T3>, TypeGuard<T4>]): TypeGuard<T1 & T2 & T3 & T4>;
|
||||
export function isAll<T1, T2, T3, T4, T5>(predicates: [TypeGuard<T1>, TypeGuard<T2>, TypeGuard<T3>, TypeGuard<T4>, TypeGuard<T5>]): TypeGuard<T1 & T2 & T3 & T4 & T5>;
|
||||
export function isAll(predicates: ReadonlyArray<TypeGuard<unknown>>): TypeGuard<unknown>;
|
||||
export function isAll(predicates: readonly Predicate[]): Predicate;
|
||||
// Evaluator overload - check if all values match the predicate
|
||||
export function isAll(predicate: Predicate | readonly Predicate[], ...values: unknown[]): boolean;
|
||||
export function isAll(predicate: Predicate | readonly Predicate[], ...values: unknown[]): boolean | Predicate {
|
||||
if (Array.isArray(predicate)) {
|
||||
const predicateArray = predicate as readonly Predicate[];
|
||||
validatePredicateArray(predicateArray, values.length === 0);
|
||||
|
||||
const combinedPredicate = (value: unknown) => predicateArray.every(singlePredicate => singlePredicate(value));
|
||||
if (values.length === 0) {
|
||||
return combinedPredicate;
|
||||
}
|
||||
|
||||
return predicateOnArray(Array.prototype.every, combinedPredicate, values);
|
||||
}
|
||||
|
||||
return predicateOnArray(Array.prototype.every, predicate as Predicate, values);
|
||||
}
|
||||
|
||||
// Predicate factory overloads - return a type guard when called with only predicates
|
||||
export function isAny<T1>(predicates: [TypeGuard<T1>]): TypeGuard<T1>;
|
||||
export function isAny<T1, T2>(predicates: [TypeGuard<T1>, TypeGuard<T2>]): TypeGuard<T1 | T2>;
|
||||
export function isAny<T1, T2, T3>(predicates: [TypeGuard<T1>, TypeGuard<T2>, TypeGuard<T3>]): TypeGuard<T1 | T2 | T3>;
|
||||
export function isAny<T1, T2, T3, T4>(predicates: [TypeGuard<T1>, TypeGuard<T2>, TypeGuard<T3>, TypeGuard<T4>]): TypeGuard<T1 | T2 | T3 | T4>;
|
||||
export function isAny<T1, T2, T3, T4, T5>(predicates: [TypeGuard<T1>, TypeGuard<T2>, TypeGuard<T3>, TypeGuard<T4>, TypeGuard<T5>]): TypeGuard<T1 | T2 | T3 | T4 | T5>;
|
||||
export function isAny(predicates: ReadonlyArray<TypeGuard<unknown>>): TypeGuard<unknown>;
|
||||
export function isAny(predicates: readonly Predicate[]): Predicate;
|
||||
// Evaluator overload - check if any value matches any predicate
|
||||
export function isAny(predicate: Predicate | readonly Predicate[], ...values: unknown[]): boolean;
|
||||
export function isAny(predicate: Predicate | readonly Predicate[], ...values: unknown[]): boolean | Predicate {
|
||||
if (Array.isArray(predicate)) {
|
||||
const predicateArray = predicate as readonly Predicate[];
|
||||
validatePredicateArray(predicateArray, values.length === 0);
|
||||
|
||||
const combinedPredicate = (value: unknown) => predicateArray.some(singlePredicate => singlePredicate(value));
|
||||
if (values.length === 0) {
|
||||
return combinedPredicate;
|
||||
}
|
||||
|
||||
return predicateOnArray(Array.prototype.some, combinedPredicate, values);
|
||||
}
|
||||
|
||||
return predicateOnArray(Array.prototype.some, predicate as Predicate, values);
|
||||
}
|
||||
|
||||
export function isOptional<T>(value: unknown, predicate: (value: unknown) => value is T): value is T | undefined {
|
||||
|
|
@ -715,8 +777,6 @@ export function isTruthy<T>(value: T | Falsy): value is T {
|
|||
return Boolean(value);
|
||||
}
|
||||
|
||||
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]
|
||||
|
|
@ -943,8 +1003,8 @@ type Assert = {
|
|||
inRange: (value: number, range: number | [number, number], message?: string) => asserts value is number;
|
||||
|
||||
// Variadic functions.
|
||||
any: (predicate: Predicate | Predicate[], ...values: unknown[]) => void | never;
|
||||
all: (predicate: Predicate, ...values: unknown[]) => void | never;
|
||||
any: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never;
|
||||
all: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never;
|
||||
|
||||
/**
|
||||
Asserts that `value` is `undefined` or satisfies the provided `assertion`.
|
||||
|
|
@ -1146,17 +1206,26 @@ function isIsMethodName(value: unknown): value is IsMethodName {
|
|||
return isMethodNames.includes(value as IsMethodName);
|
||||
}
|
||||
|
||||
export function assertAll(predicate: Predicate, ...values: unknown[]): void | never {
|
||||
export function assertAll(predicate: Predicate | readonly Predicate[], ...values: unknown[]): void | never {
|
||||
if (values.length === 0) {
|
||||
throw new TypeError('Invalid number of values');
|
||||
}
|
||||
|
||||
if (!isAll(predicate, ...values)) {
|
||||
const expectedType = isIsMethodName(predicate.name) ? methodTypeMap[predicate.name] : 'predicate returns truthy for all values';
|
||||
const predicateFunction = predicate as Predicate;
|
||||
const expectedType = !Array.isArray(predicate) && isIsMethodName(predicateFunction.name) ? methodTypeMap[predicateFunction.name] : 'predicate returns truthy for all values';
|
||||
throw new TypeError(typeErrorMessageMultipleValues(expectedType, values));
|
||||
}
|
||||
}
|
||||
|
||||
export function assertAny(predicate: Predicate | Predicate[], ...values: unknown[]): void | never {
|
||||
export function assertAny(predicate: Predicate | readonly Predicate[], ...values: unknown[]): void | never {
|
||||
if (values.length === 0) {
|
||||
throw new TypeError('Invalid number of values');
|
||||
}
|
||||
|
||||
if (!isAny(predicate, ...values)) {
|
||||
const predicates = isArray(predicate) ? predicate : [predicate];
|
||||
const expectedTypes = predicates.map(predicate => isIsMethodName(predicate.name) ? methodTypeMap[predicate.name] : 'predicate returns truthy for any value');
|
||||
const predicates = Array.isArray(predicate) ? predicate as readonly Predicate[] : [predicate as Predicate];
|
||||
const expectedTypes = predicates.map(singlePredicate => isIsMethodName(singlePredicate.name) ? methodTypeMap[singlePredicate.name] : 'predicate returns truthy for any value');
|
||||
throw new TypeError(typeErrorMessageMultipleValues(expectedTypes, values));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
113
test/test.ts
113
test/test.ts
|
|
@ -1648,12 +1648,17 @@ test('is.any', t => {
|
|||
t.false(is.any(is.integer, true, 'lol', {}));
|
||||
t.true(is.any([is.string, is.number], {}, true, '🦄'));
|
||||
t.false(is.any([is.boolean, is.number], 'unicorns', [], new Map()));
|
||||
t.is(typeof is.any([is.string, is.number]), 'function');
|
||||
|
||||
t.throws(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
is.any(null as any, true);
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
is.any([], 'value');
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
is.any(is.string);
|
||||
});
|
||||
|
|
@ -1666,6 +1671,10 @@ test('is.any', t => {
|
|||
assert.any(is.object, false, {}, 'unicorns');
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
assert.any([is.string, is.number]);
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
assert.any(is.boolean, '🦄', [], 3);
|
||||
});
|
||||
|
|
@ -1679,6 +1688,10 @@ test('is.any', t => {
|
|||
assert.any(null as any, true);
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
assert.any([], 'value');
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
assert.any(is.string);
|
||||
});
|
||||
|
|
@ -1726,12 +1739,18 @@ test('is.all', t => {
|
|||
t.false(is.all(is.set, new Map(), {}));
|
||||
|
||||
t.true(is.all(is.array, ['1'], ['2']));
|
||||
t.true(is.all([is.string, is.nonEmptyString], '🦄', 'unicorns'));
|
||||
t.false(is.all([is.string, is.number], '🦄'));
|
||||
|
||||
t.throws(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
is.all(null as any, true);
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
is.all([], 'value');
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
is.all(is.string);
|
||||
});
|
||||
|
|
@ -1744,10 +1763,22 @@ test('is.all', t => {
|
|||
assert.all(is.boolean, true, false);
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
assert.all([is.string, is.number]);
|
||||
});
|
||||
|
||||
t.notThrows(() => {
|
||||
assert.all([is.string, is.nonEmptyString], '🦄', 'unicorns');
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
assert.all(is.string, '🦄', []);
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
assert.all([is.string, is.number], '🦄');
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
assert.all(is.set, new Map(), {});
|
||||
});
|
||||
|
|
@ -1757,6 +1788,10 @@ test('is.all', t => {
|
|||
assert.all(null as any, true);
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
assert.all([], 'value');
|
||||
});
|
||||
|
||||
t.throws(() => {
|
||||
assert.all(is.string);
|
||||
});
|
||||
|
|
@ -1783,6 +1818,84 @@ test('is.all', t => {
|
|||
});
|
||||
});
|
||||
|
||||
test('is.any as predicate factory', t => {
|
||||
// Returns a type guard function when called with only predicates
|
||||
const isStringOrNumber = is.any([is.string, is.number]);
|
||||
t.is(typeof isStringOrNumber, 'function');
|
||||
t.true(isStringOrNumber('hello'));
|
||||
t.true(isStringOrNumber(123));
|
||||
t.false(isStringOrNumber(true));
|
||||
t.false(isStringOrNumber({}));
|
||||
|
||||
// Type narrowing works correctly
|
||||
const value: unknown = 'test';
|
||||
if (isStringOrNumber(value)) {
|
||||
// TypeScript should narrow to string | number
|
||||
const narrowed: string | number = value;
|
||||
t.pass(`narrowed to: ${typeof narrowed}`);
|
||||
}
|
||||
|
||||
// Works with is.optional
|
||||
t.true(is.optional(undefined, is.any([is.string, is.number])));
|
||||
t.true(is.optional('test', is.any([is.string, is.number])));
|
||||
t.true(is.optional(42, is.any([is.string, is.number])));
|
||||
t.false(is.optional(true, is.any([is.string, is.number])));
|
||||
|
||||
const predicateArray: Predicate[] = [is.string, is.number];
|
||||
const isStringOrNumberFromArray = is.any(predicateArray);
|
||||
t.is(typeof isStringOrNumberFromArray, 'function');
|
||||
t.true(isStringOrNumberFromArray('hello'));
|
||||
t.true(isStringOrNumberFromArray(123));
|
||||
t.false(isStringOrNumberFromArray(true));
|
||||
|
||||
// Type narrowing with is.optional
|
||||
const optionalValue: unknown = undefined;
|
||||
if (is.optional(optionalValue, is.any([is.string, is.number]))) {
|
||||
// TypeScript should narrow to string | number | undefined
|
||||
const narrowed: string | number | undefined = optionalValue;
|
||||
t.pass(`optional narrowed to: ${typeof narrowed}`);
|
||||
}
|
||||
|
||||
// Works with more predicates
|
||||
const isStringOrNumberOrBoolean = is.any([is.string, is.number, is.boolean]);
|
||||
t.true(isStringOrNumberOrBoolean('hello'));
|
||||
t.true(isStringOrNumberOrBoolean(123));
|
||||
t.true(isStringOrNumberOrBoolean(true));
|
||||
t.false(isStringOrNumberOrBoolean({}));
|
||||
|
||||
t.throws(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
is.any([is.string, 123 as any]);
|
||||
});
|
||||
});
|
||||
|
||||
test('is.all as predicate factory', t => {
|
||||
// Returns a type guard function when called with only predicates
|
||||
const isArrayAndNonEmpty = is.all([is.array, is.nonEmptyArray]);
|
||||
t.is(typeof isArrayAndNonEmpty, 'function');
|
||||
t.true(isArrayAndNonEmpty(['hello']));
|
||||
t.false(isArrayAndNonEmpty([]));
|
||||
t.false(isArrayAndNonEmpty('hello'));
|
||||
|
||||
// Type narrowing works correctly
|
||||
const value: unknown = ['test'];
|
||||
if (isArrayAndNonEmpty(value)) {
|
||||
// TypeScript should narrow to the intersection type
|
||||
t.true(Array.isArray(value));
|
||||
t.true(value.length > 0);
|
||||
}
|
||||
|
||||
// Works with is.optional
|
||||
t.true(is.optional(undefined, is.all([is.object, is.plainObject])));
|
||||
t.true(is.optional({foo: 'bar'}, is.all([is.object, is.plainObject])));
|
||||
t.false(is.optional([], is.all([is.object, is.plainObject])));
|
||||
|
||||
t.throws(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
is.all([is.string, 123 as any]);
|
||||
});
|
||||
});
|
||||
|
||||
test('is.formData supplemental', t => {
|
||||
const data = new window.FormData();
|
||||
t.true(is.formData(data));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue