Skip to content

Commit

Permalink
Merge pull request #2136 from GiganticMinecraft/scalafix_lint
Browse files Browse the repository at this point in the history
  • Loading branch information
KisaragiEffective authored Jun 30, 2023
2 parents 51f4397 + 4812ba8 commit 2f294a8
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 1 deletion.
7 changes: 6 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ ThisBuild / description := "ギガンティック☆整地鯖の独自要素を
// Scalafixが要求するため、semanticdbは有効化する
ThisBuild / semanticdbEnabled := true
ThisBuild / semanticdbVersion := scalafixSemanticdb.revision
ThisBuild / scalafixScalaBinaryVersion :=
CrossVersion.binaryScalaVersion(scalaVersion.value)

// endregion

Expand Down Expand Up @@ -68,6 +70,9 @@ val providedDependencies = Seq(
"org.typelevel" %% "simulacrum" % "1.0.1"
).map(_ % "provided")

val scalafixCoreDep =
"ch.epfl.scala" %% "scalafix-core" % _root_.scalafix.sbt.BuildInfo.scalafixVersion % ScalafixConfig

val testDependencies = Seq(
"org.scalamock" %% "scalamock" % "5.2.0",
"org.scalatest" %% "scalatest" % "3.2.16",
Expand Down Expand Up @@ -189,7 +194,7 @@ Compile / PB.targets := Seq(scalapb.gen() -> (Compile / sourceManaged).value / "
lazy val root = (project in file(".")).settings(
name := "SeichiAssist",
assembly / assemblyOutputPath := baseDirectory.value / "target" / "build" / "SeichiAssist.jar",
libraryDependencies := providedDependencies ++ testDependencies ++ dependenciesToEmbed,
libraryDependencies := (providedDependencies :+ scalafixCoreDep) ++ testDependencies ++ dependenciesToEmbed,
excludeDependencies := Seq(ExclusionRule(organization = "org.bukkit", name = "bukkit")),
unmanagedBase := baseDirectory.value / "localDependencies",
scalacOptions ++= Seq(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package fix

import scalafix.v1._

import scala.meta._

/**
* Lints on string interpolation where its variable part contains `case class` without `toString`.
*/
// noinspection ScalaUnusedSymbol; referred from scalafix implicitly
// NOTE: see AST on https://xuwei-k.github.io/scalameta-ast/ or https://astexplorer.net
class WarnUnoverriddenImplicitToStringCallsOnCaseClass extends SemanticRule("WarnUnoverriddenImplicitToStringCallsOnCaseClass") {
override def fix(implicit doc: SemanticDocument): Patch = {
val s = doc.tree.collect {
// string interpolation in standard library
case Term.Interpolate((prefix, _, args)) =>
if (prefix.value != "s") {
return Patch.empty
}

Patch.fromIterable(
args.collect { arg =>
val tp = arg.symbol
val info = tp.info.get
// does `tp` point to some `case class`?
val isCaseClass = info.isCase && info.isClass

// lazily evaluated since most classes are not `case class`
lazy val isToStringOverriden = info.overriddenSymbols.exists(overridenMethodSym => overridenMethodSym.value == "toString" && {
overridenMethodSym.info.get.signature match {
// def toString[](): <return type> の形の override を見つけたい。
// もし toString() の return type が String のサブタイプにならないような型であれば
// scalafix が走る前にコンパイルが落ちるので、ここで改めて return type が
// String のサブタイプであるかは考慮する必要はない
case MethodSignature(List(), List(), _) => true
case _ => false
}
})

if (!isCaseClass || isToStringOverriden) {
return Patch.empty
}

Patch.lint(new Diagnostic {
override def message: String = "Case class value shouldn't be interpolated, use `toString` " +
"if you wish to interpolate the String representation into the string"

// points to arg
override def position: _root_.scala.meta.Position = arg.pos
})
}
)
case _ => Patch.empty
}

Patch.fromIterable(s)
}
}

0 comments on commit 2f294a8

Please sign in to comment.