All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fix.DirectoryAndPackageName.scala Maven / Gradle / Ivy

The newest version!
package fix

import java.io.File
import java.util.Locale
import metaconfig.ConfDecoder
import metaconfig.Configured
import metaconfig.generic.Surface
import scala.meta.Pkg
import scala.meta.Term
import scala.meta.Tree
import scala.meta.XtensionCollectionLikeUI
import scala.meta.inputs.Input
import scalafix.Diagnostic
import scalafix.Patch
import scalafix.lint.LintSeverity
import scalafix.v1.Configuration
import scalafix.v1.Rule
import scalafix.v1.SyntacticDocument
import scalafix.v1.SyntacticRule

case class DirectoryAndPackageNameConfig(
  baseDirectory: Seq[String],
  keyword: Seq[String],
  severity: LintSeverity
)

object DirectoryAndPackageNameConfig {
  val default: DirectoryAndPackageNameConfig = DirectoryAndPackageNameConfig(
    baseDirectory = Seq(
      "/src/main/scala-2.11/",
      "/src/test/scala-2.11/",
      "/src/main/scala-2.12/",
      "/src/test/scala-2.12/",
      "/src/main/scala-2.13/",
      "/src/test/scala-2.13/",
      "/src/main/scala-2/",
      "/src/test/scala-2/",
      "/src/main/scala-3/",
      "/src/test/scala-3/",
      "/src/main/scala/",
      "/src/test/scala/",
    ),
    keyword = Seq(
      "abstract",
      "case",
      "catch",
      "class",
      "def",
      "do",
      "else",
      "enum",
      "export",
      "extends",
      "false",
      "final",
      "finally",
      "for",
      "forSome",
      "if",
      "implicit",
      "import",
      "lazy",
      "macro",
      "match",
      "ne",
      "new",
      "null",
      "object",
      "override",
      "package",
      "private",
      "protected",
      "return",
      "sealed",
      "super",
      "then",
      "this",
      "throw",
      "trait",
      "try",
      "true",
      "type",
      "val",
      "var",
      "while",
      "with",
      "yield",
    ),
    severity = LintSeverity.Warning
  )

  implicit val surface: Surface[DirectoryAndPackageNameConfig] =
    metaconfig.generic.deriveSurface[DirectoryAndPackageNameConfig]

  implicit val decoder: ConfDecoder[DirectoryAndPackageNameConfig] = {
    implicit val instance: metaconfig.ConfDecoder[scalafix.lint.LintSeverity] = { conf =>
      conf.as[String].map(_.toUpperCase(Locale.ROOT)).map {
        case "ERROR" =>
          LintSeverity.Error
        case "INFO" =>
          LintSeverity.Info
        case _ =>
          LintSeverity.Warning
      }
    }

    metaconfig.generic.deriveDecoder(default)
  }
}

class DirectoryAndPackageName(config: DirectoryAndPackageNameConfig) extends SyntacticRule("DirectoryAndPackageName") {

  def this() = this(DirectoryAndPackageNameConfig.default)

  override def withConfiguration(config: Configuration): Configured[Rule] = {
    config.conf
      .getOrElse("DirectoryAndPackageName")(this.config)
      .map(newConfig => new DirectoryAndPackageName(newConfig))
  }

  private[this] val keywords: Set[String] = config.keyword.map("`" + _ + "`").toSet

  override def isLinter = true

  private def getPackages(t: Tree): List[Term.Ref] = {
    t.collect { case p: Pkg => p.ref :: p.body.stats.flatMap(getPackages) }.flatten.distinct
  }

  override def fix(implicit doc: SyntacticDocument): Patch = {
    val packages = getPackages(doc.tree)

    val packageObjectOpt =
      doc.tree.collect { case x: Pkg.Object =>
        x
      }.headOption

    val scalaSourceOpt = PartialFunction
      .condOpt(doc.input) {
        case f: Input.VirtualFile =>
          f.path
        case f: Input.File =>
          f.path.toString
      }
      .map { path =>
        File.separatorChar match {
          case '/' =>
            path
          case c =>
            path.replace(c, '/')
        }
      }

    {
      for {
        path <- scalaSourceOpt
        if packages.nonEmpty // TODO check if empty package
        packageName = {
          val x = packages
            .flatMap(_.toString.split('.'))
            .map(p =>
              if (keywords(p)) { p.replace("`", "") }
              else p
            )
          packageObjectOpt match {
            case Some(value) =>
              x :+ value.name.value
            case None =>
              x
          }
        }
        if config.baseDirectory.find { dir =>
          path.contains(dir)
        }.exists { fullDir =>
          val splitedDir = path.split(fullDir).last.split('/').init.toList
          packageName != splitedDir
        } || {
          path.split('/').init.toList.takeRight(packageName.size) != packageName
        }
      } yield {
        Patch.lint(
          Diagnostic(
            id = "",
            message = s"inconsistent package and directory\n${path}\n${packageName.mkString("/")}",
            position = packages.last.pos,
            severity = config.severity
          )
        )
      }
    }.getOrElse(Patch.empty)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy