Skip to content
64 changes: 63 additions & 1 deletion is/__snapshots__/tuple_of_test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,69 @@ snapshot[`isTupleOf<T, R> > returns properly named predicate function 3`] = `
isNumber,
isString,
isBoolean
], isArray)
], isArrayOf(isString))
], isArray)
])"
`;

snapshot[`isTupleOf<R, T> > returns properly named predicate function 1`] = `
"isTupleOf(isArray, [
isNumber,
isString,
isBoolean
])"
`;

snapshot[`isTupleOf<R, T> > returns properly named predicate function 2`] = `"isTupleOf(isArrayOf(isString), [(anonymous)])"`;

snapshot[`isTupleOf<R, T> > returns properly named predicate function 3`] = `
"isTupleOf([
isTupleOf(isArray, [
isTupleOf(isArrayOf(isString), [
isNumber,
isString,
isBoolean
])
])
])"
`;

snapshot[`isTupleOf<T, R, L> > returns properly named predicate function 1`] = `
"isTupleOf([
isNumber,
isString,
isBoolean
], isArray, [
isString,
isBoolean,
isNumber
])"
`;

snapshot[`isTupleOf<T, R, L> > returns properly named predicate function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString), [(anonymous)])"`;

snapshot[`isTupleOf<T, R, L> > returns properly named predicate function 3`] = `
"isTupleOf([
isTupleOf([
isTupleOf([
isNumber,
isString,
isBoolean
], isArrayOf(isString), [
isString,
isBoolean,
isNumber
])
], isArray, [
isTupleOf([
isNumber,
isString,
isBoolean
], isArrayOf(isNumber), [
isNumber,
isBoolean,
isString
])
])
])"
`;
28 changes: 26 additions & 2 deletions is/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ export const is: {
*/
SyncFunction: typeof isSyncFunction;
/**
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>` or `TupleOf<T, R>`.
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>`.
*
* Use {@linkcode isUniformTupleOf} to check if the type of `x` is a tuple of uniform types.
*
Expand All @@ -839,7 +839,7 @@ export const is: {
* }
* ```
*
* With `predRest` to represent rest elements:
* With `predRest` to represent rest elements or leading rest elements:
*
* ```ts
* import { is } from "@core/unknownutil";
Expand All @@ -852,6 +852,30 @@ export const is: {
* if (isMyType(a)) {
* const _: [number, string, boolean, ...number[]] = a;
* }
*
* const isMyTypeLeadingRest = is.TupleOf(
* is.ArrayOf(is.Number),
* [is.Number, is.String, is.Boolean],
* );
* if (isMyTypeLeadingRest(a)) {
* const _: [...number[], number, string, boolean] = a;
* }
* ```
*
* With `predRest` and `predTrail` to represent middle rest elements:
*
* ```ts
* import { is } from "@core/unknownutil";
*
* const isMyType = is.TupleOf(
* [is.Number, is.String, is.Boolean],
* is.ArrayOf(is.Number),
* [is.Number, is.String, is.Boolean],
* );
* const a: unknown = [0, "a", true, 0, 1, 2, 0, "a", true];
* if (isMyType(a)) {
* const _: [number, string, boolean, ...number[], number, string, boolean] = a;
* }
* ```
*
* Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array
Expand Down
101 changes: 93 additions & 8 deletions is/tuple_of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Predicate, PredicateType } from "../type.ts";
import { isArray } from "./array.ts";

/**
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>` or `TupleOf<T, R>`.
* Return a type predicate function that returns `true` if the type of `x` is `TupleOf<T>`.
*
* Use {@linkcode isUniformTupleOf} to check if the type of `x` is a tuple of uniform types.
*
Expand All @@ -19,7 +19,7 @@ import { isArray } from "./array.ts";
* }
* ```
*
* With `predRest` to represent rest elements:
* With `predRest` to represent rest elements or leading rest elements:
*
* ```ts
* import { is } from "@core/unknownutil";
Expand All @@ -32,6 +32,30 @@ import { isArray } from "./array.ts";
* if (isMyType(a)) {
* const _: [number, string, boolean, ...number[]] = a;
* }
*
* const isMyTypeLeadingRest = is.TupleOf(
* is.ArrayOf(is.Number),
* [is.Number, is.String, is.Boolean],
* );
* if (isMyTypeLeadingRest(a)) {
* const _: [...number[], number, string, boolean] = a;
* }
* ```
*
* With `predRest` and `predTrail` to represent middle rest elements:
*
* ```ts
* import { is } from "@core/unknownutil";
*
* const isMyType = is.TupleOf(
* [is.Number, is.String, is.Boolean],
* is.ArrayOf(is.Number),
* [is.Number, is.String, is.Boolean],
* );
* const a: unknown = [0, "a", true, 0, 1, 2, 0, "a", true];
* if (isMyType(a)) {
* const _: [number, string, boolean, ...number[], number, string, boolean] = a;
* }
* ```
*
* Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array
Expand Down Expand Up @@ -62,13 +86,57 @@ export function isTupleOf<
predRest: R,
): Predicate<[...TupleOf<T>, ...PredicateType<R>]>;

export function isTupleOf<
R extends Predicate<unknown[]>,
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
>(
predRest: R,
predTup: T,
): Predicate<[...PredicateType<R>, ...TupleOf<T>]>;

export function isTupleOf<
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
R extends Predicate<unknown[]>,
L extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
>(
predTup: T,
predRest?: R,
): Predicate<TupleOf<T> | [...TupleOf<T>, ...PredicateType<R>]> {
predRest: R,
predTrail: L,
): Predicate<[...TupleOf<T>, ...PredicateType<R>, ...TupleOf<L>]>;

export function isTupleOf<
T extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
R extends Predicate<unknown[]>,
L extends readonly [Predicate<unknown>, ...Predicate<unknown>[]],
>(
predTupOrRest: T | R,
predRestOrTup?: R | T,
predTrail?: L,
): Predicate<
| TupleOf<T>
| [...TupleOf<T>, ...PredicateType<R>]
| [...PredicateType<R>, ...TupleOf<T>]
| [...TupleOf<T>, ...PredicateType<R>, ...TupleOf<L>]
> {
if (typeof predTupOrRest === "function") {
const predRest = predTupOrRest as R;
const predTup = predRestOrTup as T;
return rewriteName(
(x: unknown): x is [...PredicateType<R>, ...TupleOf<T>] => {
if (!isArray(x) || x.length < predTup.length) {
return false;
}
const offset = x.length - predTup.length;
return predTup.every((pred, i) => pred(x[offset + i])) &&
predRest(x.slice(0, offset));
},
"isTupleOf",
predRest,
predTup,
);
}
const predTup = predTupOrRest as T;
const predRest = predRestOrTup as R;
if (!predRest) {
return rewriteName(
(x: unknown): x is TupleOf<T> => {
Expand All @@ -80,19 +148,36 @@ export function isTupleOf<
"isTupleOf",
predTup,
);
} else {
} else if (!predTrail) {
return rewriteName(
(x: unknown): x is [...TupleOf<T>, ...PredicateType<R>] => {
if (!isArray(x) || x.length < predTup.length) {
return false;
}
const head = x.slice(0, predTup.length);
const tail = x.slice(predTup.length);
return predTup.every((pred, i) => pred(head[i])) && predRest(tail);
return predTup.every((pred, i) => pred(x[i])) &&
predRest(x.slice(predTup.length));
},
"isTupleOf",
predTup,
predRest,
);
} else {
return rewriteName(
(
x: unknown,
): x is [...TupleOf<T>, ...PredicateType<R>, ...TupleOf<L>] => {
if (!isArray(x) || x.length < (predTup.length + predTrail.length)) {
return false;
}
const offset = x.length - predTrail.length;
return predTup.every((pred, i) => pred(x[i])) &&
predTrail.every((pred, i) => pred(x[offset + i])) &&
predRest(x.slice(predTup.length, offset));
},
"isTupleOf",
predTup,
predRest,
predTrail,
);
}
}
Expand Down
Loading