TypeScript

ArkRegexとは?TypeScriptの正規表現に型を付ける使い方と制約

ArkRegexとは?TypeScriptの正規表現に型を付ける使い方と制約

ArkRegex(npmパッケージ名 arkregex)は、new RegExp() の代わりに呼ぶだけで、パターン文字列からマッチ結果とキャプチャグループの型を推論するTypeScriptライブラリです。ランタイムの中身は new RegExp(src, flags) を返す1行だけで、型推論はすべてコンパイル時に行われます。

この記事では、arkregex 0.0.12 を TypeScript 7.0.2・5.9.3・5.4.5 で実際にコンパイルし、推論される型、型エラーになる書き方、型チェックにかかる時間を測った結果をもとに、導入してよい場面と見送る場面を整理します。標準の RegExp で型がどこまで付くかも先に確認します。

まとめ:ArkRegexの要点

  • 何をするか:regex("^(?<y>\\d{4})$") と書くと exec() の groups.y が `${number}` 型になり、存在しないグループ名の参照はコンパイルエラーになります。
  • 実行時コスト:戻り値は本物の RegExp です(instanceof RegExp が true)。照合の速度は標準の正規表現と変わりません。
  • 型が乗るのは test() と exec() だけ:String.prototype.match・matchAll・replace に渡すと従来の型に戻ります。
  • 書き方の制約:フラグはアルファベット順("gi" は可、"ig" は型エラー)。\/ のエスケープも型エラーです。
  • 動的なパターンには使えない:string 型の変数を渡すと型エラーになり、regex.as で型を手書きする必要があります。
  • 推論の上限:選択肢8個のグループを5つ並べると TS2589(excessively deep)で型が any になり、型チェックに15.6秒かかりました。
  • 版:0.0.12(2026-10-01)。MITライセンスで、ArkTypeの依存パッケージでもありますが、0.x のため推論される型は版で変わります。

TypeScript標準のRegExpで型が付かない範囲

TypeScriptの lib.es2018 以降の定義では、RegExpExecArray の groups は { [key: string]: string } | undefined です。名前付きグループを書いても、その名前は型に反映されません。TypeScript 7.0.2 で次のコードの宣言ファイルを出力しました。

const m = /^(?<y>\d{4})-(?<mo>\d{2})$/.exec("2026-10")
export const y = m?.groups?.y        // string | undefined
export const typo = m?.groups?.yaer  // string | undefined(エラーにならない)
export const t = /^\d+$/.test("1")   // boolean

グループ名を yaer と打ち間違えてもコンパイルは通り、実行時に undefined が返るだけです。noUncheckedIndexedAccess を有効にしても結果は同じでした。test() も boolean を返すだけで、引数の型を絞り込みません。ArkRegexが埋めるのはこの部分です。正規表現そのものの記法は正規表現とは?基本記号の読み方と言語ごとの方言・ReDoS対策を実装目線で解説で扱っています。

ArkRegexの中身:regex関数と配布物

ランタイム実装:new RegExpへの委譲

npmで配布されている out/regex.js の実装は次の2行です(0.0.12)。

export const regex = ((src, flags) => new RegExp(src, flags));
Object.assign(regex, { as: regex });

パターンの解析・型の計算はすべて .d.ts の型定義側で行われ、実行時には何もしません。公式READMEの「Zero Runtime」はこの意味です。regex.as も同じ関数なので、手書きした型が正しいかどうかは実行時に一切確かめられません。Node.js 26.5.0 で regex(...) instanceof RegExp は true、regex.as === regex も true でした。

版・ライセンス・ダウンロード数

項目 内容(2026-10-07時点)
初版 0.0.1(npm公開 2025-10-28)
最新版 0.0.12(2026-10-01)
ライセンス MIT
リポジトリ arktypeio/arktype の ark/regex
依存 @ark/util 0.56.6
週間DL 2,314,430(2026-09-28〜10-04・npm downloads API)

ArkType 2.2.7 は arkregex を依存に持つため、ダウンロード数には依存解決による取得も含まれ、単体で選ばれた数としては読めません。arkregexの変更履歴を見ると、0.0.4〜0.0.6 の3回で \d の推論結果が `${bigint}` から `${number}` へ、さらに量指定子の最小が0のとき "" を含む形へ変わっています。型をスナップショットで固定しているテストは版を上げると壊れるので、バージョンは固定して使います。

インストールと推論される型(実測)

インストールはパッケージマネージャーのいずれかで行います。

npm install --save-exact [email protected]
pnpm add --save-exact [email protected]
yarn add --exact [email protected]

次の表は、各パターンを regex() に渡して TypeScript 7.0.2 で宣言ファイルを出力した結果です。

