Skip to content

Instantly share code, notes, and snippets.

@xuwei-k
Last active January 6, 2021 04:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xuwei-k/ff0953aeb7624193f02ce26e517075c3 to your computer and use it in GitHub Desktop.
Save xuwei-k/ff0953aeb7624193f02ce26e517075c3 to your computer and use it in GitHub Desktop.
package fix
import fix.FileNameConsistent._
import scalafix.Diagnostic
import scalafix.Patch
import scalafix.lint.LintSeverity
import scalafix.v1.SyntacticDocument
import scalafix.v1.SyntacticRule
import scala.meta.Defn
import scala.meta.Pkg
import scala.meta.inputs.Input
import scala.meta.inputs.Position
import java.nio.file.Files
import java.nio.file.Paths
class FileNameConsistent extends SyntacticRule("FileNameConsistent") {
override def isLinter = true
override def fix(implicit doc: SyntacticDocument): Patch = {
val defs = doc.tree
.collect {
case x: Defn.Trait if x.isTopLevel =>
TemplateDef(x, x.name.value)
case x: Defn.Class if x.isTopLevel =>
TemplateDef(x, x.name.value)
case x: Defn.Object if x.isTopLevel =>
TemplateDef(x, x.name.value)
}
val scalaSourceOpt = PartialFunction
.condOpt(doc.input) {
case f: Input.File =>
ScalaSource(
fullPath = f.path.toString,
name = f.path.toFile.getName.replace(".scala", "")
)
case f: Input.VirtualFile =>
ScalaSource(
fullPath = f.path,
name = f.path.split('/').lastOption.getOrElse("").replace(".scala", "")
)
}
scalaSourceOpt match {
case Some(src) if defs.nonEmpty =>
val names = defs.map(_.name).distinct
if (names.contains(src.name)) {
// correct
Patch.empty
} else if (src.name == "package") {
// exclude `package.scala`
Patch.empty
} else {
val classNameOpt = {
names match {
case List(x) =>
Some(x)
case _ =>
// 2つ以上あると、どのclass名に合わせてファイル名を変更すればいいか決められないため、
// 1つのときはそのまま強制書き換えし、
// 2つ以上のときは `scala.io.StdIn.readInt` で選択させる
println(
names.zipWithIndex
.map {
case (name, index) =>
s"${index} : $name"
}
.mkString(s"${src.fullPath}を、どれにrenameするか選択してください\n", "\n", "")
)
val input = scala.io.StdIn.readInt()
val result = names.lift(input)
if (result.isEmpty) {
println(s"invalid input ${input}")
}
result
}
}
classNameOpt.foreach { className =>
val separator = '/'
val newName = className + ".scala"
val newPath = src.fullPath.split(separator).init.mkString(separator.toString) + separator + newName
Files.move(
Paths.get(src.fullPath),
Paths.get(newPath)
)
}
Patch.lint(
FileNameConsistentWaring(
names = names,
path = src.fullPath,
position = defs.head.tree.pos
)
)
}
case _ =>
// no classes, traits, objects ???
Patch.empty
}
}
}
object FileNameConsistent {
case class ScalaSource(fullPath: String, name: String)
case class TemplateDef(tree: Defn, name: String)
case class FileNameConsistentWaring(names: List[String], path: String, override val position: Position)
extends Diagnostic {
override def message: String = s"file名とclass名が一致しません names = ${names.mkString("[", ", ", "]")}"
override def severity: LintSeverity = LintSeverity.Warning
}
implicit class DefnOps(private val self: Defn) extends AnyVal {
def isTopLevel: Boolean =
self.parent.exists(_.is[Pkg])
}
}
echo 'addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.24")' > project/scalafix.sbt
sbt "scalafixAll https://gist.githubusercontent.com/xuwei-k/ff0953aeb7624193f02ce26e517075c3/raw/d067a550133d9c34f1c173a82bd6b343f0899fce/FileNameConsistent.scala"
rm project/scalafix.sbt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment