commitlint の紹介

`commitlint` というツールをご紹介します。

http://marionebl.github.io/commitlint

`commitlint` とは、Git のコミット規約（commit convention）に従わせるための npm ツールです。[ESLint](https://eslint.org) のように、ルールを JavaScript で設定・開発できます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/104952/fd3ada97-2207-df05-1fd5-2d6d0799e7e7.png)

> `commitlint` helps your team adhering to a commit convention. By supporting npm-installed configurations it makes sharing of commit conventions easy.

> `commitlint` はチームがコミット規約に従うのを助けます。npm インストールされた設定をサポートすることにより、簡単にコミット規約を共有することができます。

「コミット規約」といえば、[Angular チームの規約](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits) が有名です。そのコミットメッセージ形式は、次の通りです。

```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```

例えば、こちらのコミットを見てみると、規約に従っているのが分かります。

[feat(currencyFilter): trim whitespace around an empty currency symbol · angular/angular.js@62743a5](https://github.com/angular/angular.js/commit/62743a54b79187e6c1325c0f6dec0f474147881d)

```
feat(currencyFilter): trim whitespace around an empty currency symbol

In most locales, this won't make a difference (since they do not have
whitespace around their currency symbols). In locales where there is a
whitespace separating the currency symbol from the number, it makes
sense to also remove such whitespace if the user specified an empty
currency symbol (indicating they just want the number).

Fixes #15018
Closes #15085

Closes #15105
```

`commitlint` も[大まかにはこの形式に従っており](http://marionebl.github.io/commitlint/#/concepts-commit-conventions)、ルール設定によってより細かな調整が可能です。

# セットアップ

では、実際に使ってみましょう。インストールガイドは公式サイトが詳しいので、そちらに従っていきます。

## `commitlint` インストール

まず、CLIパッケージ（`@commitlint/cli`）と設定パッケージ（`@commitlint/config-conventional`）をインストールします。

```shell-session
$ npm install --save-dev @commitlint/{cli,config-conventional}
```

- `@commitlint/cli`: https://npm.im/@commitlint/cli
- `@commitlint/config-conventional`: https://npm.im/@commitlint/config-conventional

次に、設定ファイル（`commitlint.config.js`）を作成します。

```shell-session
$ echo "module.exports = {extends: ['@commitlint/config-conventional']};" > commitlint.config.js
```

こんな感じです。

```js:commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional']
};
```

ファイル名は `.commitlintrc.js` `.commitlintrc.json` `.commitlintrc.yml` も可能ですし、`package.json` に `commitlint` フィールドを定義してもよいです。これは `cosmiconfig` の仕様ですね。

https://npm.im/cosmiconfig

## `husky` インストール

さて、お次は `husky` をインストールします。

https://npm.im/husky

```shell-session
$ npm install --save-dev husky
```

`husky` は[Gitフック](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF)を設定するためのツールです。`husky` の詳細については、手前味噌ですがこちらの記事をご覧ください。

[コミット前に Lint を強制するなら lint-staged が便利 - Qiita](https://qiita.com/ybiquitous/items/553479cfcb2cee124ae0#pre-commit-%E3%81%AE%E4%BB%A3%E3%82%8F%E3%82%8A%E3%81%AB-husky-%E3%82%92%E4%BD%BF%E3%81%86)

また、Gitフックについてはこちらの記事が詳しいです。

[Gitのフックの説明と挙動の検証 - Qiita](https://qiita.com/mima_ita/items/dcaa3789022d2a9ab929)

`package.json` を編集し、`scripts.commitmsg` を追加します。これで、コミットメッセージ編集の際に Git フックが走るようになります（これは `husky` の機能です）。

```json:package.json
{
  "scripts": {
    "commitmsg": "commitlint -e $GIT_PARAMS"
  }
}
```

## テスト

さあ、テストしてみましょう。

```shell-session
$ git commit -m "foo: this will fail"
husky > npm run -s commitmsg

⧗   input: foo: this will fail
✖   type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] [type-enum]
✖   found 1 problems, 0 warnings

husky > commit-msg hook failed (add --no-verify to bypass)


$ git commit -m "chore: lint on commitmsg"
husky > npm run -s commitmsg

⧗   input: chore: lint on commitmsg
✔   found 0 problems, 0 warnings
```

規約に沿わないコミットメッセージでは、コミットできないことが確認できます。

## CIでの実行

いくら Git フックでチェックしていても、[`git commit --no-verify`](https://git-scm.com/docs/git-commit#git-commit---no-verify) されてしまうとチェックをすり抜けられてしまいます。
また、GitHub などではブラウザ上からコミットすることも可能です。
そのようなケースでも対応できるよう、CIで `commitlint` を実行する方法も載っています。

http://marionebl.github.io/commitlint/#/guides-ci-setup

こちらは Travis CI での設定例です。`@commitlint/travis-cli` というパッケージをインストールすると、`node_modules/.bin/commitlint-travis` というコマンドがインストールされます。

```shell-session
$ npm install --save-dev @commitlint/travis-cli
```

```yaml:.travis.yml
language: node_js
script:
  - commitlint-travis
  - npm test
```

# ルール

`commitlint` には[様々なルール](http://marionebl.github.io/commitlint/#/reference-rules)が存在します。

例えば、標準設定の `@commitlint/config-conventional` から抜粋してみます（v5.2.3 時点）。

https://npm.im/@commitlint/config-conventional

- `type` は次のいずれかでなければならない（`type-enum` ルール）。
  - `build`: ビルド
  - `ci`: CI
  - `chore`: 雑事（カテゴライズする必要ないようなもの）
  - `docs`: ドキュメント
  - `feat`: 新機能
  - `fix`: バグフィックス
  - `perf`: パフォーマンス
  - `refactor`: リファクタリング
  - `revert`: コミット取り消し（[`git revert`](https://git-scm.com/docs/git-revert)）
  - `style`: コードスタイル
  - `test`: テスト
- `type` は小文字でなければならない（`type-case` ルール）。
- `type` は必ず指定しなければならない（`type-empty` ルール）。
- `scope` は小文字でなければならない（`scope-case` ルール）。
- `subject` は次のいずれかの書式（case）でなければならない（`subject-case` ルール）。
  - `sentence-case`: (例．`Sentence case`)
  - `start-case`: (例．`Start Case`)
  - `pascal-case`: (例．`PascalCase`)
  - `upper-case`: (例．`UPPERCASE`)
- `subject` は空ではいけない（`subject-empty` ルール）。
- などなど…

すべてのルールの詳細は、`README.md` またはソースコード（`index.js`）をご覧ください。

- [commitlint/README.md at v5.2.3 · marionebl/commitlint](https://github.com/marionebl/commitlint/blob/v5.2.3/%40commitlint/config-conventional/README.md)
- [commitlint/index.js at v5.2.3 · marionebl/commitlint](https://github.com/marionebl/commitlint/blob/v5.2.3/%40commitlint/config-conventional/index.js)

## 上書き

ルールを上書きすることもできます。例えば、ベースは `@commitlint/config-conventional` を採用し、`scope` を独自のものにするには、`commtlint.config.js` ファイルを次のように編集します。

```js:commtlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],

  rules: {
    'scope-enum': [2, 'always', ['web', 'job', 'api']]
  }
};
```

## 定義

ルールは `ルール名: [配列]` で定義します。ESLint に似ています。

```
'{ルール名}': [{レベル:0|1|2}, '{可否:always|never}', {値}]
```

ルールレベル:

- `0`: 無効
- `1`: 警告
- `2`: エラー

ルール定義の配列は、次の形式が可能です。

- プレーンな配列
- 配列を返す関数
- 配列を返す非同期関数
- `Promise` を返す関数

```js
{
  'header-max-length': [0, 'always', 72],
  'header-max-length': () => [0, 'always', 72],
  'header-max-length': async () => [0, 'always', 72],
  'header-max-length': () => Promise.resolve([0, 'always', 72])
}
```

定義済みのルールの一覧については、[公式ドキュメント](http://marionebl.github.io/commitlint/#/reference-rules)をご覧ください。

## 独自ルールの共有

[ESLint の "Shareable Configs"](https://eslint.org/docs/developer-guide/shareable-configs) のような機能が、`commitlint` にもあります。

[Shareable configuration - commitlint - Lint commit messages](http://marionebl.github.io/commitlint/#/concepts-shareable-config)

カスタマイズされた `rules` を、npm パッケージとして公開することができます。

# メリット／デメリット

今は仕事でもプライベートでも、このツールを使ってコミット規約を強制しています。個人的には、次のメリットを強く感じています。

## メリット

- 習慣づけ
  - わかりやすいコミットメッセージを書くことが、**習慣づけ**されます。
  - 習慣の力は大きくて、仕組みとして強制されていれば、自然と書けるようになります。
  - 書けるようなると、思考もそれに順応するようになります（このコミットは「バグ修正」なのか「リファクタリング」なのかといった判断が的確になります）。
- コミットの責任範囲の明確化
  - 色んな変更が混在したコミットを避けるようになり、１つのコミットが１つのことをするようになります。
  - 結果、レビューがしやすくなり、レビューにかかる時間を減らせます。
- 不毛な宗教論争の回避
  - （当たり前ですが）コミットメッセージの形式のブレをなくせます。
  - これは他の Lint ツールにも言えることですが、機械的に修正できるものから人間の介在をなくせば、より重要なことにリソースを集中できます。
- 迅速な内容把握
  - コミットのサマリーを見ただけで、簡単な内容が把握できます。
  - Slack などにコミットログが流れてくる場合、普通はサマリーしか表示されないので、サマリーだけで内容把握ができるようになると、大きいです。
- 変更履歴作成の簡略化
  - `lerna` や `standard-version` のような Changelog 自動生成ツールを使っている場合、コミットログから Changelog ファイルを自動生成してくれます。
  - https://npm.im/lerna
  - https://npm.im/standard-version

## デメリット

反対に、デメリットは次の点です。

- 規約が厳しすぎると、チームメンバーによってバラつきが出て、最悪守られなくなります。`type` の選択（`feat` か `fix` か）も結局は個人の判断に委ねられてしまうので、前もって規約を十分に理解してないと、その選択にもブレが生じます。
- 結果、チームメンバーのモチベーションが低下します。所詮ツールであり**銀の弾丸ではない**ので、チームに導入する際は前もっての準備とメンバーの理解が**必要不可欠**です。


# 使用例

`commitlint` のリポジトリです。コミットログと Changelog はこのようになっています。

https://github.com/marionebl/commitlint/commits/master

![image.png](https://qiita-image-store.s3.amazonaws.com/0/104952/30707517-cc6e-784c-3025-d3496cf4ab4b.png)

https://github.com/marionebl/commitlint/blob/master/CHANGELOG.md

![image.png](https://qiita-image-store.s3.amazonaws.com/0/104952/e531b2e1-0973-51ec-af17-94cdcf58ce92.png)

# 参考

[ConventionalCommits.org](https://conventionalcommits.org/) なる団体も存在します。Angular のコミット規約がベースとなっています。

興味があれば、ぜひ使ってみてください！