Skip to content

Commit b6a1163

Browse files
committed
👍 Allowed to specify key predicate for isRecordOf<T, K>
1 parent d8f7111 commit b6a1163

File tree

3 files changed

+127
-70
lines changed

3 files changed

+127
-70
lines changed

__snapshots__/is_test.ts.snap

Lines changed: 67 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
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+
39
snapshot[`isLiteralOf<T> > returns properly named function 1`] = `'isLiteralOf("hello")'`;
410
511
snapshot[`isLiteralOf<T> > returns properly named function 2`] = `"isLiteralOf(100)"`;
@@ -14,69 +20,43 @@ snapshot[`isLiteralOf<T> > returns properly named function 6`] = `"isLiteralOf(u
1420
1521
snapshot[`isLiteralOf<T> > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`;
1622
17-
snapshot[`isObjectOf<T> > returns properly named function 1`] = `
18-
"isObjectOf({
19-
a: isNumber,
20-
b: isString,
21-
c: isBoolean
22-
})"
23-
`;
23+
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber)"`;
2424
25-
snapshot[`isObjectOf<T> > returns properly named function 2`] = `"isObjectOf({a: a})"`;
25+
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous))"`;
2626
27-
snapshot[`isObjectOf<T> > returns properly named function 3`] = `
28-
"isObjectOf({
29-
a: isObjectOf({
30-
b: isObjectOf({c: isBoolean})
31-
})
32-
})"
33-
`;
27+
snapshot[`isRecordOf<T, K> > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`;
3428
35-
snapshot[`isTupleOf<T> > returns properly named function 1`] = `
29+
snapshot[`isRecordOf<T, K> > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`;
30+
31+
snapshot[`isTupleOf<T, E> > returns properly named function 1`] = `
3632
"isTupleOf([
3733
isNumber,
3834
isString,
3935
isBoolean
40-
])"
36+
], isArray)"
4137
`;
4238
43-
snapshot[`isTupleOf<T> > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`;
39+
snapshot[`isTupleOf<T, E> > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`;
4440
45-
snapshot[`isTupleOf<T> > returns properly named function 3`] = `
41+
snapshot[`isTupleOf<T, E> > returns properly named function 3`] = `
4642
"isTupleOf([
4743
isTupleOf([
4844
isTupleOf([
4945
isNumber,
5046
isString,
5147
isBoolean
52-
])
53-
])
48+
], isArray)
49+
], isArray)
5450
])"
5551
`;
5652
57-
snapshot[`isOptionalOf<T> > returns properly named function 1`] = `"isOptionalOf(isNumber)"`;
58-
59-
snapshot[`isUniformTupleOf<T> > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`;
60-
61-
snapshot[`isUniformTupleOf<T> > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`;
62-
63-
snapshot[`isUniformTupleOf<T> > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`;
64-
65-
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
66-
67-
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;
68-
6953
snapshot[`isAllOf<T> > returns properly named function 1`] = `
7054
"isAllOf([
7155
isObjectOf({a: isNumber}),
7256
isObjectOf({b: isString})
7357
])"
7458
`;
7559
76-
snapshot[`isRecordOf<T> > returns properly named function 1`] = `"isRecordOf(isNumber)"`;
77-
78-
snapshot[`isRecordOf<T> > returns properly named function 2`] = `"isRecordOf((anonymous))"`;
79-
8060
snapshot[`isOneOf<T> > returns properly named function 1`] = `
8161
"isOneOf([
8262
isNumber,
@@ -85,61 +65,67 @@ snapshot[`isOneOf<T> > returns properly named function 1`] = `
8565
])"
8666
`;
8767
88-
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
68+
snapshot[`isOptionalOf<T> > returns properly named function 1`] = `"isOptionalOf(isNumber)"`;
8969
90-
snapshot[`isTupleOf<T, E> > returns properly named function 1`] = `
91-
"isTupleOf([
70+
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 1`] = `
71+
"isReadonlyTupleOf([
9272
isNumber,
9373
isString,
9474
isBoolean
9575
], isArray)"
9676
`;
9777
98-
snapshot[`isTupleOf<T, E> > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`;
78+
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 2`] = `"isReadonlyTupleOf([(anonymous)], isArrayOf(isString))"`;
9979
100-
snapshot[`isTupleOf<T, E> > returns properly named function 3`] = `
101-
"isTupleOf([
102-
isTupleOf([
103-
isTupleOf([
80+
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 3`] = `
81+
"isReadonlyTupleOf([
82+
isReadonlyTupleOf([
83+
isReadonlyTupleOf([
10484
isNumber,
10585
isString,
10686
isBoolean
10787
], isArray)
10888
], isArray)
109-
])"
89+
], isArray)"
11090
`;
11191
112-
snapshot[`isArrayOf<T> > returns properly named function 1`] = `"isArrayOf(isNumber)"`;
92+
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
11393
114-
snapshot[`isArrayOf<T> > returns properly named function 2`] = `"isArrayOf((anonymous))"`;
94+
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;
11595
116-
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 1`] = `
117-
"isReadonlyTupleOf([
96+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 1`] = `"isReadonlyUniformTupleOf(3, isAny)"`;
97+
98+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 2`] = `"isReadonlyUniformTupleOf(3, isNumber)"`;
99+
100+
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 3`] = `"isReadonlyUniformTupleOf(3, (anonymous))"`;
101+
102+
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
103+
104+
snapshot[`isTupleOf<T> > returns properly named function 1`] = `
105+
"isTupleOf([
118106
isNumber,
119107
isString,
120108
isBoolean
121-
], isArray)"
109+
])"
122110
`;
123111
124-
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 2`] = `"isReadonlyTupleOf([(anonymous)], isArrayOf(isString))"`;
112+
snapshot[`isTupleOf<T> > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`;
125113
126-
snapshot[`isReadonlyTupleOf<T, E> > returns properly named function 3`] = `
127-
"isReadonlyTupleOf([
128-
isReadonlyTupleOf([
129-
isReadonlyTupleOf([
114+
snapshot[`isTupleOf<T> > returns properly named function 3`] = `
115+
"isTupleOf([
116+
isTupleOf([
117+
isTupleOf([
130118
isNumber,
131119
isString,
132120
isBoolean
133-
], isArray)
134-
], isArray)
135-
], isArray)"
121+
])
122+
])
123+
])"
136124
`;
137125
138-
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 1`] = `"isReadonlyUniformTupleOf(3, isAny)"`;
139-
140-
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 2`] = `"isReadonlyUniformTupleOf(3, isNumber)"`;
126+
snapshot[`isArrayOf<T> > returns properly named function 1`] = `"isArrayOf(isNumber)"`;
141127
142-
snapshot[`isReadonlyUniformTupleOf<T> > returns properly named function 3`] = `"isReadonlyUniformTupleOf(3, (anonymous))"`;
128+
snapshot[`isArrayOf<T> > returns properly named function 2`] = `"isArrayOf((anonymous))"`;
143129
144130
snapshot[`isReadonlyTupleOf<T> > returns properly named function 1`] = `
145131
"isReadonlyTupleOf([
@@ -162,3 +148,21 @@ snapshot[`isReadonlyTupleOf<T> > returns properly named function 3`] = `
162148
])
163149
])"
164150
`;
151+
152+
snapshot[`isObjectOf<T> > returns properly named function 1`] = `
153+
"isObjectOf({
154+
a: isNumber,
155+
b: isString,
156+
c: isBoolean
157+
})"
158+
`;
159+
160+
snapshot[`isObjectOf<T> > returns properly named function 2`] = `"isObjectOf({a: a})"`;
161+
162+
snapshot[`isObjectOf<T> > returns properly named function 3`] = `
163+
"isObjectOf({
164+
a: isObjectOf({
165+
b: isObjectOf({c: isBoolean})
166+
})
167+
})"
168+
`;

