Skip to content

Commit

Permalink
Merge pull request #31 from Milly/isallof
Browse files Browse the repository at this point in the history
👍 Add `isAllOf`
  • Loading branch information
lambdalisue authored Jul 25, 2023
2 parents a7055ac + 548be23 commit e00eee8
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 8 deletions.
41 changes: 33 additions & 8 deletions is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ export type ObjectOf<T extends RecordOf<Predicate<unknown>>> = 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;
* }
* ```
*/
Expand Down Expand Up @@ -255,9 +255,7 @@ export function isSymbol(x: unknown): x is symbol {
return typeof x === "symbol";
}

export type OneOf<T> = T extends (infer U)[]
? T extends Predicate<infer U>[] ? U : T
: T;
export type OneOf<T> = T extends Predicate<infer U>[] ? U : never;

/**
* Return a type predicate function that returns `true` if the type of `x` is `OneOf<T>`.
Expand All @@ -266,9 +264,9 @@ export type OneOf<T> = 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;
* }
* ```
Expand All @@ -279,6 +277,32 @@ export function isOneOf<T extends readonly Predicate<unknown>[]>(
return (x: unknown): x is OneOf<T> => preds.some((pred) => pred(x));
}

type UnionToIntersection<U> =
(U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void)
? I
: never;
export type AllOf<T> = UnionToIntersection<OneOf<T>>;

/**
* Return a type predicate function that returns `true` if the type of `x` is `AllOf<T>`.
*
* ```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<T extends readonly Predicate<unknown>[]>(
preds: T,
): Predicate<AllOf<T>> {
return (x: unknown): x is AllOf<T> => preds.every((pred) => pred(x));
}

export type OptionalPredicate<T> = Predicate<T | undefined> & {
optional: true;
};
Expand All @@ -291,7 +315,7 @@ export type OptionalPredicate<T> = Predicate<T | undefined> & {
*
* 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;
* }
* ```
Expand Down Expand Up @@ -323,5 +347,6 @@ export default {
Nullish: isNullish,
Symbol: isSymbol,
OneOf: isOneOf,
AllOf: isAllOf,
OptionalOf: isOptionalOf,
};
21 changes: 21 additions & 0 deletions is_bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => {
Expand Down
40 changes: 40 additions & 0 deletions is_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
IsExact,
} from "https://deno.land/[email protected]/testing/types.ts";
import is, {
isAllOf,
isArray,
isArrayOf,
isBigInt,
Expand Down Expand Up @@ -442,6 +443,45 @@ Deno.test("isOneOf<T>", async (t) => {
});
});

Deno.test("isAllOf<T>", 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<IsExact<typeof a, { a: number; b: string }>>;
}
});
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<T>", async (t) => {
await t.step("returns proper type predicate", () => {
const a: unknown = undefined;
Expand Down

0 comments on commit e00eee8

Please sign in to comment.