Skip to content

Commit 8d5b670

Browse files
committed
feat[isTupleOf]: add overload to leading rest elements
1 parent bd69b53 commit 8d5b670

File tree

3 files changed

+133
-3
lines changed

3 files changed

+133
-3
lines changed

is/__snapshots__/tuple_of_test.ts.snap

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,28 @@ snapshot[`isTupleOf<T, R> > returns properly named predicate function 3`] = `
4444
])"
4545
`;
4646
47+
snapshot[`isTupleOf<R, T> > returns properly named predicate function 1`] = `
48+
"isTupleOf(isArray, [
49+
isNumber,
50+
isString,
51+
isBoolean
52+
])"
53+
`;
54+
55+
snapshot[`isTupleOf<R, T> > returns properly named predicate function 2`] = `"isTupleOf(isArrayOf(isString), [(anonymous)])"`;
56+
57+
snapshot[`isTupleOf<R, T> > returns properly named predicate function 3`] = `
58+
"isTupleOf([
59+
isTupleOf(isArray, [
60+
isTupleOf(isArray, [
61+
isNumber,
62+
isString,
63+
isBoolean
64+
])
65+
])
66+
])"
67+
`;
68+
4769
snapshot[`isTupleOf<T, R, L> > returns properly named predicate function 1`] = `
4870
"isTupleOf([
4971
isNumber,

is/tuple_of.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { isArray } from "./array.ts";
1919
* }
2020
* ```
2121
*
22-
* With `predRest` to represent rest elements:
22+
* With `predRest` to represent rest elements or leading rest elements:
2323
*
2424
* ```ts
2525
* import { is } from "@core/unknownutil";
@@ -32,6 +32,14 @@ import { isArray } from "./array.ts";
3232
* if (isMyType(a)) {
3333
* const _: [number, string, boolean, ...number[]] = a;
3434
* }
35+
*
36+
* const isMyTypeLeadingRest = is.TupleOf(
37+
* is.ArrayOf(is.Number),
38+
* [is.Number, is.String, is.Boolean],
39+
* );
40+
* if (isMyTypeLeadingRest(a)) {
41+
* const _: [...number[], number, string, boolean] = a;
42+
* }
3543
* ```
3644
*
3745
* With `predRest` and `predTrail` to represent middle rest elements:
@@ -78,6 +86,14 @@ export function isTupleOf<
7886
predRest: R,
7987
): Predicate<[...TupleOf<T>, ...PredicateType<R>]>;
8088

89+
export function isTupleOf<
90+
R extends Predicate<unknown[]>,
91+
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
92+
>(
93+
predRest: R,
94+
predTup: T,
95+
): Predicate<[...PredicateType<R>, ...TupleOf<T>]>;
96+
8197
export function isTupleOf<
8298
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
8399
R extends Predicate<unknown[]>,
@@ -93,14 +109,34 @@ export function isTupleOf<
93109
R extends Predicate<unknown[]>,
94110
L extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
95111
>(
96-
predTup: T,
97-
predRest?: R,
112+
predTupOrRest: T | R,
113+
predRestOrTup?: R | T,
98114
predTrail?: L,
99115
): Predicate<
100116
| TupleOf<T>
101117
| [...TupleOf<T>, ...PredicateType<R>]
118+
| [...PredicateType<R>, ...TupleOf<T>]
102119
| [...TupleOf<T>, ...PredicateType<R>, ...TupleOf<T>]
103120
> {
121+
if (typeof predTupOrRest === "function") {
122+
const predRest = predTupOrRest as R;
123+
const predTup = predRestOrTup as T;
124+
return rewriteName(
125+
(x: unknown): x is [...PredicateType<R>, ...TupleOf<T>] => {
126+
if (!isArray(x) || x.length < predTup.length) {
127+
return false;
128+
}
129+
const rest = x.slice(0, -predTup.length);
130+
const trail = x.slice(-predTup.length);
131+
return predTup.every((pred, i) => pred(trail[i])) && predRest(rest);
132+
},
133+
"isTupleOf",
134+
predRest,
135+
predTup,
136+
);
137+
}
138+
const predTup = predTupOrRest as T;
139+
const predRest = predRestOrTup as R;
104140
if (!predRest) {
105141
return rewriteName(
106142
(x: unknown): x is TupleOf<T> => {

is/tuple_of_test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,78 @@ Deno.test("isTupleOf<T, R>", async (t) => {
115115
});
116116
});
117117

118+
Deno.test("isTupleOf<R, T>", async (t) => {
119+
await t.step("returns properly named predicate function", async (t) => {
120+
await assertSnapshot(
121+
t,
122+
isTupleOf(is.Array, [is.Number, is.String, is.Boolean]).name,
123+
);
124+
await assertSnapshot(
125+
t,
126+
isTupleOf(is.ArrayOf(is.String), [(_x): _x is string => false])
127+
.name,
128+
);
129+
await assertSnapshot(
130+
t,
131+
isTupleOf([
132+
isTupleOf(
133+
is.Array,
134+
[isTupleOf(is.Array, [is.Number, is.String, is.Boolean])],
135+
),
136+
]).name,
137+
);
138+
});
139+
140+
await t.step("returns true on T tuple", () => {
141+
const predRest = is.ArrayOf(is.Number);
142+
const predTup = [is.Number, is.String, is.Boolean] as const;
143+
assertEquals(isTupleOf(predRest, predTup)([0, "a", true]), true);
144+
assertEquals(isTupleOf(predRest, predTup)([0, 1, 2, 0, "a", true]), true);
145+
});
146+
147+
await t.step("returns false on non T tuple", () => {
148+
const predRest = is.ArrayOf(is.String);
149+
const predTup = [is.Number, is.String, is.Boolean] as const;
150+
assertEquals(isTupleOf(predRest, predTup)("a"), false, "Not an array");
151+
assertEquals(
152+
isTupleOf(predRest, predTup)([0, "a"]),
153+
false,
154+
"Less than `predTup.length`",
155+
);
156+
assertEquals(
157+
isTupleOf(predRest, predTup)([0, 1, 2]),
158+
false,
159+
"Not match `predTup` and no rest elements",
160+
);
161+
assertEquals(
162+
isTupleOf(predRest, predTup)([0, 1, 2, 0, 1, 2]),
163+
false,
164+
"Not match `predTup` and `predRest`",
165+
);
166+
assertEquals(
167+
isTupleOf(predRest, predTup)([0, 1, 2, 0, "a", true]),
168+
false,
169+
"Match `predTup` but not match `predRest`",
170+
);
171+
assertEquals(
172+
isTupleOf(predRest, predTup)(["a", "b", "c", 0, "a", "b"]),
173+
false,
174+
"Match `predRest` but not match `predTup`",
175+
);
176+
});
177+
178+
await t.step("predicated type is correct", () => {
179+
const predRest = is.ArrayOf(is.Number);
180+
const predTup = [is.Number, is.String, is.Boolean] as const;
181+
const a: unknown = [0, 1, 2, 0, "a", true];
182+
if (isTupleOf(predRest, predTup)(a)) {
183+
assertType<Equal<typeof a, [...number[], number, string, boolean]>>(
184+
true,
185+
);
186+
}
187+
});
188+
});
189+
118190
Deno.test("isTupleOf<T, R, L>", async (t) => {
119191
await t.step("returns properly named predicate function", async (t) => {
120192
await assertSnapshot(

0 commit comments

Comments
 (0)