ていうか今まで使えなかったのか。
const str = `javascript:evil();/*
https://example.com
*/`;
// 文字列がURLかを確認する
const re = /^https.*$/m;
re.exec(str); // "https://example.com" 通ってしまう
正規表現^$は行頭・行末を表すメタ文字であり、文字列先頭・末尾を表すわけではありません。
うっかり手癖で書いてしまうと上記のように脆弱性を仕込まれてしまいます。
まあ上記例は.*のほうがアウトなのですが例なのでそこはスルーで。
ということで、マルチラインモードmが有効か否かにかかわらず文字列先頭・末尾を表すエスケープシーケンス\A\zが、他の大抵の言語には用意されています。
なぜかJavaScriptにはこれまでなかったのですが、2026年ようやく実装される見込みになりました。
以下は該当のProposal、Regular Expression Buffer Boundaries for ECMAScriptの紹介です。
ステージは3で仕様は決定済、あとはブラウザに実装されるのを待つだけの状態です。
2026/10/19時点の対応状況は、SafariはTP251で実装、FirefoxもNightlyで実装されており、じきリリースされるでしょう。
Chromeは一応何かやってはいるみたいなのですが、マイルストーンではまだ未対応です。
Regular Expression Buffer Boundaries for ECMAScript
このproposalでは、正規表現にエスケープシーケンス\A・\z、および\Zを導入します。
これはそれぞれ次の正規表現(?-m:^)・(?-m:$)・(?=(?:\r\n|\n|\r|\u2028|\u2029)?(?-m:$))と同義になります。
Motivations
バッファ境界は多くの正規表現エンジンが持つ機能で、複数行フラグmの有無に関わらず、入力全体の開始位置もしくは終了位置と一致する機能です。
既存の正規表現を使って\A\z\Zを表現することは可能ですが、代替手段は非常に読みにくく、正規表現の詳細な理解が求められます。
たとえば以下の例を見てみましょう
// mフラグがないときにmのある^
const a = /^foo|(?<=^|[\u000A\u000D\u2028\u2029])bar/u;
// mフラグがあるときにmのない^
const b = /(?-m:^)foo|^bar/mu;
// \Aなら楽ちん
const c = /\Afoo|^bar/mu;
aを理解できる人は非常に少ないでしょう。
正規表現自体が読みにくいことに加え、これを完全に理解するにはグループ化・肯定後読み、メタ文字、論理和、文字クラス、UNICODEエスケープの各機能がどのように相互作用するかを読み解く必要があります。
bはだいぶマシですが、それでも括弧が出てくるうえ、グループ化・修飾子・フラグ・メタ文字の4機能が現れます。
それに比べてcは非常にわかりやすいです。
ここに出てくるのはたった2文字の簡潔なエスケープシーケンスだけであり、通常テキストであるfoo・barと容易に区別することができます。
正規表現\A・\z・\Zは多くの言語や正規表現エンジンでサポートされています。
従ってWikipedia、多くのチュートリアルやオンラインドキュメントなど豊富な解説が既に存在しており、他の代替手段に比べて学習も容易です。
Relationship to RegExp Modifiers
本proposalは、正規表現パターン修飾子のシンタックスシュガーであると考えることができます。
・\A → (?-m:^)
・\z → (?-m:$)
・\Z → (?=(?:\r\n|\n|\r|\u2028|\u2029)?(?-m:$))
従って修飾子を用いることでも同じことは可能ですが、エスケープシーケンス\A・\z・\Zは多くの言語間で共通であるため移植性が高く便利な機能です。
これらはTextMateなどのエディタや、ECMAScriptアプリケーションでも頻繁に利用されています。
そのため、この構文を導入することで移植性が向上し、Web上のドキュメントなどで使われている正規表現パターンをそのまま再利用できるようになります。
Syntax
バッファ境界は^・$に似ていますが、複数行フラグmの影響を受けません。
・\A:入力の先頭と一致。
・\z:入力の末尾と一致。
・\Z:入力の末尾と一致。最後だけ改行があってもいい。
uもしくはvフラグが必要です。
フラグがない場合はリテラルを表します。
文字クラス内ではサポートされていません。
Examples
// without buffer boundaries
const re1 = /^foo$/u;
re1.test("foo"); // true
re1.test("foo\nbar"); // false
const re2 = /^foo$/um;
re2.test("foo"); // true
re2.test("foo\nbar"); // true
// with modifiers
const re3 = /(?-m:^)foo(?-m:$)/um;
re3.test("foo"); // true
re3.test("foo\nbar"); // false
const re1 = /\Afoo\z/u;
re1.test("foo"); // true
re1.test("foo\nbar"); // false
const re2 = /\Afoo\z/um;
re2.test("foo"); // true
re2.test("foo\nbar"); // false
// バッファ境界とアンカーの混在
const re = /\Afoo|^bar$|baz\z/um;
re.test("foo"); // true
re.test("foo\n"); // true
re.test("\nfoo"); // false
re.test("bar"); // true
re.test("bar\n"); // true
re.test("\nbar"); // true
re.test("baz"); // true
re.test("baz\n"); // false
re.test("\nbaz"); // true
// 末尾改行
const re = /end\Z/;
re.test("The end"); // true
re.test("The end\n"); // true
re.test("The end\r\n"); // true
re.test("The end\u2028"); // true
re.test("The end\n...or is it?"); // false
History
・2021/10/28 ステージ1になった。
・2021/12/15 ステージ2になった。
・2026/03/10 時間不足で審査が通らず。
・2026/03/19 ステージ2.7になった。
・2026/03/21 ステージ3になった。
感想
JavaScriptの正規表現は他言語に対して劣っているので全体的になんとかしようという大きな計画がありまして、本proposalはそこから切り出された一部となります。
ところでHistoryを見ると、2021年に提出されたまま5年放置されていたのが、何故か2026年になって突然動き出したように見えますね。
これは本文中にもあるとおりバッファ境界はパターン修飾子のシンタックスシュガーという扱いであり、そしてパターン修飾子がブラウザ実装されたのがES2025なので、単にパターン修飾子が終わるまで進められなかったということでしょう。
ということで、今後はその他の正規表現関連機能も次々充実していくことになるかもしれませんね。