Skip to content

Commit

Permalink
feat(types): Pick, PickNative
Browse files Browse the repository at this point in the history
Signed-off-by: Lexus Drumgold <[email protected]>
  • Loading branch information
unicornware committed Jul 23, 2023
1 parent 5796110 commit 816ad32
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 0 deletions.
18 changes: 18 additions & 0 deletions src/types/__tests__/pick-native.spec-d.ts
Original file line number Diff line number Diff line change
@@ -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<T, K>', () => {
// Arrange
type T = Vehicle
type K = 'vin'

// Expect
expectTypeOf<TestSubject<T, K>>().toEqualTypeOf<Pick<T, K>>
})
})
170 changes: 170 additions & 0 deletions src/types/__tests__/pick.spec-d.ts
Original file line number Diff line number Diff line change
@@ -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<T, K> if T is any', () => {
// Arrange
type T = any
type K = keyof Vehicle

// Expect
expectTypeOf<TestSubject<T, K>>().toEqualTypeOf<PickNative<T, K>>()
})

it('should equal Remap<T> if T is never', () => {
// Arrange
type T = never

// Expect
expectTypeOf<TestSubject<T, keyof Vehicle>>().toEqualTypeOf<Remap<T>>()
})

describe('Remap<T> extends infer U', () => {
it('should equal {} if HasKey<U, K> extends false', () => {
expectTypeOf<TestSubject<EmptyObject, typeof empty>>().toEqualTypeOf<{}>()
expectTypeOf<TestSubject<null, 'make'>>().toEqualTypeOf<{}>()
expectTypeOf<TestSubject<undefined, 'model'>>().toEqualTypeOf<{}>()
expectTypeOf<TestSubject<unknown, 'vin'>>().toEqualTypeOf<{}>()
})

it('should equal {} if K is never', () => {
// Arrange
type T = Vehicle

// Expect
expectTypeOf<TestSubject<T, never>>().toEqualTypeOf<{}>()
})

it('should equal U if K is any', () => {
// Arrange
type T = readonly [Vehicle, Vehicle]

// Expect
expectTypeOf<TestSubject<T, any>>().toEqualTypeOf<Remap<T>>()
})

describe('K extends Stringify<infer N extends UnwrapNumeric<K>>', () => {
it('should equal {} if HasKey<U, K> extends false', () => {
expectTypeOf<TestSubject<[Vehicle], '-3' | '3'>>().toEqualTypeOf<{}>()
})

it('should equal PickNative<U, K> if K intersects keyof U', () => {
// Arrange
type T = { readonly [x: Numeric]: Vehicle; readonly '0': Vehicle }
type K1 = Numeric
type K2 = '0'
type Expect<K extends keyof T> = PickNative<Remap<T>, K>

// Expect
expectTypeOf<TestSubject<T, K1>>().toEqualTypeOf<Expect<K1>>()
expectTypeOf<TestSubject<T, K2>>().toEqualTypeOf<Expect<K2>>()
})

it('should equal PickNative<U, N> if N intersects keyof U', () => {
// Arrange
type T = readonly [Vehicle, Nilable<Vehicle>, Nullable<Vehicle>?]
type K = Stringify<Indices<T>>
type Expect = PickNative<Remap<T>, UnwrapNumeric<K>>

// Expect
expectTypeOf<TestSubject<T, K>>().toEqualTypeOf<Expect>()
})
})

describe('K extends UnwrapNumeric<infer N extends Stringify<K>>', () => {
it('should equal {} if HasKey<U, K> extends false', () => {
// Arrange
type T = { [x: NegativeNumeric]: string; [x: Numeric]: string }

// Expect
expectTypeOf<TestSubject<T, 2>>().toEqualTypeOf<{}>()
expectTypeOf<TestSubject<T, number>>().toEqualTypeOf<{}>()
})

it('should equal PickNative<U, K> if K intersects keyof U', () => {
// Arrange
type T = readonly [Vehicle, Nilable<Vehicle>, Nullable<Vehicle>?]
type K1 = number
type K2 = Indices<T>
type Expect<K extends keyof Remap<T>> = PickNative<Remap<T>, K>

// Expect
expectTypeOf<TestSubject<T, K1>>().toEqualTypeOf<Expect<K1>>()
expectTypeOf<TestSubject<T, K2>>().toEqualTypeOf<Expect<K2>>()
})

it('should equal PickNative<U, N> if N intersects keyof U', () => {
// Arrange
type T = { readonly '0'?: Vehicle; readonly '1'?: Vehicle }
type K = 0
type Expect = PickNative<Remap<T>, Stringify<K>>

// Expect
expectTypeOf<TestSubject<T, K>>().toEqualTypeOf<Expect>()
})
})

describe('K extends string', () => {
it('should equal {} if HasKey<U, K> extends false', () => {
expectTypeOf<TestSubject<Vehicle, 'vrm'>>().toEqualTypeOf<{}>()
})

it('should equal PickNative<U, K> if K intersects keyof U', () => {
// Arrange
type T = Partial<Readonly<Vehicle>>
type K = 'vin'
type Expect = PickNative<Remap<T>, K>

// Expect
expectTypeOf<TestSubject<T, K>>().toEqualTypeOf<Expect>()
})
})

describe('K extends symbol', () => {
type T = Readonly<Fn>

it('should equal {} if HasKey<U, K> extends false', () => {
expectTypeOf<TestSubject<T, typeof opaque>>().toEqualTypeOf<{}>()
})

it('should equal PickNative<U, K> if K intersects keyof U', () => {
// Arrange
type K = typeof Symbol.hasInstance

// Expect
expectTypeOf<TestSubject<T, K>>().toEqualTypeOf<PickNative<T, K>>()
})
})
})

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<TestSubject<T, K>>().toEqualTypeOf<Expect>()
})
})
})
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
16 changes: 16 additions & 0 deletions src/types/pick-native.ts
Original file line number Diff line number Diff line change
@@ -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<T, K extends keyof T> = Pick<T, K>

export type { PickNative as default }
69 changes: 69 additions & 0 deletions src/types/pick.ts
Original file line number Diff line number Diff line change
@@ -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<T>`. 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<T, K extends PropertyKey> = IsAny<T> extends true
? { [H in K]: T }
: IsNever<T> extends true
? Remap<T>
: T extends unknown
? Remap<T> extends infer U
? {
[H in keyof U as IfAny<
K,
H,
IfNever<
Intersection<
IfNumber<
H,
H | Stringify<H>,
IfNumeric<
H,
NegativeNumeric extends H
? H
: Numeric extends H
? H
: H | UnwrapNumeric<H>,
H
>
>,
K
>,
never,
H
>
>]: U[H]
}
: never
: never

export type { Pick as default }

0 comments on commit 816ad32

Please sign in to comment.