はじめましての方ははじめまして! Scala エンジニアの Javakky です。
本日は Scala Advent Calendar 2022 5日目ということで、僕が普段利用している Linter である、 Scalafix についてお話ししていこうと思います。
Linter とは?
Linter とは、コードチェックや解析をしてくれるツールのことです。
コンパイラは言語仕様に則った実行可能な形式になっているかをチェックしてくれますが、 Linter ではより詳細に、例えば、コーディング規約などに反していないかも確認してくれます。 逆に、字句レベルでの規約 (インデントなど) をチェックしたい場合にはフォーマッタを利用するといいでしょう。
Scalafix とは?
改めて、 Scalafix とは、 Scala の Linter として有名なツールの一つです。
一つ、と書いたように、 Scala には WartRemover や ScalaStyle など様々な Linter が存在します。
では、なぜ僕のプロジェクトでは Scalafix を優先して採用しているのでしょうか?それは、 Scalafix が自動書き換えを行なってくれるからです!
通常 Linter に読ませるような規約は複雑なことが多いため、警告のみしか提供していないものが多いです。しかし、 Scalafix には警告 / 自動書き換えの両方からルール実装者が選択できるようになっているため、多くのルールで書き換えの手間を必要とせずに対応を行うことができます。
Scalafix の使い方
あなたが sbt を利用してプロジェクトを管理しているなら、 Scalafix の導入は非常に簡単です。
project/plugins.sbt
に一行依存関係を追加するだけです。
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.4")
この状態で以下コマンドを実行することで、 Scalafix は動き出します。
sbt scalafixEnable scalafixAll
CI などで利用する場合、 sbt scalafix --check
を利用すれば、すべてのルール違反を警告のみに留めてくれます。また、 詳細な説明 は割愛しますが、シェルセッション内で scalafix を使えるようにするためのコマンドが scalafixEnable
なので、2回目からは省略することができます。
もし、あなたが SemanticDB を利用するような複雑なルールを使おうとしている場合、コンパイラプラグインを有効化する必要があります。
build.sbt
に以下のような設定を加えてください。
inThisBuild( List( scalaVersion := "2.12.17", // 既に書かれているはず semanticdbEnabled := true, semanticdbVersion := scalafixSemanticdb.revision // Scala 2.x 系なら必要 ) )
また、 The Scala compiler option "-〇〇" is required to
****というエラーが返される場合、該当するコンパイラオプションをプロジェクトに適用する必要があります。 (build.sbt
)
lazy val myproject = project.settings( scalacOptions += "-Ywarn-unused-import" // コンパイラオプションを追加 )
ここまで設定して、さあ、実行しよう!と思ったはいいのですが、まだ解析ルールが設定されていませんでした。プロジェクトルートに .scalafix.conf
を作成し、以下のように記述すると、 DisableSyntax ルールが追加されます。
rules = [ DisableSyntax ]
これによって、 Scalafix の組み込みルールを利用することができるようになります。
コミュニティのルール
組み込みルールを導入し、あなたのプロジェクトには平穏が訪れました。しかし、なんだか少し物足りない気持ちがあります。
Scalafix の組み込みルールは確かに便利ですが、他の Linter と比べるとルールの種類がイマイチ多くありません。
しかし大丈夫です! Scalafix ではコミュニティの開発者がそれぞれルールを作成・公開・利用できる機能があります。例えば、 build.sbt
に以下のように依存関係を追加することで scalafix-pixiv-rule を利用することができます。
ThisBuild / scalafixDependencies += "net.pixiv" %% "scalafix-pixiv-rule" % "2.4.0"
Scalafix 公式ページでもいくつかルールセットが紹介されているので、ぜひ使ってみてください!
- ついでに僕が普段利用しているルール設定も記載しておきます。
rules = [ MockitoThenToDo SingleConditionMatch RemoveUnused NoAutoTupling NoValInForComprehension ProcedureSyntax fix.scala213.FinalObject fix.scala213.Any2StringAdd fix.scala213.Varargs fix.scala213.ExplicitNullaryEtaExpansion ExplicitResultTypes ZeroIndexToHead CheckIsEmpty UnnecessarySemicolon NonCaseException UnifyEmptyList OrganizeImports ] RemoveUnused { imports = true privates = false locals = true patternvars = true params = false } OrganizeImports { coalesceToWildcardImportThreshold = 6 groupedImports = Merge groups = [ "re:javax?\\." "scala.", "*", "com.sun." ] }
scalafix-pixiv-rule
ここまで読んでくださったそこのあなた!少しだけ宣伝をさせてください。
先述した Scalafix コミュニティルールの一つとして、僕たちが提供している scalafix-pixiv-rule があります。
提供しているルールをいくつか抜粋して紹介します。他にもいくつかルールを提供しているので、よければ GitHub Repository の README も確認してみてください。
UnnecessarySemicolon
不要な行末セミコロンを削除します。
val x = 3; // rewrite to: `val x = 3` val a = 1; val b = 2
ZeroIndexToHead
Seq
のインデックスによる最初の要素へのアクセスを head
呼び出しに置換します。
Seq(1, 2, 3)(0) // rewrite to: `Seq(1, 2, 3).head
CheckIsEmpty
Option
や Seq
の空チェックで isEmpty
, nonEmpty
, isDefined
を利用するように置き換えます。
Some(1) == None // rewrite to: Some(1).isEmpty Some(1).nonEmpty // if `CheckIsEmpty.alignIsDefined = true` then rewrite to Some(1).isDefined
UnifyEmptyList
型変数指定のない List()
や List.empty
を Nil
に置き換えます。 これは、 Nil
が List[Nothing]
として定義されているためです。 また、型変数指定のある List[Any]()
は List.empty[Any]
へと置換されます。
val empty = List.empty // rewrite to: val empty = Nil val list = List[String]() // rewrite to: val list = List.empty[String]
SingleConditionMatch
単一の case
しか持たないパターンマッチを分解します。
また、 Some.unapply
のみが利用されているパターンを foreach
呼び出しに置換します。
Some(1) match { case result => println(result) } // rewrite to: println(Some(1))
おわりに
今回は、 Scalafix とはどんなツールか?どうやって使うのか?を紹介してきました。
こちら の記事では Scalafix ルールを自作する方法についても解説していますので、「こんな規約がほしいけど、誰も公開していないんだよな〜」という悩みを抱えている方は、ぜひ挑戦してみてください!