wartremoverのsbt pluginにTASTy inspector経由で実行する機能入れた

散々tweetしているし、少し前にblogに書きましたが、

xuwei-k.hatenablog.com

それを正式に本体に入れてリリースしました。という話

github.com

https://docs.scala-lang.org/scala3/reference/metaprogramming/tasty-inspect.html

モチベーションやメリットは、例えばこのあたり。 あくまで、complie時にcompiler pluginとしてではなく、別のタイミングで何度も実行可能なのがポイント。

twitter.com

大まかなアーキテクチャや内部構造

  • 依存が増えるので、wartremover本体とは別のwartremover-inspectorのsub project作成
  • wartremover-inspectorの内部自体は、色々整理したりパラメーター増やしたが、根本的には前blog書いたようなもの
  • さらに、sbt pluginと共通のinterfaceであるwartremover-inspector-commonも作成
  • wartremover-inspectorは、wartremover-inspector-commonとwartremoverのcoreに依存
  • sbt-pluginは、新たにwartremover-inspector-commonに依存
  • sbt-pluginから、derivedProjectsというsbtの機能使った暗黙的にproject追加する機能を使って、inspectorの実行projectを作っておく
  • TASTy inspectorは、NIGHTLYでないと致命的なbugがある気がするので、あえてNIGHTLY指定
  • sbtからwartremover-inspectorを呼び出す方式を色々悩んだが、引数を全部JSON文字列にして、直接リフレクションで呼び出して、結果もJSONで受け取るようにした

あとは、色々細かい機能追加しました(まだもっと色々やりたい)

  • mainとtestで別々に実行できる
  • もちろんsub project毎に実行出来る
  • exclude
  • errorやwarningをそのまま標準出力に出すか?
  • errorやwarningを受け取って、そのJSONをファイルに書き出すか?
  • 外部のwartや、ローカルに直接書いてdependsOnさせたものも正しく動くように
  • errorが見つかったらTaskを失敗にするかどうか?
  • 存在しないwartが指定されていたら、taskを失敗させるか、とりあえず存在したやつのみで継続するか?

最終的にまだ変わる可能性があるし、面倒なので、詳細な使い方あまり書きませんが、ひとまず以下のような設定書いて

project/plugins.sbt

addSbtPlugin("org.wartremover" % "sbt-wartremover" % "3.0.0-RC5")

build.sbt

scalaVersion := "3.1.2"

Compile / wartremoverInspect / wartremoverWarnings ++= /* ここに検査するwart */
Compile / wartremoverInspect / wartremoverErrors ++= /* ここに検査するwart */

wartremoverInspect というTask実行すると動くはずです。

ちなみに sbtのこれ により、明示的なproject定義がゼロだと動かないです。

バグ見つけたら教えてください。