🔤

TSで正規表現を型安全に扱う「ArkRegex」を試してみた

に公開

はじめに

最近 X で話題になっていた ArkRegex というものを触ってみたので紹介します!

https://x.com/arktypeio/status/1983210635266498649

https://arktype.io/docs/blog/arkregex

ArkRegex とは

ArkRegex は、公式によるとnew RegExp()の型安全なドロップインリプレースであると明記されています。

普段new RegExp()を使っているところを ArkRegex に置き換えると:

  • 正規表現パターンから文字列の型が推論される
  • 構文エラーが型エラーとして検出される
  • .test()で入力文字列の妥当性チェックができる
  • .exec()のキャプチャグループが型安全になる

インストール

pnpm install arkregex

使ってみる

例として、ユーザー ID のバリデーション(英小文字+数字、3〜12 文字)を作ってみる。

import { regex } from "arkregex";

const userId = regex("^[a-z0-9]{3,12}$");

console.log(userId.test("abc123")); // true
console.log(userId.test("ABC")); // false(大文字はNG)

使い方自体は普通のRegExpと全く同じですが、IDE でマウスホバーしてみると型情報が付いています。

ArkRegexの型推論

通常のRegExpRegExp型ですが、ArkRegex はRegex<string, {}>という型になり、より詳細な型情報を持ちます。

型推論がすごい

大文字小文字の組み合わせを全部推論

例えば、iフラグ(大文字小文字を区別しない)を使った場合、こんなことが起きます。

const ok = regex("^ok$", "i");

IDE でこのokの型を見ると:

Regex<"ok" | "oK" | "Ok" | "OK", { flags: "i" }>;

おぉ、大文字小文字の全組み合わせがユニオン型として推論されてる。

オプショナルなパターンも

電話番号のパターンで、ハイフンがあってもなくても OK にしたい場合:

const phone = regex("^0\\d{1,4}-?\\d{1,4}-?\\d{4}$");

この型を見ると:

Regex<
  | `0${bigint}${bigint}${bigint}` // ハイフンなし
  | `0${bigint}-${bigint}${bigint}` // 1つ目だけ
  | `0${bigint}${bigint}-${bigint}` // 2つ目だけ
  | `0${bigint}-${bigint}-${bigint}`, // 両方あり
  {}
>;

ハイフンが 2 箇所で-?になっているから、2² = 4 パターン全部が型に表現されています。
もちろん実際に動かしても期待通りになります。

console.log(phone.test("090-1234-5678")); // true
console.log(phone.test("09012345678")); // true
console.log(phone.test("090-12345678")); // true

キャプチャグループが型安全に

位置指定キャプチャグループ

セマンティックバージョニングのパターンを書いてみる:

const semver = regex("^(\\d+)\\.(\\d+)\\.(\\d+)$");

const match = semver.exec("2.1.25");
if (match) {
  console.log(match[0]); // "2.1.25"
  console.log(match[1]); // "2"
  console.log(match[2]); // "1"
  console.log(match[3]); // "25"
}

これだけなら普通なんですが、match[0]の型を見ると`${bigint}.${bigint}.${bigint}`になっています。
つまり、数字.数字.数字という形式だと型レベルで分かるわけですね。

名前付きキャプチャグループ

さらに便利なのが名前付きキャプチャグループで、メールアドレスをパースしてみる:

const email = regex("^(?<name>\\w+)@(?<domain>\\w+\\.\\w+)$");

const match = email.exec("user@example.com");
if (match && match.groups) {
  console.log(match.groups.name); // "user"
  console.log(match.groups.domain); // "example.com"
}

match.groups.と打つと、IDE でnamedomainの補完が効きます。

しかも、タイプミスすると型エラーになります。

// これは型エラーになる
// console.log(match.groups.namee); // ❌ Property 'namee' does not exist

これで typo による実行時エラーを防げます。

実際に使えそうなパターン

いくつか実用的なパターンを試してみました:

郵便番号

const postalCode = regex("^(\\d{3})-(\\d{4})$");
const match = postalCode.exec("123-4567");
if (match) {
  console.log(match[1]); // "123"
  console.log(match[2]); // "4567"
}

URL

const url = regex(
  "^(?<protocol>https?)://(?<host>[\\w.-]+)(?<path>/[\\w/.-]*)?$"
);

const match = url.exec("https://arktype.io/docs/blog/arkregex");
if (match && match.groups) {
  console.log(match.groups.protocol); // "https"
  console.log(match.groups.host); // "arktype.io"
  console.log(match.groups.path); // "/docs/blog/arkregex"
}

match.groupsの型を見ると、オプショナルなpathグループの有無によって 2 つのパターンに分かれています:

{
  protocol: "http" | "https";
  host: string;
  path: `/${string}`;
} | {
  protocol: "http" | "https";
  host: string;
  path: undefined;
}

このように、オプショナルなキャプチャグループもちゃんとundefinedの可能性を含んだ型として推論されます。

ハッシュタグの抽出

gフラグを使った例も試してみる:

const hashtag = regex("#(\\w+)", "g");
const text =
  "I love TypeScript! #typescript #arktype #arkregex and type safety!";
const tags = text.match(hashtag);

console.log(tags);
// ["#typescript", "#arktype", "#arkregex"]

ちゃんと全部拾えていますね 👍

日付

const datePattern = regex("^(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})$");
const match = datePattern.exec("2025-10-30");

if (match && match.groups) {
  console.log(match.groups.year); // "2025"
  console.log(match.groups.month); // "10"
  console.log(match.groups.day); // "30"
}

まとめ

個人的には、名前付きキャプチャグループの補完が効くのが一番嬉しいですね。
今まで"これ何だっけ?"ってなってソースを見に行くことが多かったので。

正規表現を多用するプロジェクトで、型安全性を高めたい方にはおすすめできると思います。

気になった方はぜひ試してみてください!

初めての技術記事なので、内容に間違いや補足がありましたら、コメントで教えていただけると嬉しいです 🙏

GitHubで編集を提案

Discussion