is.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -504,9 +504,9 @@ export function isReadonlyUniformTupleOf<T, N extends number>(
504504
}
505505

506506
/**
507-
* Synonym of `Record<PropertyKey, T>`
507+
* Synonym of `Record<K, T>`
508508
*/
509-
export type RecordOf<T> = Record<PropertyKey, T>;
509+
export type RecordOf<T, K extends PropertyKey = PropertyKey> = Record<K, T>;
510510

511511
/**
512512
* Return `true` if the type of `x` is `Record<PropertyKey, unknown>`.
@@ -531,7 +531,7 @@ export function isRecord(
531531
}
532532

533533
/**
534-
* Return a type predicate function that returns `true` if the type of `x` is `RecordOf<T>`.
534+
* Return a type predicate function that returns `true` if the type of `x` is `Record<K, T>`.
535535
*
536536
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
537537
*
@@ -545,21 +545,38 @@ export function isRecord(
545545
* const _: Record<PropertyKey, number> = a;
546546
* }
547547
* ```
548+
*
549+
* With predicate function for keys:
550+
*
551+
* ```ts
552+
* import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts";
553+
*
554+
* const isMyType = is.RecordOf(is.Number, is.String);
555+
* const a: unknown = {"a": 0, "b": 1};
556+
* if (isMyType(a)) {
557+
* // a is narrowed to Record<string, number>
558+
* const _: Record<string, number> = a;
559+
* }
560+
* ```
548561
*/
549-
export function isRecordOf<T>(
562+
export function isRecordOf<T, K extends PropertyKey = PropertyKey>(
550563
pred: Predicate<T>,
551-
): Predicate<RecordOf<T>> {
564+
predKey?: Predicate<K>,
565+
): Predicate<Record<K, T>> {
552566
return Object.defineProperties(
553-
(x: unknown): x is RecordOf<T> => {
567+
(x: unknown): x is Record<K, T> => {
554568
if (!isRecord(x)) return false;
555569
for (const k in x) {
556570
if (!pred(x[k])) return false;
571+
if (predKey && !predKey(k)) return false;
557572
}
558573
return true;
559574
},
560575
{
561576
name: {
562-
get: () => `isRecordOf(${inspect(pred)})`,
577+
get: predKey
578+
? () => `isRecordOf(${inspect(pred)}, ${inspect(predKey)})`
579+
: () => `isRecordOf(${inspect(pred)})`,
563580
},
564581
},
565582
);

is_test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,42 @@ Deno.test("isRecordOf<T>", async (t) => {
635635
});
636636
});
637637

638+
Deno.test("isRecordOf<T, K>", async (t) => {
639+
await t.step("returns properly named function", async (t) => {
640+
await assertSnapshot(t, isRecordOf(isNumber, isString).name);
641+
await assertSnapshot(
642+
t,
643+
isRecordOf((_x): _x is string => false, isString).name,
644+
);
645+
});
646+
await t.step("returns proper type predicate", () => {
647+
const a: unknown = { a: 0 };
648+
if (isRecordOf(isNumber, isString)(a)) {
649+
type _ = AssertTrue<
650+
IsExact<typeof a, Record<string, number>>
651+
>;
652+
}
653+
});
654+
await t.step("returns true on T record", () => {
655+
assertEquals(isRecordOf(isNumber, isString)({ a: 0 }), true);
656+
assertEquals(isRecordOf(isString, isString)({ a: "a" }), true);
657+
assertEquals(isRecordOf(isBoolean, isString)({ a: true }), true);
658+
});
659+
await t.step("returns false on non T record", () => {
660+
assertEquals(isRecordOf(isString, isString)({ a: 0 }), false);
661+
assertEquals(isRecordOf(isNumber, isString)({ a: "a" }), false);
662+
assertEquals(isRecordOf(isString, isString)({ a: true }), false);
663+
});
664+
await t.step("returns false on non K record", () => {
665+
assertEquals(isRecordOf(isNumber, isNumber)({ a: 0 }), false);
666+
assertEquals(isRecordOf(isString, isNumber)({ a: "a" }), false);
667+
assertEquals(isRecordOf(isBoolean, isNumber)({ a: true }), false);
668+
});
669+
await testWithExamples(t, isRecordOf((_: unknown): _ is unknown => true), {
670+
excludeExamples: ["record", "date", "promise"],
671+
});
672+
});
673+
638674
Deno.test("ObjectOf<T>", () => {
639675
type _ = AssertTrue<
640676
IsExact<

0 commit comments

Comments
 (0)