こんにちは。スタディサプリ Androidエンジニアの@morayl です。
本記事では、Kotlinの静的解析ツールであるdetektの解析結果をDangerでプルリクにコメントする際に、ルール名も一緒にコメントするためにしたことを紹介します。
Dangerの基礎言語であるRubyは初心者なので、有識者から学びながらトライしました。
背景と結果
私が所属するチームでは最近、detektを導入し、Dangerを使ってプルリク上にコメントが出るようにしました。
この状態では、detektの指摘コメントだけが出ています。
一見問題無さそうですが、detektの指摘はルールで管理されているため、ルール名があったほうが便利です。
ルール名が分かることは、下記の点で重要です。
- 修正する時に困る
- 指摘の詳細やどのように直したほうが良いかなどは、detektのルールガイドに載っていますが、メッセージから何のルールに引っかかっているのかは調べづらい
- 設定値(しきい値など)を調整したり無効にしたりする場合、設定ファイルはルール名で管理されているので、探す必要がある
- 理由があって指摘を個別に抑制したい場合にルール名が必要になる。(Kotlinでは、メソッドや変数に
@Suppress("ルール名")
と書くことで抑制できる。)
メッセージからルールを推察することも出来ますが、detektをある程度経験しなければ難しく、よく使う人でもルール名がちゃんと分かるかというと難しいです。
そこで、ルールも一緒にコメントされるようにしました。
このコメントの場合、「MagicNumberというルールの指摘」ということがすぐに分かります。
前提条件
detektの結果をDangerでコメントする際には、danger-checkstyle_format を使っています。
確認したライブラリのバージョンは下記です。
- detekt:1.23.4
- Danger:9.4.2
- danger-checkstyle_format:0.1.1
結論
DangerFileのdetektの処理の前に、下記を加えるだけです。
# 加筆 class ::CheckstyleError alias_method :original_message, :message def message "`#{source}`\n\n#{original_message}" end end # 元々あるdetektの処理 checkstyle_format.base_path = Dir.pwd Dir.glob("check/reports/detekt/**/*.xml").each do |file| checkstyle_format.report file end
道筋と解説
ルールはどこにあるのか
まず、そもそもルールの情報があるのかを調べます。
checkstyle_format.reportに設定するファイルはxmlです。ローカルでも出力できるので、detektをローカルで走らせます。
すると、出力されたxmlの中には、<error
の中のsource
にあることが分かりました。
<?xml version="1.0" encoding="UTF-8"?> <checkstyle version="4.3"> <file name="...HogeUtils.kt"> <error line="7" column="17" severity="warning" message="This expression contains a magic number. Consider defining it to a well named constant." source="detekt.MagicNumber" /> </file> </checkstyle>
また、プルリクに出ているメッセージはmessage
に記載されているものだということも分かります。
コメントはどのように作られるか
次に、コメント生成部分を探します。
使っているdanger-checkstyle_format
ライブラリを確認します。
その生成部分にsourceを入れることが出来れば、目的が達成できるからです。
CheckstyleError
という型には、 source
が定義してあります。(参考)
CheckstyleError.new( parent_node[:name].sub(/^#{base_path}/, ""), node[:line].to_i, node[:column].nil? ? nil : node[:column].to_i, node[:severity], node[:message], node[:source] )
次に、Dangerでコメントする部分を確認すると、ここではsource
は使われていません。(参考)
warn(error.message, file: error.file_name, line: error.line)
コメントをどうカスタマイズするか
やりたいことは「warnの第一引数のerror.messageに、sourceも含めて表示したい」です。
やり方はいくつかありますが、今回はRubyのモンキーパッチというものを使いました。
これはすでに定義されたクラスの動きを変えるもので、無闇に使うべきものではありません。
正攻法としては、「ライブラリのPRを出す」「forkして使う」「ライブラリを使わず自前で書く」などがあります。
今回は、プロダクトに影響がない部分であること・修正範囲が小さくサクッと試したかったことから、モンキーパッチを試してみることにしました。
Dangerfileが実行されるときには、すでにライブラリが読み込まれ、danger-checkstyle_format
が使えるようになっています。
そこで、下記Dangerのメソッド実行前に、CheckstyleError
のmessage
を書き換えて、source
も表示することにしました。
warn(error.message, file: error.file_name, line: error.line)
そして、これがモンキーパッチです。
class ::CheckstyleError alias_method :original_message, :message def message "`#{source}`\n\n#{original_message}" end end
class ::CheckstyleError
すでにある同じ名前のクラスの定義を書くことで、動きを上書きすることが出来ます。
alias_method :original_message, :message
alias_method
を使うと、下記の形で元あるメソッドを新しい呼び名で複製することが出来ます。
alias_method 新しいメソッド名, すでにあるメソッド名
ここでは、original_message
をというメソッドを作成し、message
と同じ動きをするようにしています。
そして最後に、元あるmessage
を上書きしています。
def message "`#{source}`\n\n#{original_message}" end
alias_methodで作られたoriginal_message
は、その時点でのmessage
の動きをするため、上書きされたmessageが使われて循環参照になることはありません。
これで、以降でmessage
が使われた場合は、source
も含めた文字列を表示するように出来ました。
最後に
今回は、Dangerでコメントするdetektの指摘内容にルール名を含めることに方法について紹介しました。
今までRubyを使う機会はほとんどありませんでしたが、今回のことを通じてRubyと少しだけ仲良くなれた気がします。
今回はモンキーパッチを使いましたが、コメントする機能はDangerにあり、checkstyleのxmlが解析できれば、ライブラリを使わず出力内容を自分で自由に決めることも出来ます。
更にやりたいこと出てきたら、今度は自前での実装に挑戦してみようと思いました。