本体にドキュメント書いてないので、とりあえず現時点のruleについてひたすら説明書いておきます
https://github.com/xuwei-k/scalafix-rules
AddExplicitImplicitTypes
implicit val foo = new Foo
を
implicit val foo: Foo = new Foo
に書き換え。そもそもscalafix本体にもっと高機能なものがあるが、そちらはSemanticRuleで重たいため、SyntacticRuleで可能なごく単純なものだけ型をつけるもの。 これに限らず、基本的にSemanticRuleやった方が正確になる可能性があるが、重たいのであえてSyntacticRuleで頑張っているものばかりである。 以下特に言及がなければSyntacticRuleである。
CirceCodec
https://github.com/xuwei-k/scalafix-rules/blob/v0.1.3/rules/src/main/scala/fix/CirceCodec.scala
circeに @JsonCodec
というmacro annotationがあるが、macro annotationはScala 3ではそのままでは動かないため、semiautoに書き換えるもの。
import io.circe.generic.JsonCodec @JsonCodec case class Foo(a: Int)
が
import io.circe.Codec import io.circe.generic.semiauto.deriveCodec case class Foo(a: Int) object Foo { implicit val codec: Codec.AsObject[Foo] = deriveCodec[Foo] }
になる。
型引数あるパターンはこれ書いてる時点で未対応だが、たぶん後で対応追加するかも。
対応しました
DirectoryAndPackageName
現状では、書き換えは実行せずに警告出すだけ。 Javaのように、Scalaでも結局packageとdirectoryをそろえる慣習になることが多いが、それが食い違っていたら警告してくれる。
DuplicateWildcardImport
import scala.util._ import scala.util.Success import scala.util.Failure
のようなimportは、ごく一部の例外を除いてワイルドカードだけ残して
import scala.util._
と同等なので、そのように書き換えるもの。
ExplicitImplicitTypes
Scala 3に備えて、implicitなvalやdefに明示的な型が書いてないと警告するもの。 wartremoverにほぼ同等のものがあるが、こちらの方がSyntacticRuleなのでかるくて素早く実行出来るはず。
FileNameConsistent
DirectoryAndPackageNameと似たような感じで、class名とfile名が明らかに食い違っている場合に警告してくれる
LambdaParamParentheses
以下のようなScalaコードは、3から括弧をつけないといけないので、それを警告してくれるもの
def a: Int => Int = { x: Int => x }
TODO: 以下のように書き換えするやつも作りたい
def a: Int => Int = { (x: Int) => x }
NoElse
https://github.com/xuwei-k/scalafix-rules/blob/v0.1.3/rules/src/main/scala/fix/NoElse.scala
半分以上、ジョークで作ったもので、個人的にそこまで実用する予定はないが、 「elseがないifは副作用だぞ!」
elseが無いif、それが有効なコードとして機能するには副作用が発生しているか帯域脱出をはらむしか無いので、副作用を局所化しようとするコードベースでは要注意対象になるのはむべなるかな。
— がくぞ (@gakuzzzz) January 9, 2022
どうしてもelseを書きたくなければAlternativeのguardを使いましょう(暴論
みたいな話題をtwitterで見かけて、何となく作ったもの。 elseがないifを検知してくれる。
ObjectSelfType
https://github.com/xuwei-k/scalafix-rules/blob/v0.1.3/rules/src/main/scala/fix/ObjectSelfType.scala
Scala 2ではなぜかobjectに以下のように書けるが、これは有効活用できる場面が皆無?で、Scala 3ではエラーなので、それを警告する。
object A { self: B => }
ただの object A{ self =>
というalias的な使い方なら、意味がある場合があるので、それは良い。あくまで型が書いてある場合。
RemoveEmptyObject
中身が空(継承もしてない)、なobjectを消す
RemovePureEff
https://github.com/xuwei-k/scalafix-rules/blob/v0.1.3/rules/src/main/scala/fix/RemovePureEff.scala
現状ではEffのライブライに特化していて、後で任意のMonadのpointに汎用化したいのだが、以下の時に、最初以外のやつはそのまま =
すればいいので、その書き換えを行う
for { a1 <- 1.pureEff[R] a2 <- 2.pureEff[R] a3 = 3.pureEff[R] a4 <- 4.pureEff[R] } yield (a1, a2, a3, a4)
for { a1 <- 1.pureEff[R] a2 = 2 a3 = 3.pureEff[R] a4 = 4 } yield (a1, a2, a3, a4)
RemoveSamePackageImport
同じpackageのclassなどは通常importしなくても参照可能なので、そのimportを消す。
(もちろん例外的なパターンでは意味がある場合があるから、普通のscalafixのやつでは消してくれない?っぽいが)
package xxx.yyy import xxx.yyy.ZZZ // このimport必要なくない?
Scala3ImportRewrite
Scala 3に合わせて、ワイルドカードを import a.b._
から import a.b.*
に変える
TODO: 逆も作りたい?
Scala3ImportWarn
Scala3ImportRewriteと同等で、書き換えせずに警告だけ出す版
Scala3Placeholder
Scala 3ではplaceholderがJavaと同じように Class[?]
としていきたいらしい?ので Class[_]
を Class[?]
に書き換える
ScalaApp
scala.App
は、結局Scala 3で実質非推奨なので、明示的な def main(args: Array[String]): Unit = {
に書き換えるもの。
何をmainの中に残して、何を外に出すか?が難しく、割と壊れやすいので改善したい
https://github.com/xuwei-k/scalafix-rules/blob/v0.1.3/rules/src/main/scala/fix/ScalaApp.scala
ちなみに、それの検知くんは別途wartremover内部に入れました
https://docs.scala-lang.org/scala3/book/methods-main-methods.html https://github.com/wartremover/wartremover/blob/ff013fca7fbeabf527fc7451f8e8df87578716c7/core/src/main/scala/wartremover/warts/ScalaApp.scala
ScalazEitherInfix
scalazの \/[A, B]
という記述を A \/ B
に変えるだけのもの。
これは当初SemanticRuleで書いたので、今はそのままだが、別に \/
というclass名は他にないだろうし、SyntacticRuleで良い気がする。
あと、もっと汎用化したい・・・?
というか、よく考えたら別にscalafmtとかでいけた気もする・・・?が、作ったのでとりあえず置いておく。
SimplifyForYield
for { res <- foo.bar } yield res
というのは、通常、単に foo.bar
に書き換え可能でfor式いらないので、その書き換えを行う。
ThrowableToNonFatal
ThrowableでcatchしているのをNonFatalに置き換え。 import追加をscalafixに任せた都合上SemanticRule。 これは別に低レベルなテキスト置換でもいける気がするが・・・。一応import追加もあるから、その点はテキスト置換だと少し面倒か?
UnnecessarySort
https://github.com/xuwei-k/scalafix-rules/blob/v0.1.3/rules/src/main/scala/fix/UnnecessarySort.scala
list.sortBy(f).head
->list.minBy(f)
list.sortBy(f).headOption
->list.minByOption(f)
list.sortBy(f).last
->list.maxBy(f)
list.sortBy(f).lastOption
->list.maxByOption(f)
sortしてから1つだけ取り出す、という操作はやってしまいがちだが、それなら全てをsortする必要はなく、専用のメソッドが標準ライブラリのcollectionに存在するので、そのパターンを検知する。 SyntacticRuleなので、当然誤検知はあり得る。
UnnecessarySortRewriteで書き換えも可能。
UnusedConstructorParams
以下のような条件で検索すれば、原理上、SyntacticRuleでも、ほぼ100%?未使用なclassのコンストラクタ引数を検知できる?と思ったので、そういう原理で作ってみたもの。実際、誤検知しない気がする・・・? 現状、警告のみで、書き換えは行わない。
- case classではない
val
が付与されてないimplicit
も付与されてない- そのclass内部のtokenを全て走査して、その名前が存在しない