[Ruby] rubocopをコミット時に自動で走らせる & 出来る限り自動で修正する


背景

rubyの静的コード解析ツールとして非常に便利なrubocopですが

  • git pushしたあとにCircleCiなどで大量に指摘されてると直すのがめんどくさくなって段々直さなくなってしまう
  • かと言っていちいちローカルで手動でrubocopを実行するのも手間

という問題があります。
そこで今回はgit commitのhookを簡単に設定出来るpre-commit gemを使ってコミット時に自動でrubocopを走らせ、かつ出来る限り自動で修正するようにする方法をご紹介します。

コミット時にrubocopが走るようにする

a. まずpre-commit gemと入っていなければrubocopをインストールします。

Gemfile
group :development do
  gem "pre-commit", require: false
  gem "rubocop", require: false
end

b. 続いてpre-commit installを実行します。

bundle exec pre-commit install

これを実行すると
{git_root_dir}/.git/hooks/pre-commitに自動でシェルスクリプトが生成されます。このpre-commitファイルがコミット時に実行される処理で、内容としてはcmd変数に実行するruby commandを代入して後はrubyのコードとして実行していることがわかります。

pre-commit
...
cmd=`git config pre-commit.ruby 2>/dev/null`
if   test -n "${cmd}"
then true
elif which rvm   >/dev/null 2>/dev/null
then cmd="rvm default do ruby"
elif which rbenv >/dev/null 2>/dev/null
then cmd="rbenv exec ruby"
else cmd="ruby"
fi

export rvm_silence_path_mismatch_check_flag=1

${cmd} -rrubygems -e '
  begin
    require "pre-commit"
    true
  rescue LoadError => e
    $stderr.puts <<-MESSAGE
pre-commit: WARNING: Skipping checks because: #{e}
pre-commit: Did you set your Ruby version?
MESSAGE
    false
  end and PreCommit.run
'

c. 上記のruby commandをbundle execに変更します。

git config pre-commit.ruby "bundle exec ruby"

d. precommitのchecksにrubocopを追記します

git config pre-commit.checks "[rubocop]"

ここまででrubocopのチェックが走るようになったはずなので何か適当なファイルを作ってrubocopが走るかチェックしてみます。

hoge.rb
def hoge
  [1, 2, 3].each{
|a| p a}
end
$ git commit -m 'hoge'                                                                                       土  8/11 23:07:25 2018
pre-commit: Stopping commit because of errors.
Inspecting 1 file
C

Offenses:

hoge.rb:2:17: C: Layout/SpaceBeforeBlockBraces: Space missing to the left of {.
  [1, 2, 3].each{
                ^
hoge.rb:2:17: C: Style/BlockDelimiters: Avoid using {...} for multi-line blocks.
  [1, 2, 3].each{
                ^
hoge.rb:3:1: C: Layout/MultilineBlockLayout: Block argument expression is not on the same line as the block start.
|a| p a}
^^^
hoge.rb:3:8: C: Layout/BlockEndNewline: Expression at 3, 8 should be on its own line.
|a| p a}
       ^

1 file inspected, 4 offenses detected

pre-commit: You can bypass this check using `git commit -n`

はい!これでrubocopのルールに従っていないコードは容赦なくコミットできなくなりました!

出来る限り自動で修正する

続いてコミット時に出来るだけ自動で修正されるようにします。
といっても簡単でgit config でrubocopのオプションを設定するだけです。

git config pre-commit.rubocop.flags ["-a"]

これでコミットすると上記のファイルは自動で修正されてコミットが通ります。

$ git commit -m 'foo'                                                                                                  日  8/12 00:05:20 2018
1 file inspected, no offenses detected
[refactoring/rubocop 5c009d6] foo
 1 file changed, 2 deletions(-)

勿論全てが自動で修正されるわけではないので手で修正しないといけない箇所もありますが、冒頭で上げた問題が解決され、かなり楽になりましたね!これならめんどくさがらずにやっていけそうです!
皆さんもよいrubocop lifeを

(補足) 自動で修正はするがstagingに載せない

上記の勝手に修正されて、それで全て通った場合そのままコミットされるのがなんか気持ち悪いので、自分は最終的にはpre-commitのオプションは外して、コミットが通らなかったあとに自動修正をかけるようにしました。

pre-commit
...
MESSAGE
    false
  end and PreCommit.run
...

# stagingされているファイルに対して自動修正をかける
git diff --name-only --cached | xargs bundle exec rubocop -a

こうしておくとコミットが通らなかった場合に自動修正はかかりますが、修正はstagingに乗らずどこが自動修正されたのか確認してからstagingに取り込んで再度コミットすることができます。