Skip to content

Commit d0f6737

Browse files
authored
Merge pull request #119 from jsr-core/tuple-rest
feat[isTupleOf]: add overloads to leading/middle rest elements
2 parents ae3696d + 1e80cdd commit d0f6737

File tree

4 files changed

+428
-20
lines changed

4 files changed

+428
-20
lines changed

is/__snapshots__/tuple_of_test.ts.snap

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,69 @@ snapshot[`isTupleOf<T, R> > returns properly named predicate function 3`] = `
3939
isNumber,
4040
isString,
4141
isBoolean
42-
], isArray)
42+
], isArrayOf(isString))
4343
], isArray)
4444
])"
4545
`;
46+
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(isArrayOf(isString), [
61+
isNumber,
62+
isString,
63+
isBoolean
64+
])
65+
])
66+
])"
67+
`;
68+
69+
snapshot[`isTupleOf<T, R, L> > returns properly named predicate function 1`] = `
70+
"isTupleOf([
71+
isNumber,
72+
isString,
73+
isBoolean
74+
], isArray, [
75+
isString,
76+
isBoolean,
77+
isNumber
78+
])"
79+
`;
80+
81+
snapshot[`isTupleOf<T, R, L> > returns properly named predicate function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString), [(anonymous)])"`;
82+
83+
snapshot[`isTupleOf<T, R, L> > returns properly named predicate function 3`] = `
84+
"isTupleOf([
85+
isTupleOf([
86+
isTupleOf([
87+
isNumber,
88+
isString,
89+
isBoolean
90+
], isArrayOf(isString), [
91+
isString,
92+
isBoolean,
93+
isNumber
94+
])
95+
], isArray, [
96+
isTupleOf([
97+
isNumber,
98+
isString,
99+
isBoolean
100+
], isArrayOf(isNumber), [
101+
isNumber,
102+
isBoolean,
103+
isString
104+
])
105+
])
106+
])"
107+
`;

