Skip to content

Commit 91cf555

Browse files
committed
👍 Add isSet and isSetOf functions
1 parent 5b37cf4 commit 91cf555

File tree

4 files changed

+175
-45
lines changed

4 files changed

+175
-45
lines changed

__snapshots__/is_test.ts.snap

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

3-
snapshot[`isUniformTupleOf<T> > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`;
4-
5-
snapshot[`isUniformTupleOf<T> > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`;
6-
7-
snapshot[`isUniformTupleOf<T> > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`;
8-
93
snapshot[`isLiteralOf<T> > returns properly named function 1`] = `'isLiteralOf("hello")'`;
104
115
snapshot[`isLiteralOf<T> > returns properly named function 2`] = `"isLiteralOf(100)"`;
@@ -20,53 +14,17 @@ snapshot[`isLiteralOf<T> > returns properly named function 6`] = `"isLiteralOf(u
2014
2115
snapshot[`isLiteralOf<T> > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`;
2216
23-
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber)"`;
24-
25-
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous))"`;
26-
2717
snapshot[`isRecordOf<T, K> > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`;
2818
2919
snapshot[`isRecordOf<T, K> > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`;
3020
31-
snapshot[`isTupleOf<T, E> > returns properly named function 1`] = `
32-
"isTupleOf([
33-
isNumber,
34-
isString,
35-
isBoolean
36-
], isArray)"
37-
`;
38-
39-
snapshot[`isTupleOf<T, E> > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`;
40-
41-
snapshot[`isTupleOf<T, E> > returns properly named function 3`] = `
42-
"isTupleOf([
43-
isTupleOf([
44-
isTupleOf([
45-
isNumber,
46-
isString,
47-
isBoolean
48-
], isArray)
49-
], isArray)
50-
])"
51-
`;
52-
5321
snapshot[`isAllOf<T> > returns properly named function 1`] = `
5422
"isAllOf([
5523
isObjectOf({a: isNumber}),
5624
isObjectOf({b: isString})
5725
])"
5826
`;
5927
60-
snapshot[`isOneOf<T> > returns properly named function 1`] = `
61-
"isOneOf([
62-
isNumber,
63-
isString,
64-
isBoolean
65-
])"
66-
`;
67-
68-
snapshot[`isOptionalOf<T> > returns properly named function 1`] = `"isOptionalOf(isNumber)"`;
69-
7028
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 1`] = `
7129
"isReadonlyTupleOf([
7230
isNumber,
@@ -89,16 +47,46 @@ snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 3`] = `
8947
], isArray)"
9048
`;
9149
92-
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
50+
snapshot[`isUniformTupleOf<T> > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`;
9351
94-
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;
52+
snapshot[`isUniformTupleOf<T> > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`;
53+
54+
snapshot[`isUniformTupleOf<T> > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`;
55+
56+
snapshot[`isTupleOf<T, E> > returns properly named function 1`] = `
57+
"isTupleOf([
58+
isNumber,
59+
isString,
60+
isBoolean
61+
], isArray)"
62+
`;
63+
64+
snapshot[`isTupleOf<T, E> > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`;
65+
66+
snapshot[`isTupleOf<T, E> > returns properly named function 3`] = `
67+
"isTupleOf([
68+
isTupleOf([
69+
isTupleOf([
70+
isNumber,
71+
isString,
72+
isBoolean
73+
], isArray)
74+
], isArray)
75+
])"
76+
`;
9577
9678
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 1`] = `"isReadonlyUniformTupleOf(3, isAny)"`;
9779
9880
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 2`] = `"isReadonlyUniformTupleOf(3, isNumber)"`;
9981
10082
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 3`] = `"isReadonlyUniformTupleOf(3, (anonymous))"`;
10183
84+
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber)"`;
85+
86+
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous))"`;
87+
88+
snapshot[`isOptionalOf<T> > returns properly named function 1`] = `"isOptionalOf(isNumber)"`;
89+
10290
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
10391
10492
snapshot[`isTupleOf<T> > returns properly named function 1`] = `
@@ -149,6 +137,18 @@ snapshot[`isReadonlyTupleOf<T> > returns properly named function 3`] = `
149137
])"
150138
`;
151139
140+
snapshot[`isOneOf<T> > returns properly named function 1`] = `
141+
"isOneOf([
142+
isNumber,
143+
isString,
144+
isBoolean
145+
])"
146+
`;
147+
148+
snapshot[`isSetOf<T> > returns properly named function 1`] = `"isSetOf(isNumber)"`;
149+
150+
snapshot[`isSetOf<T> > returns properly named function 2`] = `"isSetOf((anonymous))"`;
151+
152152
snapshot[`isObjectOf<T> > returns properly named function 1`] = `
153153
"isObjectOf({
154154
a: isNumber,
@@ -166,3 +166,7 @@ snapshot[`isObjectOf<T> > returns properly named function 3`] = `
166166
})
167167
})"
168168
`;
169+
170+
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
171+
172+
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;

