正規表現でキャプチャグループを使うときは名前を付けよう

投稿日
更新日
🈂️

はじめに

JS/TS で正規表現を扱う際、()を使ったキャプチャグループを使うことがあると思います。 キャプチャグループは大きく分けて 2 つの使われ方があり、 1 つはキャプチャした文字列を.match()などで取得したり後方参照するために使われ、 もう 1 つは|を使った論理和のために使われます。

// バージョン番号からそれぞれの数字を取得する
const matches = "v7.29.3".match(/v(\d+)\.(\d+)\.(\d+)/);
console.log(matches[1]); // => 7
console.log(matches[2]); // => 29
console.log(matches[3]); // => 3

// `Hello, world!`と`Goodbye, world!`のどちらかにマッチするかを判定する
const isMatch = /^(Hello|Goodbye), world!$/.test("Hello, world!");
console.log(isMatch); // => true

これらの使い方をするときに()を使いますが、 前者はインデックスでのアクセスになるため新たにキャプチャを増やしたときには インデックスを変え直したりと変更に弱いです。 また、後者ではキャプチャされた文字列を取り出したり後方参照することが無いためキャプチャした文字列が無駄になっており、 パフォーマンス上の損失が発生します。

今回はこれらの問題を解決するために 名前付きキャプチャグループ非キャプチャグループ を使う方法を紹介します。

解説

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

名前付きキャプチャグループは.match()での取得や後方参照をする際に インデックスでは無く名前でアクセスできるようにキャプチャグループに名前を付ける機能です。

記法としては () の中に ?<名前> を付けることで名前付きキャプチャグループを作ることができます。 名前付きキャプチャグループは.match()で取得したオブジェクトのgroupsプロパティに格納されます。 (余談ですが、.match()の返り値はマッチした文字列の配列とgroupsindexなどのプロパティの交差型です)

// major, minor, patch という名前を付けたキャプチャグループを作る
const matches = "v7.29.3".match(/v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/);
console.log(matches.groups.major); // => 7
console.log(matches.groups.minor); // => 29
console.log(matches.groups.patch); // => 3

また、後方参照する際にも名前を使うことができます。この場合は \k<名前> という記法を使います。

// バージョン番号がゾロ目かどうかを判定する
const areSame = /v(?<version>\d+)\.\k<version>\.\k<version>/.test("v7.7.7");
console.log(areSame); // => true

このように名前付きキャプチャグループを使うことで、キャプチャグループに分かりやすい名前を付けることができるため、 可読性が上がり、変更にも強くなります。

非キャプチャグループ

非キャプチャグループはキャプチャグループとは異なり、キャプチャを行わずグルーピングのみを行う機能です。 .match()などには含まれず、後方参照をすることもできません。 |を使った論理和には使用できるため、キャプチャを行わずに論理和を使うことができます。 またキャプチャを行わないため通常のキャプチャグループよりもパフォーマンスが良いとされています。

記法としては () の中に ?: を付けることで非キャプチャグループを作ることができます。

const isMatch = /^(?:Hello|Goodbye), world!$/.test("Hello, world!");
console.log(isMatch); // => true

このように非キャプチャグループを使うことで、キャプチャを行わずに論理和を使うことができるため、 パフォーマンスの改善が期待できます。 実際 MDN のドキュメント にもキャプチャグループで一致した部分文字列を使用しない場合は非キャプチャグループを使うことを推奨しています。

これらのキャプチャグループの使用を強制する方法

以上のことから通常のキャプチャグループを使う機会はほぼ無いと言えますが、 これらの機能はあまり広まっていないせいか、通常のキャプチャグループが使われることが多いです。 こうしたルールを強制するためには、 ESLint の prefer-named-capture-group を使うと良いでしょう。

このルールは通常のキャプチャグループを使うと警告を出すように設定でき、名前付きキャプチャグループか非キャプチャグループを使うように促すことができます。 もし開発チーム内で通常のキャプチャグループを使わないようにしていきたい場合は、このルールを有効にするといいでしょう。 (eslint:recommendedには含まれていないため、個別に有効にする必要があります)

終わりに

名前付きキャプチャグループと非キャプチャグループは、 便利な機能ですがあまり広まっていないせいか通常のキャプチャグループが使われることが多いです。 ESLint などを駆使して強制することで名前付きキャプチャグループと非キャプチャグループを使うようにしていきましょう。

Read next