From fd5a3bcce965cfe74e2718c9d7c509f8db3d487d Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Mon, 7 Oct 2024 10:49:09 -0400 Subject: [PATCH] Add rule to suggest `given` and `using` instead of `implicit`. --- .../src/main/scala-3/fix/GivenUsing.scala | 21 ++++++++++ .../src/main/scala-3/fix/GivenUsing.scala | 12 ++++++ .../META-INF/services/scalafix.v1.Rule | 1 + .../rules/src/main/scala/fix/GivenUsing.scala | 41 +++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 scalafix/input/src/main/scala-3/fix/GivenUsing.scala create mode 100644 scalafix/output/src/main/scala-3/fix/GivenUsing.scala create mode 100644 scalafix/rules/src/main/scala/fix/GivenUsing.scala diff --git a/scalafix/input/src/main/scala-3/fix/GivenUsing.scala b/scalafix/input/src/main/scala-3/fix/GivenUsing.scala new file mode 100644 index 0000000..4cce8fb --- /dev/null +++ b/scalafix/input/src/main/scala-3/fix/GivenUsing.scala @@ -0,0 +1,21 @@ +/* +rule = GivenUsing +*/ +package fix + +object GivenUsing { + implicit val i: Int = 1/* assert: GivenUsing + ^^^^^^^^ + Use `given` instead of `implicit val` */ + implicit val j: Int = 1 // scalafix:ok GivenUsing + + implicit def s: String = ""/* assert: GivenUsing + ^^^^^^^^ + Use `given` instead of `implicit def` */ + + implicit def conversion(i: Int): String = i.toString + + def implicitArg(implicit i: Int): Int = i/* assert: GivenUsing + ^^^^^^^^ + Use `using` instead of `implicit` */ +} diff --git a/scalafix/output/src/main/scala-3/fix/GivenUsing.scala b/scalafix/output/src/main/scala-3/fix/GivenUsing.scala new file mode 100644 index 0000000..713e025 --- /dev/null +++ b/scalafix/output/src/main/scala-3/fix/GivenUsing.scala @@ -0,0 +1,12 @@ +package fix + +object GivenUsing { + implicit val i: Int = 1 + implicit val j: Int = 1 // scalafix:ok GivenUsing + + implicit def s: String = "" + + implicit def conversion(i: Int): String = i.toString + + def implicitArg(implicit i: Int): Int = i +} diff --git a/scalafix/rules/src/main/resources/META-INF/services/scalafix.v1.Rule b/scalafix/rules/src/main/resources/META-INF/services/scalafix.v1.Rule index ade4a93..f5d12b3 100644 --- a/scalafix/rules/src/main/resources/META-INF/services/scalafix.v1.Rule +++ b/scalafix/rules/src/main/resources/META-INF/services/scalafix.v1.Rule @@ -1,3 +1,4 @@ +fix.GivenUsing fix.NoUnnecessaryCase fix.NoUnnecessaryForComprehension fix.StrictSubclassAccess diff --git a/scalafix/rules/src/main/scala/fix/GivenUsing.scala b/scalafix/rules/src/main/scala/fix/GivenUsing.scala new file mode 100644 index 0000000..a417971 --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/GivenUsing.scala @@ -0,0 +1,41 @@ +package fix + +import scalafix.v1._ +import scala.meta._ + +case class UseGiven(kw: String, position: Position) extends Diagnostic { + override lazy val message = s"Use `given` instead of `implicit $kw`" +} + +case class UseUsing(position: Position) extends Diagnostic { + override lazy val message = "Use `using` instead of `implicit`" +} + +private case class ParsedParams(allImplicitGroups: Boolean, implicitMods: List[Mod]) + +class GivenUsing extends SyntacticRule("GivenUsing") { + private def implicitMod(mods: List[Mod]): Option[Mod] = mods.find(_.is[Mod.Implicit]) + + private def parseParams(paramGroups: List[Member.ParamClauseGroup]): ParsedParams = + paramGroups.foldRight(ParsedParams(true, Nil))((group, acc) => + group.paramClauses.foldRight(acc) { + case (clause, ParsedParams(accAll, accImplicitMods)) => + implicitMod(clause.mod.toList).map(m => ParsedParams(accAll, m :: accImplicitMods)) + .orElse(clause.mod.find(_.is[Mod.Using]).map(_ => ParsedParams(accAll, accImplicitMods))) + .getOrElse(ParsedParams(false, accImplicitMods)) + } + ) + + override def fix(implicit doc: SyntacticDocument): Patch = + doc.tree.collect { + case Defn.Val(mods, _, _, _) => implicitMod(mods).fold(Patch.empty)(m => Patch.lint(UseGiven("val", m.pos))) + + case Defn.Def.After_4_7_3(mods, _, paramGroups, _, _) => + val parsed = parseParams(paramGroups) + val givenPatch = implicitMod(mods) + .filter(_ => parsed.allImplicitGroups) + .fold(Patch.empty)(m => Patch.lint(UseGiven("def", m.pos))) + val usingPatch = parsed.implicitMods.map(m => Patch.lint(UseUsing(m.pos))).asPatch + givenPatch + usingPatch + }.asPatch +}