✔️

正規表現を豊かにする ES2024 RegExp v (unicodeSets) フラグ

2023/04/05に公開
変更情報

【2023/05/17 変更】

ES2024 RegExp v (unicodeSets) フラグ

ES2024 に RegExp v (unicodeSets) フラグというものがあります。これは既存の u (unicode) フラグを改善して置き換え、機能追加することを目的としています。

https://github.com/tc39/proposal-regexp-v-flag

詳しい内容については V8 や 2ality による解説記事が詳しいです。ここではその概要をピックアップして述べたいと思います。

https://v8.dev/features/regexp-v-flag

https://2ality.com/2022/11/regexp-v-flag.html

複数のコードポイントからなる絵文字の対応(Unicode Properties of Strings)

ES2015 に u (unicode) フラグが導入され、コードポイント単位で正規表現を扱えるようになりました。

また ES2018 RegExp Unicode Property Escapes によって、Unicode Character Properties を扱えます。例えば [0-9A-Fa-f]\p{ASCII_Hex_Digit} と表現できます。

https://github.com/tc39/proposal-regexp-unicode-property-escapes

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 フラグに改められました。

https://github.com/whatwg/html/pull/7908

これは破壊的変更ですが 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?

https://github.com/whatwg/html/pull/7908#issuecomment-1466906703

Discussion