From 37c30ccc27637fe347687fcba0efc9b78870e1d6 Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Wed, 7 Aug 2024 09:48:33 +0200 Subject: [PATCH 1/2] create utils file this is mostly to reduce the unrelated diff on https://github.com/mmkal/expect-type/pull/83 --- src/index.ts | 379 ++++----------------------------------------------- src/utils.ts | 336 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 363 insertions(+), 352 deletions(-) create mode 100644 src/utils.ts diff --git a/src/index.ts b/src/index.ts index e7302d9..35ad1ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,355 +1,30 @@ -/** - * Negates a boolean type. - */ -export type Not = T extends true ? false : true - -/** - * Returns `true` if at least one of the types in the - * {@linkcode Types} array is `true`, otherwise returns `false`. - */ -export type Or = Types[number] extends false ? false : true - -/** - * Checks if all the boolean types in the {@linkcode Types} array are `true`. - */ -export type And = Types[number] extends true ? true : false - -/** - * Represents an equality type that returns {@linkcode Right} if - * {@linkcode Left} is `true`, - * otherwise returns the negation of {@linkcode Right}. - */ -export type Eq = Left extends true ? Right : Not - -/** - * Represents the exclusive OR operation on a tuple of boolean types. - * Returns `true` if exactly one of the boolean types is `true`, - * otherwise returns `false`. - */ -export type Xor = Not> - -const secret = Symbol('secret') -type Secret = typeof secret - -/** - * Checks if the given type is `never`. - */ -export type IsNever = [T] extends [never] ? true : false -/** - * Checks if the given type is `any`. - */ -export type IsAny = [T] extends [Secret] ? Not> : false -/** - * Determines if the given type is `unknown`. - */ -export type IsUnknown = [unknown] extends [T] ? Not> : false -/** - * Determines if a type is either `never` or `any`. - */ -export type IsNeverOrAny = Or<[IsNever, IsAny]> - -/** - * Determines the printable type representation for a given type. - */ -export type PrintType = - IsUnknown extends true - ? 'unknown' - : IsNever extends true - ? 'never' - : IsAny extends true - ? never // special case, can't use `'any'` because that would match `any` - : boolean extends T - ? 'boolean' - : T extends boolean - ? `literal boolean: ${T}` - : string extends T - ? 'string' - : T extends string - ? `literal string: ${T}` - : number extends T - ? 'number' - : T extends number - ? `literal number: ${T}` - : T extends null - ? 'null' - : T extends undefined - ? 'undefined' - : T extends (...args: any[]) => any - ? 'function' - : '...' - -/** - * Subjective "useful" keys from a type. For objects it's just `keyof` but for - * tuples/arrays it's the number keys. - * - * @example - * ```ts - * UsefulKeys<{a: 1; b: 2}> // 'a' | 'b' - * - * UsefulKeys<['a', 'b']> // '0' | '1' - * - * UsefulKeys // number - * ``` - */ -export type UsefulKeys = 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> because otherwise LeafTypeOf returns never, which extends everything 🤔 -export type MismatchInfo = - And<[Extends, '...'>, Not>]> extends true - ? And<[Extends, Extends]> extends true - ? Array[number], Extract[number]>> - : { - [K in UsefulKeys | UsefulKeys]: MismatchInfo< - K extends keyof Actual ? Actual[K] : never, - K extends keyof Expected ? Expected[K] : never - > - } - : StrictEqualUsingBranding extends true - ? Actual - : `Expected: ${PrintType}, Actual: ${PrintType>}` - -/** - * Represents a deeply branded type. - * - * Recursively walk a type and replace it with a branded type related to the - * original. This is useful for equality-checking stricter than - * `A extends B ? B extends A ? true : false : false`, because it detects the - * difference between a few edge-case types that vanilla TypeScript - * doesn't by default: - * - `any` vs `unknown` - * - `{ readonly a: string }` vs `{ a: string }` - * - `{ a?: string }` vs `{ a: string | undefined }` - * - * __Note__: not very performant for complex types - this should only be used - * when you know you need it. If doing an equality check, it's almost always - * better to use {@linkcode StrictEqualUsingTSInternalIdenticalToOperator}. - */ -export type DeepBrand = - IsNever extends true - ? {type: 'never'} - : IsAny extends true - ? {type: 'any'} - : IsUnknown extends true - ? {type: 'unknown'} - : T extends string | number | boolean | symbol | bigint | null | undefined | void - ? { - type: 'primitive' - value: T - } - : T extends new (...args: any[]) => any - ? { - type: 'constructor' - params: ConstructorParams - instance: DeepBrand any>>> - } - : T extends (...args: infer P) => infer R // avoid functions with different params/return values matching - ? { - type: 'function' - params: DeepBrand

