typoをJetBrainのSpell CheckerでCLIチェックする

フューチャー2アドベントカレンダー4日目です。

3日目は@tutuzさんの[Transit Gatewayを導入するときに気をつけたいポイント4選](https://qiita.com/tutuz/items/71a0407717e2f17d9ea8)、5日目は@Khdbbleさんの[EbitenでImage操作 ~キャラクターを動かす~](https://qiita.com/Khdbble/items/491a48effc8dd4bd1133)です。

# typoとは

各単語に対してのスペルミスやタイプミスのことで、語源は「typographical error（タイポグラフィカル エラー）」だそうです。QiitaとQitaと書いちゃうとか、人によってはAWSのLambdaをlambdaと書くとダメだよねといった大文字小文字の表現についてもtypoに含める人がいると感じます。


# Spell Checkerについて

人間である以上はミスタイピング・ミススペルが絶対発生しますよね。どんなに気をつけたとしても一定の割合で出てしまうと思います。

自分1人のプロジェクトであればよいですが、レビュアーや他のメンバーがコードを読むとときにtypoに気がついて指摘したり直したりすると大変なので、古来から[スペルチェッカ](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%9A%E3%83%AB%E3%83%81%E3%82%A7%E3%83%83%E3%82%AB) というツールが作られたりしました。

VSCodeであれば、[Code Spell Checker](https://qiita.com/diescake/items/98c5a099e85775cd917d) を導入すると便利そうです。存在しないと思われる単語を入力すると、リアルタイムで検証してくれるため人間とツールのダブルチェックでミスが減らせそうです。

# JetBrainのSpell Checker

私は[Goland](https://www.jetbrains.com/go/)や[IntelliJ](https://www.jetbrains.com/idea/)などJetBrain製のIDEを利用することが多いです。JetBrain製のIDEは(おそらく標準で)スペルチェックが機能として持っていて、他のエディタで書かれたコードを落としてくると、けっこうな割合でtypoに気がついて便利だなって思うことが多いです。

機能としては次のリンクの感じで、デフォルトのバンドルされた辞書での検証もできますし、波線になった単語をシームレスに追加登録して、以後検証されなくなることもでき重宝します。
https://pleiades.io/help/go/spellchecking.html


# もっと気軽にJetBrainのtypoチェックを使いたい

個人的にはお値段以上の価値がありますが[Golandは有償](https://www.jetbrains.com/go/buy/#commercial?billing=yearly)ですし、VSCodeファンなメンバーも多いため全員にJetBrain製のIDEを入れてもらうのは難しそうです。

また、自分がコードレビュー時にいちいちGolandなどのIDEにPJをImportするのも面倒なので、気軽にCLIでチェックできたらなと思っていました。IntelliJやGolandなど（おそらく他のプロダクトも）は、[コマンドラインからのIntelliJ IDEA機能の操作﻿](https://pleiades.io/help/idea/working-with-the-ide-features-from-command-line.html) にあるように、 Windowsであれば `C:\Program Files\JetBrains\GoLand 2019.3\bin` などにPathを通すと bat コマンド経由でGolandの機能が叩けます。もちろんMacでも可能です。

Spell Checker（typoチェック）には [コマンドラインからのコードインスペクションの起動
](https://pleiades.io/help/idea/command-line-code-inspector.html) を利用します。

インスペクションとは、コードなどの妥当性を検証する意味です。Golandであればtypoチェック以外にも、どこからも呼び出されていない関数が無いかチェックなど様々なチェックも同時に行っています。


# インスペクションコマンドの起動

本家のドキュメントに詳細が書いていますが、以下のようなコマンドです。

```bash
inspect project-path inspection-path output-path [-vX] [-d subdirectory-path]
```

`project-path` はチェックしたいフォルダです。カレントディレクトリ以外は絶対パスを指定する必要があります。 `inspection-path` は見慣れないですが JetBrain製IDEユーザであれば `.idea/inspectionProfiles` がデフォルトで作成されますが、それと同様のファイルの出力場所を指定します。output-pathは検証結果で、全てXMLで出力されます。

実際にとある開発中の小さなWebAPIのプロジェクトをインスペクトしてみます。

```bash
cd %GOPATH%/src/github.com/<Your Orgzanition>/<Your Project>
inspect ./ .idea/inspectionProfiles ./output
```

すると、いくつかのXMLの結果ファイルが出力されます。

```bash
Annotator.xml
BashUnresolvedVariable.xml
GoBinaryAndUnaryExpressionTypesCompatibility.xml
GoDuplicateCase.xml
GoExportedFuncWithUnexportedType.xml
GoInvalidCompositeLiteral.xml
GoRedundantImportAlias.xml
GoTypesCompatibility.xml
GoUnresolvedReference.xml
GoUnusedConst.xml
GoUnusedExportedFunction.xml
GoUnusedParameter.xml
JsonStandardCompliance.xml
SpellCheckingInspection.xml
```

このうち、今回利用したいのは `SpellCheckingInspection.xml` です。名前がそのままでわかりやすいですね。

結果をいくつか抜粋してみると、、

```xml:SpellCheckingInspection.xml
<problem>
  <file>file://$USER_HOME$/Go/src/github.com/Your-Organization/Your-Project/handler/my_handler.go</file>
  <line>39</line>
  <module>Your-Project</module>
  <entry_point TYPE="file" FQNAME="file://$USER_HOME$/Go/src/github.com/Your-Organization/Your-Project/handler/my_handler.go" />
  <problem_class severity="TYPO" attribute_key="TYPO">Typo</problem_class>
  <description>Typo: In word 'errror'</description>
  <highlighted_element>errror</highlighted_element>
  <offset>44</offset>
  <length>6</length>
</problem>

<problem>
  <file>file://$USER_HOME$/Go/src/github.com/Your-Organization/Your-Project/docs/my_datamodel.md</file>
  <line>7</line>
  <module>Your-Project</module>
  <entry_point TYPE="file" FQNAME="file://$USER_HOME$/Go/src/github.com/Your-Organization/Your-Project/docs/my_datamodel.md" />
  <problem_class severity="TYPO" attribute_key="TYPO">Typo</problem_class>
  <description>Typo: In word 'Surces'</description>
  <highlighted_element>Surces</highlighted_element>
  <offset>56</offset>
  <length>6</length>
</problem>
```

などがツラツラ出力されています。
1つ目は、おそらく `error` と書くべきところが `errror` とtypoしています。errrorがerrorだよとか思いますね。なんだか目が痛くなりますね。

2つ目は、コードではなくMarkdownですが、単語だけだとわけが分からなかったので元のファイルを見て文脈から察すると、`Sources` と書きたかったのが `Surces` とtypoしたようです。

このようにして、CLI経由でtypoを検出することができました。


# 単語を辞書に登録する

このままでも一応はtypoチェックとして機能はしますが、実際の開発フローに組み込むには乗り越えるまだまだ工夫が必要です。

今回でいうと、`Surces` といった単語が今回の業務ドメインで用いる場合、typoとして検知したくないでしょう。その場合は辞書登録すれば検知対象外となります。

辞書登録の方法は、 `inspection-path` 直下に `dictionaries` というフォルダを置き、そこに単語登録用のXMLを配備します。今回は `dictionaries/cli.xml` というファイル名にしました。

```xml:dictionaries/cli.xml
<component name="ProjectDictionaryState">
  <dictionary name="cli">
    <words>
      <w>Surces</w>
    </words>
  </dictionary>
</component>
```

辞書ファイルを配備し、もう一度 `inspect ./ .idea/inspectionProfiles ./output` コマンドを叩きます。そうすると、`Surces` は無視されるようになります。

```bash
$ grep Surces SpellCheckingInspection.xml | wc -l
0
```

単語リストメンテナンスの運用フローは検討する必要がありそうですが、最低限の回避はできそうです。


# 対象からgo.sumを除外する

PJルートを指定すると、go.sumなどチェックしなくても良いファイルが大量にtypoチェックで検知されます。typoというよりはGitHubのユーザIDだったりするのですが、存在しない単語なので大量に検知されます。

`-d subdirectory-path` で検査対象のディレクトリを指定することはできても、スキップ指定するオプションは存在しないようです。そのため、-dで個別に検査対象のディレクトリを指定するか、出力されたファイルに対して自前で除外する必要があります。個別にディレクトリ指定はさすがに大変なのと実行時間がかさむので、出力ファイルからあるディレクトリやファイルの結果を無視する方が筋が良さそうです。

inspectコマンドはドキュメントには記載されていませんが、`--format json` でJSONレイアウトで出力もできます。

```go
inspect . .idea/inspectionProfiles ./output -format json
```

出力された `SpellCheckingInspection.json` に対して、 `jq` で指定された拡張子（今回だと.goと.md）だけに絞り込みます。 `jq*の `select` 機能については[こちら](https://qiita.com/ttiger55/items/150e9a18313a55841a32) の記事が参考になりました。

```bash
cat SpellCheckingInspection.json | jq -r '.problems[] | select(.file | test("(.*.go$|.*.md$)"))'
```

これでgo.sumなど気にしなくても良いファイルを除外することができました。

いくつか繋ぎ込みが必要そうですが、GitHub ActionなどCI組み込みも見えてくると思います。その場合はライセンスが問題になってくるので、同時に複数のマシンで起動しないようにするか、専用のライセンスを購入するかなどよく確認しましょう。


# まとめ

* typoはSpell Checkツールでなるべくツール側で検知させると幸せになれる
* JetBrain製のSpell CheckerはCLIからも利用できる
* 辞書登録も可能で簡単に使えるが、対象ファイルの指定はひと手間必要