メモ

こんなtsファイルがあるとどう動くのか?というのを解説したい

import { match, P } from 'ts-pattern';

type Input = {
    text: string
}
  
function validateInput(input: Input) {
    return match(input.text)
      .with(P.not(P.string.minLength(10)), () => [false, "textは10文字より大きくしてください。"])
      .with(P.not(P.string.maxLength(100)), () => [false, "textは100文字より小さくしてください。"])
      .with(P.not(P.string.regex(/^https?:\\/\\//)), () => [false, 'url形式にしてください。'])
      .otherwise(() => [true, undefined]);
}

console.log(validateInput({
    text: "<https://example.com/aaaaaaa>"
}))

動きとしては

match(input.text)は内部のMatchExpressionを返している

export function match<const input, output = symbols.unset>(
  value: input
): Match<input, output> {
  return new MatchExpression(value, unmatched) as any;
}

P.string.minLength(10) をconsole.logして実態を見てみると、[Symbol(@ts-pattern/matcher)]な関数などを持っているobjectになっている

{
  [Symbol(@ts-pattern/matcher)]: [Function],
  optional: [Function: optional],
  and: [Function: and],
  or: [Function: or],
  select: [Function: select],
  startsWith: [Function: startsWith],
  endsWith: [Function: endsWith],
  minLength: [Function: minLength],
  maxLength: [Function: maxLength],
  includes: [Function: includes],
  regex: [Function: regex],
}

withの実装は以下

  function with(...args: any[]): MatchExpression<input, output> {
    if (this.state.matched) return this;

    const handler: (selection: unknown, value: input) => output =
      args[args.length - 1];

    const patterns: Pattern<input>[] = [args[0]];
    let predicate: ((value: input) => unknown) | undefined = undefined;

    if (args.length === 3 && typeof args[1] === 'function') {
      // case with guard as second argument
      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<string, unknown> = {};
    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);
  }