Skip to content

Commit dbc0741

Browse files
committed
💥 Add isReadonlyTupleOf and isReadonlyUniformTupleOf
Previously, `isTupleOf` and `isUniformTupleOf` returned `readonly` tuple but it is a bit difficult to strip `readonly` from the type thus we add new `isReadonlyTupleOf` and `isReadonlyUniformTupleOf` variants and make `isTupleOf` and `isUniformTupleOf` return non-readonly tuple.
1 parent 10b9440 commit dbc0741

File tree

3 files changed

+254
-53
lines changed

3 files changed

+254
-53
lines changed

__snapshots__/is_test.ts.snap

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

3-
snapshot[`isArrayOf<T> > returns properly named function 1`] = `"isArrayOf(isNumber)"`;
4-
5-
snapshot[`isArrayOf<T> > returns properly named function 2`] = `"isArrayOf((anonymous))"`;
6-
7-
snapshot[`isTupleOf<T> > returns properly named function 1`] = `
8-
"isTupleOf([
9-
isNumber,
10-
isString,
11-
isBoolean
12-
])"
13-
`;
14-
15-
snapshot[`isTupleOf<T> > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`;
16-
17-
snapshot[`isTupleOf<T> > returns properly named function 3`] = `
18-
"isTupleOf([
19-
isTupleOf([
20-
isTupleOf([
21-
isNumber,
22-
isString,
23-
isBoolean
24-
])
25-
])
26-
])"
27-
`;
28-
29-
snapshot[`isUniformTupleOf<T> > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`;
30-
31-
snapshot[`isUniformTupleOf<T> > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`;
32-
33-
snapshot[`isUniformTupleOf<T> > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`;
34-
35-
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber)"`;
36-
37-
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous))"`;
38-
393
snapshot[`isObjectOf<T> > returns properly named function 1`] = `
404
"isObjectOf({
415
a: isNumber,
@@ -54,6 +18,19 @@ snapshot[`isObjectOf<T> > returns properly named function 3`] = `
5418
})"
5519
`;
5620
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})
27+
])"
28+
`;
29+
30+
snapshot[`isArrayOf<T> > returns properly named function 1`] = `"isArrayOf(isNumber)"`;
31+
32+
snapshot[`isArrayOf<T> > returns properly named function 2`] = `"isArrayOf((anonymous))"`;
33+
5734
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
5835
5936
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;
@@ -72,8 +49,6 @@ snapshot[`isLiteralOf<T> > returns properly named function 6`] = `"isLiteralOf(u
7249
7350
snapshot[`isLiteralOf<T> > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`;
7451
75-
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
76-
7752
snapshot[`isOneOf<T> > returns properly named function 1`] = `
7853
"isOneOf([
7954
isNumber,
@@ -82,11 +57,64 @@ snapshot[`isOneOf<T> > returns properly named function 1`] = `
8257
])"
8358
`;
8459
85-
snapshot[`isAllOf<T> > returns properly named function 1`] = `
86-
"isAllOf([
87-
isObjectOf({a: isNumber}),
88-
isObjectOf({b: isString})
60+
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
61+
62+
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber)"`;
63+
64+
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous))"`;
65+
66+
snapshot[`isReadonlyTupleOf<T> > returns properly named function 1`] = `
67+
"isReadonlyTupleOf([
68+
isNumber,
69+
isString,
70+
isBoolean
8971
])"
9072
`;
9173
92-
snapshot[`isOptionalOf<T> > returns properly named function 1`] = `"isOptionalOf(isNumber)"`;
74+
snapshot[`isReadonlyTupleOf<T> > returns properly named function 2`] = `"isReadonlyTupleOf([(anonymous)])"`;
75+
76+
snapshot[`isReadonlyTupleOf<T> > returns properly named function 3`] = `
77+
"isReadonlyTupleOf([
78+
isReadonlyTupleOf([
79+
isReadonlyTupleOf([
80+
isNumber,
81+
isString,
82+
isBoolean
83+
])
84+
])
85+
])"
86+
`;
87+
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+
94+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 1`] = `"isReadonlyUniformTupleOf(3, isAny)"`;
95+
96+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 2`] = `"isReadonlyUniformTupleOf(3, isNumber)"`;
97+
98+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 3`] = `"isReadonlyUniformTupleOf(3, (anonymous))"`;
99+
100+
snapshot[`isTupleOf<T> > returns properly named function 1`] = `
101+
"isTupleOf([
102+
isNumber,
103+
isString,
104+
isBoolean
105+
])"
106+
`;
107+
108+
snapshot[`isTupleOf<T> > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`;
109+
110+
snapshot[`isTupleOf<T> > returns properly named function 3`] = `
111+
"isTupleOf([
112+
isTupleOf([
113+
isTupleOf([
114+
isNumber,
115+
isString,
116+
isBoolean
117+
])
118+
])
119+
])"
120+
`;

is.ts

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ export function isArrayOf<T>(
7979
}
8080

8181
export type TupleOf<T extends readonly Predicate<unknown>[]> = {
82+
-readonly [P in keyof T]: T[P] extends Predicate<infer U> ? U : never;
83+
};
84+
85+
export type ReadonlyTupleOf<T extends readonly Predicate<unknown>[]> = {
8286
[P in keyof T]: T[P] extends Predicate<infer U> ? U : never;
8387
};
8488

@@ -92,7 +96,7 @@ export type TupleOf<T extends readonly Predicate<unknown>[]> = {
9296
* const a: unknown = [0, "a", true];
9397
* if (is.TupleOf(predTup)(a)) {
9498
* // a is narrowed to [number, string, boolean]
95-
* const _: readonly [number, string, boolean] = a;
99+
* const _: [number, string, boolean] = a;
96100
* }
97101
* ```
98102
*
@@ -118,12 +122,50 @@ export function isTupleOf<T extends readonly Predicate<unknown>[]>(
118122
);
119123
}
120124

