From 816ad32c3d3ccde8ca4970039e971d0078103670 Mon Sep 17 00:00:00 2001 From: Lexus Drumgold Date: Sun, 23 Jul 2023 18:41:52 -0400 Subject: [PATCH] feat(types): `Pick`, `PickNative` Signed-off-by: Lexus Drumgold --- src/types/__tests__/pick-native.spec-d.ts | 18 +++ src/types/__tests__/pick.spec-d.ts | 170 ++++++++++++++++++++++ src/types/index.ts | 2 + src/types/pick-native.ts | 16 ++ src/types/pick.ts | 69 +++++++++ 5 files changed, 275 insertions(+) create mode 100644 src/types/__tests__/pick-native.spec-d.ts create mode 100644 src/types/__tests__/pick.spec-d.ts create mode 100644 src/types/pick-native.ts create mode 100644 src/types/pick.ts diff --git a/src/types/__tests__/pick-native.spec-d.ts b/src/types/__tests__/pick-native.spec-d.ts new file mode 100644 index 00000000..79a8f96f --- /dev/null +++ b/src/types/__tests__/pick-native.spec-d.ts @@ -0,0 +1,18 @@ +/** + * @file Type Tests - PickNative + * @module tutils/types/tests/unit-d/PickNative + */ + +import type Vehicle from '#fixtures/types/vehicle' +import type TestSubject from '../pick-native' + +describe('unit-d:types/PickNative', () => { + it('should equal typescript.Pick', () => { + // Arrange + type T = Vehicle + type K = 'vin' + + // Expect + expectTypeOf>().toEqualTypeOf> + }) +}) diff --git a/src/types/__tests__/pick.spec-d.ts b/src/types/__tests__/pick.spec-d.ts new file mode 100644 index 00000000..aabccae9 --- /dev/null +++ b/src/types/__tests__/pick.spec-d.ts @@ -0,0 +1,170 @@ +/** + * @file Type Tests - Pick + * @module tutils/types/tests/unit-d/Pick + */ + +import type Vehicle from '#fixtures/types/vehicle' +import type EmptyObject from '../empty-object' +import type { tag as empty } from '../empty-object' +import type Fn from '../fn' +import type Indices from '../indices' +import type Nilable from '../nilable' +import type Nullable from '../nullable' +import type Numeric from '../numeric' +import type NegativeNumeric from '../numeric-negative' +import type { tag as opaque } from '../opaque' +import type Partial from '../partial' +import type TestSubject from '../pick' +import type PickNative from '../pick-native' +import type Remap from '../remap' +import type Stringify from '../stringify' +import type UnwrapNumeric from '../unwrap-numeric' + +describe('unit-d:types/Pick', () => { + it('should equal PickNative if T is any', () => { + // Arrange + type T = any + type K = keyof Vehicle + + // Expect + expectTypeOf>().toEqualTypeOf>() + }) + + it('should equal Remap if T is never', () => { + // Arrange + type T = never + + // Expect + expectTypeOf>().toEqualTypeOf>() + }) + + describe('Remap extends infer U', () => { + it('should equal {} if HasKey extends false', () => { + expectTypeOf>().toEqualTypeOf<{}>() + expectTypeOf>().toEqualTypeOf<{}>() + expectTypeOf>().toEqualTypeOf<{}>() + expectTypeOf>().toEqualTypeOf<{}>() + }) + + it('should equal {} if K is never', () => { + // Arrange + type T = Vehicle + + // Expect + expectTypeOf>().toEqualTypeOf<{}>() + }) + + it('should equal U if K is any', () => { + // Arrange + type T = readonly [Vehicle, Vehicle] + + // Expect + expectTypeOf>().toEqualTypeOf>() + }) + + describe('K extends Stringify>', () => { + it('should equal {} if HasKey extends false', () => { + expectTypeOf>().toEqualTypeOf<{}>() + }) + + it('should equal PickNative if K intersects keyof U', () => { + // Arrange + type T = { readonly [x: Numeric]: Vehicle; readonly '0': Vehicle } + type K1 = Numeric + type K2 = '0' + type Expect = PickNative, K> + + // Expect + expectTypeOf>().toEqualTypeOf>() + expectTypeOf>().toEqualTypeOf>() + }) + + it('should equal PickNative if N intersects keyof U', () => { + // Arrange + type T = readonly [Vehicle, Nilable, Nullable?] + type K = Stringify> + type Expect = PickNative, UnwrapNumeric> + + // Expect + expectTypeOf>().toEqualTypeOf() + }) + }) + + describe('K extends UnwrapNumeric>', () => { + it('should equal {} if HasKey extends false', () => { + // Arrange + type T = { [x: NegativeNumeric]: string; [x: Numeric]: string } + + // Expect + expectTypeOf>().toEqualTypeOf<{}>() + expectTypeOf>().toEqualTypeOf<{}>() + }) + + it('should equal PickNative if K intersects keyof U', () => { + // Arrange + type T = readonly [Vehicle, Nilable, Nullable?] + type K1 = number + type K2 = Indices + type Expect> = PickNative, K> + + // Expect + expectTypeOf>().toEqualTypeOf>() + expectTypeOf>().toEqualTypeOf>() + }) + + it('should equal PickNative if N intersects keyof U', () => { + // Arrange + type T = { readonly '0'?: Vehicle; readonly '1'?: Vehicle } + type K = 0 + type Expect = PickNative, Stringify> + + // Expect + expectTypeOf>().toEqualTypeOf() + }) + }) + + describe('K extends string', () => { + it('should equal {} if HasKey extends false', () => { + expectTypeOf>().toEqualTypeOf<{}>() + }) + + it('should equal PickNative if K intersects keyof U', () => { + // Arrange + type T = Partial> + type K = 'vin' + type Expect = PickNative, K> + + // Expect + expectTypeOf>().toEqualTypeOf() + }) + }) + + describe('K extends symbol', () => { + type T = Readonly + + it('should equal {} if HasKey extends false', () => { + expectTypeOf>().toEqualTypeOf<{}>() + }) + + it('should equal PickNative if K intersects keyof U', () => { + // Arrange + type K = typeof Symbol.hasInstance + + // Expect + expectTypeOf>().toEqualTypeOf>() + }) + }) + }) + + describe('unions', () => { + it('should distribute over unions', () => { + // Arrange + type T = Vehicle | readonly [Vehicle, Vehicle['vin']] + type K = '1' | 'vin' + type Expect = { readonly 1: Vehicle['vin'] } | { vin: Vehicle['vin'] } + + // Expect + expectTypeOf>().toEqualTypeOf() + }) + }) +}) diff --git a/src/types/index.ts b/src/types/index.ts index f319339a..1a6e6fcb 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -149,6 +149,8 @@ export type { default as Overwrite } from './overwrite' export type { default as Partial } from './partial' export type { default as PartialNative } from './partial-native' export type { default as Path } from './path' +export type { default as Pick } from './pick' +export type { default as PickNative } from './pick-native' export type { default as Predicate } from './predicate' export type { default as Primitive } from './primitive' export type { default as Promisable } from './promisable' diff --git a/src/types/pick-native.ts b/src/types/pick-native.ts new file mode 100644 index 00000000..4f37e45b --- /dev/null +++ b/src/types/pick-native.ts @@ -0,0 +1,16 @@ +/** + * @file Type Definitions - PickNative + * @module tutils/types/PickNative + */ + +/** + * From `T`, pick a set of properties whose keys are in the union `K`. + * + * @see https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys + * + * @template T - Type to evaluate + * @template K - Keys to select + */ +type PickNative = Pick + +export type { PickNative as default } diff --git a/src/types/pick.ts b/src/types/pick.ts new file mode 100644 index 00000000..ffbc1a20 --- /dev/null +++ b/src/types/pick.ts @@ -0,0 +1,69 @@ +/** + * @file Type Definitions - Pick + * @module tutils/types/Pick + */ + +import type IfAny from './if-any' +import type IfNever from './if-never' +import type IfNumber from './if-number' +import type IfNumeric from './if-numeric' +import type Intersection from './intersection' +import type IsAny from './is-any' +import type IsNever from './is-never' +import type Numeric from './numeric' +import type NegativeNumeric from './numeric-negative' +import type PropertyKey from './property-key' +import type Remap from './remap' +import type Stringify from './stringify' +import type UnwrapNumeric from './unwrap-numeric' + +/** + * From `T`, pick a set of properties whose keys are in the union `K`. + * + * Type constituents in `K` must intersect `keyof Remap`. When permitted by + * indexing, property keys that are numbers and numerics (e.g. `0`, `'1'`) are + * also allowed exact*ish* matches. + * + * @see {@linkcode Remap} + * + * @todo examples + * + * @template T - Type to evaluate + * @template K - Keys to select + */ +type Pick = IsAny extends true + ? { [H in K]: T } + : IsNever extends true + ? Remap + : T extends unknown + ? Remap extends infer U + ? { + [H in keyof U as IfAny< + K, + H, + IfNever< + Intersection< + IfNumber< + H, + H | Stringify, + IfNumeric< + H, + NegativeNumeric extends H + ? H + : Numeric extends H + ? H + : H | UnwrapNumeric, + H + > + >, + K + >, + never, + H + > + >]: U[H] + } + : never + : never + +export type { Pick as default }