👷‍♂️

ArkRegex: TypeScriptで型安全な正規表現を実現する

に公開

はじめに

正規表現のキャプチャグループ名をタイポしても、実行時まで気づけない...そんな経験はありませんか?

ArkRegexは、正規表現のキャプチャグループに対してTypeScriptの型推論を提供するライブラリです。これにより、エディタ上でのコード補完やコンパイル時の型チェックが可能になります。

インストール

npm install arkregex

基本的な使い方

import { regex } from "arkregex"

// パターンを定義
const pattern = regex("^(?<year>\\d{4})-(?<month>\\d{2})$")

// exec()でマッチング
const match = pattern.exec("2024-12")

if (match?.groups) {
  console.log(match.groups.year)   // "2024" - 補完が効く!
  console.log(match.groups.month)  // "12"

  // タイポは型エラーになる
  // match.groups.yesr   // ❌ Property 'yesr' does not exist
  // match.groups.day    // ❌ Property 'day' does not exist
}

重要: exec() vs match() の違い

ArkRegexの型推論を活用するには、exec()を使用する必要があります

exec() - 型推論あり

const pattern = regex("^(?<year>\\d{4})-(?<month>\\d{2})$")
const execResult = pattern.exec("2024-12")

if (execResult?.groups) {
  const { year, month } = execResult.groups  // 型安全な分割代入

  // 以下は型エラーになる
  // execResult.groups.yesr   // ❌
  // execResult.groups.day    // ❌
}

match() - 従来通りのRegExpとして使用可能(型推論なし)

ArkRegexは従来のRegExpと同じようにStringのmatch()メソッドでの使用が可能です。
しかし戻り値が通常のRegExpMatchArrayとなり、ArkRegexの型情報は失われます。

const matchResult = "2024-12".match(pattern)

if (matchResult?.groups) {
  // 従来のRegExpと同じように使用できるが、型推論は効かない
  matchResult.groups.yesr   // エラーにならない
  matchResult.groups.day    // エラーにならない
}

実践的なパターン例

日本の郵便番号

const zipCode = regex("^(?<zip1>\\d{3})-(?<zip2>\\d{4})$")
const match = zipCode.exec("150-0002")

if (match?.groups) {
  console.log(`${match.groups.zip1}-${match.groups.zip2}`)
}

メールアドレス

const email = regex("^(?<username>[a-zA-Z0-9._-]+)@(?<domain>[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})$")
const match = email.exec("user@example.com")

if (match?.groups) {
  console.log(`Username: ${match.groups.username}`)
  console.log(`Domain: ${match.groups.domain}`)
}

URL

const url = regex("^(?<protocol>https?)://(?<host>[^/]+)(?<path>/.*)?$")
const match = url.exec("https://api.example.com/v1/users")

if (match?.groups) {
  console.log(`Protocol: ${match.groups.protocol}`)  // "https"
  console.log(`Host: ${match.groups.host}`)          // "api.example.com"
  console.log(`Path: ${match.groups.path}`)          // "/v1/users"
}

セマンティックバージョニング

const semver = regex("^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)$")
const match = semver.exec("2.1.25")

if (match?.groups) {
  const { major, minor, patch } = match.groups
  console.log(`v${major}.${minor}.${patch}`)
}

ArkTypeとの統合

ArkRegexはArkTypeのスキーマに組み込むことができます。

import { ArkErrors, type } from "arktype"
import { regex } from "arkregex"

const emailPattern = regex("^(?<username>[a-zA-Z0-9._-]+)@(?<domain>[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})$")

const userSchema = type({
  name: "string",
  email: emailPattern,
  age: "number>=0"
})

const result = userSchema({
  name: "太郎",
  email: "taro@example.com",
  age: 25
})

const error = userSchema({
  name: "花子",
  email: "invalid-email",
  age: 5
})

console.log(result);
// { name: '太郎', email: 'taro@example.com', age: 25 }
console.log(error);
// ArkErrors(1)

// instanceof ArkErrorsでErrorかの判別が可能!
if (error instanceof ArkErrors) {
  // summaryでエラーの内容の確認ができる
  console.log(error.summary);
  // email must be matched by ^(?<username>[a-zA-Z0-9._-]+)@(?<domain>[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$ (was "invalid-email")
}

パフォーマンス

ArkRegexはゼロランタイムオーバーヘッドを実現しています。

ベンチマーク条件: 100,000件 × 10回の平均

ライブラリ 平均
ArkRegex 6.41ms
RegExp 6.12ms

約5%の差(0.29ms)で、実用上は無視できるレベルです。

まとめ

  • ArkRegexは型安全な正規表現を提供

    • キャプチャグループ名の補完
    • タイポのコンパイル時検出
  • exec()を使用する

    • pattern.exec(string) の形で型推論が機能
    • string.match(pattern) では従来のRegExpとして使用可能(型推論なし)
  • ArkTypeとの統合

    • スキーマバリデーションにArkRegexパターンを組み込み可能
    • 正規表現とオブジェクト構造の両方を型安全にバリデーション
  • ゼロランタイムオーバーヘッド

    • 型チェックはコンパイル時のみ
    • 実行時パフォーマンスは通常のRegExpと同等

参考リンク

株式会社ソニックムーブ

Discussion