Skip to content

Commit eb4d161

Browse files
committed
👍 Add isUnwrapOptionalOf and isUnwrapReadonlyOf
1 parent 3abe6f9 commit eb4d161

File tree

6 files changed

+325
-113
lines changed

6 files changed

+325
-113
lines changed

_typeutil.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ export type UnionToIntersection<U> =
66
(U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void)
77
? I
88
: never;
9+
10+
export type Writable<T> = { -readonly [P in keyof T]: T[P] };
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
export const snapshot = {};
22

3+
snapshot[`isUnwrapOptionalOf<T> > returns properly named function 1`] = `"isNumber"`;
4+
5+
snapshot[`isUnwrapOptionalOf<T> > returns properly named function 2`] = `"isNumber"`;
6+
7+
snapshot[`isUnwrapOptionalOf<T> > returns properly named function 3`] = `"isNumber"`;
8+
39
snapshot[`isReadonlyOf<T> > returns properly named function 1`] = `"isReadonlyOf(isNumber)"`;
410
511
snapshot[`isReadonlyOf<T> > returns properly named function 2`] = `"isReadonlyOf(isReadonlyOf(isNumber))"`;
612
713
snapshot[`isOptionalOf<T> > returns properly named function 1`] = `"isOptionalOf(isNumber)"`;
814
915
snapshot[`isOptionalOf<T> > returns properly named function 2`] = `"isOptionalOf(isNumber)"`;
16+
17+
snapshot[`isUnwrapReadonlyOf<T> > returns properly named function 1`] = `"isNumber"`;
18+
19+
snapshot[`isUnwrapReadonlyOf<T> > returns properly named function 2`] = `"isReadonlyOf(isNumber)"`;
Lines changed: 97 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,18 @@
11
export const snapshot = {};
22

