CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-expect-type

Compile-time tests for types to make sure types don't regress into being overly permissive as changes go in over time.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

advanced-types.mddocs/

Advanced Type Operations

Complex type manipulation including union/intersection handling, type extraction, generic transformations, and specialized type analysis.

Capabilities

Union Type Extraction

Extract specific types from union types using type constraints.

/**
 * Extract types from a union that match the given constraint
 * Equivalent to TypeScript's Extract utility type
 * @param v - Optional value for type inference
 * @returns ExpectTypeOf for the extracted type (never if no match)
 */
extract<V>(v?: V): ExpectTypeOf<Extract<Actual, V>, Options>;

Usage Examples:

import { expectTypeOf } from "expect-type";

// Responsive design type example
type ResponsiveProp<T> = T | T[] | { xs?: T; sm?: T; md?: T };

interface CSSProperties {
  margin?: string;
  padding?: string;
}

declare function getResponsiveProp<T>(props: T): ResponsiveProp<T>;

const cssProps: CSSProperties = { margin: "1px", padding: "2px" };

// Extract array type from union
expectTypeOf(getResponsiveProp(cssProps))
  .extract<unknown[]>()
  .toEqualTypeOf<CSSProperties[]>();

// Extract object type from union
expectTypeOf(getResponsiveProp(cssProps))
  .extract<{ xs?: any }>()
  .toEqualTypeOf<{
    xs?: CSSProperties;
    sm?: CSSProperties;
    md?: CSSProperties;
  }>();

// Extract never when no match
type UnionType = string | number | boolean;
expectTypeOf<UnionType>()
  .extract<{ impossible: true }>()
  .toBeNever();

// Extract specific union members
expectTypeOf<"a" | "b" | "c">()
  .extract<"a" | "b">()
  .toEqualTypeOf<"a" | "b">();

Union Type Exclusion

Remove specific types from union types.

/**
 * Remove types from a union that match the given constraint
 * Equivalent to TypeScript's Exclude utility type
 * @param v - Optional value for type inference
 * @returns ExpectTypeOf for the remaining union type (never if all excluded)
 */
exclude<V>(v?: V): ExpectTypeOf<Exclude<Actual, V>, Options>;

Usage Examples:

import { expectTypeOf } from "expect-type";

// Remove array and object types, keep primitive
type ResponsiveProp<T> = T | T[] | { xs?: T; sm?: T; md?: T };

declare function getResponsiveProp<T>(props: T): ResponsiveProp<T>;

const cssProps: CSSProperties = { margin: "1px" };

expectTypeOf(getResponsiveProp(cssProps))
  .exclude<unknown[]>()
  .exclude<{ xs?: unknown }>()
  .toEqualTypeOf<CSSProperties>();

// Chain exclusions
expectTypeOf<string | number | boolean | null | undefined>()
  .exclude<null>()
  .exclude<undefined>()
  .toEqualTypeOf<string | number | boolean>();

// Exclude multiple types at once
expectTypeOf<string | number | boolean | null | undefined>()
  .exclude<null | undefined>()
  .toEqualTypeOf<string | number | boolean>();

// Exclude all types results in never
expectTypeOf<"a" | "b">()
  .exclude<"a" | "b">()
  .toBeNever();

// Complex exclusion example
type ApiResponse<T> = T | { error: string } | null;
expectTypeOf<ApiResponse<{ data: number }>>()
  .exclude<null>()
  .exclude<{ error: string }>()
  .toEqualTypeOf<{ data: number }>();

Type Transformation

Transform types through callback functions for complex type manipulation.

/**
 * Transform type via a callback function
 * The callback is not executed at runtime, only used for type inference
 * @param fn - Transformation function for type inference
 * @returns ExpectTypeOf for the transformed type
 */
map<T>(fn: (value: Actual) => T): ExpectTypeOf<T, Options>;

Usage Examples:

import { expectTypeOf } from "expect-type";

// Transform string to capitalized version
const capitalize = <S extends string>(input: S) =>
  (input.slice(0, 1).toUpperCase() + input.slice(1)) as Capitalize<S>;

expectTypeOf(capitalize)
  .map(fn => fn("hello world"))
  .toEqualTypeOf<"Hello world">();

// Transform with generic functions
declare function processData<T>(data: T[]): { 
  items: T[]; 
  count: number; 
};

