【JS】RegExp の複数行記述、RegExp 内での変数参照がしたいので、RegExp を結合する関数を作った


問題

前回の記事 1 では,コードを 80 文字/行以内に収めたいけれど,正規表現リテラルは途中で改行できないので,長い正規表現を一旦文字列型変数に代入してから + で結合 → 正規表現化という手順を踏んでいました.

しかし,この方法には以下のような問題があります.

  • “\” を文字列に含めるには \\ としてやる必要があり,エスケープが二重になって鬱陶しい.
  • 文字列型なので当然正規表現としてのシンタックス・ハイライトはつかない.

解決法

そこで,*/foo/ + /bar|z/ といったかんじで正規表現同士を結合できないか調べてみたところ,RegExp.prototype.source を使う方法を見つけた2ので,それをもとに以下の関数を作ってみました.

TypeScript

concat-regexps.ts
/**
 * @author JuthaDDA
 * @see [RegExp の複数行記述、RegExp 内での変数参照がしたいので、
 *     正規表現を結合する関数を作った - Qiita
 *     ](https://qiita.com/juthaDDA/items/f1093b968faa3d810c1c)
 * @param regExps - Babel (< 7.11.0) を使う場合は, 
 *     名前付きキャプチー・グループを含むと正しく変換されない.
 *     `@babel/plugin-transform-named-capturing-groups-regex (< 7.10.4)` も未対応.
 */
const concatRegExps = ( regExps:RegExp[], flags?:string ):RegExp => {
    return RegExp(
        regExps.reduce( ( acc, cur ) => acc + cur.source, '' ),
        flags,
    );
};

JavaScript


concat-regexps.js
/**
 * @author JuthaDDA
 * @see [RegExp の複数行記述、RegExp 内での変数参照がしたいので、
 *     正規表現を結合する関数を作った - Qiita
 *     ](https://qiita.com/juthaDDA/items/f1093b968faa3d810c1c)
 * @param {RegExp[]} regExps - Babel (< 7.11.0) を使う場合は,
 *     名前付きキャプチー・グループを含むと正しく変換されない.
 *     `@babel/plugin-transform-named-capturing-groups-regex` (< 7.10.4) も未対応.
 * @param {string}   [flags]
 * @return {RegExp}
 */
const concatRegExps = ( regExps, flags ) => {
    return RegExp(
        regExps.reduce( ( acc, cur ) => acc + cur.source, '' ),
        flags,
    );
};


説明

concatRegExps() 第 1 引数に RegExp の配列 regExps を,第 2 引数に 'g' などの flag を取ります.

RegExp.prototype.source には,“\” などをエスケープして string 化したソース・テキストが入っているので,これを regExps.reduce() で結合し,flag で指定したフラグを附けた RegExp を生成して return しています.なお,0 === regExps.length の場合の戻り値は,\(?:)\ です.

regExps の要素には,当然 RegExp 型の変数を含むことができるので,

const width = '50%';
const realNumRegExp = /[+-]?\d*\.?\d+/;
const unitRegExp = /(?:px|em|rem|vw|%)/;
const widthRegExp = concatRegExps( [ /^/, realNumRegExp, unitRegExp, /$/ ] );
const isValidWidth = widthRegExp.test( width );

のような使い方も可能です.

補足

Babel を使う場合は,@babel/plugin-transform-named-capturing-groups-regex3babel-plugin-transform-modern-regexp を入れても,名前付きキャプチャー・グループと RegExp.prototype.source を併用すると変換がおかしくなるので,第一引数の要素には名前付きキャプチャー・グループを含まないようにする必要があります.4


  1. 本記事の内容を反映して更新したので,編集前のコードは、こちら を見てください.  

  2. Cf. How can I concatenate regex literals in JavaScript? - Stack Overflow 

  3. Cf. `@babel/plugin-transform-named-capturing-groups-regex` fails to wrap all groups · Issue #10045 · babel/babel 

  4. 2020 年 9 月 17 日現在の最新版は,Babel 7.11.0, babel-plugin-transform-modern-regexp 7.10.4, babel-plugin-transform-modern-regexp 0.0.6