3-
snapshot[`isReadonlyTupleOf<T> > returns properly named function 1`] = `
4-
"isReadonlyOf(isTupleOf([
5-
isNumber,
6-
isString,
7-
isBoolean
8-
]))"
9-
`;
10-
11-
snapshot[`isReadonlyTupleOf<T> > returns properly named function 2`] = `"isReadonlyOf(isTupleOf([(anonymous)]))"`;
12-
13-
snapshot[`isReadonlyTupleOf<T> > returns properly named function 3`] = `
14-
"isReadonlyOf(isTupleOf([
15-
isReadonlyOf(isTupleOf([
16-
isReadonlyOf(isTupleOf([
17-
isNumber,
18-
isString,
19-
isBoolean
20-
]))
21-
]))
22-
]))"
23-
`;
24-
25-
snapshot[`isArrayOf<T> > returns properly named function 1`] = `"isArrayOf(isNumber)"`;
26-
27-
snapshot[`isArrayOf<T> > returns properly named function 2`] = `"isArrayOf((anonymous))"`;
28-
29-
snapshot[`isStrictOf<T> > returns properly named function 1`] = `
30-
"isStrictOf(isObjectOf({
31-
a: isNumber,
32-
b: isString,
33-
c: isBoolean
34-
}))"
35-
`;
36-
37-
snapshot[`isStrictOf<T> > returns properly named function 2`] = `"isStrictOf(isObjectOf({a: a}))"`;
38-
39-
snapshot[`isStrictOf<T> > returns properly named function 3`] = `
40-
"isStrictOf(isObjectOf({
41-
a: isStrictOf(isObjectOf({
42-
b: isStrictOf(isObjectOf({c: isBoolean}))
43-
}))
44-
}))"
45-
`;
46-
47-
snapshot[`isLiteralOf<T> > returns properly named function 1`] = `'isLiteralOf("hello")'`;
48-
49-
snapshot[`isLiteralOf<T> > returns properly named function 2`] = `"isLiteralOf(100)"`;
50-
51-
snapshot[`isLiteralOf<T> > returns properly named function 3`] = `"isLiteralOf(100n)"`;
52-
53-
snapshot[`isLiteralOf<T> > returns properly named function 4`] = `"isLiteralOf(true)"`;
54-
55-
snapshot[`isLiteralOf<T> > returns properly named function 5`] = `"isLiteralOf(null)"`;
56-
57-
snapshot[`isLiteralOf<T> > returns properly named function 6`] = `"isLiteralOf(undefined)"`;
58-
59-
snapshot[`isLiteralOf<T> > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`;
60-
61-
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 1`] = `
62-
"isReadonlyOf(isTupleOf([
63-
isNumber,
64-
isString,
65-
isBoolean
66-
], isArray))"
67-
`;
68-
69-
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 2`] = `"isReadonlyOf(isTupleOf([(anonymous)], isArrayOf(isString)))"`;
70-
71-
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 3`] = `
72-
"isReadonlyOf(isTupleOf([
73-
isReadonlyOf(isTupleOf([
74-
isReadonlyOf(isTupleOf([
75-
isNumber,
76-
isString,
77-
isBoolean
78-
], isArray))
79-
], isArray))
80-
], isArray))"
81-
`;
82-
833
snapshot[`isMapOf<T> > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`;
844
855
snapshot[`isMapOf<T> > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`;
866
87-
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`;
7+
snapshot[`isUniformTupleOf<T> > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`;
888
89-
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`;
9+
snapshot[`isUniformTupleOf<T> > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`;
9010
91-
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
11+
snapshot[`isUniformTupleOf<T> > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`;
9212
93-
snapshot[`isRecordOf<T, K> > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`;
13+
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`;
9414
95-
snapshot[`isRecordOf<T, K> > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`;
15+
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`;
9616
9717
snapshot[`isObjectOf<T> > returns properly named function 1`] = `
9818
"isObjectOf({
@@ -112,10 +32,6 @@ snapshot[`isObjectOf<T> > returns properly named function 3`] = `
11232
})"
11333
`;
11434
115-
snapshot[`isMapOf<T, K> > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`;
116-
117-
snapshot[`isMapOf<T, K> > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`;
118-
11935
snapshot[`isTupleOf<T, E> > returns properly named function 1`] = `
12036
"isTupleOf([
12137
isNumber,
@@ -138,6 +54,34 @@ snapshot[`isTupleOf<T, E> > returns properly named function 3`] = `
13854
])"
13955
`;
14056
57+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 1`] = `"isReadonlyOf(isUniformTupleOf(3, isAny))"`;
58+
59+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 2`] = `"isReadonlyOf(isUniformTupleOf(3, isNumber))"`;
60+
61+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 3`] = `"isReadonlyOf(isUniformTupleOf(3, (anonymous)))"`;
62+
63+
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 1`] = `
64+
"isReadonlyOf(isTupleOf([
65+
isNumber,
66+
isString,
67+
isBoolean
68+
], isArray))"
69+
`;
70+
71+
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 2`] = `"isReadonlyOf(isTupleOf([(anonymous)], isArrayOf(isString)))"`;
72+
73+
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 3`] = `
74+
"isReadonlyOf(isTupleOf([
75+
isReadonlyOf(isTupleOf([
76+
isReadonlyOf(isTupleOf([
77+
isNumber,
78+
isString,
79+
isBoolean
80+
], isArray))
81+
], isArray))
82+
], isArray))"
83+
`;
84+
14185
snapshot[`isTupleOf<T> > returns properly named function 1`] = `
14286
"isTupleOf([
14387
isNumber,
@@ -160,22 +104,78 @@ snapshot[`isTupleOf<T> > returns properly named function 3`] = `
160104
])"
161105
`;
162106
163-
snapshot[`isUniformTupleOf<T> > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`;
107+
snapshot[`isSetOf<T> > returns properly named function 1`] = `"isSetOf(isNumber)"`;
164108
165-
snapshot[`isUniformTupleOf<T> > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`;
109+
snapshot[`isSetOf<T> > returns properly named function 2`] = `"isSetOf((anonymous))"`;
166110
167-
snapshot[`isUniformTupleOf<T> > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`;
111+
snapshot[`isStrictOf<T> > returns properly named function 1`] = `
112+
"isStrictOf(isObjectOf({
113+
a: isNumber,
114+
b: isString,
115+
c: isBoolean
116+
}))"
117+
`;
118+
119+
snapshot[`isStrictOf<T> > returns properly named function 2`] = `"isStrictOf(isObjectOf({a: a}))"`;
120+
121+
snapshot[`isStrictOf<T> > returns properly named function 3`] = `
122+
"isStrictOf(isObjectOf({
123+
a: isStrictOf(isObjectOf({
124+
b: isStrictOf(isObjectOf({c: isBoolean}))
125+
}))
126+
}))"
127+
`;
128+
129+
snapshot[`isLiteralOf<T> > returns properly named function 1`] = `'isLiteralOf("hello")'`;
130+
131+
snapshot[`isLiteralOf<T> > returns properly named function 2`] = `"isLiteralOf(100)"`;
132+
133+
snapshot[`isLiteralOf<T> > returns properly named function 3`] = `"isLiteralOf(100n)"`;
134+
135+
snapshot[`isLiteralOf<T> > returns properly named function 4`] = `"isLiteralOf(true)"`;
136+
137+
snapshot[`isLiteralOf<T> > returns properly named function 5`] = `"isLiteralOf(null)"`;
138+
139+
snapshot[`isLiteralOf<T> > returns properly named function 6`] = `"isLiteralOf(undefined)"`;
140+
141+
snapshot[`isLiteralOf<T> > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`;
142+
143+
snapshot[`isRecordOf<T, K> > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`;
144+
145+
snapshot[`isRecordOf<T, K> > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`;
168146
169147
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
170148
171149
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;
172150
173-
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 1`] = `"isReadonlyOf(isUniformTupleOf(3, isAny))"`;
151+
snapshot[`isReadonlyTupleOf<T> > returns properly named function 1`] = `
152+
"isReadonlyOf(isTupleOf([
153+
isNumber,
154+
isString,
155+
isBoolean
156+
]))"
157+
`;
174158
175-
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 2`] = `"isReadonlyOf(isUniformTupleOf(3, isNumber))"`;
159+
snapshot[`isReadonlyTupleOf<T> > returns properly named function 2`] = `"isReadonlyOf(isTupleOf([(anonymous)]))"`;
176160
177-
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 3`] = `"isReadonlyOf(isUniformTupleOf(3, (anonymous)))"`;
161+
snapshot[`isReadonlyTupleOf<T> > returns properly named function 3`] = `
162+
"isReadonlyOf(isTupleOf([
163+
isReadonlyOf(isTupleOf([
164+
isReadonlyOf(isTupleOf([
165+
isNumber,
166+
isString,
167+
isBoolean
168+
]))
169+
]))
170+
]))"
171+
`;
178172
179-
snapshot[`isSetOf<T> > returns properly named function 1`] = `"isSetOf(isNumber)"`;
173+
snapshot[`isArrayOf<T> > returns properly named function 1`] = `"isArrayOf(isNumber)"`;
180174
181-
snapshot[`isSetOf<T> > returns properly named function 2`] = `"isSetOf((anonymous))"`;
175+
snapshot[`isArrayOf<T> > returns properly named function 2`] = `"isArrayOf((anonymous))"`;
176+
177+
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
178+
179+
snapshot[`isMapOf<T, K> > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`;
180+
181+
snapshot[`isMapOf<T, K> > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`;

is/__snapshots__/utility_test.ts.snap

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

3+
snapshot[`isIntersectionOf<T> > returns properly named function 1`] = `
4+
"isObjectOf({
5+
a: isNumber,
6+
b: isString
7+
})"
8+
`;
9+
310
snapshot[`isPartialOf<T> > returns properly named function 1`] = `
411
"isObjectOf({
512
a: isOptionalOf(isNumber),
@@ -16,6 +23,14 @@ snapshot[`isPartialOf<T> > returns properly named function 2`] = `
1623
})"
1724
`;
1825
26+
snapshot[`isUnionOf<T> > returns properly named function 1`] = `
27+
"isUnionOf([
28+
isNumber,
29+
isString,
30+
isBoolean
31+
])"
32+
`;
33+
1934
snapshot[`isOmitOf<T, K> > returns properly named function 1`] = `
2035
"isObjectOf({
2136
a: isNumber,
@@ -33,18 +48,3 @@ snapshot[`isPickOf<T, K> > returns properly named function 1`] = `
3348
`;
3449
3550
snapshot[`isPickOf<T, K> > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`;
36-
37-
snapshot[`isIntersectionOf<T> > returns properly named function 1`] = `
38-
"isObjectOf({
39-
a: isNumber,
40-
b: isString
41-
})"
42-
`;
43-
44-
snapshot[`isUnionOf<T> > returns properly named function 1`] = `
45-
"isUnionOf([
46-
isNumber,
47-
isString,
48-
isBoolean
49-
])"
50-
`;

is/annotation.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { Predicate } from "./type.ts";
2+
import type { Writable } from "../_typeutil.ts";
23
import {
34
getMetadata,
5+
getPredicateFactoryMetadata,
46
type PredicateFactoryMetadata,
57
setPredicateFactoryMetadata,
68
type WithMetadata,
@@ -59,6 +61,36 @@ type IsOptionalOfMetadata = {
5961
args: Parameters<typeof isOptionalOf>;
6062
};
6163

64+
/**
65+
* Return an `Optional` un-annotated type predicate function that returns `true` if the type of `x` is `T`.
66+
*
67+
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
68+
*
69+
* ```ts
70+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
71+
*
72+
* const isMyType = is.UnwrapOptionalOf(is.OptionalOf(is.String));
73+
* const a: unknown = "a";
74+
* if (isMyType(a)) {
75+
* // a is narrowed to string
76+
* const _: string = a;
77+
* }
78+
* ```
79+
*/
80+
export function isUnwrapOptionalOf<P extends Predicate<unknown>>(
81+
pred: P,
82+
): UnwrapOptionalOf<P> {
83+
if (!isOptional(pred)) return pred as UnwrapOptionalOf<P>;
84+
const { args } = getPredicateFactoryMetadata(pred);
85+
return args[0] as UnwrapOptionalOf<P>;
86+
}
87+
88+
type UnwrapOptionalOf<T> = T extends
89+
Predicate<undefined | infer U> & WithMetadata<IsOptionalOfMetadata>
90+
? Predicate<U>
91+
: T extends Predicate<unknown> ? T
92+
: never;
93+
6294
/**
6395
* Return `true` if the type of predicate function `x` is annotated as `Readonly`
6496
*/
@@ -106,9 +138,41 @@ type IsReadonlyOfMetadata = {
106138
args: Parameters<typeof isReadonlyOf>;
107139
};
108140

141+
/**
142+
* Return an `Readonly` un-annotated type predicate function that returns `true` if the type of `x` is `T`.
143+
*
144+
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
145+
*
146+
* ```ts
147+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
148+
*
149+
* const isMyType = is.UnwrapReadonlyOf(is.ReadonlyOf(is.TupleOf([is.String, is.Number])));
150+
* const a: unknown = ["a", 1];
151+
* if (isMyType(a)) {
152+
* // a is narrowed to [string, number]
153+
* const _: [string, number] = a;
154+
* }
155+
* ```
156+
*/
157+
export function isUnwrapReadonlyOf<P extends Predicate<unknown>>(
158+
pred: P,
159+
): UnwrapReadonlyOf<P> {
160+
if (!isReadonly(pred)) return pred as UnwrapReadonlyOf<P>;
161+
const { args } = getPredicateFactoryMetadata(pred);
162+
return args[0] as UnwrapReadonlyOf<P>;
163+
}
164+
165+
type UnwrapReadonlyOf<T> = T extends
166+
Predicate<infer U> & WithMetadata<IsReadonlyOfMetadata>
167+
? Predicate<Writable<U>>
168+
: T extends Predicate<unknown> ? T
169+
: never;
170+
109171
export default {
110172
Optional: isOptional,
111173
OptionalOf: isOptionalOf,
112174
Readonly: isReadonly,
113175
ReadonlyOf: isReadonlyOf,
176+
UnwrapOptionalOf: isUnwrapOptionalOf,
177+
UnwrapReadonlyOf: isUnwrapReadonlyOf,
114178
};

0 commit comments

Comments
 (0)