Skip to content

Commit

Permalink
UsefulKeys<T>
Browse files Browse the repository at this point in the history
  • Loading branch information
mmkal committed Oct 3, 2023
1 parent c3c4fd4 commit 89d81cc
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 8 deletions.
16 changes: 14 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,24 @@ export type PrintType<T> = IsUnknown<T> extends true
? 'function'
: '...'

/** Subjective "useful" keys from a type. For objects it's just `keyof` but for tuples/arrays it's the number keys
* @example
* UsefulKeys<{a: 1; b: 2}> // 'a' | 'b'
* UsefulKeys<['a', 'b']> // '0' | '1'
* UsefulKeys<string[]> // number
*/
export type UsefulKeys<T> = T extends any[]
? {
[K in keyof T]: K
}[number]
: keyof T

// Helper for showing end-user a hint why their type assertion is failing.
// This swaps "leaf" types with a literal message about what the actual and expected types are.
// Needs to check for Not<IsAny<Actual>> because otherwise LeafTypeOf<Actual> returns never, which extends everything 🤔
export type MismatchInfo<Actual, Expected> = And<[Extends<PrintType<Actual>, '...'>, Not<IsAny<Actual>>]> extends true
? {
[K in keyof Actual | keyof Expected]: MismatchInfo<
[K in UsefulKeys<Actual> | UsefulKeys<Expected>]: MismatchInfo<
K extends keyof Actual ? Actual[K] : never,
K extends keyof Expected ? Expected[K] : never
>
Expand Down Expand Up @@ -169,7 +181,7 @@ type MismatchArgs<ActualResult extends boolean, ExpectedResult extends boolean>
? []
: [Mismatch]

export interface ExpectTypeOfOptions {
export interface ExpectTypeOfOptions {
positive: boolean
branded: boolean
}
Expand Down
6 changes: 3 additions & 3 deletions test/__snapshots__/errors.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ test/usage.test.ts:999:999 - error TS2349: This expression is not callable.
999 expectTypeOf([1, 2, 3]).items.toBeString()
~~~~~~~~~~
test/usage.test.ts:999:999 - error TS2344: Type 'number[]' does not satisfy the constraint '{ [x: number]: never; [iterator]: \\"Expected: function, Actual: never\\"; [unscopables]: () => { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; }; length: number; toString: () => string; concat: { (...items: ConcatArray<any>[]): any[]; (...items: any[]): any[]; }; indexOf: (searchElement: any, fromIndex?: number | undefined) => number; lastIndexOf: (searchElement: any, fromIndex?: number | undefined) => number; slice: (start?: number | undefined, end?: number | undefined) => any[]; includes: (searchElement: any, fromIndex?: number | undefined) => boolean; toLocaleString: () => string; join: (separator?: string | undefined) => string; every: { <S extends any>(predicate: (value: any, index: number, array: any[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): boolean; }; some: (predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any) => boolean; forEach: (callbackfn: (value: any, index: number, array: any[]) => void, thisArg?: any) => void; map: <U>(callbackfn: (value: any, index: number, array: any[]) => U, thisArg?: any) => U[]; filter: { <S extends any>(predicate: (value: any, index: number, array: any[]) => value is S, thisArg?: any): S[]; (predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): any[]; }; reduce: { (callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any): any; (callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any, initialValue: any): any; <U>(callbackfn: (previousValue: U, currentValue: any, currentIndex: number, array: any[]) => U, initialValue: U): U; }; reduceRight: { (callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any): any; (callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any, initialValue: any): any; <U>(callbackfn: (previousValue: U, currentValue: any, currentIndex: number, array: any[]) => U, initialValue: U): U; }; find: \\"Expected: function, Actual: never\\"; findIndex: (predicate: (value: any, index: number, obj: any[]) => unknown, thisArg?: any) => number; entries: () => IterableIterator<[number, any]>; keys: () => IterableIterator<number>; values: \\"Expected: function, Actual: never\\"; pop: \\"Expected: function, Actual: never\\"; push: (...items: any[]) => number; reverse: () => any[]; shift: \\"Expected: function, Actual: never\\"; sort: (compareFn?: ((a: any, b: any) => number) | undefined) => any[]; splice: { (start: number, deleteCount?: number | undefined): any[]; (start: number, deleteCount: number, ...items: any[]): any[]; }; unshift: (...items: any[]) => number; fill: (value: any, start?: number | undefined, end?: number | undefined) => any[]; copyWithin: (target: number, start: number, end?: number | undefined) => any[]; }'.
Types of property '[iterator]' are incompatible.
Type '() => IterableIterator<number>' is not assignable to type '\\"Expected: function, Actual: never\\"'.
test/usage.test.ts:999:999 - error TS2344: Type 'number[]' does not satisfy the constraint '{ [x: number]: never; }'.
'number' index signatures are incompatible.
Type 'number' is not assignable to type 'never'.
999 expectTypeOf<any[]>().toEqualTypeOf<number[]>()
~~~~~~~~
Expand Down
Loading

0 comments on commit 89d81cc

Please sign in to comment.