summaryrefslogtreecommitdiff
path: root/scalalib
diff options
context:
space:
mode:
authorNikolay Tatarinov <5min4eq.unity@gmail.com>2018-05-06 11:26:21 +0300
committerGitHub <noreply@github.com>2018-05-06 11:26:21 +0300
commitf7a02a46f847d4433cd771840fd0b6cc314215d8 (patch)
tree612a1d01a63a75dedd1c53bbeccf3083b9f52a65 /scalalib
parent32c7515d6507e1898562203ea7c9d13a53d933c1 (diff)
downloadmill-f7a02a46f847d4433cd771840fd0b6cc314215d8.tar.gz
mill-f7a02a46f847d4433cd771840fd0b6cc314215d8.tar.bz2
mill-f7a02a46f847d4433cd771840fd0b6cc314215d8.zip
Scalafmt support (#308)
* add scalafmt module, that formats all sources files on every run * scalafmt worker that internally chaches reformatted files * move jvm process call to helper method * use scala 2.12.4 to resolve scalafmt deps; check for config file existence; add quiet flags to scalafmt CLI * make a scalafmt worker a singleton * add tests for scalafmt module * add reformatAll command * tests for reformatAll command * add docs about scalafmt support
Diffstat (limited to 'scalalib')
-rw-r--r--scalalib/src/mill/scalalib/scalafmt/ScalafmtModule.scala58
-rw-r--r--scalalib/src/mill/scalalib/scalafmt/ScalafmtWorker.scala58
-rw-r--r--scalalib/test/resources/scalafmt/core/resources/application.conf1
-rw-r--r--scalalib/test/resources/scalafmt/core/src/Main.scala7
-rw-r--r--scalalib/test/resources/scalafmt/core/src/Person.scala12
-rw-r--r--scalalib/test/src/mill/scalalib/scalafmt/ScalafmtTests.scala105
6 files changed, 241 insertions, 0 deletions
diff --git a/scalalib/src/mill/scalalib/scalafmt/ScalafmtModule.scala b/scalalib/src/mill/scalalib/scalafmt/ScalafmtModule.scala
new file mode 100644
index 00000000..3b325b4c
--- /dev/null
+++ b/scalalib/src/mill/scalalib/scalafmt/ScalafmtModule.scala
@@ -0,0 +1,58 @@
+package mill.scalalib.scalafmt
+
+import ammonite.ops.{exists, ls, pwd}
+import mill._
+import mill.define._
+import mill.scalalib._
+
+trait ScalafmtModule extends JavaModule {
+
+ def reformat(): Command[Unit] = T.command {
+ ScalafmtWorkerModule
+ .worker()
+ .reformat(
+ filesToFormat(sources()),
+ scalafmtConfig().head,
+ scalafmtDeps().map(_.path)
+ )
+ }
+
+ def scalafmtVersion: T[String] = "1.5.1"
+
+ def scalafmtConfig: Sources = T.sources(pwd / ".scalafmt.conf")
+
+ def scalafmtDeps: T[Agg[PathRef]] = T {
+ Lib.resolveDependencies(
+ ScalaWorkerModule.repositories,
+ Lib.depToDependency(_, "2.12.4"),
+ Seq(ivy"com.geirsson::scalafmt-cli:${scalafmtVersion()}")
+ )
+ }
+
+ protected def filesToFormat(sources: Seq[PathRef]) = {
+ for {
+ pathRef <- sources if exists(pathRef.path)
+ file <- ls.rec(pathRef.path) if file.isFile && file.ext == "scala"
+ } yield PathRef(file)
+ }
+
+}
+
+object ScalafmtModule extends ExternalModule with ScalafmtModule {
+
+ def reformatAll(sources: mill.main.Tasks[Seq[PathRef]]): Command[Unit] =
+ T.command {
+ val files = Task.sequence(sources.value)().flatMap(filesToFormat)
+ ScalafmtWorkerModule
+ .worker()
+ .reformat(
+ files,
+ scalafmtConfig().head,
+ scalafmtDeps().map(_.path)
+ )
+ }
+
+ implicit def millScoptTargetReads[T] = new mill.main.Tasks.Scopt[T]()
+
+ lazy val millDiscover = Discover[this.type]
+}
diff --git a/scalalib/src/mill/scalalib/scalafmt/ScalafmtWorker.scala b/scalalib/src/mill/scalalib/scalafmt/ScalafmtWorker.scala
new file mode 100644
index 00000000..d9921e9d
--- /dev/null
+++ b/scalalib/src/mill/scalalib/scalafmt/ScalafmtWorker.scala
@@ -0,0 +1,58 @@
+package mill.scalalib.scalafmt
+
+import ammonite.ops.{Path, exists}
+import mill._
+import mill.define.{Discover, ExternalModule, Worker}
+import mill.modules.Jvm
+import mill.util.Ctx
+
+import scala.collection.mutable
+
+object ScalafmtWorkerModule extends ExternalModule {
+ def worker: Worker[ScalafmtWorker] = T.worker { new ScalafmtWorker() }
+
+ lazy val millDiscover = Discover[this.type]
+}
+
+private[scalafmt] class ScalafmtWorker {
+ private val reformatted: mutable.Map[Path, Int] = mutable.Map.empty
+ private var configSig: Int = 0
+
+ def reformat(input: Seq[PathRef],
+ scalafmtConfig: PathRef,
+ scalafmtClasspath: Agg[Path])(implicit ctx: Ctx): Unit = {
+ val toFormat =
+ if (scalafmtConfig.sig != configSig) input
+ else
+ input.filterNot(ref => reformatted.get(ref.path).contains(ref.sig))
+
+ if (toFormat.nonEmpty) {
+ ctx.log.info(s"Formatting ${toFormat.size} Scala sources")
+ reformatAction(toFormat.map(_.path),
+ scalafmtConfig.path,
+ scalafmtClasspath)
+ reformatted ++= toFormat.map { ref =>
+ val updRef = PathRef(ref.path)
+ updRef.path -> updRef.sig
+ }
+ configSig = scalafmtConfig.sig
+ } else {
+ ctx.log.info(s"Everything is formatted already")
+ }
+ }
+
+ private val cliFlags = Seq("--non-interactive", "--quiet")
+
+ private def reformatAction(toFormat: Seq[Path],
+ config: Path,
+ classpath: Agg[Path])(implicit ctx: Ctx) = {
+ val configFlags =
+ if (exists(config)) Seq("--config", config.toString) else Seq.empty
+ Jvm.subprocess(
+ "org.scalafmt.cli.Cli",
+ classpath,
+ mainArgs = toFormat.map(_.toString) ++ configFlags ++ cliFlags
+ )
+ }
+
+}
diff --git a/scalalib/test/resources/scalafmt/core/resources/application.conf b/scalalib/test/resources/scalafmt/core/resources/application.conf
new file mode 100644
index 00000000..f5f89257
--- /dev/null
+++ b/scalalib/test/resources/scalafmt/core/resources/application.conf
@@ -0,0 +1 @@
+foo.bar = 2
diff --git a/scalalib/test/resources/scalafmt/core/src/Main.scala b/scalalib/test/resources/scalafmt/core/src/Main.scala
new file mode 100644
index 00000000..a5c74235
--- /dev/null
+++ b/scalalib/test/resources/scalafmt/core/src/Main.scala
@@ -0,0 +1,7 @@
+
+object Main extends App{
+ val person = Person.fromString("rockjam:25")
+ val greeting = s"hello ${person.name}, your age is: ${person.age}"
+ println(greeting)
+}
+
diff --git a/scalalib/test/resources/scalafmt/core/src/Person.scala b/scalalib/test/resources/scalafmt/core/src/Person.scala
new file mode 100644
index 00000000..b296cf85
--- /dev/null
+++ b/scalalib/test/resources/scalafmt/core/src/Person.scala
@@ -0,0 +1,12 @@
+object Person {
+ def fromString(s: String): Person = {
+ val Array(name, age) = s.split(":")
+ Person(
+ name,
+ age.toInt)
+ }
+}
+
+
+
+case class Person(name: String, age: Int)
diff --git a/scalalib/test/src/mill/scalalib/scalafmt/ScalafmtTests.scala b/scalalib/test/src/mill/scalalib/scalafmt/ScalafmtTests.scala
new file mode 100644
index 00000000..fcff52a3
--- /dev/null
+++ b/scalalib/test/src/mill/scalalib/scalafmt/ScalafmtTests.scala
@@ -0,0 +1,105 @@
+package mill.scalalib.scalafmt
+
+import ammonite.ops._
+import mill.main.Tasks
+import mill.scalalib.ScalaModule
+import mill.util.{TestEvaluator, TestUtil}
+import utest._
+import utest.framework.TestPath
+
+object ScalafmtTests extends TestSuite {
+
+ trait TestBase extends TestUtil.BaseModule {
+ def millSourcePath =
+ TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.')
+ }
+
+ object ScalafmtTestModule extends TestBase {
+ object core extends ScalaModule with ScalafmtModule {
+ def scalaVersion = "2.12.4"
+ }
+ }
+
+ val resourcePath = pwd / 'scalalib / 'test / 'resources / 'scalafmt
+
+ def workspaceTest[T, M <: TestUtil.BaseModule](
+ m: M,
+ resourcePath: Path = resourcePath)(t: TestEvaluator[M] => T)(
+ implicit tp: TestPath): T = {
+ val eval = new TestEvaluator(m)
+ rm(m.millSourcePath)
+ rm(eval.outPath)
+ mkdir(m.millSourcePath / up)
+ cp(resourcePath, m.millSourcePath)
+ t(eval)
+ }
+
+ def tests: Tests = Tests {
+ 'scalafmt - {
+ def checkReformat(reformatCommand: mill.define.Command[Unit]) =
+ workspaceTest(ScalafmtTestModule) { eval =>
+ val before = getProjectFiles(ScalafmtTestModule.core, eval)
+
+ // first reformat
+ val Right(_) = eval.apply(reformatCommand)
+
+ val firstReformat = getProjectFiles(ScalafmtTestModule.core, eval)
+
+ assert(
+ firstReformat("Main.scala").modifyTime > before("Main.scala").modifyTime,
+ firstReformat("Main.scala").content != before("Main.scala").content,
+ firstReformat("Person.scala").modifyTime > before("Person.scala").modifyTime,
+ firstReformat("Person.scala").content != before("Person.scala").content,
+ // resources files aren't modified
+ firstReformat("application.conf").modifyTime == before(
+ "application.conf").modifyTime
+ )
+
+ // cached reformat
+ val Right(_) = eval.apply(reformatCommand)
+
+ val cached = getProjectFiles(ScalafmtTestModule.core, eval)
+
+ assert(
+ cached("Main.scala").modifyTime == firstReformat("Main.scala").modifyTime,
+ cached("Person.scala").modifyTime == firstReformat("Person.scala").modifyTime,
+ cached("application.conf").modifyTime == firstReformat(
+ "application.conf").modifyTime
+ )
+
+ // reformat after change
+ write.over(cached("Main.scala").path,
+ cached("Main.scala").content + "\n object Foo")
+
+ val Right(_) = eval.apply(reformatCommand)
+
+ val afterChange = getProjectFiles(ScalafmtTestModule.core, eval)
+
+ assert(
+ afterChange("Main.scala").modifyTime > cached("Main.scala").modifyTime,
+ afterChange("Person.scala").modifyTime == cached("Person.scala").modifyTime,
+ afterChange("application.conf").modifyTime == cached(
+ "application.conf").modifyTime
+ )
+ }
+
+ 'reformat - checkReformat(ScalafmtTestModule.core.reformat())
+ 'reformatAll - checkReformat(
+ ScalafmtModule.reformatAll(Tasks(Seq(ScalafmtTestModule.core.sources))))
+ }
+ }
+
+ case class FileInfo(content: String, modifyTime: Long, path: Path)
+
+ def getProjectFiles(m: ScalaModule, eval: TestEvaluator[_]) = {
+ val Right((sources, _)) = eval.apply(m.sources)
+ val Right((resources, _)) = eval.apply(m.resources)
+
+ val sourcesFiles = sources.flatMap(p => ls.rec(p.path))
+ val resourcesFiles = resources.flatMap(p => ls.rec(p.path))
+ (sourcesFiles ++ resourcesFiles).map { p =>
+ p.name -> FileInfo(read(p), p.mtime.toMillis, p)
+ }.toMap
+ }
+
+}