pre-commitとtextlintを使ってHTMLに潜むゼロ幅スペース文字や表記ゆれを倒す

## 概要

括弧や数字の全角半角が表記揺れしていたり、`href`内にゼロ幅スペースが紛れ込んでいてリンク切れが起こってしまったりすることをtextlintを使って防ごうという記事です。

（[サクッとページ単位で確認できればいい方はこちら](https://qiita.com/soten_bluesky/items/4458dc21fc715cde5bc9)）

## textlintとは？

文章の校正をしてくれるリンターツールです。漢字よりひらがなのほうがいい場合や、「てにをは」がおかしいような文章を指摘してくれます。

## 導入手順

### 1. プロジェクトの作成

※ この工程は`package.json`を生成するものです。すでにある方はスキップしてください。

```shell
npm init
```

### 2. パッケージのインストール

```shell
npm install --save-dev textlint textlint-rule-prh # textlint関係
npm install --save-dev husky lint-staged # pre-commit関係
```

- [textlint](https://www.npmjs.com/package/textlint)：textlint本体
- [textlint-rule-prh](https://www.npmjs.com/package/textlint-rule-prh)：textlintで使う辞書を読み込めるようにするためのもの
- [@textlint/textlint-plugin-text](https://www.npmjs.com/package/@textlint/textlint-plugin-text)：もともとtextlint本体に付属するプレーンテキスト用のプラグイン

### 3. pre-commitが動作するように設定する

`package.json`に次の項目を追記してください。

```json
{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.html": "textlint"
  }
}
```

### 4. textlintの設定ファイルを作成

```shell
./node_modules/.bin/textlint --init
```

作成された`.textlintrc`ファイルの中身を次のように上書きしてください。
これでtextlintがHTMLファイルを読むようになり、オリジナルの辞書を利用するようになります。

※今回は使いませんがHTMLを読むためのプラグイン「`textlint-plugin-html`」については後述します。

```json
{
  "filters": {},
  "rules": {
    "prh": {
      "rulePaths": ["./prh.yml"]
    }
  },
  "plugins": {
    "@textlint/text": {
        "extensions": [".html"]
    }
  }
}
```

### 5. 辞書ファイルを作成

`package.json`と同じ階層に`prh.yml`というファイルを作成します。
中身を次のように設定してみてください。

```yml
version: 1
rules:
  - expected: VS Code
    patterns: VSCode
    prh: 正しい表記はVS Code

  - expected: ""
    patterns: "/[\\u034f\\u200b\\u200c\\u200d\\u200e\\u200f\\u2028\\u2029\\u202a\\u202b\\u202c\\u202d\\u202e\\u2061\\u2062\\u2063\\ufeff]/"
    prh: ゼロ幅スペースを検出しました
```

項目を増やしたいときは次の塊を追加することで対応できます。`patterns`には正規表現も利用できますし、リストで複数項目設定することもできます。

```yml
  - expected: 正しい表記
    patterns: 検出する表記
    prh: エラーメッセージ（説明文）
```

[参考：prh/prh.yml at master · prh/prh](https://github.com/prh/prh/blob/master/misc/prh.yml)

### 6. 試してみる

プロジェクト内に適当なHTMLファイルを作成します。たとえば、`test.html`など。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<p>私はVSCodeが好きです</p>
<p>ここに→【‪】←ゼロ幅スペース文字がいます</p>
</body>
</html>
```

このHTMLファイルが`package.json`と同じ階層にあるとき、次のようなコマンドでリンターに検査させられます。

```shell
./node_modules/.bin/textlint test.html
```

するとおそらく次のようなログがでるはずです。

```
D:\dummy\dummy\test.html
  10:6  ✓ error  VSCode => VS Code
正しい表記はVS Code           prh
  11:9  ✓ error  ​ =>
ゼロ幅スペースを検出しました  prh

✖ 2 problems (2 errors, 0 warnings)
✓ 2 fixable problems.
Try to run: $ textlint --fix [file]
```

出ない場合は、`node_modules`フォルダを削除し、`npm i`コマンドを実行してみてください。

### 7. コミットしてみる

うまくpre-commitが動作していれば次のようなエラーが出てコミットが中断されるはずです。
こちらもうまく出ない場合は、`node_modules`フォルダを削除し、`npm i`コマンドを実行してみてください。

#### Sourcetree
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/e24ceb34-a085-cc39-894f-12144554a565.png)
#### TortoiseGit
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142709/f781503b-766d-c2b3-e484-2b1d0936f46f.png)

## いろいろ試してみよう

検出には正規表現も使えるので、他にも様々な表記ゆれや誤実装を回収できそうです。

```yml
  - expected: （
    patterns: (
    prh: 全角括弧を利用してください

  - expected: ）
    patterns: )
    prh: 全角括弧を利用してください
```

などなど…。Webサイトの運用で表記ゆれやゼロ幅スペースにお困りの場合は試してみてください。

### タグを無視する`textlint-plugin-html`

HTMLのタグを無視させたい場合は`textlint-plugin-html`を一緒にインストールしてください。

```shell
npm i -D textlint-plugin-html
```

`.textlintrc`の設定は次のようになります。

```json
{
  "filters": {},
  "rules": {
    "prh": {
      "rulePaths": ["./prh.yml"]
    }
  },
  "plugins": [
    "html"
  ]
}
```

なお、どうやら`&copy;`などは「©」として解釈されるようなので、不要な実態参照を検出するルールはできない模様。たぶん。おそらく。


### ~~検出されない場合がある（追記）~~

~~コードの順番など、特定のケースでうまく検出されない場合がある模様。調査中。~~
~~https://github.com/textlint-rule/textlint-rule-prh/issues/74~~

問題解決済みバージョン：[textlint 11.6.2](https://github.com/textlint/textlint/releases/tag/textlint%4011.6.2)



## 参考文献

- [2. スペースは" "だけじゃない的な話](http://anti.rosx.net/etc/memo/002_space.html)
- [VS Codeでtextlintを使って文章をチェックする](https://qiita.com/azu/items/2c565a38df5ed4c9f4e1)
- [textlint + prhで表記ゆれを検出する](https://efcl.info/2015/09/14/textlint-rule-prh/)
- [husky + lint-staged で品質を保つ](https://hysryt.com/archives/1295)
- [huskyをインストールしたのに、pre-commitフックが動かない時にみる場所と対応方法](https://wp-kyoto.net/how-to-resolve-husky-not-working-issue)
- [prh/prh.yml at master · prh/prh](https://github.com/prh/prh/blob/master/misc/prh.yml)（prh.ymlの設定の見本）