Skip to content

Commit

Permalink
fix(propEq): fix improvements
Browse files Browse the repository at this point in the history
* restore the order of function overloads;
* make the last defined overload cover wider cases;
* add more tests to cover most of the cases.
  • Loading branch information
Nemo108 committed Oct 18, 2023
1 parent 96b5420 commit 235ac61
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 38 deletions.
126 changes: 94 additions & 32 deletions test/pluck.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,103 @@ const record = {
a: {name: 'foo', age: 13},
b: {name: 'bar', age: 31, desc: 'barrr'}
};
const constRecord = {
a: {name: 'foo', age: 13},
b: {name: 'bar', age: 31, desc: 'barrr'}
} as const;
const incorrectRecord = {
a: {name: 'foo', age: 13},
b: {name: 'bar', age: 31, desc: 'barrr'},
c: {}
};

// pluck(key, list)
expectType<string[]>(pluck('name', [] as Obj[]));
expectType<number[]>(pluck('age', [] as Obj[]));
expectError(pluck('nope', [] as Obj[]));

// pluck(key)(list)
expectType<string[]>(pluck('name')([] as Obj[]));
expectType<number[]>(pluck('age')([] as Obj[]));
expectError(pluck('nope')([] as Obj[]));

// pluck(__, list)(prop)
expectType<string[]>(pluck(__, [] as Obj[])('name'));
expectType<number[]>(pluck(__, [] as Obj[])('age'));
expectError(pluck(__, [] as Obj[])('nope'));

// pluck(key, record)
expectType<{ a: string, b: string }>(pluck('name', record));
expectType<{ a: number, b: number }>(pluck('age', record));
expectError(pluck('nope', record));
expectError(pluck('age', incorrectRecord));

// pluck(key)(record)
expectType<{ a: string, b: string }>(pluck('name')(record));
expectType<{ a: number, b: number }>(pluck('age')(record));
expectError(pluck('nope')(record));
expectError(pluck('age')(incorrectRecord));

// pluck(__, record)(prop)
expectType<{ a: string, b: string }>(pluck(__, record)('name'));
expectType<{ a: number, b: number }>(pluck(__, record)('age'));
expectError(pluck(__, record)('nope'));
expectError(pluck(__, incorrectRecord)('age'));
// pluck(key)
{
const getFirstItems = pluck(0);
const getName = pluck('name');
const getAge = pluck('age');
const getNope = pluck('nope');

// pluck(key)(list::Array[])
{
expectType<string[]>(getFirstItems([] as string[][]));
expectType<number[]>(getFirstItems([] as number[][]));
expectType<1[]>(getFirstItems([] as 1[][]));
expectType<Obj[]>(getFirstItems([] as Obj[][]));
expectError(getFirstItems('string')); // works in JS, but should not be valid in TS
expectError(getFirstItems({} as Obj));
}

// pluck(key)(list::Record[])
{
expectType<string[]>(getName([] as Obj[]));
expectType<number[]>(getAge([] as Obj[]));
expectError(getNope([] as Obj[]));
expectError(getFirstItems([] as Obj[]));
}

// pluck(key)(record::Record)
{
expectType<{ a: string, b: string }>(getName(record));
expectType<{ a: number, b: number }>(getAge(record));
expectType<{ readonly a: 'foo', readonly b: 'bar' }>(getName(constRecord));
expectError(getNope(record));
expectError(getAge(incorrectRecord));
// expectType<{ readonly a: undefined, readonly b: 'barrr' }>(pluck('desc')(constRecord));
// this ^ gives false negative result, but it can't be fixed right now
}
}

// pluck(key, list::Record[])
{
expectType<string[]>(pluck('name', [] as Obj[]));
expectType<number[]>(pluck('age', [] as Obj[]));
expectError(pluck('nope', [] as Obj[]));
expectError(pluck(0, [] as Obj[]));
}

