Prettierに関する小ネタ

## はじめに

以下では[Prettier](https://prettier.io/)に関する小ネタをまとめる。

## 設定ファイルを楽に書く

Prettierは以下のいずれかの形式で設定する ([Configuration File · Prettier](https://prettier.io/docs/en/configuration.html)より):

* `package.json`ファイル内の`"prettier"`キー。
* JSONかYAMLで書かれた`.prettierrc`ファイル。ファイル名に次の拡張子を付けることも可能: `.json/.yaml/.yml` 
* オブジェクトをエクスポートする`.prettierrc.js`か`prettier.config.js`。
* TOMLで書かれた`.prettierrc.toml`ファイル (`.toml`拡張子は必須) 。

Prettierのオプションに関しては以下にドキュメントが存在する:

[Options · Prettier](https://prettier.io/docs/en/options.html)

それほど数は多くないためすべて目を通すことも大変ではないが、Prettierのウェブサイト内には[Playground](https://prettier.io/playground/)という、ビジュアルに設定ファイルを生成することができる機能があるので、手っ取り早く設定ファイルを作成したい場合は利用するといい。

![Screenshot from 2019-12-02 02-45-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82777/2b61a4c9-fb4c-5a9e-36c4-d9ab543a3501.png)

エディタが左右に分割されており、左側のエディタがフォーマット前のコードを、右側のエディタがフォーマット後のコードをそれぞれ表わしている。タブのサイズやセミコロンの有無など、フォーマット時の設定を決めているのが一番左側にあるペインで、ここで各オプションを選択すると、すぐに右側のエディタにそのオプションを選択した場合のフォーマット結果が反映される。

フォーマット結果に満足できたら、最下部にある"Copy config JSON"をクリックすると、クリップボードに設定用のJSONがコピーされるので、あとは設定ファイルにペーストすればいい。

Playgroundを共有する機能などもあるため、チームで設定を議論するための場としても使うことができる。

## `eslint-config-prettier`でESLintの不要なルールを取り除く

Linterとして[ESLint](https://eslint.org/)を併用している人は多いと思うが、フォーマットに関するルールがPrettierの設定と衝突する。こうしたPrettierと競合するルールを無効化するためのライブラリに、`eslint-config-prettier`がある。

インストール方法:

```sh
$ npm install --save-dev eslint-config-prettier
```

ESLintの設定ファイルに以下を記述する:

```json
{
  "extends": [
    "他の設定",
    "prettier"
  ]
}
```

これでESLintの不要なルールが無効化される。なお、他の設定をオーバーライドできるように、必ず配列の末尾に"prettier"を記入する必要がある。

## Husky、`lint-staged`との連携

VS Codeなどで"Format On Save"を利用して保存時にフォーマットしている場合は別だが、そうした機能がないエディタを使用している場合に、フォーマットのコマンドを毎度入力することは面倒だ。他のメンバがフォーマットし忘れたコードをPRしてくる可能性もある。ところで、gitには[フック機能](https://git-scm.com/docs/githooks)があり、これを利用して特定のgit操作時に任意のアクションを実行させることができるので、たとえば「リントがパスしない/フォーマットされていない」コードのコミットを弾くことなどができる。

フックを自前で書くことももちろん可能だが (なお、`git init`時に、フックのサンプルが自動的に`.git/hooks`に生成されるので、確認しておくといい)、[Husky](https://github.com/typicode/husky)を使えばフックがより簡単に設定できる。

Huskyをインストールすると、`.git/hooks`ディレクトリにファイルが自動的に追加される:

```sh
$ ls .git/hooks      
applypatch-msg.sample  fsmonitor-watchman.sample  pre-applypatch.sample  prepare-commit-msg.sample  pre-rebase.sample	update.sample
commit-msg.sample      post-update.sample	  pre-commit.sample	 pre-push.sample	    pre-receive.sample
$ npm install --save-dev husky
$ ls .git/hooks
applypatch-msg	       fsmonitor-watchman.sample  post-merge	post-update.sample     pre-commit	   prepare-commit-msg.sample  pre-rebase.sample   sendemail-validate
applypatch-msg.sample  post-applypatch		  post-receive	pre-applypatch	       pre-commit.sample   pre-push		      pre-receive	  update
commit-msg	       post-checkout		  post-rewrite	pre-applypatch.sample  pre-merge-commit    pre-push.sample	      pre-receive.sample  update.sample
commit-msg.sample      post-commit		  post-update	pre-auto-gc	       prepare-commit-msg  pre-rebase		      push-to-checkout
```

Huskyの設定は、`package.json`または`.hukyrc`にておこなう。たとえばコミット時にテストを実行したい場合は次のようにする:

```json
// package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "npm test",
    }
  }
}
```

あるいは

```json
// .huskyrc
{
  "hooks": {
    "pre-commit": "npm test"
  }
}
```

これにより、コミット時にテストが実行され、テストが失敗した場合はコミットされないようになる。

### Prettierの設定

ESLintやPrettierでチェックをおこなう場合は、`package.json`にあらかじめ`scripts`としてコマンドを登録しておき、それをさらに`hooks`として登録するのがいいだろう。たとえば:

```json
// package.json
{
  "scripts": {
    "lint": "eslint .",
    "prettier": "prettier \"**/*.+(js|json)\"",
    "format": "npm run prettier -- --write",
    "check-format": "npm run prettier -- --list-different",
    "validate": "npm run lint && npm run check-format"
  }
  "husky": {
    "hooks": {
      "pre-commit": "npm run validate"
    }
  }
}
```

ここで、`--list-different`はフォーマットされていないファイルがある場合、そのファイル名を出力してエラーコードを返す。これによりフックが止まってくれるため、フォーマットされていないファイルがある場合はコミットできないという仕組みになる。

なお、`npm run`の際に`--`を用いることで引数を指定することができるので、実際にフォーマットをおこなう`format`と、チェックのみおこなう`check-format`の重複部分を`prettier`としてまとめている。

### コミット時に自動フォーマットする

上の設定でフォーマットされていないコードを弾くことができるようになったが、上述したように、自動フォーマットされないエディタを使用している場合は毎度フォーマット用のコマンドを打ち込むことになり面倒だ。そこで、コミット時に自動的にコードをフォーマットすることを次に考える (これをしたいかどうかは人によって好みがあると思うが) 。

上の`pre-commit`の設定を素朴に`npm run format`に変更しても、フォーマット後のファイルが`git add`されていないため期待通りの結果とならない。`&&`でコマンドをつなぐこともできるが、`lint-staged`を使うとより簡単・効率的におこなうことができる。

`lint-staged`は、変更された (`git add`された) ファイルに対してのみリントをおこなうためのライブラリだ。これにより、リントの実行時間を短くし効率化できる。名前には`lint`と書いてあるが、フォーマッタなど他のコマンドをパイプのようにつなぐこともできる。

インストール方法:

```sh
$ npm install --save-dev lint-staged
```

設定は`package.json`または`.lintstagedrc`にておこなう:

```json
// package.json
{
  "lint-staged": {
    "*": "your-cmd"
  }
}
```

あるいは

```json
// .lintstagedrc
{
  "*": "your-cmd"
}
```

のようにする。ここで、キーとなっている`"*"`は、コマンドの実行対象をglobで指定したものとなる。よって、たとえばJavaScriptとTypeScriptを対象とする場合は

```json
"*.+(js|ts)": "your-cmd"
```

のようにする。`stage`されたファイルが`"*.+(js|ts)"`によってフィルタされ、その結果に対して`"your-cmd"`コマンドが実行されるという意味だ。`"your-cmd"`の部分は配列にして複数のコマンドを記述することもできる。

ESLintとPrettierを組み合わせる場合は次のように書く:

```json
{
  "*.+(js)": [
    "eslint",
    "prettier --write",
    "git add"
  ]
}
```

また、設定内容をフックとして登録するためには、Huskyの設定ファイルを次のように変更する:

```json
"hooks": {
  "pre-commit": "lint-staged"
}
```

これにより、まずフィルターされたstagedファイルに対してリントが実行され、次にフォーマットが実行され、最後に`git add`されることとなる。ここまででエラーが発生しなければ、実際にコミットが実行される。

実行の順番をまとめると

```
commitを試みる -> pre-commit hookが呼ばれる -> lint-stagedが実行される -> 対象ファイルに対してリント、フォーマット、git addをおこなう -> 本commit
```

のようになる。