UNPKG

ts-data-forge

Version:

[![npm version](https://img.shields.io/npm/v/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![npm downloads](https://img.shields.io/npm/dm/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![License](https://img.shields.

207 lines (206 loc) 7.7 kB
/** * Compile-time type assertion utility for TypeScript type testing. * * This function performs static type relationship checking at compile-time and has no runtime effect. * It is primarily used in test files to verify that TypeScript's type inference and type relationships * work as expected. The function will cause TypeScript compilation errors if the specified type * relationship does not hold. * * ## Supported Type Relations * * ### Equality Relations * - **`"="` (strict equality)**: Asserts that types `A` and `B` are exactly the same type. * Uses TypeScript's internal type equality checking. * - **`"!="` (strict inequality)**: Asserts that types `A` and `B` are not exactly the same type. * * ### Assignability Relations * - **`"~="` (mutual assignability)**: Asserts that `A` extends `B` AND `B` extends `A`. * Types are structurally equivalent and mutually assignable. * - **`"<="` (subtype relation)**: Asserts that type `A` extends (is assignable to) type `B`. * Type `A` is a subtype of `B`. * - **`">="` (supertype relation)**: Asserts that type `B` extends (is assignable to) type `A`. * Type `A` is a supertype of `B`. * * ### Negative Assignability Relations * - **`"!<="` (not subtype)**: Asserts that type `A` does NOT extend type `B`. * - **`"!>="` (not supertype)**: Asserts that type `B` does NOT extend type `A`. * * ## Type Parameter Constraints * * @template A - The first type for comparison. Can be any TypeScript type including: * - Primitive types (string, number, boolean, etc.) * - Object types and interfaces * - Union and intersection types * - Generic types and type parameters * - Literal types and branded types * - Function types and return types * @template B - The second type for comparison. Same constraints as type `A`. * * @param _relation - A string literal representing the expected type relationship. * TypeScript's type system automatically infers and restricts the available operators * based on the actual relationship between types `A` and `B`. If an invalid relationship * is specified, TypeScript will show a compilation error. * * ## Usage Patterns * * ### Basic Type Testing * @example * ```typescript * import { expectType } from './expect-type.mjs'; * * // Primitive type equality * expectType<string, string>("="); // ✓ exact match * expectType<number, string>("!="); // ✓ different types * expectType<42, number>("<="); // ✓ literal extends primitive * expectType<number, 42>(">="); // ✓ primitive is supertype * * // Type assertions will cause compilation errors for wrong relationships: * // expectType<string, number>("="); // ❌ TypeScript error * // expectType<number, string>("<="); // ❌ TypeScript error * ``` * * ### Array and Tuple Type Validation * @example * ```typescript * // Testing array utility function return types * const zeros = Arr.zeros(3); * expectType<typeof zeros, readonly [0, 0, 0]>("="); * * const sequence = Arr.seq(5); * expectType<typeof sequence, readonly [0, 1, 2, 3, 4]>("="); * * // Dynamic length arrays * const dynamicArray = Arr.zeros(someLength); * expectType<typeof dynamicArray, readonly 0[]>("="); * ``` * * ### Function Return Type Testing * @example * ```typescript * // Testing function return types * const createUser = () => ({ id: 1, name: 'John' }); * expectType<ReturnType<typeof createUser>, { id: number; name: string }>("~="); * * // Generic function type inference * const identity = <T>(x: T): T => x; * const result = identity('hello'); * expectType<typeof result, string>("="); * ``` * * ### Union and Intersection Types * @example * ```typescript * // Union type relationships * expectType<string, string | number>("<="); // string extends union * expectType<string | number, string>(">="); // union contains string * expectType<string | number, number>(">="); // union contains number * * // Intersection type relationships * type A = { a: number }; * type B = { b: string }; * expectType<A & B, A>(">="); // intersection extends component * expectType<A, A & B>("<="); // component extends intersection * ``` * * ### Branded Type Validation * @example * ```typescript * // Testing branded number types * expectType<PositiveInt, number>("<="); // branded type extends base * expectType<number, PositiveInt>(">="); // base type is supertype * expectType<PositiveInt, NegativeInt>("!="); // different branded types * * // Type guard function validation * if (isPositiveInt(value)) { * expectType<typeof value, PositiveInt>("<="); * } * ``` * * ### Optional and Result Type Testing * @example * ```typescript * // Optional type narrowing * const optional: Optional<number> = Optional.some(42); * if (Optional.isSome(optional)) { * expectType<typeof optional, Optional.Some<number>>("<="); * } * if (Optional.isNone(optional)) { * expectType<typeof optional, Optional.None>("<="); * } * * // Result type validation * const result: Result<string, Error> = Result.ok('success'); * expectType<typeof result, Result<string, Error>>("<="); * ``` * * ### Type Guard and Validation Testing * @example * ```typescript * // Testing type guard functions * if (isRecord(value)) { * expectType<typeof value, UnknownRecord>("<="); * } * * // Testing compile-time type predicates * const obj = { key: 'value' }; * if (hasKey(obj, 'key')) { * expectType<typeof obj.key, unknown>("<="); * } * ``` * * ## Common Testing Patterns * * ### Dual Testing Strategy * Combine `expectType` with runtime assertions for comprehensive testing: * * @example * ```typescript * describe('Arr.zeros', () => { * test('should create array of zeros with correct type', () => { * const result = Arr.zeros(3); * * // Compile-time type assertion * expectType<typeof result, readonly [0, 0, 0]>("="); * * // Runtime behavior assertion * expect(result).toStrictEqual([0, 0, 0]); * }); * }); * ``` * * ### Type Relationship Validation * Test complex type hierarchies and relationships: * * @example * ```typescript * // Ensure proper type hierarchy * expectType<PositiveInt, Int>("<="); // positive is subset of int * expectType<Int, FiniteNumber>("<="); // int is subset of finite * expectType<FiniteNumber, number>("<="); // finite is subset of number * * // Verify mutual exclusion * expectType<PositiveInt, NegativeInt>("!="); // different int types * expectType<PositiveInt, NegativeInt>("!<="); // neither extends the other * expectType<NegativeInt, PositiveInt>("!<="); * ``` * * ## Important Notes * * - **Compile-time only**: This function has no runtime behavior and will be optimized away. * - **Type inference**: The available relation operators are automatically inferred by TypeScript * based on the actual type relationship between `A` and `B`. * - **Error feedback**: Invalid type relationships will cause clear TypeScript compilation errors. * - **Test organization**: Typically used in `.test.mts` files alongside runtime assertions. * - **Performance**: Has zero runtime overhead as it's purely a compile-time construct. * * @since 1.0.0 */ export const expectType = <A, B>( _relation: TypeEq<A, B> extends true ? '<=' | '=' | '>=' | '~=' : | '!=' | (TypeExtends<A, B> extends true ? '<=' | (TypeExtends<B, A> extends true ? '>=' | '~=' : '!>=') : '!<=' | (TypeExtends<B, A> extends true ? '>=' : '!>=')), ): void => undefined;