Skip to content

Commit

Permalink
fix(propEq): improve propEq typings
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Nemo108 committed Feb 2, 2024
1 parent 4d4f2ea commit 895debc
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 25 deletions.
2 changes: 1 addition & 1 deletion test/anyPass.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ expectType<boolean>(
})
);

expectError(
expectType<boolean>(
isVampire({
age: 21,
garlic_allergy: true,
Expand Down
128 changes: 109 additions & 19 deletions test/propEq.test.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,129 @@
import { expectError, expectType } from 'tsd';

import { propEq } from '../es';
import {__, propEq} from '../es';

type Obj = {
union: 'foo' | 'bar';
str: string;
num: number;
int: number;
numLike: number | `${number}`;
optional?: string;
nullable: string | null;
u: undefined;
n: null;
};
type NumArr = number[];

// ######################
// propEq(val, name, obj)
expectType<boolean>(propEq('foo', 'union', {} as Obj));
// non-union string fails
expectError(propEq('nope', 'union', {} as Obj));
// completely different type fails
expectError(propEq(2, 'union', {} as Obj));
expectType<boolean>(propEq('1' as string, 'union', {} as Obj));
// union of number with literal types should work fine
expectType<boolean>(propEq(1, 'numLike', {} as Obj));
expectType<boolean>(propEq('1', 'numLike', {} as Obj));
// optional types doesn't fire an error, if passed correct types
expectType<boolean>(propEq('str', 'optional', {} as Obj));
expectType<boolean>(propEq(undefined, 'optional', {} as Obj));
// fires an error only on wrong type
expectError(propEq(1, 'optional', {} as Obj));
expectError(propEq(null, 'optional', {} as Obj));
// nullable types doesn't fire an error, if passed correct types
expectType<boolean>(propEq('str', 'nullable', {} as Obj));
expectType<boolean>(propEq(null, 'nullable', {} as Obj));
// fires an error only on wrong type
expectError(propEq(1, 'nullable', {} as Obj));
expectError(propEq(undefined, 'nullable', {} as Obj));
// unknown field names fails
expectError(propEq('foo', 'unknown', {} as Obj));
// should work with arrays as well
expectType<boolean>(propEq(1, 0, [] as NumArr));
// numeric array should expect only numbers
expectError(propEq('foo', 0, [] as NumArr));
// array can't accept string as prop name
expectError(propEq(1, 'foo', [] as NumArr));

// ######################
// propEq(val)(name)(obj)
expectType<boolean>(propEq('foo')('union')({} as Obj));
// 'nope' is inferred as 'string' here.
expectType<boolean>(propEq('nope')('union')({} as Obj));
// completely different type fails
expectError(propEq(2)('union')({} as Obj));
expectType<boolean>(propEq('nope' as string)('union')({} as Obj));
// since we use an exact literal type, it should fire an error
expectError(propEq('nope')('union')({} as Obj));
// union of number with literal types should work fine
expectType<boolean>(propEq(1)('numLike')({} as Obj));
expectType<boolean>(propEq('1')('numLike')({} as Obj));
// optional types doesn't fire an error, if passed correct types
expectType<boolean>(propEq('str')('optional')({} as Obj));
expectType<boolean>(propEq(undefined)('optional')({} as Obj));
// fires an error only on wrong type
expectError(propEq(1)('optional')({} as Obj));
expectError(propEq(null)('optional')({} as Obj));
// nullable types doesn't fire an error, if passed correct types
expectType<boolean>(propEq('str')('nullable')({} as Obj));
expectType<boolean>(propEq(null)('nullable')({} as Obj));
// fires an error only on wrong type
expectError(propEq(1)('nullable')({} as Obj));
expectError(propEq(undefined)('nullable')({} as Obj));
// unknown field names fails
expectError(propEq('foo')('unknown')({} as Obj));

// propEq(val)(name), obj)
// ######################
// propEq(val)(name, obj)
expectType<boolean>(propEq('foo')('union', {} as Obj));
// 'nope' is inferred as 'string' here.
expectType<boolean>(propEq('nope')('union', {} as Obj));
// completely different type fails
expectError(propEq(2)('union', {} as Obj));
expectType<boolean>(propEq('nope' as string)('union', {} as Obj));
// since we use an exact literal type, it should fire an error
expectError(propEq('nope')('union', {} as Obj));
// union of number with literal types should work fine
expectType<boolean>(propEq(1)('numLike', {} as Obj));
expectType<boolean>(propEq('1')('numLike', {} as Obj));
// optional types doesn't fire an error, if passed correct types
expectType<boolean>(propEq('str')('optional', {} as Obj));
expectType<boolean>(propEq(undefined)('optional', {} as Obj));
// fires an error only on wrong type
expectError(propEq(1)('optional', {} as Obj));
expectError(propEq(null)('optional', {} as Obj));
// nullable types doesn't fire an error, if passed correct types
expectType<boolean>(propEq('str')('nullable', {} as Obj));
expectType<boolean>(propEq(null)('nullable', {} as Obj));
// fires an error only on wrong type
expectError(propEq(1)('nullable', {} as Obj));
expectError(propEq(undefined)('nullable', {} as Obj));
// unknown field names fails
expectError(propEq('foo')('unknown', {} as Obj));

// ######################
// propEq(val, name)(obj)
expectType<boolean>(propEq('foo', 'union')({} as Obj));
// 'nope' is inferred as 'string' here.
expectType<boolean>(propEq('nope', 'union')({} as Obj));
// completely different type fails
expectError(propEq(2, 'union')({} as Obj));
expectType<never>(propEq('nope' as string, 'union')({} as Obj));
// since we use an exact literal type, it should fire an error
expectType<never>(propEq('nope', 'union')({} as Obj));
// union of number with literal types should work fine
expectType<boolean>(propEq(1, 'numLike')({} as Obj));
expectType<boolean>(propEq('1', 'numLike')({} as Obj));
// optional types doesn't fire an error, if passed correct types
expectType<boolean>(propEq('str', 'optional')({} as Obj));
expectType<boolean>(propEq(undefined, 'optional')({} as Obj));
// fires an error only on wrong type
expectType<never>(propEq(1, 'optional')({} as Obj));
expectType<never>(propEq(null, 'optional')({} as Obj));
// nullable types doesn't fire an error, if passed correct types
expectType<boolean>(propEq('str', 'nullable')({} as Obj));
expectType<boolean>(propEq(null, 'nullable')({} as Obj));
// fires an error only on wrong type
expectType<never>(propEq(1, 'nullable')({} as Obj));
expectType<never>(propEq(undefined, 'nullable')({} as Obj));
// unknown field names fails
expectError(propEq('foo', 'unknown')({} as Obj));

// ##########################
// propEq(__, name, obj)(val)
expectType<boolean>(propEq(__, 'union', {} as Obj)('foo'));
// propEq(val, __, obj)(val)
expectType<boolean>(propEq('foo', __, {} as Obj)('union'));
// propEq(__, __, obj)(val, name)
expectType<boolean>(propEq(__, __, {} as Obj)('foo', 'union'));
// propEq(__, __, obj)(val)(name)
expectType<boolean>(propEq(__, __, {} as Obj)('foo')('union'));

expectError(propEq('foo', __, {} as Obj)('unknown'));
expectError(propEq(__, __, {} as Obj)('foo', 'unknown'));
expectError(propEq(__, __, {} as Obj)('foo')('unknown'));
38 changes: 33 additions & 5 deletions types/propEq.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
export function propEq<T>(val: T): {
<K extends PropertyKey>(name: K): (obj: Record<K, T>) => boolean;
<K extends PropertyKey>(name: K, obj: Record<K, T>): boolean;
import { Placeholder } from 'ramda';
import { WidenLiterals } from '../util/tools';

export function propEq(__: Placeholder): never;
export function propEq<const V>(val: V): {
<K extends number>(name: K): <U extends any[]>(array: V extends U[K] ? V[] : never) => boolean;
<K extends Exclude<PropertyKey, number>>(name: K):
<U extends Partial<Record<K, any>>>(obj: string extends V ? U : V extends U[K] ? U : never) => boolean;
<U>(__: Placeholder, obj: U):
<K extends keyof U>(name: string extends V ? U : V extends U[K] ? U : never) => boolean;
<K extends keyof U, const U>(name: K, obj: string extends V ? U : V extends U[K] ? U : never): boolean;
};
export function propEq<T, K extends PropertyKey>(val: T, name: K): (obj: Record<K, T>) => boolean;
export function propEq<K extends keyof U, U>(val: U[K], name: K, obj: U): boolean;
export function propEq<K extends number>(__: Placeholder, name: K): {
<U extends any[]>(__: Placeholder, array: U): (val: U[K]) => boolean;
<U extends any[]>(val: U[K], array: U): boolean
<V>(val: V): (array: V[]) => boolean
};
export function propEq<K extends Exclude<PropertyKey, number>>(__: Placeholder, name: K): {
<U extends Record<K, any>>(__: Placeholder, obj: U): (val: U[K]) => boolean;
<U extends Record<K, any>>(val: U[K], obj: U): boolean
<V>(val: V): (obj: Partial<Record<K, V>>) => boolean
};
export function propEq<V, K extends number>(val: V, name: K): (array: V[]) => boolean;
export function propEq<const V, K extends string | symbol>(val: V, name: K):
<U extends Partial<Record<K, any>>>(obj: U) => V extends U[K] ? boolean : never;

export function propEq<U>(__: Placeholder, ___: Placeholder, obj: U): {
<K extends keyof U>(__: Placeholder, name: K): (val: U[K]) => boolean
<K extends keyof U>(val: U[K], name: K): boolean;
<K extends keyof U>(val: U[K]): (name: K) => boolean;
};
export function propEq<K extends keyof U, const U>(__: Placeholder, name: K, obj: U): (val: U[K]) => boolean;
export function propEq<K extends keyof U, const U>(val: U[K], __: Placeholder, obj: U): (name: K) => boolean;
export function propEq<K extends keyof U, const U>(val: WidenLiterals<U[K]>, name: K, obj: U): boolean;

0 comments on commit 895debc

Please sign in to comment.