Skip to content

Commit

Permalink
feat(micro-dash): improve the typing of pickBy()
Browse files Browse the repository at this point in the history
  • Loading branch information
ersimont committed Nov 27, 2020
1 parent ecedb2e commit bcf1824
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 75 deletions.
2 changes: 1 addition & 1 deletion TODO.md
@@ -1,4 +1,4 @@
- update readmes in old repos
- landing page to link to all API docs
- coveralls
- help may be here, to combine multiple coverage runs into one report: https://github.com/angular/angular-cli/issues/11268
- see if typedoc can support that "lib" mode that would eliminate the need for @hidden yet
19 changes: 14 additions & 5 deletions projects/micro-dash/src/lib/interfaces.ts
Expand Up @@ -38,18 +38,27 @@ export type Cast<I, O> = Exclude<I, O> extends never ? I : O;
/** @hidden */
export type Narrow<I, O> = Extract<I, O> | Extract<O, I>;
/** @hidden */
export type IfCouldBe<T1, T2, If, Else = never> = Extract<T1, T2> extends never
? Extract<T2, T1> extends never
? Else
: If
export type IfCouldBe<T1, T2, If, Else = never> = Narrow<T1, T2> extends never
? Else
: If;
/** @hidden */
export type IfIndexType<T extends Key, If, Else = never> = string extends T
export type IfIndexType<T, If, Else = never> = string extends T
? If
: number extends T
? If
: Else;

/** @hidden */
type IndexKeys<T> = { [K in keyof T]: IfIndexType<K, K> }[keyof T];
/** @hidden */
type NonIndexKeys<T> = { [K in keyof T]: IfIndexType<K, never, K> }[keyof T];
/** @hidden */
export type PartialExceptIndexes<T> = { [K in IndexKeys<T>]: T[K] } &
{ [K in NonIndexKeys<T>]?: T[K] };

/** @hidden */
export type Evaluate<T> = T extends infer I ? { [K in keyof I]: T[K] } : never;