パターン 推論されたマッチ型 captures
^ok$(i) "OK" | "Ok" | "oK" | "ok" なし
^(\d{4})-(\d{2})-(\d{2})$ `${number}-${number}-${number}` `${number}`×3
^\d{3}-\d{4}$ `${number}-${number}` なし
^(get|post)$ "get" | "post" ["get"] | ["post"]
^(foo)?bar$ "bar" | "foobar" [undefined] | ["foo"]
^0[789]0-\d{4}-\d{4}$ `070-${number}-${number}` ほか2通り なし
^[a-z]+$ string なし

選択肢(|)や大文字小文字は列挙され、[a-z] のような文字範囲は string に丸められます。READMEは理由を「文字範囲をリテラルに展開すると組み合わせが爆発するため、型は不正確になり得ても誤りにはしない」と説明しています。

READMEの例と実際の推論のずれ

READMEのsemverの例 regex("^(\\d*)\\.(\\d*)\\.(\\d*)$") には `${number}.${number}.${number}` とコメントが付いていますが、0.0.12の実際の推論は ".." や `${number}..` を含む8通りのユニオンで、各キャプチャは "" | `${number}` です。0.0.6で「* はゼロ回を許すので "" を含める」修正が入ったためで、正規表現としてはこちらが正確です(/^(\d*)\.(\d*)\.(\d*)$/.test("..") は実際に true を返します)。空のバージョン番号を許したくないなら、\d* を \d+ に直すのが先です。

推論型の精度と実行時検証との違い

`${number}` は "1.5" や "-3" も含む型です。^\d{3}-\d{4}$ の型 `${number}-${number}` は、桁数も符号も表現していません。実行時の test("1.5-2") は false ですが、型の上では "1.5-2" を代入できます。ArkRegexの型は「マッチした文字列の形のおおまかな上限」であり、値を検証済みであることを示す型(ブランド型)ではありません。

test()とexec()の型付けと、型が乗らないメソッド

test()は型ガード、exec()はキャプチャに型

型定義では test(s: string): s is pattern となっており、if の中で引数が推論型へ絞り込まれます。exec() は位置キャプチャと groups に型が付き、groups の型は undefined を含みません。ただし、名前付きグループがない場合の実行時の値は undefined で、0.0.12の型定義とは一致しません。

import { regex } from "arkregex"

const ymd = regex("^(?<y>\\d{4})-(?<m>\\d{2})-(?<d>\\d{2})$")

export function parse(s: string) {
  if (ymd.test(s)) {
    const t: `${number}-${number}-${number}` = s  // 絞り込まれているので代入できる
  }
  const m = ymd.exec(s)
  if (m) {
    m.groups.y      // `${number}`
    m.groups.nope   // error TS2339: Property 'nope' does not exist
  }
}

match・matchAll・replaceの型推論の制約

同じ regex() の戻り値を文字列側のメソッドに渡すと、型はTypeScript標準のものになりました(7.0.2で確認)。

呼び出し 戻り値の型
r.exec("2026") RegexExecArray | null(groupsに型)
"2026".match(r) RegExpMatchArray | null
[..."a1".matchAll(r)] RegExpExecArray[]
"2026".replace(r, "$<y>") string(置換文字列の検査なし)

既存コードが str.match(re) で書かれている場合、new RegExp を regex に置き換えただけでは型の恩恵がありません。re.exec(str) の形へ書き換える必要があります。g フラグ付きで全件を取りたい箇所は exec() のループになるので、置き換えの手間はこの書き換えが大半を占めます。

型エラーとして検出される書き間違い

不正なパターンを渡すと、引数の型がエラーメッセージ文字列に置き換わり、TS2345として表示されます。0.0.12で出たメッセージは次のとおりです。

コード 型エラーのメッセージ new RegExpの実行時
regex("^(a)\\2$") Group 2 does not exist 例外なし
regex("^(?<x>a)\\k<y>$") Group y does not exist SyntaxError
regex("^(abc$") Missing ) SyntaxError
regex("a**") Quantifier * requires a preceding token SyntaxError
regex("a{1.5}") Quantifier {1.5} must use natural numbers 例外なし
regex("abc", "ig") フラグ型に不一致 例外なし

最も効くのは1行目です。存在しない番号の後方参照 \2 は、u フラグなしの new RegExp では8進エスケープとして解釈されて例外になりません。気付かないまま「マッチしない正規表現」が本番に出る種類の誤りを、ArkRegexはコンパイル時に止めます。

フラグの指定順序と同時指定の制約

フラグの型は d・g・i・m・s・u または v・y をこの順に任意で並べたテンプレートリテラル型です。そのため "gi" は通り、"ig" は型エラーになります。JavaScriptの new RegExp("abc", "ig") は問題なく動き、flags は "gi" に正規化されます。既存コードを移すときはフラグの順序も確認が必要で、"uv" の同時指定も型で拒否されます(実行時も SyntaxError)。

