Skip to content

Commit cef9f08

Browse files
committed
👍 Add isReadonlyOf in annotation.ts
1 parent 7a9b884 commit cef9f08

File tree

5 files changed

+180
-99
lines changed

5 files changed

+180
-99
lines changed

is/__snapshots__/annotation_test.ts.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ export const snapshot = {};
33
snapshot[`isOptionalOf<T> > returns properly named function 1`] = `"isOptionalOf(isNumber)"`;
44
55
snapshot[`isOptionalOf<T> > returns properly named function 2`] = `"isOptionalOf(isNumber)"`;
6+
7+
snapshot[`isReadonlyOf<T> > returns properly named function 1`] = `"isReadonlyOf(isNumber)"`;
8+
9+
snapshot[`isReadonlyOf<T> > returns properly named function 2`] = `"isReadonlyOf(isReadonlyOf(isNumber))"`;
Lines changed: 86 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
export const snapshot = {};
22

3-
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
4-
5-
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;
3+
snapshot[`isTupleOf<T, E> > returns properly named function 1`] = `
4+
"isTupleOf([
5+
isNumber,
6+
isString,
7+
isBoolean
8+
], isArray)"
9+
`;
610

7-
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 1`] = `"isReadonlyUniformTupleOf(3, isAny)"`;
11+
snapshot[`isTupleOf<T, E> > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`;
812

9-
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 2`] = `"isReadonlyUniformTupleOf(3, isNumber)"`;
13+
snapshot[`isTupleOf<T, E> > returns properly named function 3`] = `
14+
"isTupleOf([
15+
isTupleOf([
16+
isTupleOf([
17+
isNumber,
18+
isString,
19+
isBoolean
20+
], isArray)
21+
], isArray)
22+
])"
23+
`;
1024