/** @hidden */
export type Drop1Arg<T extends Function> = T extends (
arg1: any,
Expand Down
67 changes: 50 additions & 17 deletions projects/micro-dash/src/lib/object/pick-by.ts
@@ -1,13 +1,49 @@
import {
Cast,
Evaluate,
IfCouldBe,
IfIndexType,
KeyNarrowingIteratee,
Narrow,
Nil,
ObjectIteratee,
PartialExceptIndexes,
ValueNarrowingIteratee,
} from '../interfaces';
import { forOwn } from './for-own';

/** @hidden */
type IfDefinitelyIncluded<T, O, If, Else = never> = Exclude<T, O> extends never
? If
: Else;
/** @hidden */
type IfMaybeIncluded<T, O, If, Else = never> = IfDefinitelyIncluded<
T,
O,
Else,
IfCouldBe<T, O, If, Else>
>;
/** @hidden */
type KeysWithDefinitelyIncludedValues<T, O> = {
[K in keyof T]: IfDefinitelyIncluded<T[K], O, K>;
}[keyof T];
/** @hidden */
type KeysWithMaybeIncludedValues<T, O> = {
[K in keyof T]: IfMaybeIncluded<T[K], O, K>;
}[keyof T];
/** @hidden */
export type DefinitelyIncludedKeys<T, O> = {
[K in keyof T]: IfIndexType<
K,
IfCouldBe<O, string, IfCouldBe<K, string, Extract<O, K>, K>>,
IfDefinitelyIncluded<Cast<K, string>, O, K>
>;
}[keyof T];
/** @hidden */
type MaybeIncludedKeys<T, O> = {
[K in keyof T]: IfIndexType<K, never, IfMaybeIncluded<Cast<K, string>, O, K>>;
}[keyof T];

/**
* Creates an object composed of the `object` properties `predicate` returns truthy for.
*
Expand All @@ -22,7 +58,7 @@ import { forOwn } from './for-own';
export function pickBy<T, O>(
object: T[] | Nil,
predicate: ValueNarrowingIteratee<T[], O>,
): { [index: number]: Extract<T, O> | Extract<O, T> };
): { [index: number]: Narrow<T, O> };
export function pickBy<T>(
object: T[] | Nil,
predicate: ObjectIteratee<T, boolean>,
Expand All @@ -31,28 +67,25 @@ export function pickBy<T>(
export function pickBy<I, T extends NonNullable<I>, O>(
object: I,
predicate: ValueNarrowingIteratee<T, O>,
):
| {
[K in { [KK in keyof T]: IfCouldBe<T[KK], O, KK> }[keyof T]]:
| Extract<T[K], O>
| Extract<O, T[K]>
| (Exclude<T[K], O> extends never ? never : undefined);
}
| IfCouldBe<I, Nil, {}>;
): Evaluate<
| ({ [K in KeysWithDefinitelyIncludedValues<T, O>]: Narrow<T[K], O> } &
{ [K in KeysWithMaybeIncludedValues<T, O>]?: Narrow<T[K], O> })
| IfCouldBe<I, Nil, {}>
>;
export function pickBy<I, T extends NonNullable<I>, O>(
object: I,
predicate: KeyNarrowingIteratee<T, O>,
):
| {
[K in { [KK in keyof T]: IfCouldBe<Cast<KK, string>, O, KK> }[keyof T]]:
| T[K]
| (Cast<K, string> extends O ? never : undefined);
}
| IfCouldBe<I, Nil, {}>;
): Evaluate<
| ({
[K in DefinitelyIncludedKeys<T, O>]: T[K];
} &
{ [K in MaybeIncludedKeys<T, O>]?: T[K] })
| IfCouldBe<I, Nil, {}>
>;
export function pickBy<T>(
object: T,
predicate: ObjectIteratee<T, boolean>,
): Partial<NonNullable<T>>;
): Evaluate<PartialExceptIndexes<NonNullable<T>>>;

export function pickBy<T>(
object: T,
Expand Down
4 changes: 4 additions & 0 deletions projects/micro-dash/src/test-helpers/test-utils.ts
Expand Up @@ -51,3 +51,7 @@ export function keyIsString(_: any, key: any): key is string {
export function keyIsNumber(_: any, key: any): key is number {
return isNumber(key);
}

export function keyIsDateOrString(_: any, key: any): key is string | Date {
return isString(key) || isDate(key);
}
130 changes: 78 additions & 52 deletions projects/micro-dash/src/typing-tests/object/pick-by.dts-spec.ts
Expand Up @@ -10,6 +10,7 @@ import {
keyIsAorC,
keyIsAorNumber,
keyIsC,
keyIsDateOrString,
keyIsNumber,
keyIsString,
keyIsString2,
Expand Down Expand Up @@ -86,14 +87,14 @@ interface O {
2: string;
c: Date | Document;
}
const o = { a: 1, 2: 'b', c: document } as O;
const oOrU = o as O | undefined;
const oOrN = o as O | null;
// $ExpectType Partial<O>
declare const o: O;
declare const oOrU: O | undefined;
declare const oOrN: O | null;
// $ExpectType { 2?: string | undefined; a?: number | undefined; c?: Date | Document | undefined; }
pickBy(o, () => true);
// $ExpectType Partial<O>
// $ExpectType { 2?: string | undefined; a?: number | undefined; c?: Date | Document | undefined; }
pickBy(oOrU, () => true);
// $ExpectType Partial<O>
// $ExpectType { 2?: string | undefined; a?: number | undefined; c?: Date | Document | undefined; }
pickBy(oOrN, () => true);

// value narrowing
Expand All @@ -105,11 +106,11 @@ pickBy(oOrU, isString);
// $ExpectType {} | { 2: string; }
pickBy(oOrN, isString);

// $ExpectType { c: Date | undefined; }
// $ExpectType { c?: Date | undefined; }
pickBy(o, isDate);
// $ExpectType {} | { c: Date | undefined; }
// $ExpectType {} | { c?: Date | undefined; }
pickBy(oOrU, isDate);
// $ExpectType {} | { c: Date | undefined; }
// $ExpectType {} | { c?: Date | undefined; }
pickBy(oOrN, isDate);

// $ExpectType { 2: string; a: number; }
Expand All @@ -119,11 +120,11 @@ pickBy(oOrU, isNumberOrString);
// $ExpectType {} | { 2: string; a: number; }
pickBy(oOrN, isNumberOrString);

// $ExpectType { 2: string; c: Date | undefined; }
// $ExpectType { 2: string; c?: Date | undefined; }
pickBy(o, isDateOrString);
// $ExpectType {} | { 2: string; c: Date | undefined; }
// $ExpectType {} | { 2: string; c?: Date | undefined; }
pickBy(oOrU, isDateOrString);
// $ExpectType {} | { 2: string; c: Date | undefined; }
// $ExpectType {} | { 2: string; c?: Date | undefined; }
pickBy(oOrN, isDateOrString);

// $ExpectType {}
Expand All @@ -143,20 +144,20 @@ pickBy(oOrN, isMapOrString);
interface S2 {
a: 'a' | number;
}
const s2 = { a: 2 } as S2;
const s2OrU = { a: 2 } as S2 | undefined;
const s2OrN = { a: 2 } as S2 | null;
// $ExpectType { a: "a" | undefined; }
declare const s2: S2;
declare const s2OrU: S2 | undefined;
declare const s2OrN: S2 | null;
// $ExpectType { a?: "a" | undefined; }
pickBy(s2, isA);
// $ExpectType {} | { a: "a" | undefined; }
// $ExpectType {} | { a?: "a" | undefined; }
pickBy(s2OrU, isA);
// $ExpectType {} | { a: "a" | undefined; }
// $ExpectType {} | { a?: "a" | undefined; }
pickBy(s2OrN, isA);
// $ExpectType { a: 2 | "a" | undefined; }
// $ExpectType { a?: 2 | "a" | undefined; }
pickBy(s2, isStringOr2);
// $ExpectType {} | { a: 2 | "a" | undefined; }
// $ExpectType {} | { a?: 2 | "a" | undefined; }
pickBy(s2OrU, isStringOr2);
// $ExpectType {} | { a: 2 | "a" | undefined; }
// $ExpectType {} | { a?: 2 | "a" | undefined; }
pickBy(s2OrN, isStringOr2);

// key narrowing
Expand Down Expand Up @@ -202,11 +203,11 @@ pickBy(s, keyIsA);
pickBy(sOrU, keyIsA);
// $ExpectType {} | { a: number; }
pickBy(sOrN, keyIsA);
// $ExpectType { 2: string | undefined; a: number; }
// $ExpectType { a: number; 2?: string | undefined; }
pickBy(o, keyIsA);
// $ExpectType {} | { 2: string | undefined; a: number; }
// $ExpectType {} | { a: number; 2?: string | undefined; }
pickBy(oOrU, keyIsA);
// $ExpectType {} | { 2: string | undefined; a: number; }
// $ExpectType {} | { a: number; 2?: string | undefined; }
pickBy(oOrN, keyIsA);

// $ExpectType {}
Expand All @@ -215,11 +216,11 @@ pickBy(s, keyIsString2);
pickBy(sOrU, keyIsString2);
// $ExpectType {} | {}
pickBy(sOrN, keyIsString2);
// $ExpectType { 2: string | undefined; }
// $ExpectType { 2?: string | undefined; }
pickBy(o, keyIsString2);
// $ExpectType {} | { 2: string | undefined; }
// $ExpectType {} | { 2?: string | undefined; }
pickBy(oOrU, keyIsString2);
// $ExpectType {} | { 2: string | undefined; }
// $ExpectType {} | { 2?: string | undefined; }
pickBy(oOrN, keyIsString2);

// $ExpectType {}
Expand All @@ -228,11 +229,11 @@ pickBy(s, keyIsString3);
pickBy(sOrU, keyIsString3);
// $ExpectType {} | {}
pickBy(sOrN, keyIsString3);
// $ExpectType { 2: string | undefined; }
// $ExpectType { 2?: string | undefined; }
pickBy(o, keyIsString3);
// $ExpectType {} | { 2: string | undefined; }
// $ExpectType {} | { 2?: string | undefined; }
pickBy(oOrU, keyIsString3);
// $ExpectType {} | { 2: string | undefined; }
// $ExpectType {} | { 2?: string | undefined; }
pickBy(oOrN, keyIsString3);

// $ExpectType { c: Date | Document; }
Expand All @@ -241,11 +242,11 @@ pickBy(s, keyIsC);
pickBy(sOrU, keyIsC);
// $ExpectType {} | { c: Date | Document; }
pickBy(sOrN, keyIsC);
// $ExpectType { 2: string | undefined; c: Date | Document; }
// $ExpectType { c: Date | Document; 2?: string | undefined; }
pickBy(o, keyIsC);
// $ExpectType {} | { 2: string | undefined; c: Date | Document; }
// $ExpectType {} | { c: Date | Document; 2?: string | undefined; }
pickBy(oOrU, keyIsC);
// $ExpectType {} | { 2: string | undefined; c: Date | Document; }
// $ExpectType {} | { c: Date | Document; 2?: string | undefined; }
pickBy(oOrN, keyIsC);

// $ExpectType { a: number; c: Date | Document; }
Expand All @@ -254,11 +255,11 @@ pickBy(s, keyIsAorC);
pickBy(sOrU, keyIsAorC);
// $ExpectType {} | { a: number; c: Date | Document; }
pickBy(sOrN, keyIsAorC);
// $ExpectType { 2: string | undefined; a: number; c: Date | Document; }
// $ExpectType { a: number; c: Date | Document; 2?: string | undefined; }
pickBy(o, keyIsAorC);
// $ExpectType {} | { 2: string | undefined; a: number; c: Date | Document; }
// $ExpectType {} | { a: number; c: Date | Document; 2?: string | undefined; }
pickBy(oOrU, keyIsAorC);
// $ExpectType {} | { 2: string | undefined; a: number; c: Date | Document; }
// $ExpectType {} | { a: number; c: Date | Document; 2?: string | undefined; }
pickBy(oOrN, keyIsAorC);

// $ExpectType { a: number; }
Expand All @@ -267,25 +268,50 @@ pickBy(s, keyIsAorNumber);
pickBy(sOrU, keyIsAorNumber);
// $ExpectType {} | { a: number; }
pickBy(sOrN, keyIsAorNumber);
// $ExpectType { 2: string | undefined; a: number; }
// $ExpectType { a: number; 2?: string | undefined; }
pickBy(o, keyIsAorNumber);
// $ExpectType {} | { 2: string | undefined; a: number; }
// $ExpectType {} | { a: number; 2?: string | undefined; }
pickBy(oOrU, keyIsAorNumber);
// $ExpectType {} | { 2: string | undefined; a: number; }
// $ExpectType {} | { a: number; 2?: string | undefined; }
pickBy(oOrN, keyIsAorNumber);

const so = {} as { [key: string]: number | string };
// $ExpectType { [x: string]: string | undefined; }
pickBy(so, isString);
// $ExpectType { [x: string]: number | undefined; }
pickBy(so, isNumber);
declare const record: Record<string, number>;
// $ExpectType {}
pickBy(so, isDate);
// $ExpectType { [x: string]: string | undefined; }
pickBy(so, isDateOrString);
// $ExpectType { [x: string]: string | number; }
pickBy(so, keyIsString);
// $ExpectType { [x: string]: string | number | undefined; }
pickBy(so, keyIsA);
pickBy(record, isString);
// $ExpectType { [x: string]: number; }
pickBy(record, isNumber);
// $ExpectType { [x: string]: number; }
pickBy(record, keyIsString);
// $ExpectType {}
pickBy(so, keyIsNumber);
pickBy(record, keyIsNumber);
// $ExpectType { [x: string]: number; }
pickBy(record, keyIsDateOrString);
// $ExpectType { a: number; }
pickBy(record, keyIsA);
// $ExpectType { [x: string]: number; }
pickBy(record, () => true);

declare const composite: {
[k: number]: string | Date;
a: 'eh';
};
// $ExpectType { [x: number]: string | undefined; a: "eh"; }
pickBy(composite, isString);
// $ExpectType {}
pickBy(composite, isNumber);
// $ExpectType { [x: number]: Date | undefined; }
pickBy(composite, isDate);
// $ExpectType { [x: number]: string | Date; a: "eh"; }
pickBy(composite, isDateOrString);
// $ExpectType { [x: number]: string | Date; a: "eh"; }
pickBy(composite, keyIsString);
// $ExpectType { [x: number]: string | Date; a: "eh"; }
pickBy(composite, keyIsA);
// $ExpectType {}
pickBy(composite, keyIsNumber);
// $ExpectType { [x: number]: string | Date; a: "eh"; }
pickBy(composite, keyIsAorC);
// $ExpectType { [x: number]: string | Date; a: "eh"; }
pickBy(composite, keyIsAorNumber);
// $ExpectType { [x: number]: string | Date; a?: "eh" | undefined; }
pickBy(composite, () => true);

0 comments on commit bcf1824

Please sign in to comment.