is.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,60 @@ export function isArrayOf<T>(
178178
);
179179
}
180180

181+
/**
182+
* Return `true` if the type of `x` is `Set<unknown>`.
183+
*
184+
* ```ts
185+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
186+
*
187+
* const a: unknown = new Set([0, 1, 2]);
188+
* if (is.Set(a)) {
189+
* // a is narrowed to Set<unknown>
190+
* const _: Set<unknown> = a;
191+
* }
192+
* ```
193+
*/
194+
export const isSet = Object.defineProperties(isInstanceOf(Set), {
195+
name: {
196+
value: "isSet",
197+
},
198+
});
199+
200+
/**
201+
* Return a type predicate function that returns `true` if the type of `x` is `Set<T>`.
202+
*
203+
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
204+
*
205+
* ```ts
206+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
207+
*
208+
* const isMyType = is.SetOf(is.String);
209+
* const a: unknown = new Set(["a", "b", "c"]);
210+
* if (isMyType(a)) {
211+
* // a is narrowed to Set<string>
212+
* const _: Set<string> = a;
213+
* }
214+
* ```
215+
*/
216+
export function isSetOf<T>(
217+
pred: Predicate<T>,
218+
): Predicate<Set<T>> {
219+
return Object.defineProperties(
220+
(x: unknown): x is Set<T> => {
221+
if (!isSet(x)) return false;
222+
for (const v of x.values()) {
223+
if (!pred(v)) return false;
224+
}
225+
return true;
226+
},
227+
{
228+
name: {
229+
get: () => `isSetOf(${inspect(pred)})`,
230+
},
231+
},
232+
);
233+
}
234+
181235
/**
182236
* Tuple type of types that are predicated by an array of predicate functions.
183237
*
@@ -547,7 +601,7 @@ export type RecordOf<T, K extends PropertyKey = PropertyKey> = Record<K, T>;
547601
export function isRecord(
548602
x: unknown,
549603
): x is Record<PropertyKey, unknown> {
550-
if (isNullish(x) || isArray(x)) {
604+
if (isNullish(x) || isArray(x) || isSet(x)) {
551605
return false;
552606
}
553607
return typeof x === "object";
@@ -1111,6 +1165,8 @@ export default {
11111165
Boolean: isBoolean,
11121166
Array: isArray,
11131167
ArrayOf: isArrayOf,
1168+
Set: isSet,
1169+
SetOf: isSetOf,
11141170
TupleOf: isTupleOf,
11151171
ReadonlyTupleOf: isReadonlyTupleOf,
11161172
UniformTupleOf: isUniformTupleOf,

is_bench.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const cs: unknown[] = [
77
true,
88
["a", 1, true],
99
["a", "b", "c"],
10+
new Set(["a", "b", "c"]),
1011
{ a: "a", b: "b", c: "c" },
1112
{ a: "a", b: 1, c: true },
1213
() => {},
@@ -104,6 +105,35 @@ Deno.bench({
104105
},
105106
});
106107

108+
Deno.bench({
109+
name: "is.Set",
110+
fn: () => {
111+
for (const c of cs) {
112+
is.Set(c);
113+
}
114+
},
115+
});
116+
117+
Deno.bench({
118+
name: "is.SetOf<T>",
119+
fn: () => {
120+
const pred = is.SetOf(is.String);
121+
for (const c of cs) {
122+
pred(c);
123+
}
124+
},
125+
});
126+
127+
const isSetOfPred = is.SetOf(is.String);
128+
Deno.bench({
129+
name: "is.SetOf<T> (pre)",
130+
fn: () => {
131+
for (const c of cs) {
132+
isSetOfPred(c);
133+
}
134+
},
135+
});
136+
107137
const predTup = [is.String, is.Number, is.Boolean] as const;
108138
Deno.bench({
109139
name: "is.TupleOf<T>",

is_test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import is, {
2929
isReadonlyUniformTupleOf,
3030
isRecord,
3131
isRecordOf,
32+
isSet,
33+
isSetOf,
3234
isString,
3335
isSymbol,
3436
isSyncFunction,
@@ -56,6 +58,7 @@ const examples = {
5658
bigint: [0n, 1234567890n],
5759
boolean: [true, false],
5860
array: [[], [0, 1, 2], ["a", "b", "c"], [0, "a", true]],
61+
set: [new Set(), new Set([0, 1, 2]), new Set(["a", "b", "c"])],
5962
record: [{}, { a: 0, b: 1, c: 2 }, { a: "a", b: "b", c: "c" }],
6063
syncFunction: [function a() {}, () => {}],
6164
asyncFunction: [async function b() {}, async () => {}],
@@ -130,6 +133,7 @@ Deno.test("isAny", async (t) => {
130133
"bigint",
131134
"boolean",
132135
"array",
136+
"set",
133137
"record",
134138
"syncFunction",
135139
"asyncFunction",
@@ -150,6 +154,7 @@ Deno.test("isUnknown", async (t) => {
150154
"bigint",
151155
"boolean",
152156
"array",
157+
"set",
153158
"record",
154159
"syncFunction",
155160
"asyncFunction",
@@ -208,6 +213,36 @@ Deno.test("isArrayOf<T>", async (t) => {
208213
});
209214
});
210215

216+
Deno.test("isSet", async (t) => {
217+
await testWithExamples(t, isSet, { validExamples: ["set"] });
218+
});
219+
220+
Deno.test("isSetOf<T>", async (t) => {
221+
await t.step("returns properly named function", async (t) => {
222+
await assertSnapshot(t, isSetOf(isNumber).name);
223+
await assertSnapshot(t, isSetOf((_x): _x is string => false).name);
224+
});
225+
await t.step("returns proper type predicate", () => {
226+
const a: unknown = new Set([0, 1, 2]);
227+
if (isSetOf(isNumber)(a)) {
228+
assertType<Equal<typeof a, Set<number>>>(true);
229+
}
230+
});
231+
await t.step("returns true on T set", () => {
232+
assertEquals(isSetOf(isNumber)(new Set([0, 1, 2])), true);
233+
assertEquals(isSetOf(isString)(new Set(["a", "b", "c"])), true);
234+
assertEquals(isSetOf(isBoolean)(new Set([true, false, true])), true);
235+
});
236+
await t.step("returns false on non T set", () => {
237+
assertEquals(isSetOf(isString)(new Set([0, 1, 2])), false);
238+
assertEquals(isSetOf(isNumber)(new Set(["a", "b", "c"])), false);
239+
assertEquals(isSetOf(isString)(new Set([true, false, true])), false);
240+
});
241+
await testWithExamples(t, isSetOf((_: unknown): _ is unknown => true), {
242+
excludeExamples: ["set"],
243+
});
244+
});
245+
211246
Deno.test("TupleOf<T>", () => {
212247
assertType<
213248
Equal<
@@ -1048,6 +1083,11 @@ Deno.test("isOptionalOf<T>", async (t) => {
10481083
validExamples: ["array", "undefined"],
10491084
});
10501085
});
1086+
await t.step("with isSet", async (t) => {
1087+
await testWithExamples(t, isOptionalOf(isSet), {
1088+
validExamples: ["set", "undefined"],
1089+
});
1090+
});
10511091
await t.step("with isRecord", async (t) => {
10521092
await testWithExamples(t, isOptionalOf(isRecord), {
10531093
validExamples: ["record", "date", "promise", "undefined"],

0 commit comments

Comments
 (0)