Repository: asciidwango/js-primer (2025-08-17T05:47:31Z)

Issue:
ES2025: `RegExp.escape`
> # [`RegExp.escape`](https://github.com/tc39/proposal-regex-escaping)
> 
> `RegExp.escape` が追加される
> 
> 次のコードでは、RegExpコンストラクタで変数spaceCountの数だけ連続するホワイトスペースにマッチする正規表現オブジェクトを作成しています。 注意点として、\（バックスラッシュ）自体が、文字列中ではエスケープ文字であることに注意してください。 そのため、RegExpコンストラクタの引数のパターン文字列では、バックスラッシュからはじまる特殊文字は\（バックスラッシュ）自体をエスケープする必要があります。
> [正規表現リテラルとRegExpコンストラクタの違い ](https://jsprimer.net/basic/string/#difference-regexp-literal-regexp-constructor)
> 
> 必要とは言い切れないけど、ここで`RegExp.escape`の解説を入れるのは自然な気がする。
> ただ、サンプルコードはescapeを必要にしてないので、やる場合は1つサンプルコードを書くのが良い気がする。
> 
> String.replaceAllでまさに実用的なユースケースがあるので、ここで使う例を出すと良さそう
> 具体的なユースケースは、変数で受け取った値をreplace all したいというケースかもしれない。
> けど、replaceの時代には必要だったけど、今はreplaceAllではんぶんぐらいはいいかもしれない…
> 
> ```
> // replaceAllメソッドにも正規表現を渡せるが、この場合はエスケープが必要となるためreplaceと同じ
> console.log(str.replaceAll(/\?/g, "!")); // => "!!!"
> ```
> 
> ## 結論
> 
> - `RegExp.escape`の解説を入れる
> - `replaceAll`で実例を加える 

 _Originally posted by @azu in [#1778](https://github.com/asciidwango/js-primer/issues/1778#issuecomment-2789790232)_

## 方針

`RegExp.escape`は必須かというとそうでもないので、今まで手でエスケープしてたのをやってくれるメソッドがES2025で増えたよという紹介程度に止める。
実際のユースケースは、普通のアプリケーションだとあんまり遭遇しにくいのと replaceAll で意外と大体はカバーできる。
なので、実ユースケースもreplaceAllの中でも1つとか、軽く紹介するぐらいのイメージで行う。

## TODO

https://jsprimer.net/basic/string に追加する

- [ ] `RegExp.escape`の解説を入れる

セクション切るべきかはちょっと検討。
https://jsprimer.net/basic/string/#regexp-object
https://jsprimer.net/basic/string/#difference-regexp-literal-regexp-constructor
の最後にちょっとだけ説明を入れるとかそれぐらいのイメージではある。
セクションとして分けた方がわかりやすいなら切るぐらい


- [ ] `replaceAll`で実例を加える

```js
// replaceAllメソッドにも正規表現を渡せるが、この場合はエスケープが必要となるためreplaceと同じ
console.log(str.replaceAll(/\?/g, "!")); // => "!!!"
```
これの下に、
```js
// replaceメソッドの場合は、正規表現の特殊文字はエスケープが必要となる
console.log(str.replace(/\?/g, "!")); // => "!!!"
// replaceAllメソッドにも正規表現を渡せるが、この場合はエスケープが必要となるためreplaceと同じ
console.log(str.replaceAll(/\?/g, "!")); // => "!!!"
// RegExp.escapeを使うことで、次のようにも書ける
console.log(str.replace(new RegExp(RegExp.escape("?"), "g"), "!")); // => "!!!"
```
1つ足すぐらいのイメージ


Discussion:
@Itsuki54 さんにやってもらう



Patch:
diff --git a/source/basic/string/README.md b/source/basic/string/README.md
index 0d873d487..549f20b29 100644
--- a/source/basic/string/README.md
+++ b/source/basic/string/README.md
@@ -546,13 +546,8 @@ console.log(str.includes("いる")); // => true
 
 正規表現は正規表現オブジェクト（`RegExp`オブジェクト）として表現されます。
 正規表現オブジェクトはマッチする範囲を決める`パターン`と正規表現の検索モードを指定する`フラグ`の2つで構成されます。
-正規表現のパターン内では、次の文字は**特殊文字**と呼ばれ、特別な意味を持ちます。特殊文字として解釈されないように入力する場合には`\`（バックスラッシュ）でエスケープする必要があります。
 
-```
-\ ^ $ . * + ? ( ) [ ] { } |
-```
-
-正規表現オブジェクトを作成するには、正規表現リテラルと`RegExp`コンストラクタを使う2つの方法があります。
+正規表現オブジェクトを作成するには、正規表現リテラルと`RegExp`コンストラクタを使う2つの方法があります。正規表現リテラルは、`/`と`/`のリテラル内に正規表現のパターンを書くことで、正規表現オブジェクトを作成でき、`RegExp`コンストラクタは文字列から正規表現オブジェクトを作成できます。
 
 <!-- textlint-disable eslint -->
 <!-- doctest:disable -->
@@ -564,7 +559,15 @@ const patternB = new RegExp("パターン文字列", "フラグ");
 ```
 <!-- textlint-enable eslint -->
 
-正規表現リテラルは、`/`と`/`のリテラル内に正規表現のパターンを書くことで、正規表現オブジェクトを作成できます。
+
+正規表現のパターン内では、次の文字は**特殊文字**と呼ばれ、特別な意味を持ちます。
+
+```
+\ ^ $ . * + ? ( ) [ ] { } |
+```
+
+たとえば、`+`は1回以上の繰り返しを意味する特殊文字です。
+
 次のコードでは、`+`という1回以上の繰り返しを意味する特殊文字を使い、`a`が1回以上連続する文字列にマッチする正規表現オブジェクトを作成しています。
 
 ```js
@@ -581,6 +584,21 @@ const pattern = /a+/;
 const pattern = new RegExp("a+");
 ```
 
+正規表現では、特殊文字をそのまま文字として扱いたい場合は、バックスラッシュ（`\`）でエスケープする必要があります。
+
+たとえば、`+`という特殊文字をそのまま文字として扱いたい場合は、`\+`のようにバックスラッシュでエスケープします。
+
+また、ES2025では、`RegExp.escape`メソッドが追加され、正規表現の特殊文字を安全にエスケープできます。`RegExp.escape`メソッドを使うことで、文字列の中に正規表現として意味を持つ特殊文字が含まれていても、自動的にエスケープできます。
+
+{{book.console}}
+<!-- doctest:meta:{ "ECMAScript": "2025" } -->
+```js
+const escaped = RegExp.escape("+");
+console.log(escaped); // \+
+```
+
+<!-- "\\+" と書きたいけど、見た目が微妙なので\+にしている -->
+
 ### 正規表現リテラルと`RegExp`コンストラクタの違い {#difference-regexp-literal-regexp-constructor}
 
 <!-- 評価とコンパイルは仕様から来てる用語 https://tc39.es/ecma262/#sec-pattern -->
@@ -1047,7 +1065,7 @@ Stringの`replaceAll`メソッドでは、正規表現ではなく文字列を
 そのため、正規表現では特殊な意味を持つ`?`のような文字列も検索文字列にそのまま書いて置換ができます。
 
 {{book.console}}
-<!-- doctest:meta:{ "ECMAScript": "2021" } -->
+<!-- doctest:meta:{ "ECMAScript": "2025" } -->
 ```js
 // 検索対象となる文字列
 const str = "???";
@@ -1055,9 +1073,11 @@ const str = "???";
 console.log(str.replace("?", "!")); // => "!??"
 // replaceAllメソッドに文字列を指定した場合は、一致したものがすべて置換される
 console.log(str.replaceAll("?", "!")); // => "!!!"
-// replaceメソッドの場合は、正規表現の特殊文字はエスケープが必要となる
+// replaceメソッドに正規表現を渡す場合、正規表現の特殊文字はエスケープが必要
 console.log(str.replace(/\?/g, "!")); // => "!!!"
-// replaceAllメソッドにも正規表現を渡せるが、この場合はエスケープが必要となるためreplaceと同じ
+// RegExp.escapeとreplaceメソッドを使った場合（ES2025）
+console.log(str.replace(new RegExp(RegExp.escape("?"), "g"), "!")); // => "!!!"
+// replaceAllメソッドにも正規表現を渡せるが、この場合もエスケープが必要（replaceと同様）
 console.log(str.replaceAll(/\?/g, "!")); // => "!!!"
 ```
 
diff --git a/source/basic/string/specs/add-regexp-escape-docs/design.md b/source/basic/string/specs/add-regexp-escape-docs/design.md
new file mode 100644
index 000000000..b13858d0a
--- /dev/null
+++ b/source/basic/string/specs/add-regexp-escape-docs/design.md
@@ -0,0 +1,154 @@
+# 設計ファイル: RegExp.escape メソッドのドキュメント追加
+
+## 追加位置
+
+### ファイルパス
+`/source/basic/string/README.md`
+
+### 追加位置1: 正規表現オブジェクトセクション
+「正規表現オブジェクト」セクション（540行目付近）の`RegExp`コンストラクタの説明の後に追加
+
+理由：
+- `RegExp`関連のメソッドとして自然な位置
+- 正規表現の特殊文字のエスケープについて説明した直後なので文脈的に適切
+
+### 追加位置2: replaceメソッドのコード例
+「文字列の置換/削除」セクション（1056-1062行目）のコード例に`RegExp.escape`を使った例を追加
+- 特に説明を加えず、自然にコード例の一部として含める
+
+## ドキュメント構造
+
+### 正規表現オブジェクトセクションへの追加
+
+#### セクション名
+`### [ES2025] RegExp.escape {#regexp-escape}`
+
+#### 内容構成
+1. **導入説明**（1段落）
+   - メソッドの目的：正規表現の特殊文字を安全にエスケープ
+   - ES2025で追加された新機能であることを明記
+
+2. **基本的な使い方**（コード例1つ）
+   - シンプルな例で`RegExp.escape`の基本動作を示す
+   - 特殊文字のエスケープ例
+
+### replaceメソッドセクションへの追加
+既存のコード例に1行追加するだけ：
+```js
+// RegExp.escapeを使った場合
+console.log(str.replace(new RegExp(RegExp.escape("?"), "g"), "!")); // => "!!!"
+```
+
+## コード例の設計
+
+### 正規表現オブジェクトセクションでの基本例
+
+↓みたいに変更するイメージ
+
+## 正規表現オブジェクト {#regexp-object}
+
+<!-- パターンと正規表現オブジェクトの用語については https://github.com/asciidwango/js-primer/issues/21#issuecomment-293502813 -->
+
+文字列による検索では、固定の文字列にマッチするものしか検索できません。
+一方で正規表現による検索では、あるパターン（規則性）にマッチするという柔軟な検索ができます。
+
+正規表現は正規表現オブジェクト（`RegExp`オブジェクト）として表現されます。
+正規表現オブジェクトはマッチする範囲を決める`パターン`と正規表現の検索モードを指定する`フラグ`の2つで構成されます。
+
+正規表現オブジェクトを作成するには、正規表現リテラルと`RegExp`コンストラクタを使う2つの方法があります。正規表現リテラルは、`/`と`/`のリテラル内に正規表現のパターンを書くことで、正規表現オブジェクトを作成でき、`RegExp`コンストラクタは文字列から正規表現オブジェクトを作成できます。
+
+<!-- textlint-disable eslint -->
+<!-- doctest:disable -->
+```js
+// 正規表現リテラルで正規表現オブジェクトを作成
+const patternA = /パターン/フラグ;
+// `RegExp`コンストラクタで正規表現オブジェクトを作成
+const patternB = new RegExp("パターン文字列", "フラグ");
+```
+<!-- textlint-enable eslint -->
+
+
+正規表現のパターン内では、次の文字は**特殊文字**と呼ばれ、特別な意味を持ちます。
+
+```
+\ ^ $ . * + ? ( ) [ ] { } |
+```
+
+たとえば、`+`は1回以上の繰り返しを意味する特殊文字です。
+
+次のコードでは、`+`という1回以上の繰り返しを意味する特殊文字を使い、`a`が1回以上連続する文字列にマッチする正規表現オブジェクトを作成しています。
+
+```js
+const pattern = /a+/;
+```
+
+正規表現オブジェクトを作成するもうひとつの方法として`RegExp`コンストラクタがあります。
+`RegExp`コンストラクタでは、文字列から正規表現オブジェクトを作成できます。
+
+次のコードでは、`RegExp`コンストラクタを使って`a`が1文字以上連続している文字列にマッチする正規表現オブジェクトを作成しています。
+これは先ほどの正規表現リテラルで作成した正規表現オブジェクトと同じ意味になります。
+
+```js
+const pattern = new RegExp("a+");
+```
+
+正規表現では、特殊文字をそのまま文字として扱いたい場合は、バックスラッシュ（`\`）でエスケープする必要があります。
+
+たとえば、`+`という特殊文字をそのまま文字として扱いたい場合は、`\+`のようにバックスラッシュでエスケープします。
+
+ES2025では、`RegExp.escape`メソッドが追加され、正規表現の特殊文字を簡単にエスケープできるようになりました。このメソッドを使うことで、手動でエスケープする手間が省けます。
+
+```js
+const escaped = RegExp.escape("a+b?c");
+console.log(escaped); // => "a\+b\?c"
+```
+
+### replaceセクションでの追加例（既存コードに混ぜる）
+```js
+const str = "???";
+// ... 既存のコード例 ...
+// replaceAllメソッドに文字列を指定した場合は、一致したものがすべて置換される
+console.log(str.replaceAll("?", "!")); // => "!!!"
+// replaceメソッドの場合は、正規表現の特殊文字はエスケープが必要となる
+console.log(str.replace(/\?/g, "!")); // => "!!!"
+// RegExp.escapeを使った場合（ES2025）
+console.log(str.replace(new RegExp(RegExp.escape("?"), "g"), "!")); // => "!!!"
+// replaceAllメソッドにも正規表現を渡せるが、この場合はエスケープが必要となるためreplaceと同じ
+console.log(str.replaceAll(/\?/g, "!")); // => "!!!"
+```
+
+## 文体と形式
+
+### 既存の文体に合わせる要素
+- `{{book.console}}`タグの使用
+- コード例の前後に説明文を配置
+- コメントは`// =>`形式で結果を示す
+- ES仕様年の明記（[ES2025]タグ）
+- アンカーリンク用のID（`{#regexp-escape}`）
+
+### トーンの統一
+- 「〜できます」調の丁寧な説明
+- 技術的詳細は最小限に
+- 初心者にも理解しやすい言葉選び
+
+## 他セクションとの関連
+
+### 参照すべき既存セクション
+- 正規表現オブジェクト（line 540-）
+- 正規表現リテラルとRegExpコンストラクタの違い（line 584-）
+- 文字列の置換/削除（line 989-）
+
+### 相互参照の追加
+- 正規表現の特殊文字の説明箇所から`RegExp.escape`への言及を検討（オプション）
+
+## 制約事項の遵守
+
+### 軽い説明に留める
+- メソッドの内部実装には触れない
+- エスケープされる全特殊文字のリストは載せない
+- 基本的な使い方と利点の説明に焦点
+
+### 初心者向けの配慮
+- 正規表現の知識が浅くても理解できる説明
+- 実用的なユースケースを提示
+- 過度に技術的な用語を避ける
diff --git a/source/basic/string/specs/add-regexp-escape-docs/implementation-plan.md b/source/basic/string/specs/add-regexp-escape-docs/implementation-plan.md
new file mode 100644
index 000000000..b7f92e796
--- /dev/null
+++ b/source/basic/string/specs/add-regexp-escape-docs/implementation-plan.md
@@ -0,0 +1,76 @@
+# 実装計画ファイル: RegExp.escape メソッドのドキュメント追加
+
+## 実装手順
+
+### ステップ1: 正規表現オブジェクトセクションの修正
+**ファイル**: `/source/basic/string/README.md`  
+**位置**: 541-598行目（正規表現オブジェクトセクション全体を置き換え）
+
+#### 変更内容:
+1. 既存の正規表現オブジェクトセクションの内容を維持
+2. 特殊文字のエスケープの説明の後に`RegExp.escape`メソッドを追加
+3. ES2025のdoctestメタデータを追加
+
+具体的には以下のような構造に変更：
+- 正規表現オブジェクトの説明
+- 正規表現リテラルと`RegExp`コンストラクタの説明
+- 特殊文字の説明
+- **NEW: 特殊文字のエスケープと`RegExp.escape`メソッドの説明**
+
+### ステップ2: replaceメソッドのコード例への追加
+**ファイル**: `/source/basic/string/README.md`  
+**位置**: 1074行目（既存のコード例の中）
+
+#### 変更内容:
+既存のコード例に1行追加：
+```js
+// 既存の行の後に追加
+console.log(str.replace(/\?/g, "!")); // => "!!!"
+// RegExp.escapeを使った場合（ES2025）
+console.log(str.replace(new RegExp(RegExp.escape("?"), "g"), "!")); // => "!!!"
+```
+
+### ステップ3: doctestメタデータの追加
+両方のコード例に`<!-- doctest:meta:{ "ECMAScript": "2025" } -->`を追加して、ES2025環境でのテストを指定
+
+## 実装の注意点
+
+### 1. 既存コードへの影響
+- 既存のセクション構造を崩さない
+- 行番号の変更に注意（後続のセクションへの影響）
+
+### 2. コード例の動作確認
+- `{{book.console}}`タグを正しく配置
+- コメントの`// =>`形式を統一
+- doctestメタデータでES2025を指定
+
+### 3. 文体の統一
+- 「〜できます」「〜します」調を維持
+- 既存の説明と同じレベルの詳細度
+
+## 検証項目
+
+### 実装後の確認
+1. マークダウンの構文が正しいか
+2. コード例が実行可能か
+3. 既存のドキュメントと自然に統合されているか
+4. アンカーリンク（`#regexp-escape`）が機能するか
+
+### テスト
+- doctestが通るか確認（ES2025環境）
+- ビルドエラーがないか確認
+
+## リスクと対策
+
+### リスク1: ES2025のブラウザサポート
+**対策**: doctestメタデータで明示的にES2025を指定し、サポート状況を示す
+
+### リスク2: 既存の説明との重複
+**対策**: 正規表現の特殊文字の説明は最小限にし、既存の説明を参照
+
+## 完了基準
+- [ ] 正規表現オブジェクトセクションに`RegExp.escape`の説明を追加
+- [ ] replaceメソッドのコード例に`RegExp.escape`の使用例を追加
+- [ ] doctestメタデータを適切に設定
+- [ ] マークダウンのビルドが成功
+- [ ] 既存のドキュメントと自然に統合されている
\ No newline at end of file
diff --git a/source/basic/string/specs/add-regexp-escape-docs/requirements.md b/source/basic/string/specs/add-regexp-escape-docs/requirements.md
new file mode 100644
index 000000000..c0fc5469c
--- /dev/null
+++ b/source/basic/string/specs/add-regexp-escape-docs/requirements.md
@@ -0,0 +1,50 @@
+# 要件ファイル: RegExp.escape メソッドのドキュメント追加
+
+## 概要
+ES2025で追加される`RegExp.escape`メソッドについて、js-primerの文字列の章にドキュメントを追加する。
+
+## 機能要件
+
+### 1. ドキュメントの追加位置
+- `/source/basic/string/README.md`ファイルに追加
+- 正規表現関連のセクション（文字列の検索や置換の文脈）に追加
+
+### 2. 説明内容
+- `RegExp.escape`メソッドの基本的な説明
+- メソッドの目的：正規表現の特殊文字を安全にエスケープする
+- ES2025で追加される新機能であることを明記
+
+### 3. コード例
+- 基本的な使用例を提供
+- `replaceAll`メソッドと組み合わせた実用的な例
+- エスケープが必要な特殊文字の例（例：`?`、`.`、`*`など）
+
+### 4. 説明のトーン
+- 初心者にも理解しやすい簡潔な説明
+- 必須ではないが便利な機能であることを伝える
+- 複雑になりすぎない程度の説明に留める
+- 既存の文章を参考にする source/basic/string/README.md を合わせる
+
+## 非機能要件
+
+### 1. 文書の一貫性
+- 既存のjs-primerの文体・形式に合わせる
+- 他のメソッドの説明と同じレベルの詳細度
+
+### 2. コード例の品質
+- 実行可能で正確なコード例
+- コメントを適切に付けて理解しやすくする
+
+### 3. 互換性の言及
+- ES2025の機能であることを明確にする
+- 必要に応じてブラウザサポート状況への言及
+
+## 制約事項
+- 軽い説明に留める（issueの要望通り）
+- 過度に技術的な詳細は避ける
+- 初心者向けのプライマーの性質を維持する
+
+## 成功基準
+- `RegExp.escape`の基本的な使い方が理解できる
+- なぜこのメソッドが便利なのかが伝わる
+- 既存のドキュメントと自然に統合されている


Test patch:
diff --git a/test/front-matter-test.js b/test/front-matter-test.js
index 25f63db24..bfdf699bc 100644
--- a/test/front-matter-test.js
+++ b/test/front-matter-test.js
@@ -22,6 +22,8 @@ describe("front-matter", function() {
         `!${sourceDir}/**/SCREENSHOT.md`,
         // サンプルコードの一部
         `!${sourceDir}/use-case/nodecli/**/src/**/sample*.md`,
+        // specsディレクトリは除く
+        `!${sourceDir}/**/specs/**/*.md`,
     ]);
     describe("author", function() {
         files.forEach(filePath => {
diff --git a/test/markdown-doc-test.js b/test/markdown-doc-test.js
index 854b00e05..e9697f8ed 100644
--- a/test/markdown-doc-test.js
+++ b/test/markdown-doc-test.js
@@ -60,6 +60,7 @@ describe("doctest:md", function() {
     const files = globbySync([
         `${sourceDir}/**/*.md`,
         `!${sourceDir}/**/node_modules{,/**}`,
+        `!${sourceDir}/**/specs/**/*.md`,
         `!**/OUTLINE.md`
     ]);
     files.forEach(filePath => {