// pluck(key, list::Array[])
{
expectType<string[]>(pluck(0, [] as string[][]));
expectType<number[]>(pluck(1, [] as number[][]));
expectType<1[]>(pluck(0, [] as 1[][]));
expectType<Obj[]>(pluck(0, [] as Obj[][]));
expectError(pluck(0, 'string')); // works in JS, but should not be valid in TS
expectError(pluck(0, {} as Obj));
}

// pluck(key, record::Record)
{
expectType<{ a: string, b: string }>(pluck('name', record));
expectType<{ readonly a: 'foo', readonly b: 'bar' }>(pluck('name', constRecord));
expectType<{ a: number, b: number }>(pluck('age', record));
expectError(pluck(1, record));
expectError(pluck('nope', record));
expectError(pluck('age', incorrectRecord));
// expectType<{ readonly a: undefined, readonly b: 'barrr' }>(pluck('desc', constRecord));
// this ^ gives false negative result, but it can't be fixed right now
}

// pluck(__, list::Record[])(prop)
{
const getFromObjList = pluck(__, [] as Obj[]);

expectType<string[]>(getFromObjList('name'));
expectType<number[]>(getFromObjList('age'));
expectError(getFromObjList('nope'));
}

// pluck(__, list::Array[])(prop)
{
expectType<string[]>(pluck(__, [] as string[][])(0));
expectType<number[]>(pluck(__, [] as number[][])(1));
expectError(pluck(__, [] as Obj[])(0));
}

// pluck(__, record::Record)(prop)
{
expectType<{ a: string, b: string }>(pluck(__, record)('name'));
expectType<{ a: number, b: number }>(pluck(__, record)('age'));
expectError(pluck(__, record)('nope'));
expectError(pluck(__, incorrectRecord)('age'));
}
15 changes: 9 additions & 6 deletions types/pluck.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Placeholder } from 'ramda';

export function pluck<K extends PropertyKey>(prop: K extends Placeholder ? never : K): {
<U extends Record<K, any>, IK extends string>(obj: Record<IK, U>): { [KK in keyof typeof obj]: U[K] };
<U extends readonly unknown[] | Record<K, any>>(list: U[]): U extends readonly (infer T)[] ? T[] : U extends Record<K, infer T> ? T[] : never;
<U extends O[keyof O], UK extends keyof U, O extends Record<string, any>>(obj: K extends UK ? O : never): { [OK in keyof O]: O[OK][K] };
<U extends readonly unknown[] | Record<K, any>>(list: readonly U[]): U extends readonly (infer T)[] ? T[] : U extends Record<K, infer T> ? T[] : never;
};
export function pluck<K extends keyof U, U extends Record<any, any>, IK extends keyof any>(prop: K, record: Record<IK, U>): { [KK in keyof typeof record]: U[K] };
export function pluck<K extends keyof U, U>(prop: K, list: readonly U[]): Array<U[K]>;
export function pluck<U extends Record<any, any>, IK extends keyof any>(__: Placeholder, record: Record<IK, U>): <K extends keyof U>(prop: K) => { [KK in keyof typeof record]: U[K] };
export function pluck<U>(__: Placeholder, list: readonly U[]): <K extends keyof U>(prop: K) => Array<U[K]>;
export function pluck<U>(__: Placeholder, list: readonly U[]): <K extends keyof U>(prop: U extends readonly any[] ? number : K) => U extends readonly (infer T)[] ? T[] : U extends Record<K, infer T> ? T[] : never;
export function pluck<U extends O[keyof O], O extends Record<string, any>>(__: Placeholder, record: O): <K extends keyof U>(prop: K) => { [KK in keyof O]: O[KK][K] };
export function pluck<
K extends keyof U,
U extends C[keyof C extends string ? keyof C : number],
C extends { [k: string]: Record<string, any> } | ReadonlyArray<Record<string, any> | readonly any[]>
>(prop: K, collection: C): keyof C extends string ? { [CK in keyof C]: C[CK] extends Record<K, any> ? C[CK][K] : never } : Array<U[K]>;

0 comments on commit 235ac61

Please sign in to comment.