Skip to content

Commit 4dd37e5

Browse files
committed
👍 Add name attribute to predicate function
Now assert/ensure/maybe error message shows the definition of the predicate function
1 parent 3e6efe8 commit 4dd37e5

File tree

4 files changed

+273
-30
lines changed

4 files changed

+273
-30
lines changed

__snapshots__/is_test.ts.snap

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
export const snapshot = {};
2+
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+
39+
snapshot[`isObjectOf<T> > returns properly named function 1`] = `
40+
"isObjectOf({
41+
a: isNumber,
42+
b: isString,
43+
c: isBoolean
44+
})"
45+
`;
46+
47+
snapshot[`isObjectOf<T> > returns properly named function 2`] = `"isObjectOf({a: a})"`;
48+
49+
snapshot[`isObjectOf<T> > returns properly named function 3`] = `
50+
"isObjectOf({
51+
a: isObjectOf({
52+
b: isObjectOf({c: isBoolean})
53+
})
54+
})"
55+
`;
56+
57+
snapshot[`isInstanceOf<T> > returns properly named function 1`] = `"isInstanceOf(Date)"`;
58+
59+
snapshot[`isInstanceOf<T> > returns properly named function 2`] = `"isInstanceOf((anonymous))"`;
60+
61+
snapshot[`isLiteralOf<T> > returns properly named function 1`] = `'isLiteralOf("hello")'`;
62+
63+
snapshot[`isLiteralOf<T> > returns properly named function 2`] = `"isLiteralOf(100)"`;
64+
65+
snapshot[`isLiteralOf<T> > returns properly named function 3`] = `"isLiteralOf(100n)"`;
66+
67+
snapshot[`isLiteralOf<T> > returns properly named function 4`] = `"isLiteralOf(true)"`;
68+
69+
snapshot[`isLiteralOf<T> > returns properly named function 5`] = `"isLiteralOf(null)"`;
70+
71+
snapshot[`isLiteralOf<T> > returns properly named function 6`] = `"isLiteralOf(undefined)"`;
72+
73+
snapshot[`isLiteralOf<T> > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`;
74+
75+
snapshot[`isLiteralOneOf<T> > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`;
76+
77+
snapshot[`isOneOf<T> > returns properly named function 1`] = `
78+
"isOneOf([
79+
isNumber,
80+
isString,
81+
isBoolean
82+
])"
83+
`;
84+
85+
snapshot[`isAllOf<T> > returns properly named function 1`] = `
86+
"isAllOf([
87+
isObjectOf({a: isNumber}),
88+
isObjectOf({b: isString})
89+
])"
90+
`;
91+
92+
snapshot[`isOptionalOf<T> > returns properly named function 1`] = `"isOptionalOf(isNumber)"`;

inspect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,6 @@ function inspectRecord(
5454
}
5555

5656
function indent(level: number, text: string): string {
57-
const prefix = " ".repeat(level);
57+
const prefix = " ".repeat(level);
5858
return text.split("\n").map((line) => `${prefix}${line}`).join("\n");
5959
}

is.ts

Lines changed: 106 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { inspect } from "./inspect.ts";
2+
13
/**
24
* A type predicate function
35
*/
@@ -59,7 +61,14 @@ export function isArray(
5961
export function isArrayOf<T>(
6062
pred: Predicate<T>,
6163
): Predicate<T[]> {
62-
return (x: unknown): x is T[] => isArray(x) && x.every(pred);
64+
return Object.defineProperties(
65+
(x: unknown): x is T[] => isArray(x) && x.every(pred),
66+
{
67+
name: {
68+
get: () => `isArrayOf(${inspect(pred)})`,
69+
},
70+
},
71+
);
6372
}
6473

6574
export type TupleOf<T extends readonly Predicate<unknown>[]> = {
@@ -87,12 +96,19 @@ export type TupleOf<T extends readonly Predicate<unknown>[]> = {
8796
export function isTupleOf<T extends readonly Predicate<unknown>[]>(
8897
predTup: T,
8998
): Predicate<TupleOf<T>> {
90-
return (x: unknown): x is TupleOf<T> => {
91-
if (!isArray(x) || x.length !== predTup.length) {
92-
return false;
93-
}
94-
return predTup.every((pred, i) => pred(x[i]));
95-
};
99+
return Object.defineProperties(
100+
(x: unknown): x is TupleOf<T> => {
101+
if (!isArray(x) || x.length !== predTup.length) {
102+
return false;
103+
}
104+
return predTup.every((pred, i) => pred(x[i]));
105+
},
106+
{
107+
name: {
108+
get: () => `isTupleOf(${inspect(predTup)})`,
109+
},
110+
},
111+
);
96112
}
97113

98114
// https://stackoverflow.com/a/71700658/1273406
@@ -122,10 +138,17 @@ export type UniformTupleOf<
122138
*/
123139
export function isUniformTupleOf<T, N extends number>(
124140
n: N,
125-
pred: Predicate<T> = (_x: unknown): _x is T => true,
141+
pred: Predicate<T> = isAny,
126142
): Predicate<UniformTupleOf<T, N>> {
127-
const predTup = Array(n).fill(pred);
128-
return isTupleOf(predTup) as Predicate<UniformTupleOf<T, N>>;
143+
const predInner = isTupleOf(Array(n).fill(pred));
144+
return Object.defineProperties(
145+
(x: unknown): x is UniformTupleOf<T, N> => predInner(x),
146+
{
147+
name: {
148+
get: () => `isUniformTupleOf(${n}, ${inspect(pred)})`,
149+
},
150+
},
151+
);
129152
}
130153

131154
/**
@@ -151,13 +174,20 @@ export function isRecord(
151174
export function isRecordOf<T>(
152175
pred: Predicate<T>,
153176
): Predicate<RecordOf<T>> {
154-
return (x: unknown): x is RecordOf<T> => {
155-
if (!isRecord(x)) return false;
156-
for (const k in x) {
157-
if (!pred(x[k])) return false;
158-
}
159-
return true;
160-
};
177+
return Object.defineProperties(
178+
(x: unknown): x is RecordOf<T> => {
179+
if (!isRecord(x)) return false;
180+
for (const k in x) {
181+
if (!pred(x[k])) return false;
182+
}
183+
return true;
184+
},
185+
{
186+
name: {
187+
get: () => `isRecordOf(${inspect(pred)})`,
188+
},
189+
},
190+
);
161191
}
162192

163193
type FlatType<T> = T extends RecordOf<unknown>
@@ -204,9 +234,16 @@ export function isObjectOf<
204234
T extends RecordOf<Predicate<unknown>>,
205235
>(
206236
predObj: T,
207-
options: { strict?: boolean } = {},
237+
{ strict }: { strict?: boolean } = {},
208238
): Predicate<ObjectOf<T>> {
209-
return options.strict ? isObjectOfStrict(predObj) : isObjectOfLoose(predObj);
239+
return Object.defineProperties(
240+
strict ? isObjectOfStrict(predObj) : isObjectOfLoose(predObj),
241+
{
242+
name: {
243+
get: () => `isObjectOf(${inspect(predObj)})`,
244+
},
245+
},
246+
);
210247
}
211248

212249
function isObjectOfLoose<
@@ -261,7 +298,14 @@ export function isFunction(x: unknown): x is (...args: unknown[]) => unknown {
261298
export function isInstanceOf<T extends new (...args: any) => unknown>(
262299
ctor: T,
263300
): Predicate<InstanceType<T>> {
264-
return (x: unknown): x is InstanceType<T> => x instanceof ctor;
301+
return Object.defineProperties(
302+
(x: unknown): x is InstanceType<T> => x instanceof ctor,
303+
{
304+
name: {
305+
get: () => `isInstanceOf(${inspect(ctor)})`,
306+
},
307+
},
308+
);
265309
}
266310

267311
/**
@@ -312,8 +356,15 @@ export function isPrimitive(x: unknown): x is Primitive {
312356
/**
313357
* Return a type predicate function that returns `true` if the type of `x` is a literal type of `pred`.
314358
*/
315-
export function isLiteralOf<T extends Primitive>(pred: T): Predicate<T> {
316-
return (x: unknown): x is T => x === pred;
359+
export function isLiteralOf<T extends Primitive>(literal: T): Predicate<T> {
360+
return Object.defineProperties(
361+
(x: unknown): x is T => x === literal,
362+
{
363+
name: {
364+
get: () => `isLiteralOf(${inspect(literal)})`,
365+
},
366+
},
367+
);
317368
}
318369

319370
/**
@@ -329,10 +380,17 @@ export function isLiteralOf<T extends Primitive>(pred: T): Predicate<T> {
329380
* ```
330381
*/
331382
export function isLiteralOneOf<T extends readonly Primitive[]>(
332-
preds: T,
383+
literals: T,
333384
): Predicate<T[number]> {
334-
return (x: unknown): x is T[number] =>
335-
preds.includes(x as unknown as T[number]);
385+
return Object.defineProperties(
386+
(x: unknown): x is T[number] =>
387+
literals.includes(x as unknown as T[number]),
388+
{
389+
name: {
390+
get: () => `isLiteralOneOf(${inspect(literals)})`,
391+
},
392+
},
393+
);
336394
}
337395

338396
export type OneOf<T> = T extends Predicate<infer U>[] ? U : never;
@@ -354,7 +412,14 @@ export type OneOf<T> = T extends Predicate<infer U>[] ? U : never;
354412
export function isOneOf<T extends readonly Predicate<unknown>[]>(
355413
preds: T,
356414
): Predicate<OneOf<T>> {
357-
return (x: unknown): x is OneOf<T> => preds.some((pred) => pred(x));
415+
return Object.defineProperties(
416+
(x: unknown): x is OneOf<T> => preds.some((pred) => pred(x)),
417+
{
418+
name: {
419+
get: () => `isOneOf(${inspect(preds)})`,
420+
},
421+
},
422+
);
358423
}
359424

360425
type UnionToIntersection<U> =
@@ -380,7 +445,14 @@ export type AllOf<T> = UnionToIntersection<OneOf<T>>;
380445
export function isAllOf<T extends readonly Predicate<unknown>[]>(
381446
preds: T,
382447
): Predicate<AllOf<T>> {
383-
return (x: unknown): x is AllOf<T> => preds.every((pred) => pred(x));
448+
return Object.defineProperties(
449+
(x: unknown): x is AllOf<T> => preds.every((pred) => pred(x)),
450+
{
451+
name: {
452+
get: () => `isAllOf(${inspect(preds)})`,
453+
},
454+
},
455+
);
384456
}
385457

386458
export type OptionalPredicate<T> = Predicate<T | undefined> & {
@@ -403,10 +475,15 @@ export type OptionalPredicate<T> = Predicate<T | undefined> & {
403475
export function isOptionalOf<T>(
404476
pred: Predicate<T>,
405477
): OptionalPredicate<T> {
406-
return Object.assign(
478+
return Object.defineProperties(
407479
(x: unknown): x is Predicate<T | undefined> => isUndefined(x) || pred(x),
408480
{
409-
optional: true as const,
481+
optional: {
482+
value: true as const,
483+
},
484+
name: {
485+
get: () => `isOptionalOf(${inspect(pred)})`,
486+
},
410487
},
411488
) as OptionalPredicate<T>;
412489
}

0 commit comments

Comments
 (0)