スラッシュのエスケープと文字クラス内の推論不具合

正規表現リテラル /a\/b/ を文字列に移して regex("a\\/b") と書くと、「Escape preceding / is unnecessary and should be removed.」という型エラーになります。new RegExp("a\\/b") は u フラグ付きでも有効ですが、文字列で渡す場合 / をエスケープする必要はないので、バックスラッシュを消せば通ります。URLのパターンを移植するときはまとめて置換してください。

なお、文字クラスの中に書いた [\\/] は型エラーにならず、推論型にこのエラーメッセージの文字列がそのまま混ざりました(0.0.12・TypeScript 7.0.2で確認)。文字クラス内の / もエスケープせずに書くのが安全です。

動的なパターンとregex.asの使いどころ

regex() は引数がリテラル型であることを前提にしています。設定ファイルやDBから読んだ string 型の値を渡すと、Argument of type 'string' is not assignable to parameter of type 'never' になります。「new RegExp() の差し替え」と説明されていますが、動的なパターンにはそのまま置き換えられません。

import { regex } from "arkregex"

const fromConfig: string = String("^pattern-(.*)$")

regex(fromConfig)  // error TS2345: ... parameter of type 'never'

// 型を手で宣言する(実行時には何も検査されない)
const r = regex.as<`pattern-${string}`, { captures: [string] }>(fromConfig)

regex.as は推論が重すぎるパターンの逃げ道としても公式READMEで案内されていますが、前述のとおり中身は regex と同じ関数です。宣言した型とパターンが食い違っても誰も検出しないので、regex.as を使う箇所には、実際の文字列で exec() の結果を確かめるテストを添えます。

型チェックの重さと推論の上限(実測)

推論は選択肢の組み合わせをユニオン型として展開するため、選択肢を含むグループを並べると型の数が掛け算で増えます。tsc --extendedDiagnostics の Check time を各1回測りました(Core i9-9880H・メモリ16GB・macOS 26.6.2、strict: true・skipLibCheck: true、対象ごとに別ファイル)。

対象 TS 7.0.2 TS 5.9.3 結果
メール・URL・電話・日付の5パターン 0.14秒 0.62秒 推論できる
8択グループ×4(4,096通り) 4.61秒 13.17秒 推論できる
8択グループ×5(32,768通り) 15.57秒 34.00秒 TS2589・型はany
^([a-h])([a-h])([a-h])([a-h])([a-h])$ 0.03秒 0.29秒 string・captures 5個

5段の例では「Type instantiation is excessively deep and possibly infinite」が出て、その定数の型は any になりました。メモリ使用量はTS 7.0.2で約696MB、TS 5.9.3で約1.39GBです。この表は正規表現1つだけを含むファイルを個別に型チェックした時間で、実際のプロジェクトのビルド時間やエディタの補完の応答は測っていませんが、同じ型計算がプロジェクトの型チェックとエディタの言語サーバーでも走ります。

対処は2通りです。選択肢の列挙が型として不要なら、(a|b|c|d|e|f|g|h) を文字クラスに書き換えます。[a-h] だけにするとキャプチャが消えて後続のグループ番号や後方参照がずれるので、番号を保つなら ([a-h]) と括弧ごと残します。表の最後の行がこの形で、型は string に丸まりますが、キャプチャ5個の型は保たれ、型チェックは0.03秒で終わりました。列挙した型が必要なら、regex.as で型を手書きします。

TypeScriptの推奨版と検証版による性能差

arkregexの公式READMEは「Performs best with TS 5.9+」とだけ書いており、最低バージョンは明記していません。手元では TypeScript 5.4.5 でも、上の推論表と同じ型が出ました(skipLibCheck: true)。上の表では、Goで書き直された TypeScript 7.0.2 の型チェック時間が、8択グループを並べた2例で5.9.3の約2.2〜2.9分の1でした。重いパターンを抱えるなら7.0系の方が影響は小さく済みます。

ArkTypeのスキーマで正規表現を使う(x接頭辞)

ArkTypeの変更履歴によると、型定義に書いた正規表現リテラルの型推論は2.1.23(2025-10-14)で入り、2.1.24(2025-10-28)でarkregex 0.0.1による推論へ切り替わりました。リテラルの前に x を付ける記法は2.1.28(2025-12-03)で追加され、2.2.0(2026-03-04)でこれらが正式に告知されています。x を付けると検証後の値が exec() の結果に変わり、groups に型が付きます。ArkType 2.2.7で実行した結果です。

import { type } from "arktype"

