Skip to content

Commit 8021487

Browse files
committed
👍 Add predElse to isTupleOf and isReadonlyTupleOf
Now `isTupleOf` and `isReadonlyTupleOf` can check the type of the rest of the tuple. ```ts const predTup = [is.Number, is.String, is.Boolean] as const; const predElse = is.ArrayOf(is.Number); const pred = is.TupleOf(predTup, predElse); const a: unknown = [0, "a", true, 0, 1, 2]; if (pred(a)) { // a is narrowed to [number, string, boolean, ...number[]] const _: [number, string, boolean, ...number[]] = a; } ```
1 parent dbc0741 commit 8021487

File tree

3 files changed

+392
-70
lines changed

3 files changed

+392
-70
lines changed

__snapshots__/is_test.ts.snap

Lines changed: 83 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
export const snapshot = {};
22

3+
snapshot[`isLiteralOf<T> > returns properly named function 1`] = `'isLiteralOf("hello")'`;
4+
5+
snapshot[`isLiteralOf<T> > returns properly named function 2`] = `"isLiteralOf(100)"`;
6+
7+
snapshot[`isLiteralOf<T> > returns properly named function 3`] = `"isLiteralOf(100n)"`;
8+
9+
snapshot[`isLiteralOf<T> > returns properly named function 4`] = `"isLiteralOf(true)"`;
10+
11+
snapshot[`isLiteralOf<T> > returns properly named function 5`] = `"isLiteralOf(null)"`;
12+
13+
snapshot[`isLiteralOf<T> > returns properly named function 6`] = `"isLiteralOf(undefined)"`;
14+
15+
snapshot[`isLiteralOf<T> > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`;
16+
317
snapshot[`isObjectOf<T> > returns properly named function 1`] = `
418
"isObjectOf({
519
a: isNumber,
@@ -18,36 +32,50 @@ snapshot[`isObjectOf<T> > returns properly named function 3`] = `
1832
})"
1933
`;
2034
21-
snapshot[`isOptionalOf<T> > returns properly named function 1`] = `"isOptionalOf(isNumber)"`;
22-
23-
snapshot[`isAllOf<T> > returns properly named function 1`] = `
24-
"isAllOf([
25-
isObjectOf({a: isNumber}),
26-
isObjectOf({b: isString})
35+
snapshot[`isTupleOf<T> > returns properly named function 1`] = `
36+
"isTupleOf([
37+
isNumber,
38+
isString,
39+
isBoolean
2740
])"
2841
`;
2942
30-
snapshot[`isArrayOf<T> > returns properly named function 1`] = `"isArrayOf(isNumber)"`;
43+
snapshot[`isTupleOf<T> > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`;
3144
32-
snapshot[`isArrayOf<T> > returns properly named function 2`] = `"isArrayOf((anonymous))"`;
45+
snapshot[`isTupleOf<T> > returns properly named function 3`] = `
46+
"isTupleOf([
47+
isTupleOf([
48+
isTupleOf([
49+
isNumber,
50+
isString,
51+
isBoolean
52+
])
53+
])
54+
])"
55+
`;
3356
34-
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
57+
snapshot[`isOptionalOf<T> > returns properly named function 1`] = `"isOptionalOf(isNumber)"`;
3558
36-
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;
59+
snapshot[`isUniformTupleOf<T> > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`;
3760
38-
snapshot[`isLiteralOf<T> > returns properly named function 1`] = `'isLiteralOf("hello")'`;
61+
snapshot[`isUniformTupleOf<T> > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`;
3962
40-
snapshot[`isLiteralOf<T> > returns properly named function 2`] = `"isLiteralOf(100)"`;
63+
snapshot[`isUniformTupleOf<T> > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`;
4164
42-
snapshot[`isLiteralOf<T> > returns properly named function 3`] = `"isLiteralOf(100n)"`;
65+
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
4366
44-
snapshot[`isLiteralOf<T> > returns properly named function 4`] = `"isLiteralOf(true)"`;
67+
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;
4568
46-
snapshot[`isLiteralOf<T> > returns properly named function 5`] = `"isLiteralOf(null)"`;
69+
snapshot[`isAllOf<T> > returns properly named function 1`] = `
70+
"isAllOf([
71+
isObjectOf({a: isNumber}),
72+
isObjectOf({b: isString})
73+
])"
74+
`;
4775
48-
snapshot[`isLiteralOf<T> > returns properly named function 6`] = `"isLiteralOf(undefined)"`;
76+
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber)"`;
4977
50-
snapshot[`isLiteralOf<T> > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`;
78+
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous))"`;
5179
5280
snapshot[`isOneOf<T> > returns properly named function 1`] = `
5381
"isOneOf([
@@ -59,58 +87,74 @@ snapshot[`isOneOf<T> > returns properly named function 1`] = `
5987
6088
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
6189
62-
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber)"`;
90+
snapshot[`isTupleOf<T, E> > returns properly named function 1`] = `
91+
"isTupleOf([
92+
isNumber,
93+
isString,
94+
isBoolean
95+
], isArray)"
96+
`;
6397
64-
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous))"`;
98+
snapshot[`isTupleOf<T, E> > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`;
6599
66-
snapshot[`isReadonlyTupleOf<T> > returns properly named function 1`] = `
100+
snapshot[`isTupleOf<T, E> > returns properly named function 3`] = `
101+
"isTupleOf([
102+
isTupleOf([
103+
isTupleOf([
104+
isNumber,
105+
isString,
106+
isBoolean
107+
], isArray)
108+
], isArray)
109+
])"
110+
`;
111+
112+
snapshot[`isArrayOf<T> > returns properly named function 1`] = `"isArrayOf(isNumber)"`;
113+
114+
snapshot[`isArrayOf<T> > returns properly named function 2`] = `"isArrayOf((anonymous))"`;
115+
116+
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 1`] = `
67117
"isReadonlyTupleOf([
68118
isNumber,
69119
isString,
70120
isBoolean
71-
])"
121+
], isArray)"
72122
`;
73123
74-
snapshot[`isReadonlyTupleOf<T> > returns properly named function 2`] = `"isReadonlyTupleOf([(anonymous)])"`;
124+
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 2`] = `"isReadonlyTupleOf([(anonymous)], isArrayOf(isString))"`;
75125
76-
snapshot[`isReadonlyTupleOf<T> > returns properly named function 3`] = `
126+
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 3`] = `
77127
"isReadonlyTupleOf([
78128
isReadonlyTupleOf([
79129
isReadonlyTupleOf([
80130
isNumber,
81131
isString,
82132
isBoolean
83-
])
84-
])
85-
])"
133+
], isArray)
134+
], isArray)
135+
], isArray)"
86136
`;
87137
88-
snapshot[`isUniformTupleOf<T> > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`;
89-
90-
snapshot[`isUniformTupleOf<T> > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`;
91-
92-
snapshot[`isUniformTupleOf<T> > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`;
93-
94138
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 1`] = `"isReadonlyUniformTupleOf(3, isAny)"`;
95139
96140
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 2`] = `"isReadonlyUniformTupleOf(3, isNumber)"`;
97141
98142
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 3`] = `"isReadonlyUniformTupleOf(3, (anonymous))"`;
99143
100-
snapshot[`isTupleOf<T> > returns properly named function 1`] = `
101-
"isTupleOf([
144+
snapshot[`isReadonlyTupleOf<T> > returns properly named function 1`] = `
145+
"isReadonlyTupleOf([
102146
isNumber,
103147
isString,
104148
isBoolean
105149
])"
106150
`;
107151
108-
snapshot[`isTupleOf<T> > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`;
152+
snapshot[`isReadonlyTupleOf<T> > returns properly named function 2`] = `"isReadonlyTupleOf([(anonymous)])"`;
109153
110-
snapshot[`isTupleOf<T> > returns properly named function 3`] = `
111-
"isTupleOf([
112-
isTupleOf([
113-
isTupleOf([
154+
snapshot[`isReadonlyTupleOf<T> > returns properly named function 3`] = `
155+
"isReadonlyTupleOf([
156+
isReadonlyTupleOf([
157+
isReadonlyTupleOf([
114158
isNumber,
115159
isString,
116160
isBoolean

is.ts

Lines changed: 120 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -103,23 +103,75 @@ export type ReadonlyTupleOf<T extends readonly Predicate<unknown>[]> = {
103103
* Note that `predTup` must be `readonly` (`as const`) to infer the type of `a` correctly.
104104
* TypeScript won't argues if `predTup` is not `readonly` because of its design limitation.
105105
* https://github.com/microsoft/TypeScript/issues/34274#issuecomment-541691353
106+
*
107+
* It can also be used to check the type of the rest of the tuple like:
108+
*
109+
* ```ts
110+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
111+
*
112+
* const predTup = [is.Number, is.String, is.Boolean] as const;
113+
* const predElse = is.ArrayOf(is.Number);
114+
* const a: unknown = [0, "a", true, 0, 1, 2];
115+
* if (is.TupleOf(predTup, predElse)(a)) {
116+
* // a is narrowed to [number, string, boolean, ...number[]]
117+
* const _: [number, string, boolean, ...number[]] = a;
118+
* }
119+
* ```
106120
*/
107-
export function isTupleOf<T extends readonly Predicate<unknown>[]>(
121+
export function isTupleOf<
122+
T extends readonly Predicate<unknown>[],
123+
R extends TupleOf<T>,
124+
>(
108125
predTup: T,
109-
): Predicate<TupleOf<T>> {
110-
return Object.defineProperties(
111-
(x: unknown): x is TupleOf<T> => {
112-
if (!isArray(x) || x.length !== predTup.length) {
113-
return false;
114-
}
115-
return predTup.every((pred, i) => pred(x[i]));
116-
},
117-
{
118-
name: {
119-
get: () => `isTupleOf(${inspect(predTup)})`,
126+
): Predicate<R>;
127+
export function isTupleOf<
128+
T extends readonly Predicate<unknown>[],
129+
E extends Predicate<unknown[]>,
130+
R extends [...TupleOf<T>, ...PredicateType<E>],
131+
>(
132+
predTup: T,
133+
predElse: E,
134+
): Predicate<R>;
135+
export function isTupleOf<
136+
T extends readonly Predicate<unknown>[],
137+
E extends Predicate<unknown[]>,
138+
R1 extends TupleOf<T>,
139+
R2 extends [...TupleOf<T>, ...PredicateType<E>],
140+
>(
141+
predTup: T,
142+
predElse?: E,
143+
): Predicate<R1 | R2> {
144+
if (!predElse) {
145+
return Object.defineProperties(
146+
(x: unknown): x is R1 => {
147+
if (!isArray(x) || x.length !== predTup.length) {
148+
return false;
149+
}
150+
return predTup.every((pred, i) => pred(x[i]));
120151
},
121-
},
122-
);
152+
{
153+
name: {
154+
get: () => `isTupleOf(${inspect(predTup)})`,
155+
},
156+
},
157+
);
158+
} else {
159+
return Object.defineProperties(
160+
(x: unknown): x is R2 => {
161+
if (!isArray(x) || x.length < predTup.length) {
162+
return false;
163+
}
164+
const head = x.slice(0, predTup.length);
165+
const tail = x.slice(predTup.length);
166+
return predTup.every((pred, i) => pred(head[i])) && predElse(tail);
167+
},
168+
{
169+
name: {
170+
get: () => `isTupleOf(${inspect(predTup)}, ${inspect(predElse)})`,
171+
},
172+
},
173+
);
174+
}
123175
}
124176

125177
/**
@@ -139,18 +191,63 @@ export function isTupleOf<T extends readonly Predicate<unknown>[]>(
139191
* Note that `predTup` must be `readonly` (`as const`) to infer the type of `a` correctly.
140192
* TypeScript won't argues if `predTup` is not `readonly` because of its design limitation.
141193
* https://github.com/microsoft/TypeScript/issues/34274#issuecomment-541691353
194+
*
195+
* It can also be used to check the type of the rest of the tuple like:
196+
*
197+
* ```ts
198+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
199+
*
200+
* const predTup = [is.Number, is.String, is.Boolean] as const;
201+
* const predElse = is.ArrayOf(is.Number);
202+
* const a: unknown = [0, "a", true, 0, 1, 2];
203+
* if (is.ReadonlyTupleOf(predTup, predElse)(a)) {
204+
* // a is narrowed to readonly [number, string, boolean, ...number[]]
205+
* const _: readonly [number, string, boolean, ...number[]] = a;
206+
* }
142207
*/
143-
export function isReadonlyTupleOf<T extends readonly Predicate<unknown>[]>(
208+
export function isReadonlyTupleOf<
209+
T extends readonly Predicate<unknown>[],
210+
R extends ReadonlyTupleOf<T>,
211+
>(
144212
predTup: T,
145-
): Predicate<ReadonlyTupleOf<T>> {
146-
return Object.defineProperties(
147-
isTupleOf(predTup) as Predicate<ReadonlyTupleOf<T>>,
148-
{
149-
name: {
150-
get: () => `isReadonlyTupleOf(${inspect(predTup)})`,
213+
): Predicate<R>;
214+
export function isReadonlyTupleOf<
215+
T extends readonly Predicate<unknown>[],
216+
E extends Predicate<unknown[]>,
217+
R extends readonly [...ReadonlyTupleOf<T>, ...PredicateType<E>],
218+
>(
219+
predTup: T,
220+
predElse: E,
221+
): Predicate<R>;
222+
export function isReadonlyTupleOf<
223+
T extends readonly Predicate<unknown>[],
224+
E extends Predicate<unknown[]>,
225+
R1 extends ReadonlyTupleOf<T>,
226+
R2 extends readonly [...ReadonlyTupleOf<T>, ...PredicateType<E>],
227+
>(
228+
predTup: T,
229+
predElse?: E,
230+
): Predicate<R1 | R2> {
231+
if (!predElse) {
232+
return Object.defineProperties(
233+
isTupleOf(predTup) as Predicate<R1>,
234+
{
235+
name: {
236+
get: () => `isReadonlyTupleOf(${inspect(predTup)})`,
237+
},
151238
},
152-
},
153-
);
239+
);
240+
} else {
241+
return Object.defineProperties(
242+
isTupleOf(predTup, predElse) as unknown as Predicate<R2>,
243+
{
244+
name: {
245+
get: () =>
246+
`isReadonlyTupleOf(${inspect(predTup)}, ${inspect(predElse)})`,
247+
},
248+
},
249+
);
250+
}
154251
}
155252

156253
// https://stackoverflow.com/a/71700658/1273406

0 commit comments

Comments
 (0)