expectTypeOf(processData)
  .map(fn => fn([1, 2, 3]))
  .toEqualTypeOf<{ items: number[]; count: number }>();

// Transform complex nested types
type AsyncData<T> = Promise<{ result: T; timestamp: number }>;

declare function fetchData<T>(id: string): AsyncData<T>;

expectTypeOf(fetchData)
  .map(fn => fn("user-123"))
  .resolves
  .toEqualTypeOf<{ result: string; timestamp: number }>();

Promise Resolution

Extract the resolved type from Promise-like types.

/**
 * Extract the resolved value type from a Promise
 * Only available when Actual extends PromiseLike<T>
 */
resolves: ExpectTypeOf<ResolvedType, Options>; // where Actual extends PromiseLike<ResolvedType>

Usage Examples:

import { expectTypeOf } from "expect-type";

// Basic Promise resolution
expectTypeOf(Promise.resolve(42)).resolves.toBeNumber();
expectTypeOf(Promise.resolve("hello")).resolves.toBeString();

// Async function returns
async function fetchUser(): Promise<{ name: string; id: number }> {
  return { name: "John", id: 1 };
}

expectTypeOf(fetchUser).returns.resolves.toEqualTypeOf<{
  name: string;
  id: number;
}>();

// Nested Promise resolution
const nestedPromise: Promise<Promise<string>> = Promise.resolve(
  Promise.resolve("nested")
);

expectTypeOf(nestedPromise).resolves.resolves.toBeString();

// Promise-like objects (Thenable)
interface CustomThenable<T> {
  then<R>(callback: (value: T) => R): CustomThenable<R>;
}

declare const customPromise: CustomThenable<number>;
expectTypeOf(customPromise).resolves.toBeNumber();

// Conditional Promise resolution
type MaybePromise<T> = T | Promise<T>;

declare function process<T>(input: MaybePromise<T>): T;

expectTypeOf<MaybePromise<string>>()
  .extract<Promise<any>>()
  .resolves.toBeString();

Array Item Types

Extract the item type from array-like structures.

/**
 * Extract array item type
 * Works with arrays, readonly arrays, and ArrayLike types
 */
items: ExpectTypeOf<ItemType, Options>; // where Actual extends ArrayLike<ItemType>

Usage Examples:

import { expectTypeOf } from "expect-type";

// Basic array items
expectTypeOf([1, 2, 3]).items.toBeNumber();
expectTypeOf(["a", "b", "c"]).items.toBeString();

// Mixed type arrays
expectTypeOf([1, "two", true]).items.toEqualTypeOf<number | string | boolean>();

// Readonly arrays
expectTypeOf<readonly string[]>().items.toBeString();

// Tuple items (union of all types)
expectTypeOf<[string, number, boolean]>().items.toEqualTypeOf<string | number | boolean>();

// Nested array items
expectTypeOf([[1, 2], [3, 4]]).items.items.toBeNumber();

// ArrayLike structures
interface ArrayLike<T> {
  readonly length: number;
  readonly [n: number]: T;
}

declare const arrayLike: ArrayLike<string>;
expectTypeOf(arrayLike).items.toBeString();

// String as ArrayLike<string>
expectTypeOf("hello").items.toBeString();

// TypedArrays
expectTypeOf(new Int32Array([1, 2, 3])).items.toBeNumber();
expectTypeOf(new Uint8Array([1, 2, 3])).items.toBeNumber();

Type Guard Analysis

Analyze type guard and assertion function behavior.

/**
 * Extract the type guarded by a type guard function
 * Works with functions returning `v is T`
 */
guards: ExpectTypeOf<GuardedType, Options>; // where function returns `v is GuardedType`

/**
 * Extract the type asserted by an assertion function  
 * Works with functions using `asserts v is T`
 */
asserts: ExpectTypeOf<AssertedType, Options>; // where function uses `asserts v is AssertedType`

Usage Examples:

import { expectTypeOf } from "expect-type";

// Basic type guards
function isString(value: unknown): value is string {
  return typeof value === "string";
}

function isNumber(value: unknown): value is number {
  return typeof value === "number";
}

expectTypeOf(isString).guards.toBeString();
expectTypeOf(isNumber).guards.toBeNumber();

// Complex type guards
interface User {
  name: string;
  age: number;
}

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    typeof (value as any).name === "string" &&
    typeof (value as any).age === "number"
  );
}

expectTypeOf(isUser).guards.toEqualTypeOf<User>();

