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

## 問題

[前回の記事](https://qiita.com/juthaDDA/items/d81f45295095eb4563f4 "'rgb[a](R, G, B[, A])' を正規表現で処理して，各値をメンバーとしてもつオブジェクトを返す関数． - Qiita") [^update] では，コードを 80 文字/行以内に収めたいけれど，正規表現リテラルは途中で改行できないので，長い正規表現を一旦文字列型変数に代入してから `+` で結合 → 正規表現化という手順を踏んでいました．

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

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

[^update]: 本記事の内容を反映して更新したので，編集前のコードは、[こちら](https://qiita.com/juthaDDA/items/d81f45295095eb4563f4/revisions/9 "「'rgb[a](R, G, B[, A])' を正規表現で処理して，各値をメンバーとしてもつオブジェクトを返す関数」の編集履歴 - Qiita") を見てください． 

## 解決法

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

[^source]: Cf. [How can I concatenate regex literals in JavaScript? - Stack Overflow](https://stackoverflow.com/questions/185510/how-can-i-concatenate-regex-literals-in-javascript)

### TypeScript

```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
<details>
<div>

```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,
	);
};

```

</div>
</details>

## 説明

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

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

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

```javascript
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-regex](https://www.npmjs.com/package/@babel/plugin-transform-named-capturing-groups-regex "@babel/plugin-transform-named-capturing-groups-regex - npm")[^ptncgr] や [babel-plugin-transform-modern-regexp](https://www.npmjs.com/package/babel-plugin-transform-modern-regexp "babel-plugin-transform-modern-regexp - npm") を入れても，名前付きキャプチャー・グループと `RegExp.prototype.source` を併用すると変換がおかしくなるので，第一引数の要素には名前付きキャプチャー・グループを含まないようにする必要があります．[^version]

[^ptncgr]: Cf. [\`@babel/plugin-transform-named-capturing-groups-regex\` fails to wrap all groups · Issue #10045 · babel/babel](https://github.com/babel/babel/issues/10045)
[^version]: 2020 年 9 月 17 日現在の最新版は，Babel 7.11.0, babel-plugin-transform-modern-regexp 7.10.4, babel-plugin-transform-modern-regexp 0.0.6