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
|
//=> 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)
|
##### .all(predicate, ...values)
|
||||||
|
|
||||||
Returns `true` if **all** of the input `values` returns true in the `predicate`:
|
Returns `true` if **all** of the input `values` returns true in the `predicate`:
|
||||||
|
|
@ -587,6 +612,28 @@ is.all(is.string, '🦄', [], 'unicorns');
|
||||||
//=> false
|
//=> 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)
|
##### .optional(value, predicate)
|
||||||
|
|
||||||
Returns `true` if `value` is `undefined` or satisfies the given `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;
|
return (value: unknown): value is number => isInteger(value) && Math.abs(value % 2) === remainder;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAll(predicate: Predicate, ...values: unknown[]): boolean {
|
type TypeGuard<T> = (value: unknown) => value is T;
|
||||||
return predicateOnArray(Array.prototype.every, predicate, values);
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const predicate of predicateArray) {
|
||||||
|
if (!isFunction(predicate)) {
|
||||||
|
throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAny(predicate: Predicate | Predicate[], ...values: unknown[]): boolean {
|
// Predicate factory overloads - return a type guard when called with only predicates
|
||||||
const predicates = isArray(predicate) ? predicate : [predicate];
|
export function isAll<T1>(predicates: [TypeGuard<T1>]): TypeGuard<T1>;
|
||||||
return predicates.some(singlePredicate =>
|
export function isAll<T1, T2>(predicates: [TypeGuard<T1>, TypeGuard<T2>]): TypeGuard<T1 & T2>;
|
||||||
predicateOnArray(Array.prototype.some, singlePredicate, values),
|
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 {
|
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);
|
return Boolean(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
type TypeGuard<T> = (value: unknown) => value is T;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
type ResolveTypesOfTypeGuardsTuple<TypeGuardsOfT, ResultOfT extends unknown[] = [] > =
|
type ResolveTypesOfTypeGuardsTuple<TypeGuardsOfT, ResultOfT extends unknown[] = [] > =
|
||||||
TypeGuardsOfT extends [TypeGuard<infer U>, ...infer TOthers]
|
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;
|
inRange: (value: number, range: number | [number, number], message?: string) => asserts value is number;
|
||||||
|
|
||||||
// Variadic functions.
|
// Variadic functions.
|
||||||
any: (predicate: Predicate | Predicate[], ...values: unknown[]) => void | never;
|
any: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never;
|
||||||
all: (predicate: Predicate, ...values: unknown[]) => void | never;
|
all: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Asserts that `value` is `undefined` or satisfies the provided `assertion`.
|
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);
|
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)) {
|
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));
|
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)) {
|
if (!isAny(predicate, ...values)) {
|
||||||
const predicates = isArray(predicate) ? predicate : [predicate];
|
const predicates = Array.isArray(predicate) ? predicate as readonly Predicate[] : [predicate as Predicate];
|
||||||
const expectedTypes = predicates.map(predicate => isIsMethodName(predicate.name) ? methodTypeMap[predicate.name] : 'predicate returns truthy for any value');
|
const expectedTypes = predicates.map(singlePredicate => isIsMethodName(singlePredicate.name) ? methodTypeMap[singlePredicate.name] : 'predicate returns truthy for any value');
|
||||||
throw new TypeError(typeErrorMessageMultipleValues(expectedTypes, values));
|
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.false(is.any(is.integer, true, 'lol', {}));
|
||||||
t.true(is.any([is.string, is.number], {}, true, '🦄'));
|
t.true(is.any([is.string, is.number], {}, true, '🦄'));
|
||||||
t.false(is.any([is.boolean, is.number], 'unicorns', [], new Map()));
|
t.false(is.any([is.boolean, is.number], 'unicorns', [], new Map()));
|
||||||
|
t.is(typeof is.any([is.string, is.number]), 'function');
|
||||||
|
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
is.any(null as any, true);
|
is.any(null as any, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
t.throws(() => {
|
||||||
|
is.any([], 'value');
|
||||||
|
});
|
||||||
|
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
is.any(is.string);
|
is.any(is.string);
|
||||||
});
|
});
|
||||||
|
|
@ -1666,6 +1671,10 @@ test('is.any', t => {
|
||||||
assert.any(is.object, false, {}, 'unicorns');
|
assert.any(is.object, false, {}, 'unicorns');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
t.throws(() => {
|
||||||
|
assert.any([is.string, is.number]);
|
||||||
|
});
|
||||||
|
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
assert.any(is.boolean, '🦄', [], 3);
|
assert.any(is.boolean, '🦄', [], 3);
|
||||||
});
|
});
|
||||||
|
|
@ -1679,6 +1688,10 @@ test('is.any', t => {
|
||||||
assert.any(null as any, true);
|
assert.any(null as any, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
t.throws(() => {
|
||||||
|
assert.any([], 'value');
|
||||||
|
});
|
||||||
|
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
assert.any(is.string);
|
assert.any(is.string);
|
||||||
});
|
});
|
||||||
|
|
@ -1726,12 +1739,18 @@ test('is.all', t => {
|
||||||
t.false(is.all(is.set, new Map(), {}));
|
t.false(is.all(is.set, new Map(), {}));
|
||||||
|
|
||||||
t.true(is.all(is.array, ['1'], ['2']));
|
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(() => {
|
t.throws(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
is.all(null as any, true);
|
is.all(null as any, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
t.throws(() => {
|
||||||
|
is.all([], 'value');
|
||||||
|
});
|
||||||
|
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
is.all(is.string);
|
is.all(is.string);
|
||||||
});
|
});
|
||||||
|
|
@ -1744,10 +1763,22 @@ test('is.all', t => {
|
||||||
assert.all(is.boolean, true, false);
|
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(() => {
|
t.throws(() => {
|
||||||
assert.all(is.string, '🦄', []);
|
assert.all(is.string, '🦄', []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
t.throws(() => {
|
||||||
|
assert.all([is.string, is.number], '🦄');
|
||||||
|
});
|
||||||
|
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
assert.all(is.set, new Map(), {});
|
assert.all(is.set, new Map(), {});
|
||||||
});
|
});
|
||||||
|
|
@ -1757,6 +1788,10 @@ test('is.all', t => {
|
||||||
assert.all(null as any, true);
|
assert.all(null as any, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
t.throws(() => {
|
||||||
|
assert.all([], 'value');
|
||||||
|
});
|
||||||
|
|
||||||
t.throws(() => {
|
t.throws(() => {
|
||||||
assert.all(is.string);
|
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 => {
|
test('is.formData supplemental', t => {
|
||||||
const data = new window.FormData();
|
const data = new window.FormData();
|
||||||
t.true(is.formData(data));
|
t.true(is.formData(data));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue