Skip to content

Commit e00eee8

Browse files
authored
Merge pull request #31 from Milly/isallof
👍 Add `isAllOf`
2 parents a7055ac + 548be23 commit e00eee8

File tree

3 files changed

+94
-8
lines changed

3 files changed

+94
-8
lines changed

is.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ export type ObjectOf<T extends RecordOf<Predicate<unknown>>> = FlatType<
177177
* };
178178
* const a: unknown = { a: 0, b: "a" };
179179
* if (is.ObjectOf(predObj)(a)) {
180-
* // a is narrowed to { a: number, b: string, c?: boolean }
181-
* const _: { a: number, b: string, c?: boolean } = a;
180+
* // a is narrowed to { a: number; b: string; c?: boolean }
181+
* const _: { a: number; b: string; c?: boolean } = a;
182182
* }
183183
* ```
184184
*/
@@ -255,9 +255,7 @@ export function isSymbol(x: unknown): x is symbol {
255255
return typeof x === "symbol";
256256
}
257257

258-
export type OneOf<T> = T extends (infer U)[]
259-
? T extends Predicate<infer U>[] ? U : T
260-
: T;
258+
export type OneOf<T> = T extends Predicate<infer U>[] ? U : never;
261259

262260
/**
263261
* Return a type predicate function that returns `true` if the type of `x` is `OneOf<T>`.
@@ -266,9 +264,9 @@ export type OneOf<T> = T extends (infer U)[]
266264
* import is from "./is.ts";
267265
*
268266
* const preds = [is.Number, is.String, is.Boolean];
269-
* const a: unknown = { a: 0, b: "a", c: true };
267+
* const a: unknown = 0;
270268
* if (is.OneOf(preds)(a)) {
271-
* // a is narrowed to number | string | boolean;
269+
* // a is narrowed to number | string | boolean
272270
* const _: number | string | boolean = a;
273271
* }
274272
* ```
@@ -279,6 +277,32 @@ export function isOneOf<T extends readonly Predicate<unknown>[]>(
279277
return (x: unknown): x is OneOf<T> => preds.some((pred) => pred(x));
280278
}
281279

280+
type UnionToIntersection<U> =
281+
(U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void)
282+
? I
283+
: never;
284+
export type AllOf<T> = UnionToIntersection<OneOf<T>>;
285+
286+
/**
287+
* Return a type predicate function that returns `true` if the type of `x` is `AllOf<T>`.
288+
*
289+
* ```ts
290+
* import is from "./is.ts";
291+
*
292+
* const preds = [is.ObjectOf({ a: is.Number }), is.ObjectOf({ b: is.String })];
293+
* const a: unknown = { a: 0, b: "a" };
294+
* if (is.AllOf(preds)(a)) {
295+
* // a is narrowed to { a: number; b: string }
296+
* const _: { a: number; b: string } = a;
297+
* }
298+
* ```
299+
*/
300+
export function isAllOf<T extends readonly Predicate<unknown>[]>(
301+
preds: T,
302+
): Predicate<AllOf<T>> {
303+
return (x: unknown): x is AllOf<T> => preds.every((pred) => pred(x));
304+
}
305+
282306
export type OptionalPredicate<T> = Predicate<T | undefined> & {
283307
optional: true;
284308
};
@@ -291,7 +315,7 @@ export type OptionalPredicate<T> = Predicate<T | undefined> & {
291315
*
292316
* const a: unknown = "a";
293317
* if (is.OptionalOf(is.String)(a)) {
294-
* // a is narrowed to string | undefined;
318+
* // a is narrowed to string | undefined
295319
* const _: string | undefined = a;
296320
* }
297321
* ```
@@ -323,5 +347,6 @@ export default {
323347
Nullish: isNullish,
324348
Symbol: isSymbol,
325349
OneOf: isOneOf,
350+
AllOf: isAllOf,
326351
OptionalOf: isOptionalOf,
327352
};

is_bench.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,27 @@ Deno.bench({
267267
},
268268
});
269269

270+
const predsAll = [is.String, is.Number, is.Boolean] as const;
271+
Deno.bench({
272+
name: "is.AllOf",
273+
fn: () => {
274+
const pred = is.AllOf(predsAll);
275+
for (const c of cs) {
276+
pred(c);
277+
}
278+
},
279+
});
280+
281+
const isAllOfPred = is.AllOf(predsAll);
282+
Deno.bench({
283+
name: "is.AllOf (pre)",
284+
fn: () => {
285+
for (const c of cs) {
286+
isAllOfPred(c);
287+
}
288+
},
289+
});
290+
270291
Deno.bench({
271292
name: "is.OptionalOf",
272293
fn: () => {

is_test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
IsExact,
88
} from "https://deno.land/std@0.192.0/testing/types.ts";
99
import is, {
10+
isAllOf,
1011
isArray,
1112
isArrayOf,
1213
isBigInt,
@@ -442,6 +443,45 @@ Deno.test("isOneOf<T>", async (t) => {
442443
});
443444
});
444445

446+
Deno.test("isAllOf<T>", async (t) => {
447+
await t.step("returns proper type predicate", () => {
448+
const preds = [
449+
is.ObjectOf({ a: is.Number }),
450+
is.ObjectOf({ b: is.String }),
451+
];
452+
const a: unknown = { a: 0, b: "a" };
453+
if (isAllOf(preds)(a)) {
454+
type _ = AssertTrue<IsExact<typeof a, { a: number; b: string }>>;
455+
}
456+
});
457+
await t.step("returns true on all of T", () => {
458+
const preds = [
459+
is.ObjectOf({ a: is.Number }),
460+
is.ObjectOf({ b: is.String }),
461+
];
462+
assertEquals(isAllOf(preds)({ a: 0, b: "a" }), true);
463+
});
464+
await t.step("returns false on non of T", async (t) => {
465+
const preds = [
466+
is.ObjectOf({ a: is.Number }),
467+
is.ObjectOf({ b: is.String }),
468+
];
469+
assertEquals(
470+
isAllOf(preds)({ a: 0, b: 0 }),
471+
false,
472+
"Some properties has wrong type",
473+
);
474+
assertEquals(
475+
isAllOf(preds)({ a: 0 }),
476+
false,
477+
"Some properties does not exists",
478+
);
479+
await testWithExamples(t, isAllOf(preds), {
480+
excludeExamples: ["record"],
481+
});
482+
});
483+
});
484+
445485
Deno.test("isOptionalOf<T>", async (t) => {
446486
await t.step("returns proper type predicate", () => {
447487
const a: unknown = undefined;

0 commit comments

Comments
 (0)