11-
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 3`] = `"isReadonlyUniformTupleOf(3, (anonymous))"`;
25+
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
1226
13-
snapshot[`isMapOf<T, K> > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`;
27+
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
1428
15-
snapshot[`isMapOf<T, K> > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`;
29+
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;
1630
1731
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 1`] = `
1832
"isReadonlyTupleOf([
@@ -36,31 +50,13 @@ snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 3`] = `
3650
], isArray)"
3751
`;
3852
39-
snapshot[`isMapOf<T> > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`;
40-
41-
snapshot[`isMapOf<T> > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`;
42-
43-
snapshot[`isObjectOf<T> > returns properly named function 1`] = `
44-
"isObjectOf({
45-
a: isNumber,
46-
b: isString,
47-
c: isBoolean
48-
})"
49-
`;
50-
51-
snapshot[`isObjectOf<T> > returns properly named function 2`] = `"isObjectOf({a: a})"`;
53+
snapshot[`isSetOf<T> > returns properly named function 1`] = `"isSetOf(isNumber)"`;
5254
53-
snapshot[`isObjectOf<T> > returns properly named function 3`] = `
54-
"isObjectOf({
55-
a: isObjectOf({
56-
b: isObjectOf({c: isBoolean})
57-
})
58-
})"
59-
`;
55+
snapshot[`isSetOf<T> > returns properly named function 2`] = `"isSetOf((anonymous))"`;
6056
61-
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`;
57+
snapshot[`isMapOf<T> > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`;
6258
63-
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`;
59+
snapshot[`isMapOf<T> > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`;
6460
6561
snapshot[`isLiteralOf<T> > returns properly named function 1`] = `'isLiteralOf("hello")'`;
6662
@@ -76,59 +72,11 @@ snapshot[`isLiteralOf<T> > returns properly named function 6`] = `"isLiteralOf(u
7672
7773
snapshot[`isLiteralOf<T> > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`;
7874
79-
snapshot[`isRecordOf<T, K> > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`;
80-
81-
snapshot[`isRecordOf<T, K> > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`;
82-
83-
snapshot[`isArrayOf<T> > returns properly named function 1`] = `"isArrayOf(isNumber)"`;
84-
85-
snapshot[`isArrayOf<T> > returns properly named function 2`] = `"isArrayOf((anonymous))"`;
86-
87-
snapshot[`isTupleOf<T> > returns properly named function 1`] = `
88-
"isTupleOf([
89-
isNumber,
90-
isString,
91-
isBoolean
92-
])"
93-
`;
94-
95-
snapshot[`isTupleOf<T> > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`;
96-
97-
snapshot[`isTupleOf<T> > returns properly named function 3`] = `
98-
"isTupleOf([
99-
isTupleOf([
100-
isTupleOf([
101-
isNumber,
102-
isString,
103-
isBoolean
104-
])
105-
])
106-
])"
107-
`;
108-
109-
snapshot[`isTupleOf<T, E> > returns properly named function 1`] = `
110-
"isTupleOf([
111-
isNumber,
112-
isString,
113-
isBoolean
114-
], isArray)"
115-
`;
116-
117-
snapshot[`isTupleOf<T, E> > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`;
75+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 1`] = `"isReadonlyUniformTupleOf(3, isAny)"`;
11876
119-
snapshot[`isTupleOf<T, E> > returns properly named function 3`] = `
120-
"isTupleOf([
121-
isTupleOf([
122-
isTupleOf([
123-
isNumber,
124-
isString,
125-
isBoolean
126-
], isArray)
127-
], isArray)
128-
])"
129-
`;
77+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 2`] = `"isReadonlyUniformTupleOf(3, isNumber)"`;
13078
131-
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
79+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 3`] = `"isReadonlyUniformTupleOf(3, (anonymous))"`;
13280
13381
snapshot[`isStrictOf<T> > returns properly named function 1`] = `
13482
"isStrictOf(isObjectOf({
@@ -148,6 +96,16 @@ snapshot[`isStrictOf<T> > returns properly named function 3`] = `
14896
}))"
14997
`;
15098
99+
snapshot[`isUniformTupleOf<T> > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`;
100+
101+
snapshot[`isUniformTupleOf<T> > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`;
102+
103+
snapshot[`isUniformTupleOf<T> > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`;
104+
105+
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`;
106+
107+
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`;
108+
151109
snapshot[`isReadonlyTupleOf<T> > returns properly named function 1`] = `
152110
"isReadonlyTupleOf([
153111
isNumber,
@@ -170,12 +128,54 @@ snapshot[`isReadonlyTupleOf<T> > returns properly named function 3`] = `
170128
])"
171129
`;
172130
173-
snapshot[`isSetOf<T> > returns properly named function 1`] = `"isSetOf(isNumber)"`;
131+
snapshot[`isRecordOf<T, K> > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`;
174132
175-
snapshot[`isSetOf<T> > returns properly named function 2`] = `"isSetOf((anonymous))"`;
133+
snapshot[`isRecordOf<T, K> > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`;
176134
177-
snapshot[`isUniformTupleOf<T> > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`;
135+
snapshot[`isTupleOf<T> > returns properly named function 1`] = `
136+
"isTupleOf([
137+
isNumber,
138+
isString,
139+
isBoolean
140+
])"
141+
`;
178142
179-
snapshot[`isUniformTupleOf<T> > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`;
143+
snapshot[`isTupleOf<T> > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`;
180144
181-
snapshot[`isUniformTupleOf<T> > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`;
145+
snapshot[`isTupleOf<T> > returns properly named function 3`] = `
146+
"isTupleOf([
147+
isTupleOf([
148+
isTupleOf([
149+
isNumber,
150+
isString,
151+
isBoolean
152+
])
153+
])
154+
])"
155+
`;
156+
157+
snapshot[`isMapOf<T, K> > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`;
158+
159+
snapshot[`isMapOf<T, K> > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`;
160+
161+
snapshot[`isArrayOf<T> > returns properly named function 1`] = `"isArrayOf(isNumber)"`;
162+
163+
snapshot[`isArrayOf<T> > returns properly named function 2`] = `"isArrayOf((anonymous))"`;
164+
165+
snapshot[`isObjectOf<T> > returns properly named function 1`] = `
166+
"isObjectOf({
167+
a: isNumber,
168+
b: isString,
169+
c: isBoolean
170+
})"
171+
`;
172+
173+
snapshot[`isObjectOf<T> > returns properly named function 2`] = `"isObjectOf({a: a})"`;
174+
175+
snapshot[`isObjectOf<T> > returns properly named function 3`] = `
176+
"isObjectOf({
177+
a: isObjectOf({
178+
b: isObjectOf({c: isBoolean})
179+
})
180+
})"
181+
`;

is/__snapshots__/utility_test.ts.snap

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

3+
snapshot[`isUnionOf<T> > returns properly named function 1`] = `
4+
"isUnionOf([
5+
isNumber,
6+
isString,
7+
isBoolean
8+
])"
9+
`;
10+
311
snapshot[`isPartialOf<T> > returns properly named function 1`] = `
412
"isObjectOf({
513
a: isOptionalOf(isNumber),
@@ -23,28 +31,20 @@ snapshot[`isIntersectionOf<T> > returns properly named function 1`] = `
2331
})"
2432
`;
2533
26-
snapshot[`isUnionOf<T> > returns properly named function 1`] = `
27-
"isUnionOf([
28-
isNumber,
29-
isString,
30-
isBoolean
31-
])"
32-
`;
33-
34-
snapshot[`isOmitOf<T, K> > returns properly named function 1`] = `
34+
snapshot[`isPickOf<T, K> > returns properly named function 1`] = `
3535
"isObjectOf({
3636
a: isNumber,
3737
c: isBoolean
3838
})"
3939
`;
4040
41-
snapshot[`isOmitOf<T, K> > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`;
41+
snapshot[`isPickOf<T, K> > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`;
4242
43-
snapshot[`isPickOf<T, K> > returns properly named function 1`] = `
43+
snapshot[`isOmitOf<T, K> > returns properly named function 1`] = `
4444
"isObjectOf({
4545
a: isNumber,
4646
c: isBoolean
4747
})"
4848
`;
4949
50-
snapshot[`isPickOf<T, K> > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`;
50+
snapshot[`isOmitOf<T, K> > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`;

is/annotation.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,56 @@ type IsOptionalOfMetadata = {
5959
args: Parameters<typeof isOptionalOf>;
6060
};
6161

62+
/**
63+
* Return `true` if the type of predicate function `x` is annotated as `Readonly`
64+
*/
65+
export function isReadonly<P extends Predicate<unknown>>(
66+
x: P,
67+
): x is P & WithMetadata<IsReadonlyOfMetadata> {
68+
const m = getMetadata(x);
69+
if (m == null) return false;
70+
return (m as PredicateFactoryMetadata).name === "isReadonlyOf";
71+
}
72+
73+
/**
74+
* Return an `Readonly` annotated type predicate function that returns `true` if the type of `x` is `T`.
75+
*
76+
* Note that this function does nothing but annotate the predicate function as `Readonly`.
77+
*
78+
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
79+
*
80+
* ```ts
81+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
82+
*
83+
* const isMyType = is.ReadonlyOf(is.TupleOf([is.String, is.Number]));
84+
* const a: unknown = ["a", 1];
85+
* if (isMyType(a)) {
86+
* // a is narrowed to readonly [string, number]
87+
* const _: readonly [string, number] = a;
88+
* }
89+
* ```
90+
*/
91+
export function isReadonlyOf<T>(
92+
pred: Predicate<T>,
93+
):
94+
& Predicate<Readonly<T>>
95+
& WithMetadata<IsReadonlyOfMetadata> {
96+
return setPredicateFactoryMetadata(
97+
(x: unknown): x is Readonly<T> => pred(x),
98+
{ name: "isReadonlyOf", args: [pred] },
99+
) as
100+
& Predicate<Readonly<T>>
101+
& WithMetadata<IsReadonlyOfMetadata>;
102+
}
103+
104+
type IsReadonlyOfMetadata = {
105+
name: "isReadonlyOf";
106+
args: Parameters<typeof isReadonlyOf>;
107+
};
108+
62109
export default {
63110
Optional: isOptional,
64111
OptionalOf: isOptionalOf,
112+
Readonly: isReadonly,
113+
ReadonlyOf: isReadonlyOf,
65114
};

is/annotation_test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import {
2323
isSyncFunction,
2424
isUndefined,
2525
} from "./core.ts";
26-
import is, { isOptionalOf } from "./annotation.ts";
26+
import { isObjectOf, isTupleOf, isUniformTupleOf } from "./factory.ts";
27+
import is, { isOptionalOf, isReadonlyOf } from "./annotation.ts";
2728

2829
const examples = {
2930
string: ["", "Hello world"],
@@ -152,6 +153,33 @@ Deno.test("isOptionalOf<T>", async (t) => {
152153
});
153154
});
154155

156+
Deno.test("isReadonlyOf<T>", async (t) => {
157+
await t.step("returns properly named function", async (t) => {
158+
await assertSnapshot(t, isReadonlyOf(isNumber).name);
159+
// Nesting does nothing
160+
await assertSnapshot(t, isReadonlyOf(isReadonlyOf(isNumber)).name);
161+
});
162+
await t.step("returns proper type predicate", () => {
163+
const a: unknown = undefined;
164+
if (isReadonlyOf(isNumber)(a)) {
165+
assertType<Equal<typeof a, Readonly<number>>>(true);
166+
}
167+
if (isReadonlyOf(isTupleOf([isString, isNumber, isBoolean]))(a)) {
168+
assertType<Equal<typeof a, Readonly<[string, number, boolean]>>>(true);
169+
}
170+
if (isReadonlyOf(isUniformTupleOf(3, isString))(a)) {
171+
assertType<Equal<typeof a, Readonly<[string, string, string]>>>(true);
172+
}
173+
if (
174+
isReadonlyOf(isObjectOf({ a: isString, b: isNumber, c: isBoolean }))(a)
175+
) {
176+
assertType<
177+
Equal<typeof a, Readonly<{ a: string; b: number; c: boolean }>>
178+
>(true);
179+
}
180+
});
181+
});
182+
155183
Deno.test("is", async (t) => {
156184
const mod = await import("./annotation.ts");
157185
const casesOfAliasAndIsFunction = Object.entries(mod)

0 commit comments

Comments
 (0)