Skip to content

Commit

Permalink
Improve the Output range and cover edge cases for ValueOf<T> type (#…
Browse files Browse the repository at this point in the history
…334)

* cover edge cases for `valueOf` type

* Modify `ValueOf` to handle readonly arrays

* add test case for readonly and optional obj type
  • Loading branch information
ajitjha393 committed Nov 19, 2022
1 parent e0307a2 commit 66a6169
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/rare-forks-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ts-essentials": patch
---

Improve the `ValueOf` utility type to cover the edge cases
9 changes: 8 additions & 1 deletion lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type IsAny<T> = 0 extends 1 & T ? true : false;
export type IsNever<T> = [T] extends [never] ? true : false;
export type IsUnknown<T> = IsAny<T> extends true ? false : unknown extends T ? true : false;
export type AnyArray<T = any> = Array<T> | ReadonlyArray<T>;
export type AnyFunction<TArgs extends any[] = any[], TReturnType = any> = (...args: TArgs) => TReturnType;

export type ArrayOrSingle<T> = T | T[];

Expand Down Expand Up @@ -424,7 +425,13 @@ export type Opaque<Type, Token extends string> = Token extends StringLiteral<Tok
: never;

/** Easily extract the type of a given object's values */
export type ValueOf<T> = T[keyof T];
export type ValueOf<T> = T extends Primitive
? T
: T extends AnyArray
? T[number]
: T extends AnyFunction
? ReturnType<T>
: T[keyof T];

/** Easily extract the type of a given array's elements */
export type ElementOf<T extends readonly any[]> = T extends readonly (infer ET)[] ? ET : never;
Expand Down
51 changes: 51 additions & 0 deletions test/value-of.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { AssertTrue as Assert, IsExact } from "conditional-type-checks";
import { ValueOf, Primitive, AnyArray } from "../lib/types";

declare const array: number[];
declare const func: (...arg: any[]) => boolean;
declare const writableObj: { propA: string; propB: number; propC: () => void };
declare const optionalObj: { propA?: string; propB?: number; propC?: () => void };
declare const readonlyObj: { readonly propA: string; readonly propB: number; readonly propC: () => void };
declare const tuple: [boolean, string, number];
declare const primitive: Primitive;
declare const primitiveOrObject: Primitive | typeof writableObj;
declare const objOrFunc: typeof func | typeof writableObj;
declare const arrayOrUndefined: number[] | undefined;

// declare const readonly
declare const readonlyArray: AnyArray<string | number>;
declare const readonlyArrayOrUndefined: AnyArray<string | number> | undefined;
declare const readonlyTuple: readonly [boolean, string, number];
declare const readonlyTupleOrUndefined: readonly [boolean, string, number] | undefined;

function testValueOf() {
type cases = [
Assert<IsExact<ValueOf<typeof writableObj>, string | number | (() => void)>>,
Assert<IsExact<ValueOf<typeof optionalObj>, string | number | (() => void) | undefined>>,
Assert<IsExact<ValueOf<typeof readonlyObj>, string | number | (() => void)>>,
Assert<IsExact<ValueOf<typeof array>, number>>,
Assert<IsExact<ValueOf<typeof arrayOrUndefined>, number | undefined>>,
Assert<IsExact<ValueOf<typeof readonlyArray>, string | number>>,
Assert<IsExact<ValueOf<typeof readonlyArrayOrUndefined>, string | number | undefined>>,
Assert<IsExact<ValueOf<typeof tuple>, boolean | string | number>>,
Assert<IsExact<ValueOf<typeof readonlyTuple>, boolean | string | number>>,
Assert<IsExact<ValueOf<typeof readonlyTupleOrUndefined>, boolean | string | number | undefined>>,
Assert<IsExact<ValueOf<typeof func>, boolean>>,
Assert<IsExact<ValueOf<string>, string>>,
Assert<IsExact<ValueOf<Array<number | undefined>>, number | undefined>>,
Assert<IsExact<ValueOf<AnyArray<number | undefined>>, number | undefined>>,
// valueOf for constant primitive literals will be the literal type
Assert<IsExact<ValueOf<"const">, "const">>,
Assert<IsExact<ValueOf<23>, 23>>,
Assert<IsExact<ValueOf<undefined>, undefined>>,
Assert<IsExact<ValueOf<{ a: "1"; b: 2 }>, "1" | 2>>,
// Combination of these types
Assert<IsExact<ValueOf<typeof primitive>, Primitive>>,
Assert<IsExact<ValueOf<typeof primitiveOrObject>, ValueOf<Primitive> | ValueOf<typeof writableObj>>>,
Assert<IsExact<ValueOf<typeof objOrFunc>, ValueOf<typeof writableObj | ValueOf<typeof func>>>>,
Assert<IsExact<ValueOf<[number[], string[]]>, number[] | string[]>>,
Assert<IsExact<ValueOf<number[] | string | null>, number | string | null>>,
Assert<IsExact<ValueOf<boolean[] | typeof writableObj>, boolean | ValueOf<typeof writableObj>>>,
Assert<IsExact<ValueOf<[string, number] | { keyA: bigint; keyB: null }>, string | number | bigint | null>>,
];
}

0 comments on commit 66a6169

Please sign in to comment.