はじめに
linter&formatterの導入を解説した記事はかなりたくさんネット上にあって非常に助かるのだが、いずれもシンプルか自分のニーズにずれている部分があるため自分で書くこととした。
この手の話は、IDEを使えばいいじゃん、で片付きそうであるがチームで共通化させようとするとエディタの好みは様々であるし、そもそもエディタの設定を共通化すること自体が結構難しいと思う。
というわけで、現在も一定のニーズはあると思っているので、今後誰かの役に立てれば幸いである。
複数言語をすべてgit-hookで連携するのはしんどい
業務で使用しているプロジェクトは多くの場合、UIとバックエンドが混在したリポジトリだったりで、単一言語で構成されるものは少ないのではないかと思う。
その場合ひとつのツールでgit-hookを連携したくなるもので、今回はPython製のpre-commit
を使用することでそれが解決できたのその備忘録として残しておく。
git-hook連携ツール:pre-commitを使用する
pre-commitはしっかりしたドキュメントが以下にある。
このツールの良いところは、複数言語対応しているところで、公式ページには以下の言語が紹介されていた。
- conda
- docker
- docker_image
- fail
- golang
- node
- python
- python_venv
- ruby
- rust
- swift
- pcre
- pygrep
- script
- system
実は上記以外にも連携させる裏技があるようで、例えばScalaなんかもできそうだったのだが、今回はうまく行かなかったので、後日チャレンジとしたい。
インストール
$ pip install pre-commit
設定ファイル
ルートディレクトリに.pre-commit-config.yaml
を作成する。
例えば、例としてpre-commit自身が使用している設定を見ると次のようになっている。
repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.1.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-docstring-first - id: check-json - id: check-yaml - id: debug-statements - id: name-tests-test - id: requirements-txt-fixer - id: double-quote-string-fixer - repo: https://gitlab.com/pycqa/flake8 rev: 3.7.7 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-autopep8 rev: v1.4.3 hooks: - id: autopep8 - repo: https://github.com/pre-commit/pre-commit rev: v1.14.4 hooks: - id: validate_manifest - repo: https://github.com/asottile/pyupgrade rev: v1.12.0 hooks: - id: pyupgrade - repo: https://github.com/asottile/reorder_python_imports rev: v1.4.0 hooks: - id: reorder-python-imports language_version: python3 - repo: https://github.com/asottile/add-trailing-comma rev: v1.0.0 hooks: - id: add-trailing-comma - repo: meta hooks: - id: check-hooks-apply - id: check-useless-excludes
見るとなんとなくわかると思うが、repo
でgithubのレポジトリURLを指定している。これでpre-commitがrev
で指定したバージョンを初めて実行するときにinstallしてくれる。
ちなみに、すべてのhookは以下にまとまっているので探すのは簡単である。
設定の反映
$ pre-commit install
単純な実行
pre-commitはgit-hookとして以外にも、各ツールのラッパーCLIを持っているので、簡単に実行が可能。
# srcディレクトリ以下の`.py`に対してflake8を実行 $ pre-commit run flake8 --files src/* # リポジトリ全てのファイルに対して全てのhookを実行 $ pre-commit run --all-files
汎用的なチェック
冒頭の例でも記載したが、pre-commit-hooks
リポジトリには汎用的なcheker(formatter)が用意されており、かなり便利だったので今回使用したものを記載しておく。
- repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.4.0 hooks: - id: detect-aws-credentials args: ["--allow-missing-credentials"] - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - id: check-toml - id: detect-private-key - id: check-json
trailing-whitespace
-> 末尾の空白除去end-of-file-fixer
-> ファイル末尾に改行挿入check-yaml
->YAML
ファイルの構文チェッカーcheck-toml
->TOML
ファイルの構文チェッカーcheck-json
->JSON
ファイルの構文チェッカーdetect-aws-credentials
-> AWSのシークレットアクセスキーが紛れ込んでないかチェックdetect-private-key
-> 秘密鍵が紛れ込んでいないかチェック
個人的に機密情報のチェッカーはかなりありがたかった。
どこまで検知してくれるのかはまだわかっていない(AWSはセットしているプロファイルからだけ?)ので過信は禁物だが、ちゃんと検知してくれるならかなりありがたいチェックだ。
各言語の方針
Python
調べたところ、Pythonの静的解析ツールは非常に沢山あってよくわからん。
今回は以下を参考にさせていただき、次のツールを使うこととした。
formatter
- black
- isort
linter
- flake8
ちなみにisort
に関しては、pythonファイル内で、sys.path.append(..)
みたいにスクリプト内でPYTHONPATHを調整していると順序を書き換えられてうまく動かなる。そもそもスクリプト内での調整自体が推奨されていないとのことなので、この際に別の手を考えたい。
この場合準備すべき点は2つ
.pre-commit-config.yaml
にフックの設定- 各ツールの設定ファイルを作成
pre-commitへのhook設置
hook設置は冒頭で説明したとおり、基本の構成でよい。
一点注意があるとすれば、今回はblackとisortの設定ファイルをpyproject.toml
で管理するのだが、isortに関しては引数(additional_dependencies
...intall時の引数となるもの)にtoml
を指定してやる必要がある。
- repo: https://github.com/ambv/black rev: stable hooks: - id: black verbose: true - repo: https://gitlab.com/pycqa/flake8 rev: 3.7.7 hooks: - id: flake8 verbose: true - repo: https://github.com/pre-commit/mirrors-isort rev: v4.3.20 hooks: - id: isort verbose: true additional_dependencies: [toml]
各設定ファイル
.flake8
[flake8] ignore = E203,W503,W504 max-line-length = 99 max-complexity = 18 select = B,C,E,F,W,T4,B9
pyproject.toml
[tool.black] line-length = 99 include = '\.pyi?$' exclude = ''' /( \.git | \.hg | \.mypy_cache | \.tox | \.venv | _build | buck-out | build | dist | \.idea | node | project | \.sbt | node_modules | out | package.json | package-lock.json | \.gitignore )/ ''' [tool.isort] include_trailing_comma = true line_length = 99 multi_line_output = 3 force_grid_wrap = 0 use_parentheses = true
ここでの注意は3点ある。
tool.*
は意味のある書き方.flake8
のmax-line-length
はpyproject.toml
のline-length
と合わせる。- flake8とblackはいくつか競合する項目がある。
ignore
のとおり無視する必要がある。 詳細はblackのgithubを見ると記載があるので参考にされたい。
Node.js
node.jsについてはJavaScriptとして、フロントエンドで使用するとき同様に
- linter -> eslint
- formatter -> prettier
で対応した。
ここもやることはPython同様に
.pre-commit-config.yaml
にフックの設定- 各ツールの設定ファイルを作成
で進めていく。
1. pre-commitへフック設置
先程も説明したとおり、additional_dependencies
がinstallする際のオプションのような役割となる。
eslintのプラグインも同様に当該ディレクティブへリストで記載する。
- repo: https://github.com/prettier/prettier rev: 1.19.1 hooks: - id: prettier verbose: true - repo: https://github.com/pre-commit/mirrors-eslint rev: v6.8.0 hooks: - id: eslint verbose: true args: ["--fix"] additional_dependencies: - eslint@6.8.0 - eslint-config-google@0.14.0 - eslint-plugin-node@11.0.0 - eslint-config-prettier@6.9.0
ここで新しいディレクティブ:args
が出現しているが、これはCLI実行時のオプションと等価で、この場合eslint --fix
を実行したのと同じ結果が得られる。
あと、eslintに関してはverbose
をture
にしたほうが良い。これによりwarnが表示されるようになる。
個人的にはいくつかerror
としたくないルールもあるので、その時々でwarnを見ながら意図したもの確認するようにした。
2. 設定ファイルの作成
設定自体は色々なたたき台がネット上に用意されているが、今回はNode.jsとして使用する初めての経験だったので記載しておく。
{ "env": { "es6": true, "node": true }, "extends": [ "eslint:recommended", "google", "plugin:node/recommended", "prettier" ], "parserOptions": { "sourceType": "module" }, "plugins": ["node"], "rules": { "camelcase": "warn", "no-empty": "warn", "no-undef": "warn", "no-var": "warn", "node/no-unsupported-features": [ "error", { "version": 8 } ], "require-jsdoc": "off" } }
終わりに
今回はこれらをイチから作成したが、正直かなりめんどくさかった。
今後はcookiecutterのテンプレートを自作して、それに取り込むとか考えたい。
後はScalafmtとの連携についても後日調査したいと思う。
参考
ツール集
GitHub - pre-commit/pre-commit-hooks: Some out-of-the-box hooks for pre-commit
GitHub - prettier/prettier: Prettier is an opinionated code formatter.
GitHub - pre-commit/mirrors-eslint: Mirror of eslint node package for pre-commit.
GitHub - psf/black: The uncompromising Python code formatter
GitHub - pre-commit/mirrors-isort: Mirror of the isort package for pre-commit.
eslint-plugin
GitHub - google/eslint-config-google: ESLint shareable config for the Google JavaScript style guide
GitHub - mysticatea/eslint-plugin-node: Additional ESLint's rules for Node.js
失敗したScalafmt + pre-commit
Reusable pre-commit hooks in Scala projects - SoftwareMill Tech Blog
Code formatting: scalafmt and the git pre-commit hook
その他
eslintの設定からprettierとの併用までの流れ - Qiita
Step by Stepで始めるESLint - Qiita
pre-commit時にformatterを実行する - Qiita
Python製のツールpre-commitでGitのpre-commit hookを楽々管理!! | Developers.IO
【Python】sys.pathに追加したディレクトリからimportする処理でPEP8に違反した際の対処 - Qiita