// @ts-ignore import { Pattern } from './types/Pattern'; // @ts-ignore import { Match } from './types/Match'; // @ts-ignore import * as symbols from './internals/symbols'; // @ts-ignore import { matchPattern } from './internals/helpers'; // @ts-ignore type MatchState = | { matched: true; value: output } | { matched: false; value: undefined }; const unmatched: MatchState = { matched: false, value: undefined, }; /** * `match` creates a **pattern matching expression**. * * Use `.with(pattern, handler)` to pattern match on the input. * * Use `.exhaustive()` or `.otherwise(() => defaultValue)` to end the expression and get the result. * * [Read the documentation for `match` on GitHub](https://github.com/gvergnaud/ts-pattern#match) * * @example * declare let input: "A" | "B"; * * return match(input) * .with("A", () => "It's an A!") * .with("B", () => "It's a B!") * .exhaustive(); * */ export function match( value: input ): Match { return new MatchExpression(value, unmatched) as any; } /** * This class represents a match expression. It follows the * builder pattern, we chain methods to add features to the expression * until we call `.exhaustive`, `.otherwise` or the unsafe `.run` * method to execute it. * * The types of this class aren't public, the public type definition * can be found in src/types/Match.ts. */ class MatchExpression { constructor(private input: input, private state: MatchState) {} with(...args: any[]): MatchExpression { if (this.state.matched) return this; const handler: (selection: unknown, value: input) => output = args[args.length - 1]; const patterns: Pattern[] = [args[0]]; let predicate: ((value: input) => unknown) | undefined = undefined; if (args.length === 3 && typeof args[1] === 'function') { // case with guard as second argument patterns.push(args[0]); predicate = args[1]; } else if (args.length > 2) { // case with several patterns patterns.push(...args.slice(1, args.length - 1)); } let hasSelections = false; let selected: Record = {}; const select = (key: string, value: unknown) => { hasSelections = true; selected[key] = value; }; const matched = patterns.some((pattern) => matchPattern(pattern, this.input, select)) && (predicate ? Boolean(predicate(this.input)) : true); const selections = hasSelections ? symbols.anonymousSelectKey in selected ? selected[symbols.anonymousSelectKey] : selected : this.input; const state = matched ? { matched: true as const, value: handler(selections, this.input), } : unmatched; return new MatchExpression(this.input, state); } when( predicate: (value: input) => unknown, handler: (selection: input, value: input) => output ): MatchExpression { if (this.state.matched) return this; const matched = Boolean(predicate(this.input)); return new MatchExpression( this.input, matched ? { matched: true, value: handler(this.input, this.input) } : unmatched ); } otherwise(handler: (value: input) => output): output { if (this.state.matched) return this.state.value; return handler(this.input); } exhaustive(): output { return this.run(); } run(): output { if (this.state.matched) return this.state.value; let displayedValue; try { displayedValue = JSON.stringify(this.input); } catch (e) { displayedValue = this.input; } throw new Error( `Pattern matching error: no pattern matches value ${displayedValue}` ); } returnType() { return this; } }