Skip to content

Commit

Permalink
feat(types): [Values] allow T to extend unknown
Browse files Browse the repository at this point in the history
Signed-off-by: Lexus Drumgold <unicornware@flexdevelopment.llc>
  • Loading branch information
unicornware committed Aug 1, 2023
1 parent df11f1f commit da89078
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 40 deletions.
135 changes: 122 additions & 13 deletions src/types/__tests__/values.spec-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@
*/

import type Book from '#fixtures/interfaces/book'
import type Publisher from '#fixtures/interfaces/publisher'
import type Person from '#fixtures/interfaces/person'
import type Vehicle from '#fixtures/types/vehicle'
import type EmptyArray from '../empty-array'
import type EmptyObject from '../empty-object'
import type Fn from '../fn'
import type Nilable from '../nilable'
import type Nullable from '../nullable'
import type NumberString from '../number-string'
import type Opaque from '../opaque'
import type { tag as opaque } from '../opaque'
import type Spread from '../spread'
import type Stringify from '../stringify'
import type TestSubject from '../values'

describe('unit-d:types/Values', () => {
it('should equal EmptyArray if T is never', () => {
expectTypeOf<TestSubject<never>>().toEqualTypeOf<EmptyArray>()
})

it('should equal EmptyArray if T is unknown', () => {
expectTypeOf<TestSubject<unknown>>().toEqualTypeOf<EmptyArray>()
})

it('should equal T[] if T is any', () => {
// Arrange
type T = any
Expand All @@ -27,34 +33,137 @@ describe('unit-d:types/Values', () => {
expectTypeOf<TestSubject<T>>().toEqualTypeOf<T[]>()
})

it('should equal any[] if object extends T', () => {
it('should equal any[] if IsEqual<T, object> extends true', () => {
expectTypeOf<TestSubject<object>>().toEqualTypeOf<any[]>()
})

describe('T extends ObjectCurly', () => {
it('should equal T[Head<Stringify<Path<T, true>>, Dot>][]', () => {
it('should equal Spread<T>[Stringify<keyof Spread<T>>][]', () => {
// Arrange
type T = Book
type Expect = Spread<T>[Stringify<keyof Spread<T>>][]

// Expect
expectTypeOf<TestSubject<T>>().toEqualTypeOf<T[keyof T][]>()
expectTypeOf<TestSubject<T>>().toEqualTypeOf<Expect>()
})

describe('EmptyObject extends T', () => {
it('should equal EmptyArray if EmptyObject extends T', () => {
it('should equal EmptyArray', () => {
expectTypeOf<TestSubject<{}>>().toEqualTypeOf<EmptyArray>()
expectTypeOf<TestSubject<EmptyObject>>().toEqualTypeOf<EmptyArray>()
})
})
})

describe('T extends Primitive', () => {
describe('T extends NIL', () => {
it('should equal EmptyArray', () => {
expectTypeOf<TestSubject<null>>().toEqualTypeOf<EmptyArray>()
expectTypeOf<TestSubject<undefined>>().toEqualTypeOf<EmptyArray>()
})
})

describe('T extends bigint', () => {
it('should equal EmptyArray', () => {
expectTypeOf<TestSubject<0n>>().toEqualTypeOf<EmptyArray>()
})

describe('T extends object', () => {
it('should equal Spread<T>[Stringify<keyof Spread<T>>][]', () => {
// Arrange
type T = Opaque<bigint, symbol> & { id?: string }
type Expect = Spread<T>[Stringify<keyof Spread<T>>][]

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

describe('T extends boolean', () => {
it('should equal EmptyArray', () => {
expectTypeOf<TestSubject<false>>().toEqualTypeOf<EmptyArray>()
})

describe('T extends object', () => {
it('should equal Spread<T>[Stringify<keyof Spread<T>>][]', () => {
// Arrange
type T = Opaque<true, symbol> & { id?: string }
type Expect = Spread<T>[Stringify<keyof Spread<T>>][]

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

describe('T extends number', () => {
it('should equal EmptyArray', () => {
expectTypeOf<TestSubject<0>>().toEqualTypeOf<EmptyArray>()
})

describe('T extends object', () => {
it('should equal Spread<T>[Stringify<keyof Spread<T>>][]', () => {
// Arrange
type T = Opaque<number, symbol> & { id?: string }
type Expect = Spread<T>[Stringify<keyof Spread<T>>][]

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

describe('T extends string', () => {
describe('IsLiteral<T> extends true', () => {
it('should equal Spread<T>[Stringify<keyof Spread<T>>][]', () => {
// Arrange
type T = 'vin'
type Expect = Spread<T>[Stringify<keyof Spread<T>>][]

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

describe('number extends Length<T>', () => {
it('should equal Spread<T>[Stringify<keyof Spread<T>>][]', () => {
// Arrange
type T = Vehicle['vin']
type Expect = Spread<T>[Stringify<keyof Spread<T>>][]

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

describe('T extends symbol', () => {
it('should equal EmptyArray', () => {
expectTypeOf<TestSubject<typeof opaque>>().toEqualTypeOf<EmptyArray>()
})

describe('T extends object', () => {
it('should equal Spread<T>[Stringify<keyof Spread<T>>][]', () => {
// Arrange
type T = Opaque<symbol, symbol> & { id?: string }
type Expect = Spread<T>[Stringify<keyof Spread<T>>][]

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

describe('T extends Readonly<Fn>', () => {
it('should equal T[Head<Stringify<Path<T, true>>, Dot>][]', () => {
it('should equal Spread<T>[Stringify<keyof Spread<T>>][]', () => {
// Arrange
type T = Readonly<Fn> & { data: number; id: string }
type T = Readonly<Fn & { id: string }>
type Expect = Spread<T>[Stringify<keyof Spread<T>>][]

// Expect
expectTypeOf<TestSubject<T>>().toEqualTypeOf<NumberString[]>()
expectTypeOf<TestSubject<T>>().toEqualTypeOf<Expect>()
})

describe('Fn extends T', () => {
Expand All @@ -79,7 +188,7 @@ describe('unit-d:types/Values', () => {
describe('number extends Length<T>', () => {
it('should equal T', () => {
// Arrange
type T = Nullable<Vehicle>[]
type T = readonly Nullable<Vehicle>[]

// Expect
expectTypeOf<TestSubject<T>>().toEqualTypeOf<T>()
Expand All @@ -90,8 +199,8 @@ describe('unit-d:types/Values', () => {
describe('unions', () => {
it('should distribute over unions', () => {
// Arrange
type T = Nilable<Book | Publisher>
type Expect = Book[keyof Book][] | Publisher[keyof Publisher][] | []
type T = Person | Vehicle
type Expect = Person[keyof Person][] | Vehicle[keyof Vehicle][]

// Expect
expectTypeOf<TestSubject<T>>().toEqualTypeOf<Expect>()
Expand Down
53 changes: 26 additions & 27 deletions src/types/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@
* @module tutils/types/Values
*/

import type Dot from './dot'
import type EmptyArray from './empty-array'
import type Get from './get'
import type Head from './head'
import type IfAny from './if-any'
import type IfEqual from './if-equal'
import type IfNever from './if-never'
import type Nilable from './nilable'
import type Path from './path'
import type Stringify from './stringify'
import type IfSymbol from './if-symbol'
import type IsAny from './is-any'
import type IsEqual from './is-equal'
import type IsNever from './is-never'
import type Spread from './spread'

/**
* Construct an array type where items are `T`'s own enumerable string-keyed
* property values.
* Construct an array type representing `T`'s own string-keyed property values.
*
* @see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/values
*
Expand All @@ -27,6 +23,12 @@ import type Stringify from './stringify'
* type X = Values<readonly [number, string]>
* // readonly [number, string]
* @example
* type X = Values<'abc'>
* // ('a' | 'b' | 'c')[]
* @example
* type X = Values<string>
* // (string | undefined)[]
* @example
* type X = Values<Map<number, string>>
* // []
* @example
Expand All @@ -38,25 +40,22 @@ import type Stringify from './stringify'
* @example
* type X = Values<never>
* // []
* @example
* type X = Values<unknown>
* // []
*
* @template T - Type to evaluate
*/
type Values<T extends Nilable<object>> = IfAny<
T,
T[],
IfNever<
T,
EmptyArray,
T extends readonly unknown[]
? T
: object extends T
? IfEqual<T, object, any[], EmptyArray>
: T extends object
? Head<Stringify<Path<T, true>>, Dot> extends infer H extends string
? IfNever<H, EmptyArray, { [K in H]: Get<T, K> }[H][]>
: never
: EmptyArray
>
>
type Values<T> = IsAny<T> extends true
? T[]
: IsNever<T> extends true
? EmptyArray
: T extends readonly unknown[]
? T
: Spread<IsEqual<T, object> extends true ? any : T> extends infer U
? IsNever<keyof U> extends true
? EmptyArray
: Get<{ [K in keyof U as IfSymbol<K, never, K>]: U[K] }, any>[]
: never

export type { Values as default }

0 comments on commit da89078

Please sign in to comment.