const User = type({
  birthday: "x/^(?<month>\\d{2})-(?<day>\\d{2})-(?<year>\\d{4})$/",
  code: "/^[A-Z]{3}$/"
})

const data = User.assert({ birthday: "05-21-1993", code: "ABC" })
data.birthday.groups  // { day: `${number}`; month: `${number}`; year: `${number}` }
// 実行結果: {"month":"05","day":"21","year":"1993"}

const bad = User({ birthday: "1993-05-21", code: "abc" })
// birthday must be matched by ^(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})$ (was "1993-05-21")
// code must be matched by ^[A-Z]{3}$ (was "abc")

入力値の検証とキャプチャの取り出しを1か所で済ませたいなら、arkregex単体よりこちらが向いています。ArkType 2.2.5 では、正規表現リテラル内の \\ が1個の \ に潰されていた挙動が修正されています。2.2.4以前で "/\\\\d/" と書いて数字に一致させていた定義は、2.2.5以降は「バックスラッシュ+d」に一致する意味に変わるので、更新時に確認が必要です。ArkType本体の使い方はArkTypeとは?TypeScript実行時型検証の使い方とZodとの違いを参照してください。

ArkRegexの導入を見送るべき場面

型が付く恩恵は、パターンがソースコードに直書きされ、exec() でキャプチャを取り出している箇所に集中します。次に当てはまるなら、導入しても得るものは少なくなります。

  • パターンの大半が外部から来る:全箇所が regex.as になり、型は手書きの宣言と変わりません。
  • 使い方が test() だけで、キャプチャを取らない:得られるのは型ガードと構文チェックだけです。構文ミスはESLintの no-invalid-regexp でも検出できます。
  • 入力検証が目的:ArkRegexの型は「形のおおまかな上限」で、値の検証は実行時に行う必要があります。フォームやAPIの入力は、ArkTypeのスキーマやZodの使い方入門|npmでのインストールとz.infer・safeParse・Zod 4.6の変更点で扱うバリデーションライブラリに任せます。
  • ReDoSの対策を期待している:regex("^(a+)+$") は警告なく `a${string}${string}` に推論されました。バックトラック爆発は検出対象外です。対策はReDoSとは?正規表現のバックトラック爆発を6言語7エンジンで実測し対策を判断するで検出ツールの実力差とともに扱っています。

逆に、ログ行・ファイル名・バージョン文字列のように、決まった形式の文字列から名前付きグループで値を取り出すコードが多いなら、導入する価値は大きくなります。グループ名の打ち間違いと存在しないグループの参照が、レビューではなくコンパイラで止まるからです。

よくある質問

ArkRegexとは何ですか?

TypeScriptのバリデーションライブラリArkTypeの開発元が公開している、new RegExp() に型を付けるライブラリです。パターン文字列からマッチ結果とキャプチャグループの型を推論し、実行時は new RegExp() と同じ動作をします。

TypeScriptで正規表現の名前付きキャプチャに型を付けるには?

標準の RegExp では groups が { [key: string]: string } | undefined になり、グループ名は型に反映されません。regex() で作った正規表現の exec() を使うと、groups の各名前に型が付き、存在しない名前の参照はコンパイルエラーになります。str.match() 経由では型が付かない点に注意してください。

TypeScriptのバージョンはいくつ以上が必要ですか?

公式READMEはTypeScript 5.9以上を推奨していますが、最低動作版は明記していません。5.4.5でも基本的な推論は動きました。8択グループを並べた重いパターンでは、TypeScript 7.0.2の型チェック時間が5.9.3の約2.2〜2.9分の1でした。

ArkTypeを入れなくてもArkRegexだけで使えますか?

使えます。arkregex は単独のnpmパッケージで、ランタイムの依存は @ark/util だけです。ArkTypeを使っている場合は、2.1.24以降のスキーマ内の正規表現リテラルでも同じ推論が働きます。

VS Codeでregex()のパターンをシンタックスハイライトできますか?

公式READMEは、ArkTypeのVS Code拡張(arktypeio.arkdark)を入れると regex の呼び出しにシンタックスハイライトが付くと案内しています。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.27 テックブログ Brotli 脆弱性:CVE-2026-111497の影響確認とNode.js・Python・.NETの更新手順
  2. 2025.02.27 コラム トラッキングビーコン(Webビーコン)とは?仕組み・用途・確認と対策を徹底解説
  3. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処
  4. 2026.10.23 テックブログ Renta!はいつ再開?不正アクセスでメールアドレス等が流出したおそれ・再開時のパスワード再設定と運営側の対応
  5. 2026.07.09 テックブログ Muse Imageとは?Metaの画像生成AIの使い方・API料金(1枚0.01ドル)と日本での提供状況【2026年10月】

RELATED POSTS 関連記事

目次