All files pattern.ts

52.38% Statements 55/105
71.42% Branches 5/7
44.44% Functions 4/9
52.38% Lines 55/105

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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 1061x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 6x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x     2x           2x 1x 1x 1x 1x       1x 1x       1x 1x     1x 1x 1x                                     1x 1x                                   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
/**
 * Pattern matching primitives. Each constructor returns a lightweight
 * matcher object whose `matches` method returns a type guard.
 */
 
export interface Matcher<T> {
    readonly matches: (value: unknown) => value is T;
}
 
const literalMatcher = <
    const L extends string | number | boolean | null | undefined,
>(
    value: L,
): Matcher<L> => ({
    matches: (candidate): candidate is L => candidate === value,
});
const typeMatcher = <T>(
        type:
            | "string"
            | "number"
            | "boolean"
            | "bigint"
            | "symbol"
            | "undefined"
            | "object"
            | "function",
    ): Matcher<T> => {
        const matches = (value: unknown): value is T => {
            if (type === "undefined") {
                return value === undefined;
            }
            if (type === "object") {
                return (
                    (typeof value === "object" && value !== null) ||
                    typeof value === "function"
                );
            }
            return typeof value === type;
        };
        return { matches };
    },
    whenMatcher = <T>(
        predicate: (value: unknown) => value is T,
    ): Matcher<T> => ({
        matches: predicate,
    }),
    whenMatcherAny = <T>(
        predicate: (value: unknown) => boolean,
    ): Matcher<T> => ({
        matches: (value: unknown): value is T => predicate(value),
    }),
    isNestedMatcher = (expected: unknown): expected is Matcher<unknown> =>
        typeof expected === "object" &&
        expected !== null &&
        "matches" in expected,
    // oxlint-disable-next-line typescript/no-unnecessary-type-parameters
    keysMatch = <S extends object>(shape: S, candidate: object): boolean => {
        // oxlint-disable-next-line typescript/no-unsafe-type-assertion
        for (const key of Object.keys(shape) as (keyof S)[]) {
            if (!(key in candidate)) {
                return false;
            }
            // oxlint-disable-next-line typescript/no-unsafe-type-assertion
            const expected = shape[key],
                // oxlint-disable-next-line typescript/no-unsafe-type-assertion
                actual = candidate[key as keyof object];
            if (isNestedMatcher(expected)) {
                if (!expected.matches(actual)) {
                    return false;
                }
            } else if (actual !== expected) {
                return false;
            }
        }
        return true;
    },
    structuralMatcher = <S extends object, T extends S>(
        shape: S,
        refine?: (value: S) => value is T,
    ): Matcher<T> => ({
        matches: (value: unknown): value is T => {
            if (typeof value !== "object" || value === null) {
                return false;
            }
            // oxlint-disable-next-line typescript/no-unsafe-type-assertion
            const candidate = value as S;
            if (!keysMatch(shape, candidate)) {
                return false;
            }
            if (refine && !refine(candidate)) {
                return false;
            }
            return true;
        },
    });
 
export const P = {
    literal: literalMatcher,
    type: typeMatcher,
    when: whenMatcher,
    any: whenMatcherAny,
    shape: structuralMatcher,
} as const;
 
export type Pattern<T> = Matcher<T>;