// Generic type guards
function isArray<T>(value: unknown, itemGuard: (item: unknown) => item is T): value is T[] {
  return Array.isArray(value) && value.every(itemGuard);
}

expectTypeOf(isArray)
  .map(fn => fn([], isString))
  .guards.toEqualTypeOf<string[]>();

// Assertion functions
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== "string") {
    throw new TypeError("Expected string");
  }
}

expectTypeOf(assertIsString).asserts.toBeString();

// Complex assertions
function assertIsUser(value: unknown): asserts value is User {
  if (!isUser(value)) {
    throw new TypeError("Expected User object");
  }
}

expectTypeOf(assertIsUser).asserts.toEqualTypeOf<User>();

Instance Type Analysis

Extract instance types from constructor functions and classes.

/**
 * Extract the instance type of a constructor
 * Only available when Actual is a constructor function
 */
instance: ExpectTypeOf<InstanceType<Actual>, Options>; // where Actual is constructor

Usage Examples:

import { expectTypeOf } from "expect-type";

// Built-in constructors
expectTypeOf(Date).instance.toHaveProperty("toISOString");
expectTypeOf(Date).instance.toHaveProperty("getTime");
expectTypeOf(Array).instance.toHaveProperty("push");
expectTypeOf(Array).instance.toHaveProperty("length");

// Custom classes
class User {
  constructor(public name: string, public age: number) {}
  
  greet(): string {
    return `Hello, I'm ${this.name}`;
  }
}

expectTypeOf(User).instance.toHaveProperty("name").toBeString();
expectTypeOf(User).instance.toHaveProperty("age").toBeNumber();
expectTypeOf(User).instance.toHaveProperty("greet").returns.toBeString();

// Generic classes
class Container<T> {
  constructor(public value: T) {}
  
  getValue(): T {
    return this.value;
  }
}

declare const StringContainer: typeof Container<string>;
expectTypeOf(StringContainer).instance.toHaveProperty("value").toBeString();
expectTypeOf(StringContainer).instance.toHaveProperty("getValue").returns.toBeString();

// Abstract classes
abstract class Shape {
  abstract area(): number;
  
  describe(): string {
    return `Area: ${this.area()}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }
  
  area(): number {
    return Math.PI * this.radius ** 2;
  }
}

expectTypeOf(Circle).instance.toHaveProperty("area").returns.toBeNumber();
expectTypeOf(Circle).instance.toHaveProperty("describe").returns.toBeString();

Complex Type Combinations

Combine multiple advanced type operations for sophisticated type testing.

import { expectTypeOf } from "expect-type";

// Complex API response handling
type ApiResponse<T> = 
  | { success: true; data: T }
  | { success: false; error: string }
  | null;

type UserData = { id: number; name: string };

// Extract successful response data
expectTypeOf<ApiResponse<UserData>>()
  .exclude<null>()
  .extract<{ success: true; data: any }>()
  .toHaveProperty("data")
  .toEqualTypeOf<UserData>();

// Complex generic transformation
type AsyncResult<T> = Promise<T | { error: string }>;

declare function fetchWithRetry<T>(
  fetcher: () => Promise<T>
): AsyncResult<T>;

expectTypeOf(fetchWithRetry)
  .map(fn => fn(() => Promise.resolve({ user: "test" })))
  .resolves
  .exclude<{ error: string }>()
  .toEqualTypeOf<{ user: string }>();

// Nested container types
type Container<T> = {
  items: T[];
  metadata: {
    count: number;
    hasMore: boolean;
  };
};

type AsyncContainer<T> = Promise<Container<T>>;

expectTypeOf<AsyncContainer<string>>()
  .resolves
  .toHaveProperty("items")
  .items
  .toBeString();

expectTypeOf<AsyncContainer<User>>()
  .resolves
  .toHaveProperty("metadata")
  .toHaveProperty("count")
  .toBeNumber();

Limitations

  1. Performance: Complex type operations like .branded can cause TypeScript to slow down or give up on very deep types.

  2. Type Depth: Very deeply nested type transformations may hit TypeScript's recursion limits.

  3. Generic Resolution: Some advanced operations may not work perfectly with unresolved generic types.

  4. Runtime Safety: These operations are purely compile-time - they provide no runtime type checking or validation.

docs

advanced-types.md

basic-types.md

functions.md

index.md

objects.md

type-equality.md

tile.json