125+
/**
126+
* Return a type predicate function that returns `true` if the type of `x` is `ReadonlyTupleOf<T>`.
127+
*
128+
* ```ts
129+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
130+
*
131+
* const predTup = [is.Number, is.String, is.Boolean] as const;
132+
* const a: unknown = [0, "a", true];
133+
* if (is.ReadonlyTupleOf(predTup)(a)) {
134+
* // a is narrowed to readonly [number, string, boolean]
135+
* const _: readonly [number, string, boolean] = a;
136+
* }
137+
* ```
138+
*
139+
* Note that `predTup` must be `readonly` (`as const`) to infer the type of `a` correctly.
140+
* TypeScript won't argues if `predTup` is not `readonly` because of its design limitation.
141+
* https://github.com/microsoft/TypeScript/issues/34274#issuecomment-541691353
142+
*/
143+
export function isReadonlyTupleOf<T extends readonly Predicate<unknown>[]>(
144+
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)})`,
151+
},
152+
},
153+
);
154+
}
155+
121156
// https://stackoverflow.com/a/71700658/1273406
122157
export type UniformTupleOf<
123158
T,
124159
N extends number,
125160
R extends readonly T[] = [],
126-
> = R["length"] extends N ? R : UniformTupleOf<T, N, readonly [T, ...R]>;
161+
> = R["length"] extends N ? R : UniformTupleOf<T, N, [T, ...R]>;
162+
163+
export type ReadonlyUniformTupleOf<
164+
T,
165+
N extends number,
166+
R extends readonly T[] = [],
167+
> = R["length"] extends N ? R
168+
: ReadonlyUniformTupleOf<T, N, readonly [T, ...R]>;
127169

128170
/**
129171
* Return a type predicate function that returns `true` if the type of `x` is `UniformTupleOf<T>`.
@@ -134,12 +176,12 @@ export type UniformTupleOf<
134176
* const a: unknown = [0, 1, 2, 3, 4];
135177
* if (is.UniformTupleOf(5)(a)) {
136178
* // a is narrowed to [unknown, unknown, unknown, unknown, unknown]
137-
* const _: readonly [unknown, unknown, unknown, unknown, unknown] = a;
179+
* const _: [unknown, unknown, unknown, unknown, unknown] = a;
138180
* }
139181
*
140182
* if (is.UniformTupleOf(5, is.Number)(a)) {
141183
* // a is narrowed to [number, number, number, number, number]
142-
* const _: readonly [number, number, number, number, number] = a;
184+
* const _: [number, number, number, number, number] = a;
143185
* }
144186
* ```
145187
*/
@@ -159,9 +201,41 @@ export function isUniformTupleOf<T, N extends number>(
159201
}
160202

161203
/**
162-
* Synonym of `Record<string | number | symbol, T>`
204+
* Return a type predicate function that returns `true` if the type of `x` is `ReadonlyUniformTupleOf<T>`.
205+
*
206+
* ```ts
207+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
208+
*
209+
* const a: unknown = [0, 1, 2, 3, 4];
210+
* if (is.ReadonlyUniformTupleOf(5)(a)) {
211+
* // a is narrowed to readonly [unknown, unknown, unknown, unknown, unknown]
212+
* const _: readonly [unknown, unknown, unknown, unknown, unknown] = a;
213+
* }
214+
*
215+
* if (is.ReadonlyUniformTupleOf(5, is.Number)(a)) {
216+
* // a is narrowed to readonly [number, number, number, number, number]
217+
* const _: readonly [number, number, number, number, number] = a;
218+
* }
219+
* ```
220+
*/
221+
export function isReadonlyUniformTupleOf<T, N extends number>(
222+
n: N,
223+
pred: Predicate<T> = isAny,
224+
): Predicate<ReadonlyUniformTupleOf<T, N>> {
225+
return Object.defineProperties(
226+
isUniformTupleOf(n, pred) as Predicate<ReadonlyUniformTupleOf<T, N>>,
227+
{
228+
name: {
229+
get: () => `isReadonlyUniformTupleOf(${n}, ${inspect(pred)})`,
230+
},
231+
},
232+
);
233+
}
234+
235+
/**
236+
* Synonym of `Record<PropertyKey, T>`
163237
*/
164-
export type RecordOf<T> = Record<string | number | symbol, T>;
238+
export type RecordOf<T> = Record<PropertyKey, T>;
165239

166240
/**
167241
* Return `true` if the type of `x` is `RecordOf<unknown>`.
@@ -524,7 +598,9 @@ export default {
524598
Array: isArray,
525599
ArrayOf: isArrayOf,
526600
TupleOf: isTupleOf,
601+
ReadonlyTupleOf: isReadonlyTupleOf,
527602
UniformTupleOf: isUniformTupleOf,
603+
ReadonlyUniformTupleOf: isReadonlyUniformTupleOf,
528604
Record: isRecord,
529605
RecordOf: isRecordOf,
530606
ObjectOf: isObjectOf,

0 commit comments

Comments
 (0)