- return: DeepBrand - this: DeepBrand> - props: DeepBrand> - } - : T extends any[] - ? { - type: 'array' - items: {[K in keyof T]: T[K]} - } - : { - type: 'object' - properties: {[K in keyof T]: DeepBrand} - readonly: ReadonlyKeys - required: RequiredKeys - optional: OptionalKeys - constructorParams: DeepBrand> - } - -/** - * Extracts the keys from a type that are required (not optional). - */ -export type RequiredKeys = Extract< - { - [K in keyof T]-?: {} extends Pick ? never : K - }[keyof T], - keyof T -> -/** - * Gets the keys of an object type that are optional. - */ -export type OptionalKeys = Exclude> - -// adapted from some answers to https://github.com/type-challenges/type-challenges/issues?q=label%3A5+label%3Aanswer -// prettier-ignore -/** - * Extracts the keys from a type that are not readonly. - */ -export type ReadonlyKeys = Extract<{ - [K in keyof T]-?: ReadonlyEquivalent< - {[_K in K]: T[K]}, - {-readonly [_K in K]: T[K]} - > extends true ? never : K; -}[keyof T], keyof T>; - -// prettier-ignore -/** - * Determines if two types, are equivalent in a `readonly` manner. - */ -type ReadonlyEquivalent = Extends< - (() => T extends X ? true : false), - (() => T extends Y ? true : false) -> - -/** - * Checks if one type extends another. - */ -export type Extends = IsNever extends true ? IsNever : [L] extends [R] ? true : false -export type ExtendsUsingBranding = Extends, DeepBrand> -export type ExtendsExcludingAnyOrNever = IsAny extends true ? IsAny : Extends - -/** - * Checks if two types are strictly equal using - * the TypeScript internal identical-to operator. - * - * @see {@link https://github.com/microsoft/TypeScript/issues/55188#issuecomment-1656328122 much history} - */ -type StrictEqualUsingTSInternalIdenticalToOperator = - (() => T extends (L & T) | T ? true : false) extends () => T extends (R & T) | T ? true : false - ? IsNever extends IsNever - ? true - : false - : false - -/** - * Checks if two types are strictly equal using branding. - */ -export type StrictEqualUsingBranding = And< - [ExtendsUsingBranding, ExtendsUsingBranding] -> - -/** - * Represents a type that checks if two types are equal, using - * a hopefully performant approach. - * It first checks if the types are strictly equal using - * {@linkcode StrictEqualUsingTSInternalIdenticalToOperator}. - * If they are not strictly equal, it falls back to using the - * {@linkcode StrictEqualUsingBranding} type. - */ -export type HopefullyPerformantEqual = - StrictEqualUsingTSInternalIdenticalToOperator extends true ? true : StrictEqualUsingBranding - -/** - * Extracts the parameter types from a function type. - */ -export type Params = Actual extends (...args: infer ParameterTypes) => any ? ParameterTypes : never -/** - * Represents the constructor parameters of a class or constructor function. - * If the constructor takes no arguments, an empty array is returned. - */ -export type ConstructorParams = Actual extends new (...args: infer P) => any - ? Actual extends new () => any - ? P | [] - : P - : never - -const mismatch = Symbol('mismatch') -type Mismatch = {[mismatch]: 'mismatch'} - -/** - * A type which should match anything passed as a value but *doesn't* - * match {@linkcode Mismatch}. It helps TypeScript select the right overload - * for {@linkcode PositiveExpectTypeOf.toEqualTypeOf `.toEqualTypeOf()`} and - * {@linkcode PositiveExpectTypeOf.toMatchTypeOf `.toMatchTypeOf()`}. - */ -const avalue = Symbol('avalue') -/** - * Represents a value that can be of various types. - */ -type AValue = {[avalue]?: undefined} | string | number | boolean | symbol | bigint | null | undefined | void - -/** - * Represents the type of mismatched arguments between - * the actual result and the expected result. - * - * If {@linkcode ActualResult} and {@linkcode ExpectedResult} are equivalent, - * the type resolves to an empty tuple `[]`, indicating no mismatch. - * If they are not equivalent, it resolves to a tuple containing the element - * {@linkcode Mismatch}, signifying a discrepancy between - * the expected and actual results. - */ -type MismatchArgs = - Eq extends true ? [] : [Mismatch] - -/** - * Represents the options for the {@linkcode ExpectTypeOf} function. - */ -export interface ExpectTypeOfOptions { - positive: boolean - branded: boolean -} - -const inverted = Symbol('inverted') -type Inverted = {[inverted]: T} - -const expectNull = Symbol('expectNull') -type ExpectNull = {[expectNull]: T; result: ExtendsExcludingAnyOrNever} - -const expectUndefined = Symbol('expectUndefined') -type ExpectUndefined = {[expectUndefined]: T; result: ExtendsExcludingAnyOrNever} - -const expectNumber = Symbol('expectNumber') -type ExpectNumber = {[expectNumber]: T; result: ExtendsExcludingAnyOrNever} - -const expectString = Symbol('expectString') -type ExpectString = {[expectString]: T; result: ExtendsExcludingAnyOrNever} - -const expectBoolean = Symbol('expectBoolean') -type ExpectBoolean = {[expectBoolean]: T; result: ExtendsExcludingAnyOrNever} - -const expectVoid = Symbol('expectVoid') -type ExpectVoid = {[expectVoid]: T; result: ExtendsExcludingAnyOrNever} - -const expectFunction = Symbol('expectFunction') -type ExpectFunction = {[expectFunction]: T; result: ExtendsExcludingAnyOrNever any>} - -const expectObject = Symbol('expectObject') -type ExpectObject = {[expectObject]: T; result: ExtendsExcludingAnyOrNever} - -const expectArray = Symbol('expectArray') -type ExpectArray = {[expectArray]: T; result: ExtendsExcludingAnyOrNever} - -const expectSymbol = Symbol('expectSymbol') -type ExpectSymbol = {[expectSymbol]: T; result: ExtendsExcludingAnyOrNever} - -const expectAny = Symbol('expectAny') -type ExpectAny = {[expectAny]: T; result: IsAny} - -const expectUnknown = Symbol('expectUnknown') -type ExpectUnknown = {[expectUnknown]: T; result: IsUnknown} - -const expectNever = Symbol('expectNever') -type ExpectNever = {[expectNever]: T; result: IsNever} - -const expectNullable = Symbol('expectNullable') -type ExpectNullable = {[expectNullable]: T; result: Not>>} - -/** - * Represents a scolder function that checks if the result of an expecter - * matches the specified options. - */ -type Scolder< - Expecter extends {result: boolean}, - Options extends {positive: boolean}, -> = Expecter['result'] extends Options['positive'] - ? () => true - : Options['positive'] extends true - ? Expecter - : Inverted +import { + StrictEqualUsingTSInternalIdenticalToOperator, + MismatchInfo, + AValue, + MismatchArgs, + Extends, + StrictEqualUsingBranding, + Scolder, + ExpectAny, + ExpectUnknown, + ExpectNever, + ExpectFunction, + ExpectObject, + ExpectArray, + ExpectNumber, + ExpectString, + ExpectBoolean, + ExpectVoid, + ExpectSymbol, + ExpectNull, + ExpectUndefined, + ExpectNullable, + Params, + ConstructorParams, +} from './utils' + +export * from './utils' // backcompat, consider removing in next major version /** * Represents the positive assertion methods available for type checking in the diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..e1f6e12 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,336 @@ +/** + * Negates a boolean type. + */ +export type Not = T extends true ? false : true +/** + * Returns `true` if at least one of the types in the + * {@linkcode Types} array is `true`, otherwise returns `false`. + */ + +export type Or = Types[number] extends false ? false : true +/** + * Checks if all the boolean types in the {@linkcode Types} array are `true`. + */ + +export type And = Types[number] extends true ? true : false +/** + * Represents an equality type that returns {@linkcode Right} if + * {@linkcode Left} is `true`, + * otherwise returns the negation of {@linkcode Right}. + */ + +export type Eq = Left extends true ? Right : Not +/** + * Represents the exclusive OR operation on a tuple of boolean types. + * Returns `true` if exactly one of the boolean types is `true`, + * otherwise returns `false`. + */ + +export type Xor = Not> +const secret = Symbol('secret') +type Secret = typeof secret +/** + * Checks if the given type is `never`. + */ + +export type IsNever = [T] extends [never] ? true : false +/** + * Checks if the given type is `any`. + */ +export type IsAny = [T] extends [Secret] ? Not> : false +/** + * Determines if the given type is `unknown`. + */ +export type IsUnknown = [unknown] extends [T] ? Not> : false +/** + * Determines if a type is either `never` or `any`. + */ +export type IsNeverOrAny = Or<[IsNever, IsAny]> +/** + * Determines the printable type representation for a given type. + */ + +export type PrintType = + IsUnknown extends true + ? 'unknown' + : IsNever extends true + ? 'never' + : IsAny extends true + ? never // special case, can't use `'any'` because that would match `any` + : boolean extends T + ? 'boolean' + : T extends boolean + ? `literal boolean: ${T}` + : string extends T + ? 'string' + : T extends string + ? `literal string: ${T}` + : number extends T + ? 'number' + : T extends number + ? `literal number: ${T}` + : T extends null + ? 'null' + : T extends undefined + ? 'undefined' + : T extends (...args: any[]) => any + ? 'function' + : '...' +/** + * Subjective "useful" keys from a type. For objects it's just `keyof` but for + * tuples/arrays it's the number keys. + * + * @example + * ```ts + * UsefulKeys<{a: 1; b: 2}> // 'a' | 'b' + * + * UsefulKeys<['a', 'b']> // '0' | '1' + * + * UsefulKeys // number + * ``` + */ + +export type UsefulKeys = 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> because otherwise LeafTypeOf returns never, which extends everything 🤔 + +export type MismatchInfo = + And<[Extends, '...'>, Not>]> extends true + ? And<[Extends, Extends]> extends true + ? Array[number], Extract[number]>> + : { + [K in UsefulKeys | UsefulKeys]: MismatchInfo< + K extends keyof Actual ? Actual[K] : never, + K extends keyof Expected ? Expected[K] : never + > + } + : StrictEqualUsingBranding extends true + ? Actual + : `Expected: ${PrintType}, Actual: ${PrintType>}` +/** + * Represents a deeply branded type. + * + * Recursively walk a type and replace it with a branded type related to the + * original. This is useful for equality-checking stricter than + * `A extends B ? B extends A ? true : false : false`, because it detects the + * difference between a few edge-case types that vanilla TypeScript + * doesn't by default: + * - `any` vs `unknown` + * - `{ readonly a: string }` vs `{ a: string }` + * - `{ a?: string }` vs `{ a: string | undefined }` + * + * __Note__: not very performant for complex types - this should only be used + * when you know you need it. If doing an equality check, it's almost always + * better to use {@linkcode StrictEqualUsingTSInternalIdenticalToOperator}. + */ + +export type DeepBrand = + IsNever extends true + ? {type: 'never'} + : IsAny extends true + ? {type: 'any'} + : IsUnknown extends true + ? {type: 'unknown'} + : T extends string | number | boolean | symbol | bigint | null | undefined | void + ? { + type: 'primitive' + value: T + } + : T extends new (...args: any[]) => any + ? { + type: 'constructor' + params: ConstructorParams + instance: DeepBrand any>>> + } + : T extends (...args: infer P) => infer R // avoid functions with different params/return values matching + ? { + type: 'function' + params: DeepBrand

