Skip to content

Commit 349a6f6

Browse files
committed
👍 Add isTupleOf<T>
1 parent 54b8a5c commit 349a6f6

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

is.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,35 @@ export function isArrayOf<T>(
4242
return (x: unknown): x is T[] => isArray(x) && x.every(pred);
4343
}
4444

45+
export type TupleOf<T extends readonly Predicate<unknown>[]> = {
46+
-readonly [P in keyof T]: T[P] extends Predicate<infer U> ? U : never;
47+
};
48+
49+
/**
50+
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>`.
51+
*
52+
* ```ts
53+
* import is from "./is.ts";
54+
*
55+
* const predTup = [is.Number, is.String, is.Boolean] as const;
56+
* const a: unknown = [0, "a", true];
57+
* if (is.TupleOf(predTup)(a)) {
58+
* // a is narrowed to [number, string, boolean]
59+
* const _: [number, string, boolean] = a;
60+
* }
61+
* ```
62+
*/
63+
export function isTupleOf<T extends readonly Predicate<unknown>[]>(
64+
predTup: T,
65+
): Predicate<TupleOf<T>> {
66+
return (x: unknown): x is TupleOf<T> => {
67+
if (!isArray(x) || x.length !== predTup.length) {
68+
return false;
69+
}
70+
return predTup.every((pred, i) => pred(x[i]));
71+
};
72+
}
73+
4574
/**
4675
* Synonym of `Record<string | number | symbol, T>`
4776
*/
@@ -103,6 +132,7 @@ export default {
103132
Boolean: isBoolean,
104133
Array: isArray,
105134
ArrayOf: isArrayOf,
135+
TupleOf: isTupleOf,
106136
Record: isRecord,
107137
RecordOf: isRecordOf,
108138
Function: isFunction,

is_test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import is, {
1313
isRecord,
1414
isRecordOf,
1515
isString,
16+
isTupleOf,
1617
isUndefined,
1718
} from "./is.ts";
1819

@@ -22,6 +23,7 @@ Deno.test("is defines aliases of functions", () => {
2223
assertStrictEquals(is.Boolean, isBoolean);
2324
assertStrictEquals(is.Array, isArray);
2425
assertStrictEquals(is.ArrayOf, isArrayOf);
26+
assertStrictEquals(is.TupleOf, isTupleOf);
2527
assertStrictEquals(is.Record, isRecord);
2628
assertStrictEquals(is.RecordOf, isRecordOf);
2729
assertStrictEquals(is.Function, isFunction);
@@ -112,6 +114,26 @@ Deno.test("isArrayOf<T>", async (t) => {
112114
});
113115
});
114116

117+
Deno.test("isTupleOf<T>", async (t) => {
118+
await t.step("returns true on T tuple", () => {
119+
const predTup = [isNumber, isString, isBoolean] as const;
120+
assertEquals(isTupleOf(predTup)([0, "a", true]), true);
121+
});
122+
await t.step("returns false on non T tuple", () => {
123+
const predTup = [isNumber, isString, isBoolean] as const;
124+
assertEquals(isTupleOf(predTup)([0, 1, 2]), false);
125+
assertEquals(isTupleOf(predTup)([0, "a"]), false);
126+
assertEquals(isTupleOf(predTup)([0, "a", true, 0]), false);
127+
});
128+
await t.step("returns proper type predicate", () => {
129+
const predTup = [isNumber, isString, isBoolean] as const;
130+
const a: unknown = [0, "a", true];
131+
if (isTupleOf(predTup)(a)) {
132+
const _: [number, string, boolean] = a;
133+
}
134+
});
135+
});
136+
115137
Deno.test("isRecord", async (t) => {
116138
await t.step("returns true on record", () => {
117139
assertEquals(isRecord({}), true);

0 commit comments

Comments
 (0)