3

我有一个文件src/main/scala/foo.scala需要在 package 内bar。理想情况下,文件应该在里面src/main/scala/bar/foo.scala

// src/main/scala/foo.scala


package bar

// ... 

如何在整个项目中自动修复此问题,以使文件夹结构与包结构匹配?

是否有任何 SBT 插件等可以帮助我解决此问题?

4

2 回答 2

3

据我所知,没有这样的工具,尽管 AFAIR IntelliJ 可以警告包目录不匹配。

我能想到的最好的方法是自定义 scalafix ( https://scalacenter.github.io/scalafix/ ) 规则 - scalafix/scalameta 将用于检查文件的实际包,将其转换为预期的目录,如果它们不同,则移动文件。

我建议使用 scalafix/scalameta,因为有一些极端情况,例如:

  • 你可以写你的包,比如:

    package a
    package b
    package c
    

    和它几乎一样package a.b.c,只是它自动从a和导入所有内容b

  • 你可以package object在你的文件中,然后如果你有

package a.b

package object c

这个文件应该在a/b/c目录中

所以我更愿意检查文件是否不属于任何使用现有工具的文件。

如果您确定没有此类情况(我不会不检查),您可以:

  • 将第一行与正则表达式 ( ^package (.*))匹配
  • 翻译a.b.ca/b/c( matched.split('.').map(_.trim).mkString(File.separator))
  • 将生成的位置与实际位置进行比较(我建议解析绝对文件位置)
  • 必要时移动文件

如果可能有比这更复杂的情况,我可以通过查询 scalafix/scalameta 实用程序来替换第一步。

于 2020-05-09T11:51:54.550 回答
2

这是一个 sbt 插件提供的packageStructureToDirectoryStructure任务,它从源文件中读取包语句,创建相应的目录,然后将文件移动到它们

import sbt._
import sbt.Keys._
import better.files._

object PackagesToDirectories extends AutoPlugin {
  object autoImport {
    val packageStructureToDirectoryStructure = taskKey[Unit]("Make directory structure match package structure")
  }

  import autoImport._

  override def trigger = allRequirements

  override lazy val projectSettings = Seq(
    packageStructureToDirectoryStructure := {
      val log = streams.value.log
      log.info(s"Refactoring directory structure to match package structure...")
      val sourceFiles = (Compile / sources).value
      val sourceBase = (Compile / scalaSource).value

      def packageStructure(lines: Traversable[String]): String = {
        val packageObjectRegex = """package object\s(.+)\s\{""".r
        val packageNestingRegex = """package\s(.+)\s\{""".r
        val packageRegex = """package\s(.+)""".r
        lines
          .collect {
            case packageObjectRegex(name) => name
            case packageNestingRegex(name) => name
            case packageRegex(name) => name
          }
          .flatMap(_.split('.'))
          .mkString("/")
      }

      sourceFiles.foreach { sourceFile =>
        val packagePath = packageStructure(sourceFile.toScala.lines)
        val destination = file"$sourceBase/$packagePath"
        destination.createDirectoryIfNotExists(createParents = true)
        val result = sourceFile.toScala.moveToDirectory(destination)
        log.info(s"$sourceFile moved to $result")
      }
    }
  )

}

警告:确保在运行之前备份项目。

于 2020-05-09T11:52:55.523 回答