Skip to content

Commit 895debc

Browse files
committed
fix(propEq): improve propEq typings
* remove unnecessary constraint from propEq value: the function should receive any type values of object, no matter what parameter it received; * add additional types to use with placeholder
1 parent 4d4f2ea commit 895debc

3 files changed

Lines changed: 143 additions & 25 deletions

File tree

test/anyPass.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ expectType<boolean>(
2525
})
2626
);
2727

28-
expectError(
28+
expectType<boolean>(
2929
isVampire({
3030
age: 21,
3131
garlic_allergy: true,

test/propEq.test.ts

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,129 @@
11
import { expectError, expectType } from 'tsd';
22

3-
import { propEq } from '../es';
3+
import {__, propEq} from '../es';
44

55
type Obj = {
66
union: 'foo' | 'bar';
77
str: string;
8-
num: number;
8+
int: number;
9+
numLike: number | `${number}`;
10+
optional?: string;
11+
nullable: string | null;
912
u: undefined;
1013
n: null;
1114
};
15+
type NumArr = number[];
1216

17+
// ######################
1318
// propEq(val, name, obj)
1419
expectType<boolean>(propEq('foo', 'union', {} as Obj));
15-
// non-union string fails
16-
expectError(propEq('nope', 'union', {} as Obj));
17-
// completely different type fails
18-
expectError(propEq(2, 'union', {} as Obj));
20+
expectType<boolean>(propEq('1' as string, 'union', {} as Obj));
21+
// union of number with literal types should work fine
22+
expectType<boolean>(propEq(1, 'numLike', {} as Obj));
23+
expectType<boolean>(propEq('1', 'numLike', {} as Obj));
24+
// optional types doesn't fire an error, if passed correct types
25+
expectType<boolean>(propEq('str', 'optional', {} as Obj));
26+
expectType<boolean>(propEq(undefined, 'optional', {} as Obj));
27+
// fires an error only on wrong type
28+
expectError(propEq(1, 'optional', {} as Obj));
29+
expectError(propEq(null, 'optional', {} as Obj));
30+
// nullable types doesn't fire an error, if passed correct types
31+
expectType<boolean>(propEq('str', 'nullable', {} as Obj));
32+
expectType<boolean>(propEq(null, 'nullable', {} as Obj));
33+
// fires an error only on wrong type
34+
expectError(propEq(1, 'nullable', {} as Obj));
35+
expectError(propEq(undefined, 'nullable', {} as Obj));
36+
// unknown field names fails
37+
expectError(propEq('foo', 'unknown', {} as Obj));
38+
// should work with arrays as well
39+
expectType<boolean>(propEq(1, 0, [] as NumArr));
40+
// numeric array should expect only numbers
41+
expectError(propEq('foo', 0, [] as NumArr));
42+
// array can't accept string as prop name
43+
expectError(propEq(1, 'foo', [] as NumArr));
1944

45+
// ######################
2046
// propEq(val)(name)(obj)
2147
expectType<boolean>(propEq('foo')('union')({} as Obj));
22-
// 'nope' is inferred as 'string' here.
23-
expectType<boolean>(propEq('nope')('union')({} as Obj));
24-
// completely different type fails
25-
expectError(propEq(2)('union')({} as Obj));
48+
expectType<boolean>(propEq('nope' as string)('union')({} as Obj));
49+
// since we use an exact literal type, it should fire an error
50+
expectError(propEq('nope')('union')({} as Obj));
51+
// union of number with literal types should work fine
52+
expectType<boolean>(propEq(1)('numLike')({} as Obj));
53+
expectType<boolean>(propEq('1')('numLike')({} as Obj));
54+
// optional types doesn't fire an error, if passed correct types
55+
expectType<boolean>(propEq('str')('optional')({} as Obj));
56+
expectType<boolean>(propEq(undefined)('optional')({} as Obj));
57+
// fires an error only on wrong type
58+
expectError(propEq(1)('optional')({} as Obj));
59+
expectError(propEq(null)('optional')({} as Obj));
60+
// nullable types doesn't fire an error, if passed correct types
61+
expectType<boolean>(propEq('str')('nullable')({} as Obj));
62+
expectType<boolean>(propEq(null)('nullable')({} as Obj));
63+
// fires an error only on wrong type
64+
expectError(propEq(1)('nullable')({} as Obj));
65+
expectError(propEq(undefined)('nullable')({} as Obj));
66+
// unknown field names fails
67+
expectError(propEq('foo')('unknown')({} as Obj));
2668

27-
// propEq(val)(name), obj)
69+
// ######################
70+
// propEq(val)(name, obj)
2871
expectType<boolean>(propEq('foo')('union', {} as Obj));
29-
// 'nope' is inferred as 'string' here.
30-
expectType<boolean>(propEq('nope')('union', {} as Obj));
31-
// completely different type fails
32-
expectError(propEq(2)('union', {} as Obj));
72+
expectType<boolean>(propEq('nope' as string)('union', {} as Obj));
73+
// since we use an exact literal type, it should fire an error
74+
expectError(propEq('nope')('union', {} as Obj));
75+
// union of number with literal types should work fine
76+
expectType<boolean>(propEq(1)('numLike', {} as Obj));
77+
expectType<boolean>(propEq('1')('numLike', {} as Obj));
78+
// optional types doesn't fire an error, if passed correct types
79+
expectType<boolean>(propEq('str')('optional', {} as Obj));
80+
expectType<boolean>(propEq(undefined)('optional', {} as Obj));
81+
// fires an error only on wrong type
82+
expectError(propEq(1)('optional', {} as Obj));
83+
expectError(propEq(null)('optional', {} as Obj));
84+
// nullable types doesn't fire an error, if passed correct types
85+
expectType<boolean>(propEq('str')('nullable', {} as Obj));
86+
expectType<boolean>(propEq(null)('nullable', {} as Obj));
87+
// fires an error only on wrong type
88+
expectError(propEq(1)('nullable', {} as Obj));
89+
expectError(propEq(undefined)('nullable', {} as Obj));
90+
// unknown field names fails
91+
expectError(propEq('foo')('unknown', {} as Obj));
3392

93+
// ######################
3494
// propEq(val, name)(obj)
3595
expectType<boolean>(propEq('foo', 'union')({} as Obj));
36-
// 'nope' is inferred as 'string' here.
37-
expectType<boolean>(propEq('nope', 'union')({} as Obj));
38-
// completely different type fails
39-
expectError(propEq(2, 'union')({} as Obj));
96+
expectType<never>(propEq('nope' as string, 'union')({} as Obj));
97+
// since we use an exact literal type, it should fire an error
98+
expectType<never>(propEq('nope', 'union')({} as Obj));
99+
// union of number with literal types should work fine
100+
expectType<boolean>(propEq(1, 'numLike')({} as Obj));
101+
expectType<boolean>(propEq('1', 'numLike')({} as Obj));
102+
// optional types doesn't fire an error, if passed correct types
103+
expectType<boolean>(propEq('str', 'optional')({} as Obj));
104+
expectType<boolean>(propEq(undefined, 'optional')({} as Obj));
105+
// fires an error only on wrong type
106+
expectType<never>(propEq(1, 'optional')({} as Obj));
107+
expectType<never>(propEq(null, 'optional')({} as Obj));
108+
// nullable types doesn't fire an error, if passed correct types
109+
expectType<boolean>(propEq('str', 'nullable')({} as Obj));
110+
expectType<boolean>(propEq(null, 'nullable')({} as Obj));
111+
// fires an error only on wrong type
112+
expectType<never>(propEq(1, 'nullable')({} as Obj));
113+
expectType<never>(propEq(undefined, 'nullable')({} as Obj));
114+
// unknown field names fails
115+
expectError(propEq('foo', 'unknown')({} as Obj));
116+
117+
// ##########################
118+
// propEq(__, name, obj)(val)
119+
expectType<boolean>(propEq(__, 'union', {} as Obj)('foo'));
120+
// propEq(val, __, obj)(val)
121+
expectType<boolean>(propEq('foo', __, {} as Obj)('union'));
122+
// propEq(__, __, obj)(val, name)
123+
expectType<boolean>(propEq(__, __, {} as Obj)('foo', 'union'));
124+
// propEq(__, __, obj)(val)(name)
125+
expectType<boolean>(propEq(__, __, {} as Obj)('foo')('union'));
126+
127+
expectError(propEq('foo', __, {} as Obj)('unknown'));
128+
expectError(propEq(__, __, {} as Obj)('foo', 'unknown'));
129+
expectError(propEq(__, __, {} as Obj)('foo')('unknown'));

types/propEq.d.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,34 @@
1-
export function propEq<T>(val: T): {
2-
<K extends PropertyKey>(name: K): (obj: Record<K, T>) => boolean;
3-
<K extends PropertyKey>(name: K, obj: Record<K, T>): boolean;
1+
import { Placeholder } from 'ramda';
2+
import { WidenLiterals } from '../util/tools';
3+
4+
export function propEq(__: Placeholder): never;
5+
export function propEq<const V>(val: V): {
6+
<K extends number>(name: K): <U extends any[]>(array: V extends U[K] ? V[] : never) => boolean;
7+
<K extends Exclude<PropertyKey, number>>(name: K):
8+
<U extends Partial<Record<K, any>>>(obj: string extends V ? U : V extends U[K] ? U : never) => boolean;
9+
<U>(__: Placeholder, obj: U):
10+
<K extends keyof U>(name: string extends V ? U : V extends U[K] ? U : never) => boolean;
11+
<K extends keyof U, const U>(name: K, obj: string extends V ? U : V extends U[K] ? U : never): boolean;
412
};
5-
export function propEq<T, K extends PropertyKey>(val: T, name: K): (obj: Record<K, T>) => boolean;
6-
export function propEq<K extends keyof U, U>(val: U[K], name: K, obj: U): boolean;
13+
export function propEq<K extends number>(__: Placeholder, name: K): {
14+
<U extends any[]>(__: Placeholder, array: U): (val: U[K]) => boolean;
15+
<U extends any[]>(val: U[K], array: U): boolean
16+
<V>(val: V): (array: V[]) => boolean
17+
};
18+
export function propEq<K extends Exclude<PropertyKey, number>>(__: Placeholder, name: K): {
19+
<U extends Record<K, any>>(__: Placeholder, obj: U): (val: U[K]) => boolean;
20+
<U extends Record<K, any>>(val: U[K], obj: U): boolean
21+
<V>(val: V): (obj: Partial<Record<K, V>>) => boolean
22+
};
23+
export function propEq<V, K extends number>(val: V, name: K): (array: V[]) => boolean;
24+
export function propEq<const V, K extends string | symbol>(val: V, name: K):
25+
<U extends Partial<Record<K, any>>>(obj: U) => V extends U[K] ? boolean : never;
26+
27+
export function propEq<U>(__: Placeholder, ___: Placeholder, obj: U): {
28+
<K extends keyof U>(__: Placeholder, name: K): (val: U[K]) => boolean
29+
<K extends keyof U>(val: U[K], name: K): boolean;
30+
<K extends keyof U>(val: U[K]): (name: K) => boolean;
31+
};
32+
export function propEq<K extends keyof U, const U>(__: Placeholder, name: K, obj: U): (val: U[K]) => boolean;
33+
export function propEq<K extends keyof U, const U>(val: U[K], __: Placeholder, obj: U): (name: K) => boolean;
34+
export function propEq<K extends keyof U, const U>(val: WidenLiterals<U[K]>, name: K, obj: U): boolean;

0 commit comments

Comments
 (0)