diff --git a/build.sbt b/build.sbt index d9f23c24e5..dce2b6a4a2 100644 --- a/build.sbt +++ b/build.sbt @@ -69,13 +69,13 @@ def isOnly(scalaV: String) = Seq( crossScalaVersions := Seq(scalaV) ) -lazy val `scalafmt-sbt` = project +lazy val `scalafmt-cli-sbt` = project .configs(IntegrationTest) .settings( allSettings, Defaults.itSettings, mimaPreviousArtifacts := Set.empty, - moduleName := "sbt-scalafmt", + moduleName := "sbt-cli-scalafmt", isOnly(scala212), sbtPlugin := true, sbtVersion in Global := "1.0.0", @@ -90,6 +90,17 @@ lazy val `scalafmt-sbt` = project ) .dependsOn(cli) +lazy val `scalafmt-sbt` = project + .settings( + allSettings, + mimaPreviousArtifacts := Set.empty, + moduleName := "sbt-scalafmt", + isOnly(scala212), + sbtPlugin := true, + sbtVersion in Global := "1.0.0" + ) + .dependsOn(coreJVM) + lazy val `scalafmt-sbt-tests` = project .settings( allSettings, @@ -336,7 +347,7 @@ def ciCommands = Seq( Nil ), Command.command("ci-sbt-scalafmt") { s => - "scalafmt-sbt/it:test" :: + "scalafmt-cli-sbt/it:test" :: s }, Command.command("ci-publish") { s => diff --git a/scalafmt-cli-sbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala b/scalafmt-cli-sbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala new file mode 100644 index 0000000000..6b36b5370f --- /dev/null +++ b/scalafmt-cli-sbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala @@ -0,0 +1,25 @@ +package org.scalafmt.sbt +import scala.meta.internal.tokenizers.PlatformTokenizerCache +import sbt._ +import Keys._ + +object ScalafmtPlugin extends AutoPlugin { + override def trigger: PluginTrigger = allRequirements + object autoImport { + val scalafmt: Command = + Command.args("scalafmt", "run the scalafmt command line interface.") { + case (state, args) => + org.scalafmt.cli.Cli.main("--non-interactive" +: args.toArray) + PlatformTokenizerCache.megaCache.clear() + state + } + } + override def globalSettings: Seq[Def.Setting[_]] = + Seq( + commands += autoImport.scalafmt + ) ++ + addCommandAlias("scalafmtTest", "scalafmt --test") ++ + addCommandAlias("scalafmtDiffTest", "scalafmt --diff --test") ++ + addCommandAlias("scalafmtDiff", "scalafmt --diff") + +} diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala index efd414d62c..b4d1df3d56 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala @@ -80,7 +80,11 @@ object Scalafmt { **/ def configWithDialect( config: ScalafmtConfig, - dialect: Dialect - ): ScalafmtConfig = + dialect: Dialect): ScalafmtConfig = config.withDialect(dialect) + + def configForSbt( + config: ScalafmtConfig + ): ScalafmtConfig = + config.forSbt } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala index f0c4d875e1..c7cfb4a17d 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ScalafmtConfig.scala @@ -152,6 +152,8 @@ case class ScalafmtConfig( def withDialect(dialect: Dialect): ScalafmtConfig = copy(runner = runner.copy(dialect = dialect)) + def forSbt: ScalafmtConfig = copy(runner = runner.forSbt) + def reformatDocstrings: Boolean = docstrings != Docstrings.preserve def scalaDocs: Boolean = docstrings == Docstrings.ScalaDoc lazy val alignMap: Map[String, Regex] = diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/.gitignore b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/.gitignore similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/.gitignore rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/.gitignore diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/.scalafmt.conf b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/.scalafmt.conf similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/.scalafmt.conf rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/.scalafmt.conf diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/build.sbt b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/build.sbt similarity index 94% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/build.sbt rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/build.sbt index 36e9a417e6..66f23f8e3b 100644 --- a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/build.sbt +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/build.sbt @@ -59,7 +59,7 @@ TaskKey[Unit]("check") := { new File("project/plugins.sbt"), """ |addSbtPlugin( - | "com.geirsson" % "sbt-scalafmt" % System.getProperty("plugin.version") + | "com.geirsson" % "sbt-cli-scalafmt" % System.getProperty("plugin.version") |) """.stripMargin ) diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p1/src/main/scala/Test.scala b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p1/src/main/scala/Test.scala similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p1/src/main/scala/Test.scala rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p1/src/main/scala/Test.scala diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p1/src/test/scala/MainTest.scala b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p1/src/test/scala/MainTest.scala similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p1/src/test/scala/MainTest.scala rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p1/src/test/scala/MainTest.scala diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p2/src/main/scala/Test.scala b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p2/src/main/scala/Test.scala similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p2/src/main/scala/Test.scala rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p2/src/main/scala/Test.scala diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p2/src/test/scala/MainTest.scala b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p2/src/test/scala/MainTest.scala similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p2/src/test/scala/MainTest.scala rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p2/src/test/scala/MainTest.scala diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p3/src/main/java/TestJava.java b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p3/src/main/java/TestJava.java new file mode 100644 index 0000000000..b74418b3f9 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p3/src/main/java/TestJava.java @@ -0,0 +1,6 @@ +public class TestJava +{ + public static void main(String[] args) { + System.out.println("Hello World!"); + } +} \ No newline at end of file diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p3/src/main/scala/Test.scala b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p3/src/main/scala/Test.scala similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p3/src/main/scala/Test.scala rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p3/src/main/scala/Test.scala diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p3/src/test/java/MainTestJava.java b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p3/src/test/java/MainTestJava.java new file mode 100644 index 0000000000..1d77ec9372 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p3/src/test/java/MainTestJava.java @@ -0,0 +1,6 @@ +public class MainTestJava +{ + public static void main(String[] args) { + System.out.println("Hello World!"); + } +} \ No newline at end of file diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p3/src/test/scala/MainTest.scala b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p3/src/test/scala/MainTest.scala similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/p3/src/test/scala/MainTest.scala rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/p3/src/test/scala/MainTest.scala diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/project/build.properties b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/project/build.properties similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/project/build.properties rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/project/build.properties diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/project/plugins.sbt b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/project/plugins.sbt new file mode 100644 index 0000000000..72bea2da5c --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/project/plugins.sbt @@ -0,0 +1,3 @@ +addSbtPlugin ( + "com.geirsson" % "sbt-cli-scalafmt" % System.getProperty("plugin.version")) + diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/test b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/test similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/test rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt-cli/test diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/.scalafmt.conf b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/.scalafmt.conf new file mode 100644 index 0000000000..84f13659a0 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/.scalafmt.conf @@ -0,0 +1 @@ +style = IntelliJ diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/.scalafmt6.conf b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/.scalafmt6.conf new file mode 100644 index 0000000000..5ae2d1e7f6 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/.scalafmt6.conf @@ -0,0 +1,4 @@ +style = default +project.excludeFilters = [ + MainTest +] \ No newline at end of file diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/build.sbt b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/build.sbt new file mode 100644 index 0000000000..aa0907aba3 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/build.sbt @@ -0,0 +1,148 @@ +import java.io.File + +fork in ThisBuild := true + +lazy val p123 = project + .in(file(".")) + .aggregate( + p1, + p2, + p3 + ) + +lazy val p1 = project.settings( + scalaVersion := "2.10.5" +) +lazy val p2 = project.settings( + scalaVersion := "2.11.8" +) +lazy val p3 = project.settings( + scalaVersion := "2.12.1" +) +lazy val p4 = project.settings( + scalaVersion := "2.12.1" +) +lazy val p5 = project.settings( + scalaVersion := "2.12.1", + scalafmtOnCompile := true +) +lazy val p6 = project.settings( + scalaVersion := "2.12.1", + scalafmtConfig := Some(file(".scalafmt6.conf")) +) +lazy val p7 = project.settings( + scalaVersion := "2.12.1", + scalafmtConfig := None +) + +def assertContentsEqual(file: File, expected: String): Unit = { + val obtained = + scala.io.Source.fromFile(file).getLines().mkString("\n") + + if (obtained.trim != expected.trim) { + val msg = + s"""File: $file + |Obtained output: + |$obtained + |Expected: + |$expected + |""".stripMargin + System.err.println(msg) + throw new Exception(msg) + } +} + +TaskKey[Unit]("check") := { + (1 to 4).foreach { i => + val expectedTest = + """ + |object Test { + | foo( + | a, // comment + | b + | ) + |} + """.stripMargin + val expectedMainTest = expectedTest.replaceFirst("Test", "MainTest") + assertContentsEqual( + file(s"p$i/src/main/scala/Test.scala"), + expectedTest + ) + assertContentsEqual( + file(s"p$i/src/test/scala/MainTest.scala"), + expectedMainTest + ) + } + + + assertContentsEqual( + file(s"p5/src/main/scala/Test.scala"), + """ + |object Test { + | def foo(a: Int, // comment + | b: Double) = ??? + |} + """.stripMargin + ) + assertContentsEqual( + file(s"p5/src/test/scala/MainTest.scala"), + """ + |object MainTest { + | def foo(a: Int, // comment + | b: Double) = ??? + |} + """.stripMargin + ) + + + assertContentsEqual( + file(s"p6/src/main/scala/Test.scala"), + """ + |object Test { + | foo(a, // comment + | b) + |} + """.stripMargin + ) + assertContentsEqual( + file(s"p6/src/test/scala/MainTest.scala"), + """ + |object + |MainTest + |{ + | foo(a, // comment + | b) + |} + | + """.stripMargin + ) + + + assertContentsEqual( + file(s"p7/src/main/scala/Test.scala"), + """ + |object Test { + | foo(a, // comment + | b) + |} + """.stripMargin + ) + assertContentsEqual( + file(s"p7/src/test/scala/MainTest.scala"), + """ + |object MainTest { + | foo(a, // comment + | b) + |} + """.stripMargin + ) + + assertContentsEqual( + file("project/plugins.sbt"), + """ + |addSbtPlugin( + | "com.geirsson" % "sbt-scalafmt" % System.getProperty("plugin.version") + |) + """.stripMargin + ) +} diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p1 b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p1 new file mode 120000 index 0000000000..c84e7ded70 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p1 @@ -0,0 +1 @@ +../sbt-cli/p1 \ No newline at end of file diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p2 b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p2 new file mode 120000 index 0000000000..52bef95218 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p2 @@ -0,0 +1 @@ +../sbt-cli/p2 \ No newline at end of file diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p3 b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p3 new file mode 120000 index 0000000000..e0c44641c8 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p3 @@ -0,0 +1 @@ +../sbt-cli/p3 \ No newline at end of file diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p4/p4.sbt b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p4/p4.sbt new file mode 100644 index 0000000000..19856ebd14 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p4/p4.sbt @@ -0,0 +1,4 @@ +lazy val settings = + Seq( + scalaVersion := + "2.12.1") \ No newline at end of file diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p4/src b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p4/src new file mode 120000 index 0000000000..aca4df1db7 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p4/src @@ -0,0 +1 @@ +../p3/src \ No newline at end of file diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p5/src/main/scala/Test.scala b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p5/src/main/scala/Test.scala new file mode 100644 index 0000000000..d7a1ffe57f --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p5/src/main/scala/Test.scala @@ -0,0 +1,6 @@ +object +Test +{ + def foo(a: Int, // comment + b: Double) = ??? +} diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p5/src/test/scala/MainTest.scala b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p5/src/test/scala/MainTest.scala new file mode 100644 index 0000000000..16abf1979e --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p5/src/test/scala/MainTest.scala @@ -0,0 +1,6 @@ +object +MainTest +{ + def foo(a: Int, // comment + b: Double) = ??? +} diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p6/src b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p6/src new file mode 120000 index 0000000000..aca4df1db7 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p6/src @@ -0,0 +1 @@ +../p3/src \ No newline at end of file diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p7/src b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p7/src new file mode 120000 index 0000000000..aca4df1db7 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/p7/src @@ -0,0 +1 @@ +../p3/src \ No newline at end of file diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/project/build.properties b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/project/build.properties new file mode 100644 index 0000000000..94005e587c --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.0.0 diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/project/plugins.sbt b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/project/plugins.sbt similarity index 100% rename from scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/helloworld/project/plugins.sbt rename to scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/project/plugins.sbt diff --git a/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/test b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/test new file mode 100644 index 0000000000..f2923679e3 --- /dev/null +++ b/scalafmt-sbt-tests/src/sbt-test/scalafmt-sbt/sbt/test @@ -0,0 +1,31 @@ +-> p123/compile:scalafmtCheck +-> p123/test:scalafmtCheck +> p123/compile:scalafmt +> p123/test:scalafmt +> p123/compile:scalafmtCheck +> p123/test:scalafmtCheck + +> scalafmtSbt +> scalafmtSbtCheck + +> p4/scalafmtOnly src/main/scala/Test.scala src/test/scala/MainTest.scala p4.sbt +> p4/compile:scalafmtCheck +> p4/test:scalafmtCheck +> p4/scalafmtSbtCheck + +> p5/compile +> p5/test:compile +> p5/compile:scalafmtCheck +> p5/test:scalafmtCheck + +> p6/compile:scalafmt +> p6/test:scalafmt +> p6/compile:scalafmtCheck +> p6/test:scalafmtCheck + +> p7/compile:scalafmt +> p7/test:scalafmt +> p7/compile:scalafmtCheck +> p7/test:scalafmtCheck + +> check diff --git a/scalafmt-sbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala b/scalafmt-sbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala index 6b36b5370f..58f23f46fd 100644 --- a/scalafmt-sbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala +++ b/scalafmt-sbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala @@ -1,25 +1,211 @@ package org.scalafmt.sbt + +import org.scalafmt.config.{Config, ScalafmtConfig} +import org.scalafmt.{Formatted, Scalafmt} +import sbt.Keys._ +import sbt.{Def, _} +import complete.DefaultParsers._ +import metaconfig.Configured +import sbt.util.Logger + import scala.meta.internal.tokenizers.PlatformTokenizerCache -import sbt._ -import Keys._ +import scala.util.{Failure, Success, Try} object ScalafmtPlugin extends AutoPlugin { override def trigger: PluginTrigger = allRequirements + object autoImport { - val scalafmt: Command = - Command.args("scalafmt", "run the scalafmt command line interface.") { - case (state, args) => - org.scalafmt.cli.Cli.main("--non-interactive" +: args.toArray) - PlatformTokenizerCache.megaCache.clear() - state + val scalafmt = taskKey[Unit]("Format Scala sources.") + val scalafmtCheck = + taskKey[Boolean]( + "Fails if a Scala source is mis-formatted. Does not write to files.") + val scalafmtOnCompile = + settingKey[Boolean]( + "Format Scala source files on compile, off by default.") + val scalafmtConfig = taskKey[Option[File]]( + "Optional location of .scalafmt.conf file. If None the default config is used.") + val scalafmtSbt = taskKey[Unit]( + "Format *.sbt and project/*.scala files for this sbt build.") + val scalafmtSbtCheck = + taskKey[Boolean]( + "Fails if a *.sbt or project/*.scala source is mis-formatted. Does not write to files.") + val scalafmtOnly = inputKey[Unit]("Format only given file.") + } + import autoImport._ + + private val scalafmtDoFormatOnCompile = + taskKey[Unit]("Format Scala source files if scalafmtOnCompile is on.") + + private val scalaConfig = + scalafmtConfig.map(_.map(Config.fromHoconFile(_) match { + case Configured.Ok(conf) => conf + case Configured.NotOk(e) => throw new MessageOnlyException(e.msg) + }).getOrElse(ScalafmtConfig.default)) + private val sbtConfig = scalaConfig.map(_.forSbt) + + private def filterSource(source: File, config: ScalafmtConfig): Boolean = + config.project.matcher.matches(source.toString) + private def filterScala(source: File): Boolean = + source.toString.endsWith(".scala") + private def filterSbt(source: File): Boolean = + source.toString.endsWith(".sbt") + + private type Input = String + private type Output = String + + private def withFormattedSources[T]( + sources: Seq[File], + config: ScalafmtConfig + )( + onError: (File, Throwable) => T, + onFormat: (File, Input, Output) => T + ): Seq[Option[T]] = { + sources + .withFilter(filterSource(_, config)) + .map( + file => { + val input = IO.read(file) + val output = Scalafmt.format(input, config) + + output match { + case Formatted.Failure(e) => + if (config.runner.fatalWarnings) { + throw e + } else if (config.runner.ignoreWarnings) { + // do nothing + None + } else { + Some(onError(file, e)) + } + case Formatted.Success(code) => + Some(onFormat(file, input, code)) + } + } + ) + } + + private def formatSources( + sources: Seq[File], + config: ScalafmtConfig, + log: Logger + ): Unit = { + val cnt = withFormattedSources(sources, config)( + (file, e) => { + log.error(s"Error in ${file.toString}: $e") + 0 + }, + (file, input, output) => { + if (input != output) { + IO.write(file, output) + 1 + } else { + 0 + } + } + ).flatten.sum + if (cnt > 1) { + log.info(s"Reformatted $cnt Scala sources") + } + PlatformTokenizerCache.megaCache.clear() + } + + private def checkSources( + sources: Seq[File], + config: ScalafmtConfig, + log: Logger + ): Boolean = { + val res = withFormattedSources(sources, config)( + (file, e) => { + log.error(s"Error in ${file.toString}: $e") + false + }, + (file, input, output) => { + val diff = input != output + if (diff) { + throw new MessageOnlyException( + s"${file.toString} isn't formatted properly!") + } + diff } + ).flatten.forall(x => x) + PlatformTokenizerCache.megaCache.clear() + res } - override def globalSettings: Seq[Def.Setting[_]] = - Seq( - commands += autoImport.scalafmt - ) ++ - addCommandAlias("scalafmtTest", "scalafmt --test") ++ - addCommandAlias("scalafmtDiffTest", "scalafmt --diff --test") ++ - addCommandAlias("scalafmtDiff", "scalafmt --diff") + private lazy val sbtSources = thisProject.map( + proj => { + val rootSbt = + BuildPaths.configurationSources(proj.base).filterNot(_.isHidden) + val projectSbt = + (BuildPaths.projectStandard(proj.base) * GlobFilter("*.sbt")).get + .filterNot(_.isHidden) + rootSbt ++ projectSbt + } + ) + private lazy val projectSources = thisProject.map(proj => + (BuildPaths.projectStandard(proj.base) * GlobFilter("*.scala")).get) + + lazy val scalafmtConfigSettings: Seq[Def.Setting[_]] = Seq( + scalafmt := formatSources( + (unmanagedSources in scalafmt).value.filter(filterScala), + scalaConfig.value, + streams.value.log), + scalafmtSbt := { + formatSources(sbtSources.value, sbtConfig.value, streams.value.log) + formatSources(projectSources.value, scalaConfig.value, streams.value.log) + }, + scalafmtCheck := + checkSources( + (unmanagedSources in scalafmt).value.filter(filterScala), + scalaConfig.value, + streams.value.log), + scalafmtSbtCheck := { + checkSources(sbtSources.value, sbtConfig.value, streams.value.log) + checkSources(projectSources.value, scalaConfig.value, streams.value.log) + }, + scalafmtDoFormatOnCompile := Def.settingDyn { + if (scalafmtOnCompile.value) { + scalafmt in resolvedScoped.value.scope + } else { + Def.task(()) + } + }.value, + compileInputs in compile := (compileInputs in compile) + .dependsOn(scalafmtDoFormatOnCompile) + .value, + scalafmtOnly := { + val files = spaceDelimited("").parsed + val absFiles = files.flatMap(fileS => { + Try { IO.resolve(baseDirectory.value, new File(fileS)) } match { + case Failure(e) => + streams.value.log.error(s"Error with $fileS file: $e") + None + case Success(file) => Some(file) + } + }) + + val scalaFiles = absFiles.filter(filterScala) + formatSources(scalaFiles, scalaConfig.value, streams.value.log) + val sbtFiles = absFiles.filter(filterSbt) + formatSources(sbtFiles, sbtConfig.value, streams.value.log) + } + ) + + override def projectSettings: Seq[Def.Setting[_]] = + Seq(Compile, Test).flatMap(inConfig(_)(scalafmtConfigSettings)) + + override def buildSettings: Seq[Def.Setting[_]] = Seq( + scalafmtConfig := { + val path = (baseDirectory in ThisBuild).value / ".scalafmt.conf" + if (path.exists()) { + Some(path) + } else { + None + } + } + ) + + override def globalSettings: Seq[Def.Setting[_]] = Seq( + scalafmtOnCompile := false + ) }