たった４文字でコード検索の精度がブチあがる正規表現

:::note info
この記事は、不動産情報サイト [LIFULL HOME'S](https://www.homes.co.jp/) などを運営する[株式会社LIFULL](https://lifull.com/) の **[LIFULL Advent Calendar 2022](https://qiita.com/advent-calendar/2022/lifull)** の記事です。
:::

まだまだ入社1年目、株式会社LIFULLの暴れ回る新卒、 @pal4de です。
正規表現が大好きです。

これは、その中でもとくにお気に入りな正規表現「単語境界 `\b`」の検索における威力を説き、褒め称える記事です。
_「正規表現、まァ基本は一通り勉強したな...」_ というあなたにオススメしたい、ステップアップにピッタリなヤツです。

## これは何？

改めて、 正規表現パターン `\b` は「**単語境界**」を表す記法です[^実装依存]。
Word **B**oundaryのBですね。
ここでは、「単語」は**単語構成文字** `\w` = `[A-Za-z0-9_]` の連続と定義します[^ロケール依存]。

[^実装依存]: 言語や処理系によって実装依存が激しい正規表現ですが、ほとんどのケースで使えます！！！

[^ロケール依存]: 正規表現の処理系によってはロケールに依存しますが、あまり心配しなくても多分大丈夫

### 挙動の例

実際の挙動を見てみるとこんな感じ[^ignore-case]。

[^ignore-case]: 大文字・小文字を区別しないものとして見て下さい

| 正規表現 | 検索対象文字列 | マッチする位置 |
|:--|:--|:--|
| `\b` | let count; | <b><font color="ed6103">\|</font></b>let<b><font color="ed6103">\|</font></b> <b><font color="ed6103">\|</font></b>count<b><font color="ed6103">\|</font></b>;  |
| `\b` | snake_case | <b><font color="ed6103">\|</font></b>snake_case<b><font color="ed6103">\|</font></b> |
| `\b` | kebab-case | <b><font color="ed6103">\|</font></b>kebab<b><font color="ed6103">\|</font></b>-<b><font color="ed6103">\|</font></b>case<b><font color="ed6103">\|</font></b> |
| `\b` | 1: IMAGE001 | <b><font color="ed6103">\|</font></b>1<b><font color="ed6103">\|</font></b>: <b><font color="ed6103">\|</font></b>IMAGE001<b><font color="ed6103">\|</font></b> |
| `\bimage\b` | var image, imageSize, yourImage; | var <b><font color="ed6103">image</font></b>, imageSize, yourImage; |

アンダースコア`_`の扱いがやや意外ですが[^アンダースコア]、おおよそ直感的ではないでしょうか。ご覧の通り「単語の両端にマッチする」雰囲気が感じ取れます。

[^アンダースコア]: 多くの言語ではこれらの文字が連続が変数や関数名として扱われることからこのようになっていると考えられます。「単語」という名前が誤解を与えている

文頭`^`や文末`$`などと同様、`\b`は文字そのものではなく**文字の隣**にマッチする表現です[^アンカー]。

[^アンカー]: このような表現をアンカーと呼びます。ちなみに`.`とか`[a-z]`は文字クラスと言います。

特に嬉しさを表現しているのが一番最後の`image`の例。
パターン `\bimage\b` は`image`の両サイドに単語境界`\b`が引かれている表現となっています。
つまり、日本語に意訳するなら「ちょうどピッタリ`image`にマッチする」ぐらいの意味になります。

## 何がうれしい？

先ほど挙げた例で、grepやVSCodeのファイル横断検索を使用したことがあるなら嬉しさがわかってくるのではないでしょうか。

そう、Language Serverなどによるリッチな言語サポートがなくとも、`\b`の 2文字×2=4文字 で変数名や関数名などのトークンの**厳密な一致による検索**が行えるようになるわけです。
この単語境界`\b`は、**新しい[^github-code-search][GitHub Code Search](https://github.com/features/code-search)でも使用可能**というのですから喜びはひとしおです。

[^github-code-search]: 2022年12月現在、パブリックプレビューが公開中

もう少し具体的な例として `/\buser\b/i` をした例を示します。

下記のように**プレフィックスやサフィックスに惑わされない検索**が行えるようになり、検索結果ノイズもグッと減ります。

| マッチする   | マッチしない             |
|:-------------|:-------------------------|
| $<font color="ed6103">\|</font>user<font color="ed6103">\|</font> =      | $user_type =             |
| class <font color="ed6103">\|</font>User<font color="ed6103">\|</font>() | class UserOrganization() |
| "./<font color="ed6103">\|</font>user<font color="ed6103">\|</font>.php" | "./special_user.php"     |


### そして次回予告へ

さらに、「変数はいらないなあ...」とか「CSSのクラス名だから`-`の場合は区切りとみなしたくないなあ...」みたいな場合には検索クエリに**肯定先読み**、**否定先読み**、**肯定後読み**、**否定後読み**を組み合わせて検索結果をさらに絞り込んでいくことになります。

みんなが苦手なヤツです。羅列の仕方もイヤな感じを演出しています。

「`(肯定|否定)[先後]読み`編」もぜひご覧ください👇

https://qiita.com/pal4de/items/7e2292cfd7ac424e0836

## まとめ

- `\b`は文字ではなく、**単語境界**を表します。
- 単語とは`[A-Za-z0-9_]+`で表されるものです。
- 正規表現を使った検索時、`\b`で両端を挟んでやることにより、プレフィックス等が紛れ込まなくなり、regex searchが捗ります。

----

[弊社アドベントカレンダー](https://qiita.com/advent-calendar/2022/lifull)の他の記事もお楽しみください👇

https://qiita.com/advent-calendar/2022/lifull

----

## オマケ

### 等価な表現

`\b`は`(?<=\W|^)(?=\w)|(?<=\w)(?=\W|$)`と等価です。恐らく。

日本語に書き下すなら次のような感じです。

| 断片 | 日本語訳 |
|:-|:--|
| `(?<=\W\|^)` | マッチ開始位置より左隣が「非単語構成文字または文字列先頭」 |
| `(?=\w)` | この位置より右隣が単語構成文字 |
| `\|` | または |
| `(?<=\w)` | この位置より左隣が単語構成文字 |
|`(?=\W\|$)` | この位置より右隣が「非単語構成文字または文字列終端」 |

### 逆の表現 `\B`

ちなみに、`\B`で逆の意味、つまり「単語の境界以外」になります。

使ったことはありません。

大文字で逆の意味になるのは正規表現あるあるですね。大変大変覚えやすい。

---

ここまで読んでくれてありがとう 😘