All files primitive.ts

100% Statements 75/75
100% Branches 8/8
100% Functions 3/3
100% Lines 75/75

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 761x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 15x 30x 30x 30x 1x 1x 1x 30x 30x 30x 1x 1x 1x 1x 1x 1x  
import type { Exact, ValueOf } from "type-fest";
 
type UnaryFn<T, R> = (shape: T) => R;
 
// `Extract` drops optional handlers (`undefined`) so `PatternReturns` also works
// when `P`'s constraint has optional keys.
type PatternReturns<P> = ReturnType<
    Extract<ValueOf<P>, (...args: never[]) => unknown>
>;
 
type Handlers<T extends string | number, R> = { [K in T]: UnaryFn<K, R> };
 
// The fallback is a *second argument*, not a property of the handler map,
// because its parameter is the remainder `Exclude<T, keyof Handled>` and TypeScript
// fixes a property's contextual type before it infers its sibling keys. A later
// argument, by contrast, is contextually typed from inference on an earlier
// one, so the split is what makes the remainder expressible at all.
// See development/library.md.
type Fallback<T extends string | number, Handled, R> = UnaryFn<
    Exclude<T, keyof Handled>,
    R
>;
 
// TypeScript does not apply the excess-property check to a generic constraint,
// so `Exact` restores it for the generic forms: a handler map can otherwise
// carry keys outside `T`.
 
// oxlint-disable typescript/unified-signatures
// Strict returns: one common `R`. Overload order is load-bearing:
//   #1 Handlers (first) -> the exhaustive form and the autocomplete popup
//   #2 Fallback (last)  -> accepts a partial handler map plus a fallback
interface MatcherStrict<T extends string | number> {
    <R>(handlers: Handlers<T, R>): UnaryFn<T, R>;
    <R, Handled extends Exact<Partial<Handlers<T, R>>, Handled>>(
        handlers: Handled & Partial<Handlers<T, R>>,
        fallback: Fallback<T, Handled, R>,
    ): UnaryFn<T, R>;
}
// oxlint-enable typescript/unified-signatures
 
// Widened returns: the union of every handler's return type. `P` is inferred
// from the whole handler map, whose closed constraint supplies the
// contextual/autocomplete type.
// oxlint-disable typescript/unified-signatures
interface MatcherWidening<T extends string | number> {
    <P extends Exact<Handlers<T, unknown>, P>>(
        handlers: P,
    ): UnaryFn<T, PatternReturns<P>>;
    <R, Handled extends Exact<Partial<Handlers<T, unknown>>, Handled>>(
        handlers: Handled,
        fallback: Fallback<T, Handled, R>,
    ): UnaryFn<T, PatternReturns<Handled> | R>;
}
// oxlint-enable typescript/unified-signatures
 
type HandlerMap = Record<string | number, UnaryFn<never, unknown> | undefined>;
 
const dispatch =
    (handlers: HandlerMap, fallback?: UnaryFn<never, unknown>) =>
    (shape: string | number): unknown =>
        (
            handlers[shape] ??
            fallback ??
            (() => {
                throw new Error(`Unhandled shape: ${shape}`);
            })
        )(
            // oxlint-disable-next-line typescript/no-unsafe-type-assertion
            shape as never,
        );
 
export const getMatcher = <T extends string | number>(): MatcherStrict<T> =>
    dispatch;
export const getMatcherW = <T extends string | number>(): MatcherWidening<T> =>
    dispatch;