diff --git a/is.ts b/is.ts index 492a233..15a693e 100644 --- a/is.ts +++ b/is.ts @@ -177,8 +177,8 @@ export type ObjectOf>> = FlatType< * }; * const a: unknown = { a: 0, b: "a" }; * if (is.ObjectOf(predObj)(a)) { - * // a is narrowed to { a: number, b: string, c?: boolean } - * const _: { a: number, b: string, c?: boolean } = a; + * // a is narrowed to { a: number; b: string; c?: boolean } + * const _: { a: number; b: string; c?: boolean } = a; * } * ``` */ @@ -255,9 +255,7 @@ export function isSymbol(x: unknown): x is symbol { return typeof x === "symbol"; } -export type OneOf = T extends (infer U)[] - ? T extends Predicate[] ? U : T - : T; +export type OneOf = T extends Predicate[] ? U : never; /** * Return a type predicate function that returns `true` if the type of `x` is `OneOf`. @@ -266,9 +264,9 @@ export type OneOf = T extends (infer U)[] * import is from "./is.ts"; * * const preds = [is.Number, is.String, is.Boolean]; - * const a: unknown = { a: 0, b: "a", c: true }; + * const a: unknown = 0; * if (is.OneOf(preds)(a)) { - * // a is narrowed to number | string | boolean; + * // a is narrowed to number | string | boolean * const _: number | string | boolean = a; * } * ``` @@ -279,6 +277,32 @@ export function isOneOf[]>( return (x: unknown): x is OneOf => preds.some((pred) => pred(x)); } +type UnionToIntersection = + (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) + ? I + : never; +export type AllOf = UnionToIntersection>; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `AllOf`. + * + * ```ts + * import is from "./is.ts"; + * + * const preds = [is.ObjectOf({ a: is.Number }), is.ObjectOf({ b: is.String })]; + * const a: unknown = { a: 0, b: "a" }; + * if (is.AllOf(preds)(a)) { + * // a is narrowed to { a: number; b: string } + * const _: { a: number; b: string } = a; + * } + * ``` + */ +export function isAllOf[]>( + preds: T, +): Predicate> { + return (x: unknown): x is AllOf => preds.every((pred) => pred(x)); +} + export type OptionalPredicate = Predicate & { optional: true; }; @@ -291,7 +315,7 @@ export type OptionalPredicate = Predicate & { * * const a: unknown = "a"; * if (is.OptionalOf(is.String)(a)) { - * // a is narrowed to string | undefined; + * // a is narrowed to string | undefined * const _: string | undefined = a; * } * ``` @@ -323,5 +347,6 @@ export default { Nullish: isNullish, Symbol: isSymbol, OneOf: isOneOf, + AllOf: isAllOf, OptionalOf: isOptionalOf, }; diff --git a/is_bench.ts b/is_bench.ts index d1a0404..2c5bff9 100644 --- a/is_bench.ts +++ b/is_bench.ts @@ -267,6 +267,27 @@ Deno.bench({ }, }); +const predsAll = [is.String, is.Number, is.Boolean] as const; +Deno.bench({ + name: "is.AllOf", + fn: () => { + const pred = is.AllOf(predsAll); + for (const c of cs) { + pred(c); + } + }, +}); + +const isAllOfPred = is.AllOf(predsAll); +Deno.bench({ + name: "is.AllOf (pre)", + fn: () => { + for (const c of cs) { + isAllOfPred(c); + } + }, +}); + Deno.bench({ name: "is.OptionalOf", fn: () => { diff --git a/is_test.ts b/is_test.ts index 55fb6a4..70a03a4 100644 --- a/is_test.ts +++ b/is_test.ts @@ -7,6 +7,7 @@ import type { IsExact, } from "https://deno.land/std@0.192.0/testing/types.ts"; import is, { + isAllOf, isArray, isArrayOf, isBigInt, @@ -442,6 +443,45 @@ Deno.test("isOneOf", async (t) => { }); }); +Deno.test("isAllOf", async (t) => { + await t.step("returns proper type predicate", () => { + const preds = [ + is.ObjectOf({ a: is.Number }), + is.ObjectOf({ b: is.String }), + ]; + const a: unknown = { a: 0, b: "a" }; + if (isAllOf(preds)(a)) { + type _ = AssertTrue>; + } + }); + await t.step("returns true on all of T", () => { + const preds = [ + is.ObjectOf({ a: is.Number }), + is.ObjectOf({ b: is.String }), + ]; + assertEquals(isAllOf(preds)({ a: 0, b: "a" }), true); + }); + await t.step("returns false on non of T", async (t) => { + const preds = [ + is.ObjectOf({ a: is.Number }), + is.ObjectOf({ b: is.String }), + ]; + assertEquals( + isAllOf(preds)({ a: 0, b: 0 }), + false, + "Some properties has wrong type", + ); + assertEquals( + isAllOf(preds)({ a: 0 }), + false, + "Some properties does not exists", + ); + await testWithExamples(t, isAllOf(preds), { + excludeExamples: ["record"], + }); + }); +}); + Deno.test("isOptionalOf", async (t) => { await t.step("returns proper type predicate", () => { const a: unknown = undefined;