正規表現を豊かにする ES2024 RegExp v (unicodeSets) フラグ
変更情報
【2023/05/17 変更】
- 2023年5月の TC39 会議で Stage 4 になったため、タイトルを変更
- HTML Standard の
pattern
属性に取り込まれたので修正
ES2024 RegExp v (unicodeSets) フラグ
ES2024 に RegExp v (unicodeSets) フラグというものがあります。これは既存の u (unicode) フラグを改善して置き換え、機能追加することを目的としています。
詳しい内容については V8 や 2ality による解説記事が詳しいです。ここではその概要をピックアップして述べたいと思います。
複数のコードポイントからなる絵文字の対応(Unicode Properties of Strings)
ES2015 に u (unicode) フラグが導入され、コードポイント単位で正規表現を扱えるようになりました。
また ES2018 RegExp Unicode Property Escapes によって、Unicode Character Properties を扱えます。例えば [0-9A-Fa-f]
を \p{ASCII_Hex_Digit}
と表現できます。
const regexGreekSymbol = /\p{Script_Extensions=Greek}/u;
regexGreekSymbol.test('π');
// → true
ここで問題となるのが絵文字です。絵文字は複数のコードポイントで表されることがあるため、単一のコードポイントについて扱う Unicode Character Properties では不十分です。
// Unicode Character Properties で “Emoji” が定義されている
const re = /^\p{Emoji}$/u;
// 1つのコードポイントからなる絵文字
re.test('⚽'); // '\u26BD'
// → true ✅
// 複数のコードポイントからなる絵文字
re.test('👨🏾⚕️'); // '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F'
// → false ❌
幸いなことに Unicode にはいくつかの Properties of Strings が定義されています。これを新たに取り入れることで複数のコードポイントからなる絵文字も対応可能となります。
// Unicode Properties of Strings で “RGI_Emoji” が定義されている
const re = /^\p{RGI_Emoji}$/v;
// 1つのコードポイントからなる絵文字
re.test('⚽'); // '\u26BD'
// → true ✅
// 複数のコードポイントからなる絵文字
re.test('👨🏾⚕️'); // '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F'
// → true ✅
具体的には以下の Unicode Properties of Strings が使えるようになります。
- Basic_Emoji
- Emoji_Keycap_Sequence
- RGI_Emoji_Modifier_Sequence
- RGI_Emoji_Flag_Sequence
- RGI_Emoji_Tag_Sequence
- RGI_Emoji_ZWJ_Sequence
- RGI_Emoji
もちろん今後 Unicode 側で新たに追加された時には、それに応じて ECMAScript も更新されることとなります。
集合演算とリテラル
今まで u フラグで扱えた Unicode Character Properties と新たに追加される Unicode Properties of Strings に対して集合演算が導入されます。
なお複数のコードポイントからなる文字列を集合としてひとまとめにするために \q{…}
が導入されます。
--
差集合 [A--B]
で差集合を扱えます。
例えばギリシャ文字から特定の文字を取り除いた正規表現が書けるようになります。
/[\p{Script_Extensions=Greek}--[αβγ]]/v.test('α'); // → false
/[\p{Script_Extensions=Greek}--[α-γ]]/v.test('β'); // → false
もちろん Unicode Properties of Strings に対しても演算が適用できます。
/^\p{RGI_Emoji_Tag_Sequence}$/v.test('🏴'); // → true
/^[\p{RGI_Emoji_Tag_Sequence}--\q{🏴}]$/v.test('🏴'); // → false
&&
積集合(共通部分) [A&&B]
で積集合(共通部分)を扱えます。
例えば Ascii なホワイトスペースに対する正規表現が書けるようになります。
const re = /[\p{White_Space}&&\p{ASCII}]/v;
re.test('\n'); // → true
re.test('\u2028'); // → false
和集合(合併)
これは特に新しいシンタックスというわけではないですが、今まで通り […]
で和集合(合併)が扱えます。
const re = /^[\p{Emoji_Keycap_Sequence}\p{ASCII}\q{🇧🇪|abc}xyz0-9]$/v;
re.test('4️⃣'); // → true
re.test('_'); // → true
re.test('🇧🇪'); // → true
re.test('abc'); // → true
re.test('x'); // → true
re.test('4'); // → true
case-insensitive matching の改善
u フラグと i (ignoreCase) フラグを同時に使った場合に [^\P{…}]
を扱うと奇妙な動作をします。
const re1 = /\p{Lowercase_Letter}/giu;
const re2 = /[^\P{Lowercase_Letter}]/giu;
const string = 'aAbBcC4#';
string.replaceAll(re1, 'X');
// → 'XXXXXX4#'
string.replaceAll(re2, 'X');
// → 'aAbBcC4#'
v フラグではこれが修正されます。
const re1 = /\p{Lowercase_Letter}/giv;
const re2 = /[^\P{Lowercase_Letter}]/giv;
const string = 'aAbBcC4#';
string.replaceAll(re1, 'X');
// → 'XXXXXX4#'
string.replaceAll(re2, 'X');
// → 'XXXXXX4#'
HTML Standard との関係
HTML の <input>
要素には pattern
属性が定義されています。これは与えられた文字列から u フラグのついた正規表現が作られる仕様となっていましたが v フラグに改められました。
これは破壊的変更ですが Chrome Beta & Canary で試した結果によると、多く見積もってもページ読み込み時に全体の 0.015% しか影響がなく、また極端に壊れるサイトは見受けられませんでした。
Based on Chrome Beta & Canary data so far, it looks like the
HTMLPatternRegExpUnicodeSetIncompatibilitiesWithUnicodeMode
use counter gets hit for roughly 0.015% of page loads. Given that this number is the upper bound of the number of actual incompatibility issues, I’m hopeful we can proceed with this change without breaking the Web. Thoughts?
Discussion