文字列に潜む特殊文字を構文として解釈されないように置き換えるRegExp.escape
RegExpオブジェクトを生成するときに正規表現のテキストをそのままの文字として扱いたい時があります。これまでは手動でエスケープする必要がありましたが、RegExp.escapeを使うことで構文として解釈されないような文字列に変換し、文字通りの並びとして検索できます。RegExp.escapeを使ってエスケープ漏れとは縁のない生活を送りましょう。
RegExp.escape
RegExpオブジェクトを作る時は、new RegExp(str)のように正規表現のテキストstrを渡します。
正規表現のテキストをただの文字として扱いたい場合、new RegExp(str)ではうまく動かないことがあります。
例えば、正規表現のテキストの中に特殊文字が含まれていると、それらが構文として解釈されてしまい、意図した通りに文字列がマッチしないです。
const str = 'abc*';
const regexp = new RegExp(str);
console.log(regexp.test('abc')); // true
console.log(regexp.test('abc*')); // true
console.log(regexp.test('ab')); // true
上記は特殊文字*を含む文字列abc*を正規表現のテキストとして渡しています。
*は正規表現の構文であり、直前の文字が0回以上繰り返されることを意味します。そのため、上記の例ではabc*だけではなく、abcやabにもマッチしてしまいます。
abc*という文字列のままマッチさせたい時は、RegExp.escapeを使うことで構文として解釈されないように特定の文字列に変換し、文字通りの並びとして検索することができます。
const str = 'abc*';
// \\x61bc\\*
const escapedStr = RegExp.escape(str);
const regexp = new RegExp(escapedStr);
console.log(regexp.test('abc')); // false
console.log(regexp.test('abc*')); // true
console.log(regexp.test('ab')); // false
abc*という文字列がRegExp.escapeによって\\x61bc\\*に変換され、abやabcにはマッチしなくなりました。
RegExp.escapeは手動では達成できない複数のパターンを考慮して実装されています。
単純に正規表現の構文の前に\を追加するだけでは対処できないケースをカバーしてくれるので、古いブラウザのバージョンをサポートしていない限りはRegExp.escapeを使うようにしましょう。
変更の規則
先頭の文字が0~9,a~z,A~Zである場合
先ほどの例で、RegExp.escape('abc*')は\\x61bc\\*に変換されました。\\*は想定通りですが、aが\\x61に変換されるのは、少し意外だった人もいるのではないでしょうか。
RegExp.escapeは、テキストの先頭の文字が0~9,a~z,A~Zである場合、特殊なエスケープをします。
これらの文字は、\xに続く16進数コード(例: aはASCIIコードが61なので\x61)としてエスケープされます。
これは、別の文字列の後ろに連結して使用する場合に、前の文字列を引き継いだ構文として解釈されないようにするためです。
const str = '0';
const regexp = new RegExp('(.)\\1' + str);
console.log(regexp.test('aa0')); // false
const escapedStr = RegExp.escape(str);
const escapedRegexp = new RegExp('(.)\\1' + escapedStr);
console.log(escapedRegexp.test('aa0')); // true
上の例では、(.)\\1の後ろに0を連結しています。0をエスケープしない場合、new RegExp('(.)\\10')となり「後方参照 10」のように解釈されてしまいます。
RegExp.escapeを使うことで、0は\x30に変換され、new RegExp('(.)\\1\\x30')となり「後方参照 1」と「0」で分けて解釈してくれるので、意図した通りにaa0がマッチします。
特殊文字
^・$・\・.・*・+・?・(・ )・[, ]・{・}・|・/は、\を前に付けてエスケープされます。
const str = '^$\.*+?()[]{}|/';
// \\^\\$\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|\\/
console.log(RegExp.escape(str));
これは、正規表現の構文として解釈されないようにするためです。
区切り文字,・-・=・<・>・#・&・!・%・:・;・@・~・'・`・"は\xに続く16進数コードとしてエスケープされます。
const str = ',-=<>#&!%:;@~`" ';
// \\x2c\\x2d\\x3d\\x3c\\x3e\\x23\\x26\\x21\\x25\\x3a\\x3b\\x40\\x7e\\x60\\x22\\x20
console.log(RegExp.escape(str));
\nのような制御文字は、\を前につけてエスケープされます。
const str = '\f\n\r\t\v';
// \\f\\n\\r\\t\\v
console.log(RegExp.escape(str));
その他の文字
タブ、全角スペースなどの空白文字は、\uに続くUTF-16コード単位へエスケープされます。
const str = ' ';
// \\u3000
console.log(RegExp.escape(str));
ab�(文字化け対策のため、全角バックスラッシュで書くとab\uD800)や�ab(\uDFFFab)のような孤立サロゲート(文字列に対してisWellFormed()でチェックできます。)も\uに続くUTF-16コード単位へエスケープされます。
const str = 'ab� �ab';
// \\x61b\�\\x20\�ab
console.log(RegExp.escape(str));
おわりに
正規表現に渡す文字列をただの文字列として扱うためのRegExp.escapeについて紹介しました。
RegExp.escapeを使うことで、正規表現の構文として解釈されないようにエスケープされた文字列に変換できるので、
手動でエスケープする必要がなくなり、エスケープ漏れの心配もなくなります。
RegExp.escapeはBaseline 2025で導入されたので、対象の環境で利用できる場合は積極的に活用していきましょう。