Rubyを書いているのに行末にセミコロンを打ってしまうことが時々あります。
JavaScriptとかJavaとかC言語とか、セミコロンを書かないといけない(or 書いたほうが良い)言語を書いたあとにRubyに戻ってくると特に顕著に発生します。
そういうことがあるたびに、「あ、Rubyってセミコロン要らないんだ」 と初学者の頃の新鮮な気持ちに立ち帰る事が出来るわけですが、うっかり気づかすにリポジトリにpushしてしまいレビューアーに「;
m9(^Д^)」というコメントを付けられるのは業腹です。(実際は優しく指摘されます)
そこで、Gitフックを使ってRubyコードの行末に ;
があったらcommitできなくしてみました。
前提
- 毎回RuboCopを回したいわけではない。
- addしたファイルの変更行だけをチェックしたい。
やってみた
リポジトリの .git/hooks/pre-commit
に以下を書きます。忘れずに実行権限もつけておきましょう。
#!/usr/bin/env ruby
def check_ruby_file(adds)
adds.select {|add|
/;\z/.match? add
}.instance_eval {
any? ? "Find ';' in Ruby file's line end." : 'ok'
}
end
def check_adds(filename, adds)
results = []
results << check_ruby_file(adds) if /\.rb\z/.match? filename
results.all? {|result| result == 'ok' } ? 'ok' : results.join("\n")
end
changed_files = `git diff --cached --name-only HEAD`
exit_code = changed_files.split(/\R/).map {|filename|
[filename, `git diff --cached -U0 HEAD -- "#{filename}"`]
}.map {|(filename, diffs)|
[
filename,
diffs.split(/\R/).select {|diff| /\A\+[^\+]/.match?(diff) }
]
}.reject {|(_, adds)|
adds.empty?
}.map {|(filename, adds)|
[filename, adds, check_adds(filename, adds)]
}.reject {|(_, _, result)|
result == 'ok'
}.tap {|elements|
puts 'Invalid adds detected.'
elements.each do |(filename, adds, result)|
puts filename
puts " \e[31m#{adds.join("\n ")}\e[0m"
puts " #{result}"
end
}.any? ? 1 : 0
exit exit_code
なかなか雑なコードですが、これでも実用上困らない程度には動いています。
行末セミコロン以外もチェックしたければcheck_ruby_fileメソッドを変更、.rbファイル以外も見たければcheck_addsメソッドを変更すれば拡張できます。
正規表現で引っ掛けているだけなので、コメント行の末尾にセミコロンがあったり、ヒアドキュメントの中にJavaScriptのコードがあったりすると誤検出します。が、うっかりcommit -> pushしてしまうのを事前に防ぎたいだけですので、内容を見て問題なければ --no-verify
オプションをつけてcommitしてしまっています。
まとめ
今回はGitフックを書いてみる練習を兼ねて作成してみました。
複雑なチェックをしたければRuboCopなどLintツールを使うべきでしょうが、簡単なチェックくらいは手慣れた言語で簡単に作成できます。