Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] fix(propEq): improve propEq typings #73

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion test/allPass.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ expectType<boolean>(
})
);

expectError(
expectType<boolean>(
isVampire({
age: 40,
garlic_allergy: true,
Expand Down
4 changes: 2 additions & 2 deletions 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 All @@ -35,7 +35,7 @@ expectError(
})
);

expectError(
expectType<boolean>(
isVampire({
age: 40,
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}`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${number} is a neat trick! Allows strings that only contain number characters, very cool

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'));
35 changes: 30 additions & 5 deletions types/propEq.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
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';

Comment on lines +1 to +2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { Placeholder } from 'ramda';
import { WidenLiterals } from '../util/tools';
import { Placeholder, 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah crap, I missed this when I left that other comment about this MR not being a problem

array: V extends U[K] ? V[] : never

This is the thing we can't have. It breaks combining with other functions, egR.filter(R.propEq('type', 'something')). The predicate type signatures don't match because of the : never

<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;
Loading