お手軽構築！docker-compose+Circle CI環境下でのスペルチェック環境

## この記事 is 何

ソースコードをcommit/push（後の本流へmerge）した後に「あっ、これtypoしてる...」という経験はありませんか？
英語が苦手な私はよくあります。VSCodeの拡張機能でスペルチェッカーを入れてますが、それでも見落としがちです...
そのようなときにCIで自動で検出/ブロック出来たらいいですよね！

この記事では、その対策方法について解説しました。

## この記事では解説しないこと
解説している素晴らしい記事はたくさんあるので、既存の記事にお任せしました！

 - Docker、Docker-composeの導入方法/解説
 - Circle CIとは？
 - githubとCircle CIの連携方法
 - Visual Studio Codeの導入方法/解説

## 動作環境

 - Local環境
   - Windows 10
   - Docker Desktop 2.2.0.4
   - docker-compose 1.25.4
   - Visual Studio Code 1.66.1
     - Code Spell Checker 2.1.11
 - github
   - Freeプラン
 - Circle CI
   - Freeプラン
   - version: 2.1
   - machine: circleci/classic:edge
     - 非推奨のため、今後変更予定ですが、本記事には影響しないはず...

## ディレクトリ構成
```
/
├ .circleci
│ └ config.yml
├ app
│ └ アプリ本体
├ cspell
│ └ project-words.txt
├ docker
│ ├ app
│ │ └ Dockerfile
│ ├ spell_checker
│ │ └ Dockerfile
│ └ ...
├ cspell.json
└ docker-compose.yml
```

appディレクトリ以下に実際のアプリを記述する構成を使っていますが、本記事の内容はroot直下に直接記述する方式でも採用可能です。
cspellについては記事内で説明します。