is/mod.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -823,7 +823,7 @@ export const is: {
823823
*/
824824
SyncFunction: typeof isSyncFunction;
825825
/**
826-
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>` or `TupleOf<T, R>`.
826+
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>`.
827827
*
828828
* Use {@linkcode isUniformTupleOf} to check if the type of `x` is a tuple of uniform types.
829829
*
@@ -839,7 +839,7 @@ export const is: {
839839
* }
840840
* ```
841841
*
842-
* With `predRest` to represent rest elements:
842+
* With `predRest` to represent rest elements or leading rest elements:
843843
*
844844
* ```ts
845845
* import { is } from "@core/unknownutil";
@@ -852,6 +852,30 @@ export const is: {
852852
* if (isMyType(a)) {
853853
* const _: [number, string, boolean, ...number[]] = a;
854854
* }
855+
*
856+
* const isMyTypeLeadingRest = is.TupleOf(
857+
* is.ArrayOf(is.Number),
858+
* [is.Number, is.String, is.Boolean],
859+
* );
860+
* if (isMyTypeLeadingRest(a)) {
861+
* const _: [...number[], number, string, boolean] = a;
862+
* }
863+
* ```
864+
*
865+
* With `predRest` and `predTrail` to represent middle rest elements:
866+
*
867+
* ```ts
868+
* import { is } from "@core/unknownutil";
869+
*
870+
* const isMyType = is.TupleOf(
871+
* [is.Number, is.String, is.Boolean],
872+
* is.ArrayOf(is.Number),
873+
* [is.Number, is.String, is.Boolean],
874+
* );
875+
* const a: unknown = [0, "a", true, 0, 1, 2, 0, "a", true];
876+
* if (isMyType(a)) {
877+
* const _: [number, string, boolean, ...number[], number, string, boolean] = a;
878+
* }
855879
* ```
856880
*
857881
* Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array

is/tuple_of.ts

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Predicate, PredicateType } from "../type.ts";
33
import { isArray } from "./array.ts";
44

55
/**
6-
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>` or `TupleOf<T, R>`.
6+
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>`.
77
*
88
* Use {@linkcode isUniformTupleOf} to check if the type of `x` is a tuple of uniform types.
99
*
@@ -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,30 @@ 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+
* }
43+
* ```
44+
*
45+
* With `predRest` and `predTrail` to represent middle rest elements:
46+
*
47+
* ```ts
48+
* import { is } from "@core/unknownutil";
49+
*
50+
* const isMyType = is.TupleOf(
51+
* [is.Number, is.String, is.Boolean],
52+
* is.ArrayOf(is.Number),
53+
* [is.Number, is.String, is.Boolean],
54+
* );
55+
* const a: unknown = [0, "a", true, 0, 1, 2, 0, "a", true];
56+
* if (isMyType(a)) {
57+
* const _: [number, string, boolean, ...number[], number, string, boolean] = a;
58+
* }
3559
* ```
3660
*
3761
* Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array
@@ -62,13 +86,57 @@ export function isTupleOf<
6286
predRest: R,
6387
): Predicate<[...TupleOf<T>, ...PredicateType<R>]>;
6488

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+
6597
export function isTupleOf<
6698
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
6799
R extends Predicate<unknown[]>,
100+
L extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
68101
>(
69102
predTup: T,
70-
predRest?: R,
71-
): Predicate<TupleOf<T> | [...TupleOf<T>, ...PredicateType<R>]> {
103+
predRest: R,
104+
predTrail: L,
105+
): Predicate<[...TupleOf<T>, ...PredicateType<R>, ...TupleOf<L>]>;
106+
107+
export function isTupleOf<
108+
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
109+
R extends Predicate<unknown[]>,
110+
L extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
111+
>(
112+
predTupOrRest: T | R,
113+
predRestOrTup?: R | T,
114+
predTrail?: L,
115+
): Predicate<
116+
| TupleOf<T>
117+
| [...TupleOf<T>, ...PredicateType<R>]
118+
| [...PredicateType<R>, ...TupleOf<T>]
119+
| [...TupleOf<T>, ...PredicateType<R>, ...TupleOf<L>]
120+
> {
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 offset = x.length - predTup.length;
130+
return predTup.every((pred, i) => pred(x[offset + i])) &&
131+
predRest(x.slice(0, offset));
132+
},
133+
"isTupleOf",
134+
predRest,
135+
predTup,
136+
);
137+
}
138+
const predTup = predTupOrRest as T;
139+
const predRest = predRestOrTup as R;
72140
if (!predRest) {
73141
return rewriteName(
74142
(x: unknown): x is TupleOf<T> => {
@@ -80,19 +148,36 @@ export function isTupleOf<
80148
"isTupleOf",
81149
predTup,
82150
);
83-
} else {
151+
} else if (!predTrail) {
84152
return rewriteName(
85153
(x: unknown): x is [...TupleOf<T>, ...PredicateType<R>] => {
86154
if (!isArray(x) || x.length < predTup.length) {
87155
return false;
88156
}
89-
const head = x.slice(0, predTup.length);
90-
const tail = x.slice(predTup.length);
91-
return predTup.every((pred, i) => pred(head[i])) && predRest(tail);
157+
return predTup.every((pred, i) => pred(x[i])) &&
158+
predRest(x.slice(predTup.length));
159+
},
160+
"isTupleOf",
161+
predTup,
162+
predRest,
163+
);
164+
} else {
165+
return rewriteName(
166+
(
167+
x: unknown,
168+
): x is [...TupleOf<T>, ...PredicateType<R>, ...TupleOf<L>] => {
169+
if (!isArray(x) || x.length < (predTup.length + predTrail.length)) {
170+
return false;
171+
}
172+
const offset = x.length - predTrail.length;
173+
return predTup.every((pred, i) => pred(x[i])) &&
174+
predTrail.every((pred, i) => pred(x[offset + i])) &&
175+
predRest(x.slice(predTup.length, offset));
92176
},
93177
"isTupleOf",
94178
predTup,
95179
predRest,
180+
predTrail,
96181
);
97182
}
98183
}

0 commit comments

Comments
 (0)