+ return: DeepBrand + this: DeepBrand> + props: DeepBrand> + } + : T extends any[] + ? { + type: 'array' + items: { + [K in keyof T]: T[K] + } + } + : { + type: 'object' + properties: { + [K in keyof T]: DeepBrand + } + readonly: ReadonlyKeys + required: RequiredKeys + optional: OptionalKeys + constructorParams: DeepBrand> + } +/** + * Extracts the keys from a type that are required (not optional). + */ + +export type RequiredKeys = Extract< + { + [K in keyof T]-?: {} extends Pick ? never : K + }[keyof T], + keyof T +> +/** + * Gets the keys of an object type that are optional. + */ +export type OptionalKeys = Exclude> +// adapted from some answers to https://github.com/type-challenges/type-challenges/issues?q=label%3A5+label%3Aanswer +// prettier-ignore +/** + * Extracts the keys from a type that are not readonly. + */ + +export type ReadonlyKeys = Extract<{ + [K in keyof T]-?: ReadonlyEquivalent< + { + [_K in K]: T[K]; + }, { + -readonly [_K in K]: T[K]; + } + > extends true ? never : K; +}[keyof T], keyof T>; +// prettier-ignore +/** + * Determines if two types, are equivalent in a `readonly` manner. + */ +type ReadonlyEquivalent = Extends< + (() => T extends X ? true : false), (() => T extends Y ? true : false) +>; +/** + * Checks if one type extends another. + */ + +export type Extends = IsNever extends true ? IsNever : [L] extends [R] ? true : false +export type ExtendsUsingBranding = Extends, DeepBrand> +export type ExtendsExcludingAnyOrNever = IsAny extends true ? IsAny : Extends +/** + * Checks if two types are strictly equal using + * the TypeScript internal identical-to operator. + * + * @see {@link https://github.com/microsoft/TypeScript/issues/55188#issuecomment-1656328122 much history} + */ +export type StrictEqualUsingTSInternalIdenticalToOperator = + (() => T extends (L & T) | T ? true : false) extends () => T extends (R & T) | T ? true : false + ? IsNever extends IsNever + ? true + : false + : false +/** + * Checks if two types are strictly equal using branding. + */ + +export type StrictEqualUsingBranding = And< + [ExtendsUsingBranding, ExtendsUsingBranding] +> +/** + * Represents a type that checks if two types are equal, using + * a hopefully performant approach. + * It first checks if the types are strictly equal using + * {@linkcode StrictEqualUsingTSInternalIdenticalToOperator}. + * If they are not strictly equal, it falls back to using the + * {@linkcode StrictEqualUsingBranding} type. + */ + +export type HopefullyPerformantEqual = + StrictEqualUsingTSInternalIdenticalToOperator extends true ? true : StrictEqualUsingBranding +/** + * Extracts the parameter types from a function type. + */ + +export type Params = Actual extends (...args: infer ParameterTypes) => any ? ParameterTypes : never +/** + * Represents the constructor parameters of a class or constructor function. + * If the constructor takes no arguments, an empty array is returned. + */ +export type ConstructorParams = Actual extends new (...args: infer P) => any + ? Actual extends new () => any + ? P | [] + : P + : never +const mismatch = Symbol('mismatch') +type Mismatch = {[mismatch]: 'mismatch'} +/** + * A type which should match anything passed as a value but *doesn't* + * match {@linkcode Mismatch}. It helps TypeScript select the right overload + * for {@linkcode PositiveExpectTypeOf.toEqualTypeOf `.toEqualTypeOf()`} and + * {@linkcode PositiveExpectTypeOf.toMatchTypeOf `.toMatchTypeOf()`}. + */ +const avalue = Symbol('avalue') +/** + * Represents a value that can be of various types. + */ +export type AValue = {[avalue]?: undefined} | string | number | boolean | symbol | bigint | null | undefined | void +/** + * Represents the type of mismatched arguments between + * the actual result and the expected result. + * + * If {@linkcode ActualResult} and {@linkcode ExpectedResult} are equivalent, + * the type resolves to an empty tuple `[]`, indicating no mismatch. + * If they are not equivalent, it resolves to a tuple containing the element + * {@linkcode Mismatch}, signifying a discrepancy between + * the expected and actual results. + */ +export type MismatchArgs = + Eq extends true ? [] : [Mismatch] +/** + * Represents the options for the {@linkcode ExpectTypeOf} function. + */ + +export interface ExpectTypeOfOptions { + positive: boolean + branded: boolean +} +const inverted = Symbol('inverted') +type Inverted = {[inverted]: T} +const expectNull = Symbol('expectNull') +export type ExpectNull = {[expectNull]: T; result: ExtendsExcludingAnyOrNever} +const expectUndefined = Symbol('expectUndefined') +export type ExpectUndefined = {[expectUndefined]: T; result: ExtendsExcludingAnyOrNever} +const expectNumber = Symbol('expectNumber') +export type ExpectNumber = {[expectNumber]: T; result: ExtendsExcludingAnyOrNever} +const expectString = Symbol('expectString') +export type ExpectString = {[expectString]: T; result: ExtendsExcludingAnyOrNever} +const expectBoolean = Symbol('expectBoolean') +export type ExpectBoolean = {[expectBoolean]: T; result: ExtendsExcludingAnyOrNever} +const expectVoid = Symbol('expectVoid') +export type ExpectVoid = {[expectVoid]: T; result: ExtendsExcludingAnyOrNever} +const expectFunction = Symbol('expectFunction') +export type ExpectFunction = {[expectFunction]: T; result: ExtendsExcludingAnyOrNever any>} +const expectObject = Symbol('expectObject') +export type ExpectObject = {[expectObject]: T; result: ExtendsExcludingAnyOrNever} +const expectArray = Symbol('expectArray') +export type ExpectArray = {[expectArray]: T; result: ExtendsExcludingAnyOrNever} +const expectSymbol = Symbol('expectSymbol') +export type ExpectSymbol = {[expectSymbol]: T; result: ExtendsExcludingAnyOrNever} +const expectAny = Symbol('expectAny') +export type ExpectAny = {[expectAny]: T; result: IsAny} +const expectUnknown = Symbol('expectUnknown') +export type ExpectUnknown = {[expectUnknown]: T; result: IsUnknown} +const expectNever = Symbol('expectNever') +export type ExpectNever = {[expectNever]: T; result: IsNever} +const expectNullable = Symbol('expectNullable') +export type ExpectNullable = {[expectNullable]: T; result: Not>>} +/** + * Represents a scolder function that checks if the result of an expecter + * matches the specified options. + */ +export type Scolder< + Expecter extends {result: boolean}, + Options extends {positive: boolean}, +> = Expecter['result'] extends Options['positive'] + ? () => true + : Options['positive'] extends true + ? Expecter + : Inverted From 2715cd227643ff1056a7341a3c7301ac7b47502b Mon Sep 17 00:00:00 2001 From: Misha Kaletsky Date: Wed, 7 Aug 2024 09:56:21 +0200 Subject: [PATCH 2/2] fix spacing --- src/utils.ts | 62 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index e1f6e12..34f4145 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,54 +2,58 @@ * Negates a boolean type. */ export type Not = T extends true ? false : true + /** * Returns `true` if at least one of the types in the * {@linkcode Types} array is `true`, otherwise returns `false`. */ - export type Or = Types[number] extends false ? false : true + /** * Checks if all the boolean types in the {@linkcode Types} array are `true`. */ - export type And = Types[number] extends true ? true : false + /** * Represents an equality type that returns {@linkcode Right} if * {@linkcode Left} is `true`, * otherwise returns the negation of {@linkcode Right}. */ - export type Eq = Left extends true ? Right : Not + /** * Represents the exclusive OR operation on a tuple of boolean types. * Returns `true` if exactly one of the boolean types is `true`, * otherwise returns `false`. */ - export type Xor = Not> + const secret = Symbol('secret') type Secret = typeof secret + /** * Checks if the given type is `never`. */ - export type IsNever = [T] extends [never] ? true : false + /** * Checks if the given type is `any`. */ export type IsAny = [T] extends [Secret] ? Not> : false + /** * Determines if the given type is `unknown`. */ export type IsUnknown = [unknown] extends [T] ? Not> : false + /** * Determines if a type is either `never` or `any`. */ export type IsNeverOrAny = Or<[IsNever, IsAny]> + /** * Determines the printable type representation for a given type. */ - export type PrintType = IsUnknown extends true ? 'unknown' @@ -76,6 +80,7 @@ export type PrintType = : T extends (...args: any[]) => any ? 'function' : '...' + /** * Subjective "useful" keys from a type. For objects it's just `keyof` but for * tuples/arrays it's the number keys. @@ -89,16 +94,15 @@ export type PrintType = * UsefulKeys // number * ``` */ - export type UsefulKeys = 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> because otherwise LeafTypeOf returns never, which extends everything 🤔 - export type MismatchInfo = And<[Extends, '...'>, Not>]> extends true ? And<[Extends, Extends]> extends true @@ -112,6 +116,7 @@ export type MismatchInfo = : StrictEqualUsingBranding extends true ? Actual : `Expected: ${PrintType}, Actual: ${PrintType>}` + /** * Represents a deeply branded type. * @@ -128,7 +133,6 @@ export type MismatchInfo = * when you know you need it. If doing an equality check, it's almost always * better to use {@linkcode StrictEqualUsingTSInternalIdenticalToOperator}. */ - export type DeepBrand = IsNever extends true ? {type: 'never'} @@ -172,26 +176,27 @@ export type DeepBrand = optional: OptionalKeys constructorParams: DeepBrand> } + /** * Extracts the keys from a type that are required (not optional). */ - export type RequiredKeys = Extract< { [K in keyof T]-?: {} extends Pick ? never : K }[keyof T], keyof T > + /** * Gets the keys of an object type that are optional. */ export type OptionalKeys = Exclude> + // adapted from some answers to https://github.com/type-challenges/type-challenges/issues?q=label%3A5+label%3Aanswer // prettier-ignore /** * Extracts the keys from a type that are not readonly. */ - export type ReadonlyKeys = Extract<{ [K in keyof T]-?: ReadonlyEquivalent< { @@ -201,6 +206,7 @@ export type ReadonlyKeys = Extract<{ } > extends true ? never : K; }[keyof T], keyof T>; + // prettier-ignore /** * Determines if two types, are equivalent in a `readonly` manner. @@ -208,13 +214,16 @@ export type ReadonlyKeys = Extract<{ type ReadonlyEquivalent = Extends< (() => T extends X ? true : false), (() => T extends Y ? true : false) >; + /** * Checks if one type extends another. */ - export type Extends = IsNever extends true ? IsNever : [L] extends [R] ? true : false + export type ExtendsUsingBranding = Extends, DeepBrand> + export type ExtendsExcludingAnyOrNever = IsAny extends true ? IsAny : Extends + /** * Checks if two types are strictly equal using * the TypeScript internal identical-to operator. @@ -227,13 +236,14 @@ export type StrictEqualUsingTSInternalIdenticalToOperator = ? true : false : false + /** * Checks if two types are strictly equal using branding. */ - export type StrictEqualUsingBranding = And< [ExtendsUsingBranding, ExtendsUsingBranding] > + /** * Represents a type that checks if two types are equal, using * a hopefully performant approach. @@ -242,14 +252,14 @@ export type StrictEqualUsingBranding = And< * If they are not strictly equal, it falls back to using the * {@linkcode StrictEqualUsingBranding} type. */ - export type HopefullyPerformantEqual = StrictEqualUsingTSInternalIdenticalToOperator extends true ? true : StrictEqualUsingBranding + /** * Extracts the parameter types from a function type. */ - export type Params = Actual extends (...args: infer ParameterTypes) => any ? ParameterTypes : never + /** * Represents the constructor parameters of a class or constructor function. * If the constructor takes no arguments, an empty array is returned. @@ -259,8 +269,10 @@ export type ConstructorParams = Actual extends new (...args: infer P) => ? P | [] : P : never + const mismatch = Symbol('mismatch') type Mismatch = {[mismatch]: 'mismatch'} + /** * A type which should match anything passed as a value but *doesn't* * match {@linkcode Mismatch}. It helps TypeScript select the right overload @@ -268,10 +280,12 @@ type Mismatch = {[mismatch]: 'mismatch'} * {@linkcode PositiveExpectTypeOf.toMatchTypeOf `.toMatchTypeOf()`}. */ const avalue = Symbol('avalue') + /** * Represents a value that can be of various types. */ export type AValue = {[avalue]?: undefined} | string | number | boolean | symbol | bigint | null | undefined | void + /** * Represents the type of mismatched arguments between * the actual result and the expected result. @@ -284,44 +298,60 @@ export type AValue = {[avalue]?: undefined} | string | number | boolean | symbol */ export type MismatchArgs = Eq extends true ? [] : [Mismatch] + /** * Represents the options for the {@linkcode ExpectTypeOf} function. */ - export interface ExpectTypeOfOptions { positive: boolean branded: boolean } + const inverted = Symbol('inverted') type Inverted = {[inverted]: T} + const expectNull = Symbol('expectNull') export type ExpectNull = {[expectNull]: T; result: ExtendsExcludingAnyOrNever} + const expectUndefined = Symbol('expectUndefined') export type ExpectUndefined = {[expectUndefined]: T; result: ExtendsExcludingAnyOrNever} + const expectNumber = Symbol('expectNumber') export type ExpectNumber = {[expectNumber]: T; result: ExtendsExcludingAnyOrNever} + const expectString = Symbol('expectString') export type ExpectString = {[expectString]: T; result: ExtendsExcludingAnyOrNever} + const expectBoolean = Symbol('expectBoolean') export type ExpectBoolean = {[expectBoolean]: T; result: ExtendsExcludingAnyOrNever} + const expectVoid = Symbol('expectVoid') export type ExpectVoid = {[expectVoid]: T; result: ExtendsExcludingAnyOrNever} + const expectFunction = Symbol('expectFunction') export type ExpectFunction = {[expectFunction]: T; result: ExtendsExcludingAnyOrNever any>} + const expectObject = Symbol('expectObject') export type ExpectObject = {[expectObject]: T; result: ExtendsExcludingAnyOrNever} + const expectArray = Symbol('expectArray') export type ExpectArray = {[expectArray]: T; result: ExtendsExcludingAnyOrNever} + const expectSymbol = Symbol('expectSymbol') export type ExpectSymbol = {[expectSymbol]: T; result: ExtendsExcludingAnyOrNever} + const expectAny = Symbol('expectAny') export type ExpectAny = {[expectAny]: T; result: IsAny} + const expectUnknown = Symbol('expectUnknown') export type ExpectUnknown = {[expectUnknown]: T; result: IsUnknown} + const expectNever = Symbol('expectNever') export type ExpectNever = {[expectNever]: T; result: IsNever} + const expectNullable = Symbol('expectNullable') export type ExpectNullable = {[expectNullable]: T; result: Not>>} + /** * Represents a scolder function that checks if the result of an expecter * matches the specified options.