（閑話: 上記のツリーは [AsciiTree](https://codogue.com/asciitree/) を使用して作りました。手軽にツリーかけるので便利ですよ！）

## 環境構築方針
 - docker + docker-composeを使う
   - ローカル環境を汚したくない
     - 今回使用するライブラリの都合上、node.jsの環境が必要
 - Visual Studio Code（以下VSCode）上でのスペルチェックと連動させる
 - VSCodeを使っていない開発者がいる場合でも、同様にスペルチェックできるようにする
   - やっぱり自分の手慣れた環境で開発したいですよね！
 - Circle CIでtypoがあった場合は自動でこけるようにする

## 実際に導入してみよう！

まずはローカル環境の構築から。

### 1. VSCodeにスペルチェッカーを導入
（VSCode使用者向け）VSCodeにはスペルチェッカーのCode Spell Checkerという素晴らしい拡張機能が存在します。まずは、下記の記事参考に導入しましょう。
[VSCode に Code Spell Checker を導入して typo と戦う](https://qiita.com/diescake/items/98c5a099e85775cd917d) 

### 2. スペルチェック用のDockerコンテナを導入
docker経由でコマンドライン上からでもスペルチェックをできるようにしましょう。
VSCode拡張のCode Spell Checkerのベースとなっている、 [cspell](https://cspell.org/) を使用します。
プロジェクトの `docker-compose.yml` にスペルチェック用のコンテナの定義を追加します。

```docker-compose.yml
services:
    spell_checker:
        build: ./docker/spell_checker
        tty: true
        volumes:
            - ./:/usr/src/app
```

```Dockerfile:docker/spell_checker/Dockerfile
FROM node:latest

RUN npm install -g cspell

WORKDIR /usr/src/app
```

`volumes`でプロジェクト全体を共有することにより、スペルチェックをできるようにするのがポイント。

:::note info
`build`での指定パスはプロジェクトの流儀に合わせてください。本記事のプロジェクトでは、Docker周りの設定は`docker/[containerName]/...`に置くようにしています。
:::

### 3. コンテナ上でスペルチェックを実行
2.の手順でコンテナを定義、以下のように実行することでスペルチェックができるようになっているはずです！

```sh:command line
 # コンテナの立ち上げ（コンテナが上がってないときに1回実行する）
docker-compose up -d cspell

 # スペルチェックの実行
 # 今回は.php, .jsファイルへのスペルチェックを想定
 # すべてのファイルを対象とするには "**" を引数に実行する（割と重い。対象の拡張子がわかっている場合は、拡張子を指定したほうが無難かも）
docker-compose exec spell_checker cspell "**/*.php" "**/*.js" --gitignore
```

実行結果
```sh:command line
PS D:\sources\project> docker-compose exec spell_checker bash
root@f1c7d382e567:/usr/src/app# cspell "**/*.php" "**/*.js" --gitignore
  1/225 ./laravel/app/Console/Commands/Make/MakeGraphqlDefinitionCommand.php 537.01ms
  2/225 ./laravel/app/Console/Commands/Make/MakeModelCommand.php 7.38ms
  ...
  225/225 ./mock/webpack.config.js 16.85ms
  CSpell: Files checked: 225, Issues found: 1 in 1 files
```

cspellを実行する際に `--gitignore` を引数で渡すのがポイントです。これをすることでプロジェクト内の `.gitignore` が読み込まれ、 `node_modules` など、プロジェクトのリポジトリ上では管理しないファイルをチェック対象外にすることができます。

他にも様々なオプションがあるので、使いこなしたい方は公式ドキュメントを読んでみてください！

### 2,3-番外 ローカルのnode.jsを使用する
node.jsがインストールされているよ！という方は、 `cspell` パッケージをインストールするだけでもローカルで実行可能です。

```sh:command line
npm install -g cspell
```

スペルチェックの実行方法

```sh:command line
cspell "**/*.php" "**/*.js" --gitignore
```

### 4. 例外ワードを設定できるようにする

スペルチェックにかけていると、ソース内の固有名称や、コマンド名、関数名などでスペルチェックに引っかかる場合が出てきます。
スペルチェッカー側に「この単語はあっているよ！」という情報を伝える設定ファイルを定義しましょう。

まず、プロジェクトのrootに `cspell.json` を作成します。

```cspell.json
{
    "version": "0.2",
    "dictionaryDefinitions": [
        {
          "name": "project-words",
          "path": "./cspell/project-words.txt",
          "addWords": true
        }
    ],
    "dictionaries": ["project-words"]
}
```

`project-words` という辞書を作成し、使用するといった内容が記載されています。（詳細は [公式ドキュメント](https://cspell.org/configuration/) をご参照ください。）
次に、辞書ファイル（許可する単語帳）を作成します。
プロジェクトのroot配下に `cspell` ディレクトリを作成し、 `project-words.txt` を作成し、許可する単語を改行区切りで記述していきます。

```cspell/project-words.txt
laravel
graphql
...
```

:::note info
登録する単語が多くてだるい...と思いましたか？次の手順でプロジェクト内の既存の未登録単語は一括出力できるので、ご安心ください！
:::

:::note info
VSCodeの拡張機能のCode Spell Checkerは開いているフォルダ直下の `cspell.json` を自動で見てくれるため、ここでの設定は共有されます。便利！
:::

### 5. プロジェクト内の未登録単語を辞書に登録

導入時のプロジェクト内には、未登録単語がいくらかあると思います。
なので、未登録単語を抽出し、辞書へ登録します。
以下のコマンドを実行することで、未登録単語一覧を出力できます。

```sh:command line
 # --words-only: 未登録単語のみ出力
 # --unique: 重複を削除
 # --no-progress: 進捗を非表示（チェック対象のファイル名などを非表示）
docker-compose exec spell_checker cspell "**/*.php" "**/*.js" --gitignore --words-only --unique --no-progress
```

すると、以下のように単語一覧が出力されるので、結果をproject-words.txtにコピペします。

```sh:command line
PS D:\sources\project> docker-compose exec spell_checker cspell "**/*.php" "**/*.js" --gitignore --words-only --unique --no-progress
koyuu
hogehoge
fugafuga
CSpell: Files checked: 225, Issues found: 3 in 2 files
```

```cspell/project-words.txt
koyuu
hogehoge
fugafuga
```

また、この手順で本当のtypoがあった場合、githubでのtypo修正のissueを立てるといいでしょう。

### 6. Circle CIへスペルチェックのテストを定義

これで、スペルチェックをする方法は確立できたので、CIでの自動テストにスペルチェッカーの登録を行います。
Circle CIの設定ファイルに、スペルチェックを行うstepを登録します。

```.circleci/config.yml
jobs:
  test:
    steps:
      - ...(既存の処理）
      # docker-composeのコンテナ立ち上げの処理が、既存の処理になかった場合、下記を追加
      - run: docker-compose up -d spell_checker
      - run:
          name: spell check
          command: docker-compose exec spell_checker cspell "**/*.php" "**/*.js" --gitignore
          when: always
```

scpellは未登録単語が検出した場合に、終了ステータスが`1`、検出されなかった場合`0`になるため、そのまま流せば実行できます。

また、「辞書ファイル散らからない・・・？」と思う方もいるでしょう。その場合、最低限の秩序を保つために、並び替えがされているかをテストするのも有効かと思います。

```.circleci/config.yml
jobs:
  test:
    steps:
      - ...(既存の処理）
      - run:
          name: is spell check project dictionary sorted
          # note: diffコマンドは差分がある場合、終了ステータスが1になるので、ソート前と後に差分があったらCI上でエラーとみなされる
          command: diff ./cspell/project-words.txt <(cat ./cspell/project-words.txt | sort)
          when: always
```

## 実際の導入例
私の個人プロジェクトへ導入した際の差分になります。（専用のリポジトリ用意はできてないです。申し訳なし）
導入前にtypoしてますね...

アプリ本体のコードもあるので、ファイル多いですが、参考にどうぞ！
https://github.com/moriyadetteiu/clapton/pull/160/files

## まとめ

というわけで、typo撲滅を自動化できる部分は自動化しようぜ！という記事でした。
既存の記事を拝見してた時に「あれ、VSCodeとcircle CIでうまく連携するスペルチェックの記事見かけないな」と思ってたので、同じこと思ってた人の助けになれれば幸いです！
はじめてのQiita投稿のため、至らないところがあるかと思いますが、その際はご指摘いただけると嬉しいです！

現在転職活動をする予定のため、お声がけありましたら、お待ちしております。

## 参考記事、参考サイト

 - [cspell](https://cspell.org/)
   - cspellの公式ドキュメント
 - [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)
   - VSCodeの拡張機能`Code Spell Checker`のマーケットプレイス
 - [僕たちは英語ができない](https://qiita.com/mima_ita/items/579fcc3a65cee4ef9b01)
   - スペルチェックのベース部分を参考にさせていただきました。感謝
 - [VSCode に Code Spell Checker を導入して typo と戦う](https://qiita.com/diescake/items/98c5a099e85775cd917d)