diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 628b63ef03..a122439f18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,9 +29,38 @@ jobs: - run: # for GitOps tests git config --global user.email "scalafmt@scalameta.org" && git config --global user.name "scalafmt" - - run: TEST="2.12" sbt ci-test + - run: TEST="2.12" sbt ci-test-jvm shell: bash - - run: TEST="2.13" sbt ci-test + - run: TEST="2.13" sbt ci-test-jvm + shell: bash + test-scala-native: + strategy: + fail-fast: false + matrix: + os: [macOS-latest, ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: olafurpg/setup-scala@v13 + with: + java-version: adopt@1.8 + - if: matrix.os == 'windows-latest' + name: setup windows environment + run: ./bin/scala-native-setup/windows-setup.sh + - if: matrix.os == 'macOS-latest' + name: setup macOS environment + run: ./bin/scala-native-setup/macos-setup.sh + - if: matrix.os == 'ubuntu-latest' + name: setup ubuntu environment + run: ./bin/scala-native-setup/ubuntu-setup.sh + - name: run tests + run: | + git fetch --tags -f + # for GitOps tests + git config --global user.email "scalafmt@scalameta.org" && git config --global user.name "scalafmt" + - run: TEST="2.12" sbt ci-test-native + shell: bash + - run: TEST="2.13" sbt ci-test-native shell: bash formatting: runs-on: ubuntu-latest diff --git a/bin/scala-native-setup/macos-setup.sh b/bin/scala-native-setup/macos-setup.sh new file mode 100755 index 0000000000..671aa6e382 --- /dev/null +++ b/bin/scala-native-setup/macos-setup.sh @@ -0,0 +1,3 @@ +apt-get install -y clang-12.0 +PATH=/usr/lib/llvm-12.0/bin:${PATH} +clang --version \ No newline at end of file diff --git a/bin/scala-native-setup/ubuntu-setup.sh b/bin/scala-native-setup/ubuntu-setup.sh new file mode 100755 index 0000000000..671aa6e382 --- /dev/null +++ b/bin/scala-native-setup/ubuntu-setup.sh @@ -0,0 +1,3 @@ +apt-get install -y clang-12.0 +PATH=/usr/lib/llvm-12.0/bin:${PATH} +clang --version \ No newline at end of file diff --git a/bin/scala-native-setup/windows-setup.sh b/bin/scala-native-setup/windows-setup.sh new file mode 100644 index 0000000000..e7c60b68e0 --- /dev/null +++ b/bin/scala-native-setup/windows-setup.sh @@ -0,0 +1,3 @@ +choco install llvm +echo "${env:ProgramFiles}\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append +clang --version \ No newline at end of file diff --git a/build.sbt b/build.sbt index a4b2c9746c..6135eb45b1 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,6 @@ import Dependencies._ import sbtcrossproject.CrossPlugin.autoImport.crossProject +import scala.scalanative.build._ def parseTagVersion: String = { import scala.sys.process._ @@ -37,11 +38,6 @@ inThisBuild( Resolver.sonatypeRepo("releases"), Resolver.sonatypeRepo("snapshots") ), - libraryDependencies ++= List( - munit.value % Test, - scalacheck % Test, - scalametaTestkit % Test - ), testFrameworks += new TestFramework("munit.Framework") ) ) @@ -50,8 +46,9 @@ name := "scalafmtRoot" publish / skip := true addCommandAlias("native-image", "cli/nativeImage") +addCommandAlias("scala-native", "cliNative/compile;cliNative/nativeLink") -commands += Command.command("ci-test") { s => +commands += Command.command("ci-test-jvm") { s => val scalaVersion = sys.env.get("TEST") match { case Some("2.12") => scala212 case _ => scala213 @@ -64,7 +61,18 @@ commands += Command.command("ci-test") { s => s } -lazy val dynamic = project +commands += Command.command("ci-test-native") { s => + val scalaVersion = sys.env.get("TEST") match { + case Some("2.12") => scala212 + case _ => scala213 + } + s"++$scalaVersion" :: + "testsNative/test" :: + s +} + +lazy val dynamic = crossProject(JVMPlatform, NativePlatform) + .withoutSuffixFor(JVMPlatform) .in(file("scalafmt-dynamic")) .settings( moduleName := "scalafmt-dynamic", @@ -76,14 +84,23 @@ lazy val dynamic = project "io.get-coursier" % "interface" % "0.0.17", "com.typesafe" % "config" % "1.4.1", munit.value % Test, - scalametaTestkit % Test + scalametaTestkit.value % Test ), scalacOptions ++= scalacJvmOptions.value ) + .nativeSettings( + libraryDependencies ++= List( + "org.ekrich" %%% "sjavatime" % "1.1.5" + ), + scalaNativeNativeConfig + ) .dependsOn(interfaces) .enablePlugins(BuildInfoPlugin) -lazy val interfaces = project +lazy val dynamicNative = dynamic.native + +lazy val interfaces = crossProject(JVMPlatform, NativePlatform) + .withoutSuffixFor(JVMPlatform) .in(file("scalafmt-interfaces")) .settings( moduleName := "scalafmt-interfaces", @@ -100,14 +117,16 @@ lazy val interfaces = project } ) -lazy val core = crossProject(JVMPlatform) +lazy val interfacesNative = interfaces.native + +lazy val core = crossProject(JVMPlatform, NativePlatform) + .withoutSuffixFor(JVMPlatform) .in(file("scalafmt-core")) .settings( moduleName := "scalafmt-core", buildInfoSettings, scalacOptions ++= scalacJvmOptions.value, libraryDependencies ++= Seq( - metaconfig.value, scalameta.value, // scala-reflect is an undeclared dependency of fansi, see #1252. // Scalafmt itself does not require scala-reflect. @@ -128,21 +147,22 @@ lazy val core = crossProject(JVMPlatform) } } ) - // .jsSettings( - // libraryDependencies ++= List( - // metaconfigHocon.value, - // scalatest.value % Test // must be here for coreJS/test to run anything - // ) - // ) + .nativeSettings( + libraryDependencies ++= List( + metaconfigSconfig.value + ), + scalaNativeNativeConfig + ) .jvmSettings( Test / run / fork := true, libraryDependencies ++= List( - metaconfigTypesafe.value + metaconfigTypesafe.value, + metaconfig.value ) ) .enablePlugins(BuildInfoPlugin) -lazy val coreJVM = core.jvm -// lazy val coreJS = core.js + +lazy val coreNative = core.native import sbtassembly.AssemblyPlugin.defaultUniversalScript @@ -160,10 +180,26 @@ val scalacJvmOptions = Def.setting { } } -lazy val cli = project +lazy val scalaNativeNativeConfig = + nativeConfig ~= { + _.withMode(Mode.debug) + .withLinkStubs(true) + } + +lazy val cli = crossProject(JVMPlatform, NativePlatform) + .withoutSuffixFor(JVMPlatform) .in(file("scalafmt-cli")) .settings( moduleName := "scalafmt-cli", + libraryDependencies ++= Seq( + "com.github.scopt" %%% "scopt" % "4.0.1", + // undeclared transitive dependency of coursier-small + "org.scala-lang.modules" %% "scala-xml" % "1.3.0" + ), + scalacOptions ++= scalacJvmOptions.value, + Compile / mainClass := Some("org.scalafmt.cli.Cli") + ) + .jvmSettings( assembly / mainClass := Some("org.scalafmt.cli.Cli"), assembly / assemblyOption := (assembly / assemblyOption).value .withPrependShellScript(Some(defaultUniversalScript(shebang = false))), @@ -174,15 +210,6 @@ lazy val cli = project val oldStrategy = (assembly / assemblyMergeStrategy).value oldStrategy(x) }, - libraryDependencies ++= Seq( - "com.googlecode.java-diff-utils" % "diffutils" % "1.3.0", - "com.martiansoftware" % "nailgun-server" % "0.9.1", - "com.github.scopt" %% "scopt" % "4.0.1", - // undeclared transitive dependency of coursier-small - "org.scala-lang.modules" %% "scala-xml" % "1.3.0" - ), - scalacOptions ++= scalacJvmOptions.value, - Compile / mainClass := Some("org.scalafmt.cli.Cli"), nativeImageVersion := "20.1.0", nativeImageOptions ++= { sys.env @@ -195,30 +222,44 @@ lazy val cli = project .filter(identity) .map(_ => "--static") .toSeq - } + }, + libraryDependencies += "com.martiansoftware" % "nailgun-server" % "0.9.1" + ) + .nativeSettings( + scalaNativeNativeConfig ) - .dependsOn(coreJVM, dynamic) + .dependsOn(core, dynamic) .enablePlugins(NativeImagePlugin) -lazy val tests = project +lazy val cliNative = cli.native + +lazy val tests = crossProject(JVMPlatform, NativePlatform) + .withoutSuffixFor(JVMPlatform) .in(file("scalafmt-tests")) .settings( publish / skip := true, libraryDependencies ++= Seq( // Test dependencies - "com.lihaoyi" %% "scalatags" % "0.10.0", - scalametaTestkit, + "com.lihaoyi" %%% "scalatags" % "0.10.0", + scalametaTestkit.value, munit.value ), scalacOptions ++= scalacJvmOptions.value, - javaOptions += "-Dfile.encoding=UTF8", buildInfoPackage := "org.scalafmt.tests", buildInfoKeys := Seq[BuildInfoKey]( - "resourceDirectory" -> (Test / resourceDirectory).value + "resourceDirectory" -> (baseDirectory.value / ".." / "shared" / "src" / "test" / "resources") ) ) .enablePlugins(BuildInfoPlugin) - .dependsOn(coreJVM, dynamic, cli) + .dependsOn(core, dynamic, cli) + .nativeSettings( + scalaNativeNativeConfig + ) + .jvmSettings( + javaOptions += "-Dfile.encoding=UTF8" + ) + +lazy val testsNative = tests.native lazy val benchmarks = project .in(file("scalafmt-benchmarks")) @@ -226,7 +267,7 @@ lazy val benchmarks = project publish / skip := true, moduleName := "scalafmt-benchmarks", libraryDependencies ++= Seq( - scalametaTestkit + scalametaTestkit.value ), run / javaOptions ++= Seq( "-Djava.net.preferIPv4Stack=true", @@ -245,7 +286,7 @@ lazy val benchmarks = project "-server" ) ) - .dependsOn(coreJVM) + .dependsOn(core.jvm) .enablePlugins(JmhPlugin) lazy val docs = project @@ -255,7 +296,7 @@ lazy val docs = project publish / skip := true, mdoc := (Compile / run).evaluated ) - .dependsOn(cli, dynamic) + .dependsOn(cli.jvm, dynamic.jvm) .enablePlugins(DocusaurusPlugin) val V = "\\d+\\.\\d+\\.\\d+" diff --git a/docs/contributing-scalafmt.md b/docs/contributing-scalafmt.md index 9cee44eabe..d43b266c66 100644 --- a/docs/contributing-scalafmt.md +++ b/docs/contributing-scalafmt.md @@ -81,7 +81,7 @@ To build a native image of the command-line interface using - From the project root directory, - run `sbt cli/assembly` - - run `java -jar scalafmt-cli/target/scala-2.13/scalafmt.jar`, to execute recently built artifacts + - run `java -jar scalafmt-cli/jvm/target/scala-2.13/scalafmt.jar`, to execute recently built artifacts ## Random stuff diff --git a/docs/installation.md b/docs/installation.md index d3deb51c9d..99fbc652dc 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -466,7 +466,7 @@ handle parse and config errors. Here is an example how to extend `ScalafmtReporter`. -```scala mdoc:file:scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ConsoleScalafmtReporter.scala +```scala mdoc:file:scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ConsoleScalafmtReporter.scala ``` diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a6eef1f6e1..7389dd32eb 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -18,13 +18,14 @@ object Dependencies { ) } - val scalametaTestkit = "org.scalameta" %% "testkit" % scalametaV + val scalametaTestkit = Def.setting("org.scalameta" %%% "testkit" % scalametaV) val scalacheck = "org.scalacheck" %% "scalacheck" % scalacheckV val munit = Def.setting("org.scalameta" %%% "munit" % munitV) val scalameta = Def.setting("org.scalameta" %%% "scalameta" % scalametaV excludeAll scalapb.value) val metaconfig = Def.setting("com.geirsson" %%% "metaconfig-core" % metaconfigV) + val metaconfigSconfig = Def.setting("com.geirsson" %%% "metaconfig-sconfig" % metaconfigV) val metaconfigTypesafe = Def.setting("com.geirsson" %%% "metaconfig-typesafe-config" % metaconfigV) val metaconfigHocon = Def.setting("com.geirsson" %%% "metaconfig-hocon" % metaconfigV) diff --git a/project/plugins.sbt b/project/plugins.sbt index 7ce4745b20..00bd57dce1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -15,3 +15,6 @@ addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.0") + +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.0.0") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.1") diff --git a/scalafmt-cli/jvm/src/main/scala/org/scalafmt/cli/CliUtils.scala b/scalafmt-cli/jvm/src/main/scala/org/scalafmt/cli/CliUtils.scala new file mode 100644 index 0000000000..07cc73f769 --- /dev/null +++ b/scalafmt-cli/jvm/src/main/scala/org/scalafmt/cli/CliUtils.scala @@ -0,0 +1,31 @@ +package org.scalafmt.cli + +import com.martiansoftware.nailgun.NGContext +import org.scalafmt.util.AbsoluteFile + +trait CliUtils { this: Cli => + + def nailMain(nGContext: NGContext): Unit = { + val workingDirectory = + AbsoluteFile.fromPath(nGContext.getWorkingDirectory).getOrElse { + throw new IllegalStateException( + s"Expected absolute path, " + + s"obtained nGContext.getWorkingDirectory = ${nGContext.getWorkingDirectory}" + ) + } + val exit = mainWithOptions( + nGContext.getArgs, + CliOptions.default.copy( + common = CliOptions.default.common.copy( + cwd = workingDirectory, + out = nGContext.out, + in = nGContext.in, + err = nGContext.err + ) + ) + ) + nGContext.exit(exit.code) + } + + protected val isNative: Boolean = false +} diff --git a/scalafmt-cli/jvm/src/main/scala/org/scalafmt/cli/TermUtils.scala b/scalafmt-cli/jvm/src/main/scala/org/scalafmt/cli/TermUtils.scala new file mode 100644 index 0000000000..12bae5c6eb --- /dev/null +++ b/scalafmt-cli/jvm/src/main/scala/org/scalafmt/cli/TermUtils.scala @@ -0,0 +1,13 @@ +package org.scalafmt.cli + +import java.sql.Timestamp + +trait TermUtils { + + // Copy/pasted over from coursier, but unused in scalafmt + private val format = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + protected def formatTimestamp(ts: Long): String = + format.format(new Timestamp(ts)) + + def noConsole = System.console() == null +} diff --git a/scalafmt-cli/jvm/src/main/scala/org/scalafmt/cli/UpdateDIsplayThread.scala b/scalafmt-cli/jvm/src/main/scala/org/scalafmt/cli/UpdateDIsplayThread.scala new file mode 100644 index 0000000000..90cf622218 --- /dev/null +++ b/scalafmt-cli/jvm/src/main/scala/org/scalafmt/cli/UpdateDIsplayThread.scala @@ -0,0 +1,262 @@ +package org.scalafmt.cli + +import scala.collection.mutable.ArrayBuffer +import java.io.Writer +import scala.annotation.tailrec +import java.util.concurrent.ConcurrentHashMap + +import java.util.concurrent.LinkedBlockingDeque +import java.util.concurrent.TimeUnit + +import org.scalafmt.cli.TermDisplay._ + +protected class UpdateDisplayThread( + out: Writer, + var fallbackMode: Boolean +) extends Thread("TermDisplay") { + + private val refreshInterval = 1000 / 60 + private val fallbackRefreshInterval = 1000 + + import Terminal.Ansi + + setDaemon(true) + + private var width = 80 + private var currentHeight = 0 + + private val q = new LinkedBlockingDeque[Message] + + def update(): Unit = { + if (q.size() == 0) + q.put(Message.Update) + } + + def end(): Unit = { + q.put(Message.Stop) + join() + } + + private val downloads = new ArrayBuffer[String] + private val doneQueue = new ArrayBuffer[(String, Info)] + val infos = new ConcurrentHashMap[String, Info] + + def newEntry( + url: String, + info: Info, + fallbackMessage: => String + ): Unit = { + assert(!infos.containsKey(url)) + val prev = infos.putIfAbsent(url, info) + assert(prev == null) + + if (fallbackMode) { + // FIXME What about concurrent accesses to out from the thread above? + out.write(fallbackMessage) + out.flush() + } + + downloads.synchronized { + downloads.append(url) + } + + update() + } + + def removeEntry( + url: String, + success: Boolean, + fallbackMessage: => String + )( + update0: Info => Info + ): Unit = { + downloads.synchronized { + downloads -= url + + val info = infos.remove(url) + + if (success) + doneQueue += (url -> update0(info)) + } + + if (fallbackMode && success) { + // FIXME What about concurrent accesses to out from the thread above? + out.write(fallbackMessage) + out.flush() + } + + update() + } + + private def reflowed(url: String, info: Info) = { + val extra = info match { + case downloadInfo: DownloadInfo => + val pctOpt = downloadInfo.fraction.map(100.0 * _) + + if (downloadInfo.length.isEmpty && downloadInfo.downloaded == 0L) + "" + else { + val pctOptStr = + pctOpt.map(pct => f"$pct%.2f %%, ").toIterable.mkString + val downloadInfoStr = downloadInfo.length.map(" / " + _).mkString + s"($pctOptStr${downloadInfo.downloaded}$downloadInfoStr)" + } + + case updateInfo: CheckUpdateInfo => + "Checking for updates" + } + + val baseExtraWidth = width / 5 + + val total = url.length + 1 + extra.length + val (url0, extra0) = + if (total >= width) { // or > ? If equal, does it go down 2 lines? + val overflow = total - width + 1 + + val extra0 = + if (extra.length > baseExtraWidth) + extra.take( + (baseExtraWidth max (extra.length - overflow)) - 1 + ) + "…" + else + extra + + val total0 = url.length + 1 + extra0.length + val overflow0 = total0 - width + 1 + + val url0 = + if (total0 >= width) + url.take( + ((width - baseExtraWidth - 1) max (url.length - overflow0)) - 1 + ) + "…" + else + url + + (url0, extra0) + } else + (url, extra) + + (url0, extra0) + } + + private def truncatedPrintln(s: String): Unit = { + + out.clearLine(2) + + if (s.length <= width) + out.write(s + "\n") + else + out.write(s.take(width - 1) + "…\n") + } + + @tailrec private def updateDisplayLoop(lineCount: Int): Unit = { + currentHeight = lineCount + + Option(q.poll(100L, TimeUnit.MILLISECONDS)) match { + case None => updateDisplayLoop(lineCount) + case Some(Message.Stop) => // poison pill + case Some(Message.Update) => + val (done0, downloads0) = downloads.synchronized { + val q = doneQueue.toVector + .filter { case (url, _) => + !url.endsWith(".sha1") && !url.endsWith(".md5") + } + .sortBy { case (url, _) => url } + + doneQueue.clear() + + val dw = downloads.toVector + .map { url => url -> infos.get(url) } + .sortBy { case (_, info) => -info.fraction.sum } + + (q, dw) + } + + for ((url, info) <- done0 ++ downloads0) { + assert(info != null, s"Incoherent state ($url)") + + truncatedPrintln(url) + out.clearLine(2) + out.write(s" ${info.display()}\n") + } + + val displayedCount = (done0 ++ downloads0).length + + if (displayedCount < lineCount) { + for (_ <- 1 to 2; _ <- displayedCount until lineCount) { + out.clearLine(2) + out.down(1) + } + + for (_ <- displayedCount until lineCount) + out.up(2) + } + + for (_ <- downloads0.indices) + out.up(2) + + out.left(10000) + + out.flush() + Thread.sleep(refreshInterval) + updateDisplayLoop(downloads0.length) + } + } + + @tailrec private def fallbackDisplayLoop(previous: Set[String]): Unit = + Option(q.poll(100L, TimeUnit.MILLISECONDS)) match { + case None => fallbackDisplayLoop(previous) + case Some(Message.Stop) => // poison pill + + // clean up display + for (_ <- 1 to 2; _ <- 0 until currentHeight) { + out.clearLine(2) + out.down(1) + } + for (_ <- 0 until currentHeight) { + out.up(2) + } + + case Some(Message.Update) => + val downloads0 = downloads.synchronized { + downloads.toVector + .map { url => url -> infos.get(url) } + .sortBy { case (_, info) => -info.fraction.sum } + } + + var displayedSomething = false + for ((url, info) <- downloads0 if previous(url)) { + assert(info != null, s"Incoherent state ($url)") + + val (url0, extra0) = reflowed(url, info) + + displayedSomething = true + out.write(s"$url0 $extra0\n") + } + + if (displayedSomething) + out.write("\n") + + out.flush() + Thread.sleep(fallbackRefreshInterval) + fallbackDisplayLoop(previous ++ downloads0.map { case (url, _) => + url + }) + } + + override def run(): Unit = { + + Terminal.consoleDim("cols") match { + case Some(cols) => + width = cols + out.clearLine(2) + case None => + fallbackMode = true + } + + if (fallbackMode) + fallbackDisplayLoop(Set.empty) + else + updateDisplayLoop(0) + } +} diff --git a/scalafmt-cli/native/src/main/scala/org/scalafmt/cli/CliUtils.scala b/scalafmt-cli/native/src/main/scala/org/scalafmt/cli/CliUtils.scala new file mode 100644 index 0000000000..ab18ca0088 --- /dev/null +++ b/scalafmt-cli/native/src/main/scala/org/scalafmt/cli/CliUtils.scala @@ -0,0 +1,5 @@ +package org.scalafmt.cli + +trait CliUtils { + protected val isNative: Boolean = true +} diff --git a/scalafmt-cli/native/src/main/scala/org/scalafmt/cli/TermUtils.scala b/scalafmt-cli/native/src/main/scala/org/scalafmt/cli/TermUtils.scala new file mode 100644 index 0000000000..de1a428e10 --- /dev/null +++ b/scalafmt-cli/native/src/main/scala/org/scalafmt/cli/TermUtils.scala @@ -0,0 +1,9 @@ +package org.scalafmt.cli + +trait TermUtils { + + // Copy/pasted over from coursier, but not used in scalafmt + protected def formatTimestamp(ts: Long): String = ??? + + def noConsole = false +} diff --git a/scalafmt-cli/native/src/main/scala/org/scalafmt/cli/UpdateDisplayThread.scala b/scalafmt-cli/native/src/main/scala/org/scalafmt/cli/UpdateDisplayThread.scala new file mode 100644 index 0000000000..968b7885c3 --- /dev/null +++ b/scalafmt-cli/native/src/main/scala/org/scalafmt/cli/UpdateDisplayThread.scala @@ -0,0 +1,280 @@ +package org.scalafmt.cli + +import scala.collection.mutable.ArrayBuffer +import java.io.Writer +import scala.annotation.tailrec + +import org.scalafmt.cli.TermDisplay._ +import java.util.ArrayDeque +import java.util.HashMap + +/* Single threaded version of UpdateDisplayThread. + Prints to out with every update, keeping its state + in between calls. + */ +private[cli] class UpdateDisplayThread( + out: Writer, + var fallbackMode: Boolean +) { + + import Terminal.Ansi + + private var width = 80 + private var currentHeight = 0 + + private val q = new ArrayDeque[Message]() + + sealed trait UpdateDisplayState + case class FallbackDisplay(previous: Set[String]) extends UpdateDisplayState + case class UpdateDisplay(lineCount: Int) extends UpdateDisplayState + case object NotRunning extends UpdateDisplayState + + var state: UpdateDisplayState = null + + def update(): Unit = { + if (q.size == 0) + q.add(Message.Update) + state match { + case FallbackDisplay(previous) => + fallbackDisplayIter(previous) + case UpdateDisplay(lineCount) => + updateDisplayIter(lineCount) + case _ => () + } + } + + def end(): Unit = state = NotRunning + + private val downloads = new ArrayBuffer[String] + private val doneQueue = new ArrayBuffer[(String, Info)] + val infos = new HashMap[String, Info]() + + def newEntry( + url: String, + info: Info, + fallbackMessage: => String + ): Unit = { + assert(!infos.containsKey(url)) + val prev = infos.put(url, info) + assert(prev == null) + + if (fallbackMode) { + // FIXME What about concurrent accesses to out from the thread above? + out.write(fallbackMessage) + out.flush() + } + + downloads.append(url) + + update() + } + + def removeEntry( + url: String, + success: Boolean, + fallbackMessage: => String + )( + update0: Info => Info + ): Unit = { + downloads.synchronized { + downloads -= url + + val info = infos.remove(url) + + if (success) + doneQueue += (url -> update0(info)) + } + + if (fallbackMode && success) { + // FIXME What about concurrent accesses to out from the thread above? + out.write(fallbackMessage) + out.flush() + } + + update() + } + + private def reflowed(url: String, info: Info) = { + val extra = info match { + case downloadInfo: DownloadInfo => + val pctOpt = downloadInfo.fraction.map(100.0 * _) + + if (downloadInfo.length.isEmpty && downloadInfo.downloaded == 0L) + "" + else { + val pctOptStr = + pctOpt.map(pct => f"$pct%.2f %%, ").toIterable.mkString + val downloadInfoStr = downloadInfo.length.map(" / " + _).mkString + s"($pctOptStr${downloadInfo.downloaded}$downloadInfoStr)" + } + + case updateInfo: CheckUpdateInfo => + "Checking for updates" + } + + val baseExtraWidth = width / 5 + + val total = url.length + 1 + extra.length + val (url0, extra0) = + if (total >= width) { // or > ? If equal, does it go down 2 lines? + val overflow = total - width + 1 + + val extra0 = + if (extra.length > baseExtraWidth) + extra.take( + (baseExtraWidth max (extra.length - overflow)) - 1 + ) + "…" + else + extra + + val total0 = url.length + 1 + extra0.length + val overflow0 = total0 - width + 1 + + val url0 = + if (total0 >= width) + url.take( + ((width - baseExtraWidth - 1) max (url.length - overflow0)) - 1 + ) + "…" + else + url + + (url0, extra0) + } else + (url, extra) + + (url0, extra0) + } + + private def truncatedPrintln(s: String): Unit = { + + out.clearLine(2) + + if (s.length <= width) + out.write(s + "\n") + else + out.write(s.take(width - 1) + "…\n") + } + + @tailrec private def updateDisplayIter(lineCount: Int): Unit = { + currentHeight = lineCount + Option(q.poll()) match { + case None => state = UpdateDisplay(lineCount) + case Some(Message.Stop) => state = NotRunning // poison pill + case Some(Message.Update) => + val (done0, downloads0) = downloads.synchronized { // removed sync + val q = doneQueue.toVector + .filter { case (url, _) => + !url.endsWith(".sha1") && !url.endsWith(".md5") + } + .sortBy { case (url, _) => url } + + doneQueue.clear() + + val dw = downloads.toVector + .map { url => url -> infos.get(url) } + .sortBy { case (_, info) => -info.fraction.sum } + + (q, dw) + } + + for ((url, info) <- done0 ++ downloads0) { + assert(info != null, s"Incoherent state ($url)") + + truncatedPrintln(url) + out.clearLine(2) + out.write(s" ${info.display()}\n") + } + + val displayedCount = (done0 ++ downloads0).length + + if (displayedCount < lineCount) { + for (_ <- 1 to 2; _ <- displayedCount until lineCount) { + out.clearLine(2) + out.down(1) + } + + for (_ <- displayedCount until lineCount) + out.up(2) + } + + for (_ <- downloads0.indices) + out.up(2) + + out.left(10000) + + out.flush() + + if (q.size != 0) + updateDisplayIter(downloads0.length) + else + state = UpdateDisplay(lineCount) + } + } + + @tailrec private def fallbackDisplayIter(previous: Set[String]): Unit = + Option(q.poll()) match { + case None => + state = FallbackDisplay(previous) + case Some(Message.Stop) => // poison pill + + // clean up display + for (_ <- 1 to 2; _ <- 0 until currentHeight) { + out.clearLine(2) + out.down(1) + } + for (_ <- 0 until currentHeight) { + out.up(2) + } + + state = NotRunning + + case Some(Message.Update) => + val downloads0 = downloads.synchronized { + downloads.toVector + .map { url => url -> infos.get(url) } + .sortBy { case (_, info) => -info.fraction.sum } + } + + var displayedSomething = false + for ((url, info) <- downloads0 if previous(url)) { + assert(info != null, s"Incoherent state ($url)") + + val (url0, extra0) = reflowed(url, info) + + displayedSomething = true + out.write(s"$url0 $extra0\n") + } + + if (displayedSomething) + out.write("\n") + + out.flush() + + val updatedPrevious = previous ++ downloads0.map { case (url, _) => + url + } + + if (q.size != 0) + fallbackDisplayIter(updatedPrevious) + else + state = FallbackDisplay(updatedPrevious) + } + + def start() = run() + + private def run(): Unit = { + Terminal.consoleDim("cols") match { + case Some(cols) => + width = cols + out.clearLine(2) + case None => + fallbackMode = true + } + + if (fallbackMode) + fallbackDisplayIter(Set.empty) + else + updateDisplayIter(0) + } + +} diff --git a/scalafmt-cli/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/native-image.properties b/scalafmt-cli/shared/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/native-image.properties similarity index 100% rename from scalafmt-cli/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/native-image.properties rename to scalafmt-cli/shared/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/native-image.properties diff --git a/scalafmt-cli/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/reflection.json b/scalafmt-cli/shared/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/reflection.json similarity index 100% rename from scalafmt-cli/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/reflection.json rename to scalafmt-cli/shared/src/main/resources/META-INF/native-image/org.scalafmt/scalafmt-cli/reflection.json diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/Cli.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/Cli.scala similarity index 84% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/Cli.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/Cli.scala index b3e1e1416d..b6066a25cb 100644 --- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/Cli.scala +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/Cli.scala @@ -1,36 +1,13 @@ package org.scalafmt.cli -import com.martiansoftware.nailgun.NGContext import java.nio.file.{Files, Paths} import org.scalafmt.Versions -import org.scalafmt.util.AbsoluteFile import scala.io.Source import scala.util.control.NoStackTrace -object Cli { - def nailMain(nGContext: NGContext): Unit = { - val workingDirectory = - AbsoluteFile.fromPath(nGContext.getWorkingDirectory).getOrElse { - throw new IllegalStateException( - s"Expected absolute path, " + - s"obtained nGContext.getWorkingDirectory = ${nGContext.getWorkingDirectory}" - ) - } - val exit = mainWithOptions( - nGContext.getArgs, - CliOptions.default.copy( - common = CliOptions.default.common.copy( - cwd = workingDirectory, - out = nGContext.out, - in = nGContext.in, - err = nGContext.err - ) - ) - ) - nGContext.exit(exit.code) - } +class Cli extends CliUtils { private def throwIfError(exit: ExitCode): Unit = { if (exit != ExitCode.Ok) { @@ -38,11 +15,6 @@ object Cli { } } - def main(args: Array[String]): Unit = { - val exit = mainWithOptions(args, CliOptions.default) - sys.exit(exit.code) - } - def exceptionThrowingMain(args: Array[String]): Unit = { exceptionThrowingMainWithOptions(args, CliOptions.default) } @@ -106,7 +78,7 @@ object Cli { // - `scalafmt-core` if the specified `version` setting match with build version // (or if the `version` is not specified). options.getVersionIfDifferent match { - case Some(v) if isNativeImage => + case Some(v) if isNativeImage || isNative => Left( s"""error: invalid Scalafmt version. | @@ -163,3 +135,10 @@ object Cli { } } } + +object Cli extends Cli { + def main(args: Array[String]): Unit = { + val exit = mainWithOptions(args, CliOptions.default) + sys.exit(exit.code) + } +} diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/CliArgParser.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/CliArgParser.scala similarity index 100% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/CliArgParser.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/CliArgParser.scala diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/CliOptions.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/CliOptions.scala similarity index 100% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/CliOptions.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/CliOptions.scala diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ExitCode.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ExitCode.scala similarity index 93% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/ExitCode.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ExitCode.scala index b9e7f74f43..62721e31dc 100644 --- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ExitCode.scala +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ExitCode.scala @@ -1,6 +1,7 @@ package org.scalafmt.cli import scala.collection.mutable +import org.scalafmt.CompatCollections sealed abstract case class ExitCode(code: Int, name: String) { def isOk: Boolean = this == ExitCode.Ok @@ -14,8 +15,7 @@ object ExitCode { // for example how the name is calculated for merged exit codes. private var counter = 0 private val allInternal = mutable.ListBuffer.empty[ExitCode] - private val cache = - new java.util.concurrent.ConcurrentHashMap[Int, ExitCode] + private val cache = CompatCollections.concurrentMap[Int, ExitCode] private def generateExitStatus(implicit name: sourcecode.Name) = { val code = counter counter = if (counter == 0) 1 else counter << 1 diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/FileFetchMode.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/FileFetchMode.scala similarity index 100% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/FileFetchMode.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/FileFetchMode.scala diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/InputMethod.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/InputMethod.scala similarity index 100% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/InputMethod.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/InputMethod.scala diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/Scalafmt210.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/Scalafmt210.scala similarity index 100% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/Scalafmt210.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/Scalafmt210.scala diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtCliReporter.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ScalafmtCliReporter.scala similarity index 100% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtCliReporter.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ScalafmtCliReporter.scala diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtCoreRunner.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ScalafmtCoreRunner.scala similarity index 94% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtCoreRunner.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ScalafmtCoreRunner.scala index 93296f65ec..ca24343505 100644 --- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtCoreRunner.scala +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ScalafmtCoreRunner.scala @@ -6,7 +6,7 @@ import util.control.Breaks import org.scalafmt.Error.{MisformattedFile, NoMatchingFiles} import org.scalafmt.{Formatted, Scalafmt, Versions} import org.scalafmt.config.{ProjectFiles, ScalafmtConfig} -import org.scalafmt.CompatCollections.ParConverters._ +import org.scalafmt.CompatCollections.CompatParConverters._ import scala.meta.internal.tokenizers.PlatformTokenizerCache import scala.meta.parsers.ParseException @@ -22,11 +22,10 @@ object ScalafmtCoreRunner extends ScalafmtRunner { ExitCode.UnexpectedError } { scalafmtConf => options.common.debug.println(s"parsed config (v${Versions.version})") - val filterMatcher = ProjectFiles.FileMatcher( + lazy val filterMatcher = ProjectFiles.FileMatcher( scalafmtConf.project, options.customExcludes ) - val inputMethods = getInputMethods(options, filterMatcher.matchesFile) if (inputMethods.isEmpty && options.mode.isEmpty && !options.stdIn) throw NoMatchingFiles @@ -36,7 +35,7 @@ object ScalafmtCoreRunner extends ScalafmtRunner { newTermDisplay(options, inputMethods, termDisplayMessage) val exitCode = new AtomicReference(ExitCode.Ok) Breaks.breakable { - inputMethods.par.foreach { inputMethod => + inputMethods.compatPar.foreach { inputMethod => val code = handleFile(inputMethod, options, scalafmtConf) exitCode.getAndUpdate(ExitCode.merge(code, _)) if (options.check && !code.isOk) Breaks.break diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtDynamicRunner.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ScalafmtDynamicRunner.scala similarity index 97% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtDynamicRunner.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ScalafmtDynamicRunner.scala index 4af280c6ea..481b261e7c 100644 --- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtDynamicRunner.scala +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ScalafmtDynamicRunner.scala @@ -5,7 +5,7 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.atomic.{AtomicInteger, AtomicReference} -import org.scalafmt.CompatCollections.ParConverters._ +import org.scalafmt.CompatCollections.CompatParConverters._ import org.scalafmt.Error.{MisformattedFile, NoMatchingFiles} import org.scalafmt.dynamic.ScalafmtDynamicError import org.scalafmt.interfaces.Scalafmt @@ -55,7 +55,7 @@ object ScalafmtDynamicRunner extends ScalafmtRunner { val exitCode = new AtomicReference(ExitCode.Ok) breakable { - inputMethods.par.foreach { inputMethod => + inputMethods.compatPar.foreach { inputMethod => try { val code = handleFile(inputMethod, session, options) exitCode.getAndUpdate(ExitCode.merge(code, _)) diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtRunner.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ScalafmtRunner.scala similarity index 100% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/ScalafmtRunner.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/ScalafmtRunner.scala diff --git a/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/TermDisplay.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/TermDisplay.scala new file mode 100644 index 0000000000..367f0c255e --- /dev/null +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/TermDisplay.scala @@ -0,0 +1,273 @@ +package org.scalafmt.cli + +/** This code is copy/pasted from (Apache 2 licence) + * https://github.com/alexarchambault/coursier/blob/51fefe5c29d95752ce487f60d333b1f8a91dd1b0/cache/src/main/scala/coursier/TermDisplay.scala + * + * which in turn was copy/pasted from (MIT licence) + * https://github.com/lihaoyi/Ammonite/blob/10854e3b8b454a74198058ba258734a17af32023/terminal/src/main/scala/ammonite/terminal/Utils.scala + */ +import scala.util.Try + +import java.io.File +import java.io.Writer +import java.io.BufferedReader +import java.io.InputStreamReader + +object Terminal { + + private lazy val pathedTput = + if (new File("/usr/bin/tput").exists()) "/usr/bin/tput" else "tput" + + def consoleDim(s: String): Option[Int] = + if (System.getenv("TERM") == null) None + else if (!new File("/dev/tty").exists()) None + else { + import sys.process._ + val nullLog = new ProcessLogger { + def out(s: => String): Unit = {} + def err(s: => String): Unit = {} + def buffer[T](f: => T): T = f + } + Try { + val processBuilder = new java.lang.ProcessBuilder() + processBuilder.command( + Seq("bash", "-c", s"$pathedTput $s 2> /dev/tty"): _* + ); + val process = processBuilder.start() + + new BufferedReader(new InputStreamReader(process.getInputStream())) + .readLine() + .toInt + }.toOption + } + + implicit class Ansi(val output: Writer) extends AnyVal { + private def control(n: Int, c: Char) = output.write("\u001b[" + n + c) + + /** Move up `n` squares + */ + def up(n: Int): Unit = if (n > 0) control(n, 'A') + + /** Move down `n` squares + */ + def down(n: Int): Unit = if (n > 0) control(n, 'B') + + /** Move left `n` squares + */ + def left(n: Int): Unit = if (n > 0) control(n, 'D') + + /** Clear the current line + * + * n=0: clear from cursor to end of line n=1: clear from cursor to start of + * line n=2: clear entire line + */ + def clearLine(n: Int): Unit = control(n, 'K') + } + +} + +object TermDisplay extends TermUtils { + + def defaultFallbackMode: Boolean = { + val env0 = sys.env.get("COURSIER_PROGRESS").map(_.toLowerCase).collect { + case "true" | "enable" | "1" => true + case "false" | "disable" | "0" => false + } + def compatibilityEnv = sys.env.get("COURSIER_NO_TERM").nonEmpty + + def nonInteractive = noConsole + + def insideEmacs = sys.env.contains("INSIDE_EMACS") + def ci = sys.env.contains("CI") + + val env = env0.getOrElse(compatibilityEnv) + + env || nonInteractive || insideEmacs || ci + } + + private[cli] sealed abstract class Info extends Product with Serializable { + def fraction: Option[Double] + def display(): String + } + + private[cli] case class DownloadInfo( + downloaded: Long, + previouslyDownloaded: Long, + length: Option[Long], + startTime: Long, + updateCheck: Boolean + ) extends Info { + + /** 0.0 to 1.0 */ + def fraction: Option[Double] = length.map(downloaded.toDouble / _) + + /** Byte / s */ + def rate(): Option[Double] = { + val currentTime = System.currentTimeMillis() + if (currentTime > startTime) + Some( + (downloaded - previouslyDownloaded).toDouble / + (System.currentTimeMillis() - startTime) * 1000.0 + ) + else + None + } + + // Scala version of http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java/3758880#3758880 + private def byteCount(bytes: Long, si: Boolean = false): String = { + val unit = if (si) 1000 else 1024 + if (bytes < unit) + bytes + " B" + else { + val exp = (math.log(bytes) / math.log(unit)).toInt + val pre = + (if (si) "kMGTPE" + else "KMGTPE").charAt(exp - 1) + + (if (si) "" + else "i") + f"${bytes / math.pow(unit, exp)}%.1f ${pre}B" + } + } + + def display(): String = { + val decile = (10.0 * fraction.getOrElse(0.0)).toInt + assert(decile >= 0) + assert(decile <= 10) + + fraction.fold(" " * 6)(p => f"${100.0 * p}%5.1f%%") + + " [" + ("#" * decile) + (" " * (10 - decile)) + "] " + + downloaded + " source files formatted" + } + } + + private[cli] case class CheckUpdateInfo( + currentTimeOpt: Option[Long], + remoteTimeOpt: Option[Long], + isDone: Boolean + ) extends Info { + def fraction = None + def display(): String = { + if (isDone) + (currentTimeOpt, remoteTimeOpt) match { + case (Some(current), Some(remote)) => + if (current < remote) + s"Updated since ${formatTimestamp(current)} (${formatTimestamp(remote)})" + else if (current == remote) + s"No new update since ${formatTimestamp(current)}" + else + s"Warning: local copy newer than remote one (${formatTimestamp(current)} > ${formatTimestamp(remote)})" + case (Some(_), None) => + // FIXME Likely a 404 Not found, that should be taken into account by the cache + "No modified time in response" + case (None, Some(remote)) => + s"Last update: ${formatTimestamp(remote)}" + case (None, None) => + "" // ??? + } + else + currentTimeOpt match { + case Some(current) => + s"Checking for updates since ${formatTimestamp(current)}" + case None => + "" // ??? + } + } + } + + private[cli] sealed abstract class Message extends Product with Serializable + private[cli] object Message { + case object Update extends Message + case object Stop extends Message + } + +} + +object Cache { + trait Logger { + def foundLocally(url: String, f: File): Unit = {} + def startTask(url: String, file: File): Unit = {} + def taskProgress(url: String, downloaded: Long): Unit = {} + def completedTask(url: String, success: Boolean): Unit = {} + def checkingUpdates(url: String, currentTimeOpt: Option[Long]): Unit = {} + } +} + +class TermDisplay( + out: Writer, + val fallbackMode: Boolean = TermDisplay.defaultFallbackMode +) extends Cache.Logger { + + import TermDisplay._ + + private val updateThread = new UpdateDisplayThread(out, fallbackMode) + + def init(): Unit = { + updateThread.start() + } + + def stop(): Unit = { + updateThread.end() + } + + override def startTask(msg: String, file: File): Unit = + updateThread.newEntry( + msg, + DownloadInfo( + 0L, + 0L, + None, + System.currentTimeMillis(), + updateCheck = false + ), + s"$msg\n" + ) + + def taskLength( + url: String, + totalLength: Long, + alreadyDownloaded: Long + ): Unit = { + val info = updateThread.infos.get(url) + assert(info != null) + val newInfo = info match { + case info0: DownloadInfo => + info0.copy( + length = Some(totalLength), + previouslyDownloaded = alreadyDownloaded + ) + case _ => + throw new Exception(s"Incoherent display state for $url") + } + updateThread.infos.put(url, newInfo) + + updateThread.update() + } + override def taskProgress(url: String, downloaded: Long): Unit = { + val info = updateThread.infos.get(url) + if (info != null) { // We might not want the progress bar. + val newInfo = info match { + case info0: DownloadInfo => + info0.copy(downloaded = downloaded) + case _ => + throw new Exception(s"Incoherent display state for $url") + } + updateThread.infos.put(url, newInfo) + + updateThread.update() + } + } + + override def completedTask(url: String, success: Boolean): Unit = + updateThread.removeEntry(url, success, s"$url\n")(x => x) + + override def checkingUpdates( + url: String, + currentTimeOpt: Option[Long] + ): Unit = + updateThread.newEntry( + url, + CheckUpdateInfo(currentTimeOpt, None, isDone = false), + s"$url\n" + ) + +} diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/WriteMode.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/WriteMode.scala similarity index 100% rename from scalafmt-cli/src/main/scala/org/scalafmt/cli/WriteMode.scala rename to scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/WriteMode.scala diff --git a/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Chunk.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Chunk.scala new file mode 100644 index 0000000000..297233b4d1 --- /dev/null +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Chunk.scala @@ -0,0 +1,12 @@ +package org.scalafmt.cli.difflib + +import java.util + +class Chunk[T](position: Int, lines: util.List[T]) { + + def getPosition: Int = position + def getLines: util.List[T] = lines + def size: Int = lines.size() + + override def toString: String = s"Chunk($getPosition, $getLines, $size)" +} diff --git a/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Delta.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Delta.scala new file mode 100644 index 0000000000..cb8666eb50 --- /dev/null +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Delta.scala @@ -0,0 +1,28 @@ +package org.scalafmt.cli.difflib + +sealed abstract class Delta[T](original: Chunk[T], revised: Chunk[T]) { + + sealed abstract class TYPE + object TYPE { + case object CHANGE extends TYPE + case object DELETE extends TYPE + case object INSERT extends TYPE + } + def getType: TYPE + def getOriginal: Chunk[T] = original + def getRevised: Chunk[T] = revised + + override def toString: String = s"Delta($getType, $getOriginal, $getRevised)" +} +class ChangeDelta[T](original: Chunk[T], revised: Chunk[T]) + extends Delta(original, revised) { + override def getType: TYPE = TYPE.CHANGE +} +class InsertDelta[T](original: Chunk[T], revised: Chunk[T]) + extends Delta(original, revised) { + override def getType: TYPE = TYPE.INSERT +} +class DeleteDelta[T](original: Chunk[T], revised: Chunk[T]) + extends Delta(original, revised) { + override def getType: TYPE = TYPE.DELETE +} diff --git a/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/DiffAlgorithm.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/DiffAlgorithm.scala new file mode 100644 index 0000000000..5006f98140 --- /dev/null +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/DiffAlgorithm.scala @@ -0,0 +1,7 @@ +package org.scalafmt.cli.difflib + +import java.util + +trait DiffAlgorithm[T] { + def diff(original: util.List[T], revised: util.List[T]): Patch[T] +} diff --git a/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/DiffUtils.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/DiffUtils.scala new file mode 100644 index 0000000000..46ad882bd6 --- /dev/null +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/DiffUtils.scala @@ -0,0 +1,163 @@ +package org.scalafmt.cli.difflib + +import java.util + +object DiffUtils { + def generateUnifiedDiff( + original: String, + revised: String, + originalLines: util.List[String], + patch: Patch[String], + contextSize: Int + ): util.List[String] = { + if (!patch.getDeltas.isEmpty) { + val ret: util.List[String] = new util.ArrayList() + ret.add("--- " + original) + ret.add("+++ " + revised) + val patchDeltas: util.List[Delta[String]] = + new util.ArrayList(patch.getDeltas) + val deltas: util.List[Delta[String]] = new util.ArrayList() + + var delta = patchDeltas.get(0) + deltas.add(delta) + + if (patchDeltas.size() > 1) { + for (i <- 1 until patchDeltas.size) { + val position = delta.getOriginal.getPosition // store + // the + // current + // position + // of + // the first Delta + // Check if the next Delta is too close to the current + // position. + // And if it is, add it to the current set + val nextDelta = patchDeltas.get(i) + if ( + (position + delta.getOriginal.size + contextSize) >= + (nextDelta.getOriginal.getPosition - contextSize) + ) { + deltas.add(nextDelta) + } else { // if it isn't, output the current set, + // then create a new set and add the current Delta to + // it. + val curBlock = processDeltas(originalLines, deltas, contextSize) + ret.addAll(curBlock) + deltas.clear() + deltas.add(nextDelta) + } + delta = nextDelta + } + } + val curBlock = processDeltas(originalLines, deltas, contextSize) + ret.addAll(curBlock) + ret + } else { + new util.ArrayList[String]() + } + } + def diff( + original: util.List[String], + revised: util.List[String] + ): Patch[String] = + new MyersDiff[String]().diff(original, revised) + + private def processDeltas( + origLines: util.List[String], + deltas: util.List[Delta[String]], + contextSize: Int + ) = { + val buffer = new util.ArrayList[String] + var origTotal = 0 // counter for total lines output from Original + var revTotal = 0 + var line = 0 + var curDelta = deltas.get(0) + // NOTE: +1 to overcome the 0-offset Position + var origStart = curDelta.getOriginal.getPosition + 1 - contextSize + if (origStart < 1) origStart = 1 + var revStart = curDelta.getRevised.getPosition + 1 - contextSize + if (revStart < 1) revStart = 1 + // find the start of the wrapper context code + var contextStart = curDelta.getOriginal.getPosition - contextSize + if (contextStart < 0) contextStart = 0 // clamp to the start of the file + // output the context before the first Delta + line = contextStart + while ({ + line < curDelta.getOriginal.getPosition + }) { // + buffer.add(" " + origLines.get(line)) + origTotal += 1 + revTotal += 1 + + line += 1 + } + // output the first Delta + buffer.addAll(getDeltaText(curDelta)) + origTotal += curDelta.getOriginal.getLines.size + revTotal += curDelta.getRevised.getLines.size + var deltaIndex = 1 + while ({ + deltaIndex < deltas.size + }) { // for each of the other Deltas + val nextDelta = deltas.get(deltaIndex) + val intermediateStart = + curDelta.getOriginal.getPosition + curDelta.getOriginal.getLines.size + line = intermediateStart + while ({ + line < nextDelta.getOriginal.getPosition + }) { // output the code between the last Delta and this one + buffer.add(" " + origLines.get(line)) + origTotal += 1 + revTotal += 1 + + line += 1 + } + buffer.addAll(getDeltaText(nextDelta)) // output the Delta + + origTotal += nextDelta.getOriginal.getLines.size + revTotal += nextDelta.getRevised.getLines.size + curDelta = nextDelta + deltaIndex += 1 + } + // Now output the post-Delta context code, clamping the end of the file + contextStart = + curDelta.getOriginal.getPosition + curDelta.getOriginal.getLines.size + line = contextStart + while ({ + (line < (contextStart + contextSize)) && (line < origLines.size) + }) { + buffer.add(" " + origLines.get(line)) + origTotal += 1 + revTotal += 1 + + line += 1 + } + // Create and insert the block header, conforming to the Unified Diff + // standard + val header = new StringBuffer + header.append("@@ -") + header.append(origStart) + header.append(",") + header.append(origTotal) + header.append(" +") + header.append(revStart) + header.append(",") + header.append(revTotal) + header.append(" @@") + buffer.add(0, header.toString) + buffer + } + + private def getDeltaText(delta: Delta[String]) = { + import scala.collection.JavaConverters._ + val buffer = new util.ArrayList[String] + for (line <- delta.getOriginal.getLines.asScala) { + buffer.add("-" + line) + } + for (line <- delta.getRevised.getLines.asScala) { + buffer.add("+" + line) + } + buffer + } + +} diff --git a/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/DifferentiationFailedException.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/DifferentiationFailedException.scala new file mode 100644 index 0000000000..cc3f7774e1 --- /dev/null +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/DifferentiationFailedException.scala @@ -0,0 +1,3 @@ +package org.scalafmt.cli.difflib + +class DifferentiationFailedException(message: String) extends Exception(message) diff --git a/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Equalizer.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Equalizer.scala new file mode 100644 index 0000000000..5ea6a13a4d --- /dev/null +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Equalizer.scala @@ -0,0 +1,12 @@ +package org.scalafmt.cli.difflib + +trait Equalizer[T] { + def equals(original: T, revised: T): Boolean +} +object Equalizer { + def default[T]: Equalizer[T] = new Equalizer[T] { + override def equals(original: T, revised: T): Boolean = { + original == revised + } + } +} diff --git a/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/MyersDiff.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/MyersDiff.scala new file mode 100644 index 0000000000..4fbbebdcc1 --- /dev/null +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/MyersDiff.scala @@ -0,0 +1,131 @@ +package org.scalafmt.cli.difflib + +import java.util + +class MyersDiff[T](equalizer: Equalizer[T]) extends DiffAlgorithm[T] { + def this() = this(Equalizer.default[T]) + override def diff( + original: util.List[T], + revised: util.List[T] + ): Patch[T] = { + try { + buildRevision(buildPath(original, revised), original, revised) + } catch { + case e: DifferentiationFailedException => + e.printStackTrace() + new Patch[T]() + } + } + private def buildRevision( + _path: PathNode, + orig: util.List[T], + rev: util.List[T] + ): Patch[T] = { + var path = _path + val patch = new Patch[T] + if (path.isSnake) path = path.prev + while ( + path != null && + path.prev != null && + path.prev.j >= 0 + ) { + if (path.isSnake) + throw new IllegalStateException( + "bad diffpath: found snake when looking for diff" + ) + val i = path.i + val j = path.j + path = path.prev + val ianchor = path.i + val janchor = path.j + val original = + new Chunk[T]( + ianchor, + copyOfRange(orig, ianchor, i) + ) + val revised = + new Chunk[T]( + janchor, + copyOfRange(rev, janchor, j) + ) + val delta: Delta[T] = + if (original.size == 0 && revised.size != 0) { + new InsertDelta[T](original, revised) + } else if (original.size > 0 && revised.size == 0) { + new DeleteDelta[T](original, revised) + } else { + new ChangeDelta[T](original, revised) + } + patch.addDelta(delta) + if (path.isSnake) { + path = path.prev + } + } + patch + } + + private def copyOfRange(original: util.List[T], fromIndex: Int, to: Int) = + new util.ArrayList[T](original.subList(fromIndex, to)) + + def buildPath( + orig: util.List[T], + rev: util.List[T] + ): PathNode = { + + val N = orig.size() + val M = rev.size() + + val MAX = N + M + 1 + val size = 1 + 2 * MAX + val middle = size / 2 + val diagonal = new Array[PathNode](size) + + diagonal(middle + 1) = new Snake(0, -1, null) + var d = 0 + while (d < MAX) { + var k = -d + while (k <= d) { + val kmiddle = middle + k + val kplus = kmiddle + 1 + val kminus = kmiddle - 1 + var prev: PathNode = null + var i = 0 + if ((k == -d) || (k != d && diagonal(kminus).i < diagonal(kplus).i)) { + i = diagonal(kplus).i + prev = diagonal(kplus) + } else { + i = diagonal(kminus).i + 1 + prev = diagonal(kminus) + } + diagonal(kminus) = null // no longer used + + var j = i - k + var node: PathNode = new DiffNode(i, j, prev) + // orig and rev are zero-based + // but the algorithm is one-based + // that's why there's no +1 when indexing the sequences + while ( + i < N && + j < M && + equalizer.equals(orig.get(i), rev.get(j)) + ) { + i += 1 + j += 1 + } + if (i > node.i) { + node = new Snake(i, j, node) + } + diagonal(kmiddle) = node + if (i >= N && j >= M) { + return diagonal(kmiddle) + } + + k += 2 + } + diagonal(middle + d - 1) = null + d += 1 + } + // According to Myers, this cannot happen + throw new DifferentiationFailedException("could not find a diff path") + } +} diff --git a/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Patch.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Patch.scala new file mode 100644 index 0000000000..13f5f3d319 --- /dev/null +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/Patch.scala @@ -0,0 +1,21 @@ +package org.scalafmt.cli.difflib + +import java.util +import java.util.{Collections, Comparator} + +class Patch[T] { + private val deltas: util.List[Delta[T]] = new util.ArrayList() + private val comparator: Comparator[Delta[T]] = new Comparator[Delta[T]] { + override def compare(o1: Delta[T], o2: Delta[T]): Int = + o1.getOriginal.getPosition.compareTo(o2.getOriginal.getPosition) + } + def addDelta(delta: Delta[T]): Unit = { + deltas.add(delta) + } + def getDeltas: util.List[Delta[T]] = { + Collections.sort(deltas, comparator) + deltas + } + + override def toString: String = s"Patch($deltas)" +} diff --git a/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/PathNode.scala b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/PathNode.scala new file mode 100644 index 0000000000..06ba155e1e --- /dev/null +++ b/scalafmt-cli/shared/src/main/scala/org/scalafmt/cli/difflib/PathNode.scala @@ -0,0 +1,38 @@ +package org.scalafmt.cli.difflib + +sealed abstract class PathNode(val i: Int, val j: Int, val prev: PathNode) { + + def isSnake: Boolean + final def isBootstrap: Boolean = { + i < 0 || j < 0 + } + final def previousSnake: PathNode = { + if (isBootstrap) null + else if (!isSnake && prev != null) prev.previousSnake + else this + } + + override def toString: String = { + val buf = new StringBuffer("[") + var node = this + while (node != null) { + buf.append("(") + buf.append(Integer.toString(node.i)) + buf.append(",") + buf.append(Integer.toString(node.j)) + buf.append(")") + node = node.prev + } + buf.append("]") + buf.toString + } +} + +final class DiffNode(i: Int, j: Int, prev: PathNode) + extends PathNode(i, j, if (prev == null) null else prev.previousSnake) { + override def isSnake: Boolean = false +} + +final class Snake(i: Int, j: Int, prev: PathNode) extends PathNode(i, j, prev) { + override def isSnake: Boolean = true +} diff --git a/scalafmt-cli/src/main/scala/org/scalafmt/cli/TermDisplay.scala b/scalafmt-cli/src/main/scala/org/scalafmt/cli/TermDisplay.scala deleted file mode 100644 index 4633712131..0000000000 --- a/scalafmt-cli/src/main/scala/org/scalafmt/cli/TermDisplay.scala +++ /dev/null @@ -1,525 +0,0 @@ -package org.scalafmt.cli - -/** This code is copy/pasted from (Apache 2 licence) - * https://github.com/alexarchambault/coursier/blob/51fefe5c29d95752ce487f60d333b1f8a91dd1b0/cache/src/main/scala/coursier/TermDisplay.scala - * - * which in turn was copy/pasted from (MIT licence) - * https://github.com/lihaoyi/Ammonite/blob/10854e3b8b454a74198058ba258734a17af32023/terminal/src/main/scala/ammonite/terminal/Utils.scala - */ -import scala.annotation.tailrec -import scala.collection.mutable.ArrayBuffer -import scala.util.Try - -import java.io.File -import java.io.Writer -import java.sql.Timestamp -import java.util.concurrent._ - -object Terminal { - - private lazy val pathedTput = - if (new File("/usr/bin/tput").exists()) "/usr/bin/tput" else "tput" - - def consoleDim(s: String): Option[Int] = - if (System.getenv("TERM") == null) None - else if (!new File("/dev/tty").exists()) None - else { - import sys.process._ - val nullLog = new ProcessLogger { - def out(s: => String): Unit = {} - def err(s: => String): Unit = {} - def buffer[T](f: => T): T = f - } - Try( - Process(Seq("bash", "-c", s"$pathedTput $s 2> /dev/tty")) - .!!(nullLog) - .trim - .toInt - ).toOption - } - - implicit class Ansi(val output: Writer) extends AnyVal { - private def control(n: Int, c: Char) = output.write("\u001b[" + n + c) - - /** Move up `n` squares - */ - def up(n: Int): Unit = if (n > 0) control(n, 'A') - - /** Move down `n` squares - */ - def down(n: Int): Unit = if (n > 0) control(n, 'B') - - /** Move left `n` squares - */ - def left(n: Int): Unit = if (n > 0) control(n, 'D') - - /** Clear the current line - * - * n=0: clear from cursor to end of line n=1: clear from cursor to start of - * line n=2: clear entire line - */ - def clearLine(n: Int): Unit = control(n, 'K') - } - -} - -object TermDisplay { - - def defaultFallbackMode: Boolean = { - val env0 = sys.env.get("COURSIER_PROGRESS").map(_.toLowerCase).collect { - case "true" | "enable" | "1" => true - case "false" | "disable" | "0" => false - } - def compatibilityEnv = sys.env.get("COURSIER_NO_TERM").nonEmpty - - def nonInteractive = System.console() == null - - def insideEmacs = sys.env.contains("INSIDE_EMACS") - def ci = sys.env.contains("CI") - - val env = env0.getOrElse(compatibilityEnv) - - env || nonInteractive || insideEmacs || ci - } - - private sealed abstract class Info extends Product with Serializable { - def fraction: Option[Double] - def display(): String - } - - private case class DownloadInfo( - downloaded: Long, - previouslyDownloaded: Long, - length: Option[Long], - startTime: Long, - updateCheck: Boolean - ) extends Info { - - /** 0.0 to 1.0 */ - def fraction: Option[Double] = length.map(downloaded.toDouble / _) - - /** Byte / s */ - def rate(): Option[Double] = { - val currentTime = System.currentTimeMillis() - if (currentTime > startTime) - Some( - (downloaded - previouslyDownloaded).toDouble / - (System.currentTimeMillis() - startTime) * 1000.0 - ) - else - None - } - - // Scala version of http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java/3758880#3758880 - private def byteCount(bytes: Long, si: Boolean = false): String = { - val unit = if (si) 1000 else 1024 - if (bytes < unit) - bytes + " B" - else { - val exp = (math.log(bytes) / math.log(unit)).toInt - val pre = - (if (si) "kMGTPE" - else "KMGTPE").charAt(exp - 1) + - (if (si) "" - else "i") - f"${bytes / math.pow(unit, exp)}%.1f ${pre}B" - } - } - - def display(): String = { - val decile = (10.0 * fraction.getOrElse(0.0)).toInt - assert(decile >= 0) - assert(decile <= 10) - - fraction.fold(" " * 6)(p => f"${100.0 * p}%5.1f%%") + - " [" + ("#" * decile) + (" " * (10 - decile)) + "] " + - downloaded + " source files formatted" - } - } - - private val format = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - private def formatTimestamp(ts: Long): String = - format.format(new Timestamp(ts)) - - private case class CheckUpdateInfo( - currentTimeOpt: Option[Long], - remoteTimeOpt: Option[Long], - isDone: Boolean - ) extends Info { - def fraction = None - def display(): String = { - if (isDone) - (currentTimeOpt, remoteTimeOpt) match { - case (Some(current), Some(remote)) => - if (current < remote) - s"Updated since ${formatTimestamp(current)} (${formatTimestamp(remote)})" - else if (current == remote) - s"No new update since ${formatTimestamp(current)}" - else - s"Warning: local copy newer than remote one (${formatTimestamp(current)} > ${formatTimestamp(remote)})" - case (Some(_), None) => - // FIXME Likely a 404 Not found, that should be taken into account by the cache - "No modified time in response" - case (None, Some(remote)) => - s"Last update: ${formatTimestamp(remote)}" - case (None, None) => - "" // ??? - } - else - currentTimeOpt match { - case Some(current) => - s"Checking for updates since ${formatTimestamp(current)}" - case None => - "" // ??? - } - } - } - - private sealed abstract class Message extends Product with Serializable - private object Message { - case object Update extends Message - case object Stop extends Message - } - - private val refreshInterval = 1000 / 60 - private val fallbackRefreshInterval = 1000 - - private class UpdateDisplayThread( - out: Writer, - var fallbackMode: Boolean - ) extends Thread("TermDisplay") { - - import Terminal.Ansi - - setDaemon(true) - - private var width = 80 - private var currentHeight = 0 - - private val q = new LinkedBlockingDeque[Message] - - def update(): Unit = { - if (q.size() == 0) - q.put(Message.Update) - } - - def end(): Unit = { - q.put(Message.Stop) - join() - } - - private val downloads = new ArrayBuffer[String] - private val doneQueue = new ArrayBuffer[(String, Info)] - val infos = new ConcurrentHashMap[String, Info] - - def newEntry( - url: String, - info: Info, - fallbackMessage: => String - ): Unit = { - assert(!infos.containsKey(url)) - val prev = infos.putIfAbsent(url, info) - assert(prev == null) - - if (fallbackMode) { - // FIXME What about concurrent accesses to out from the thread above? - out.write(fallbackMessage) - out.flush() - } - - downloads.synchronized { - downloads.append(url) - } - - update() - } - - def removeEntry( - url: String, - success: Boolean, - fallbackMessage: => String - )( - update0: Info => Info - ): Unit = { - downloads.synchronized { - downloads -= url - - val info = infos.remove(url) - - if (success) - doneQueue += (url -> update0(info)) - } - - if (fallbackMode && success) { - // FIXME What about concurrent accesses to out from the thread above? - out.write(fallbackMessage) - out.flush() - } - - update() - } - - private def reflowed(url: String, info: Info) = { - val extra = info match { - case downloadInfo: DownloadInfo => - val pctOpt = downloadInfo.fraction.map(100.0 * _) - - if (downloadInfo.length.isEmpty && downloadInfo.downloaded == 0L) - "" - else { - val pctOptStr = - pctOpt.map(pct => f"$pct%.2f %%, ").toIterable.mkString - val downloadInfoStr = downloadInfo.length.map(" / " + _).mkString - s"($pctOptStr${downloadInfo.downloaded}$downloadInfoStr)" - } - - case updateInfo: CheckUpdateInfo => - "Checking for updates" - } - - val baseExtraWidth = width / 5 - - val total = url.length + 1 + extra.length - val (url0, extra0) = - if (total >= width) { // or > ? If equal, does it go down 2 lines? - val overflow = total - width + 1 - - val extra0 = - if (extra.length > baseExtraWidth) - extra.take( - (baseExtraWidth max (extra.length - overflow)) - 1 - ) + "…" - else - extra - - val total0 = url.length + 1 + extra0.length - val overflow0 = total0 - width + 1 - - val url0 = - if (total0 >= width) - url.take( - ((width - baseExtraWidth - 1) max (url.length - overflow0)) - 1 - ) + "…" - else - url - - (url0, extra0) - } else - (url, extra) - - (url0, extra0) - } - - private def truncatedPrintln(s: String): Unit = { - - out.clearLine(2) - - if (s.length <= width) - out.write(s + "\n") - else - out.write(s.take(width - 1) + "…\n") - } - - @tailrec private def updateDisplayLoop(lineCount: Int): Unit = { - currentHeight = lineCount - - Option(q.poll(100L, TimeUnit.MILLISECONDS)) match { - case None => updateDisplayLoop(lineCount) - case Some(Message.Stop) => // poison pill - case Some(Message.Update) => - val (done0, downloads0) = downloads.synchronized { - val q = doneQueue.toVector - .filter { case (url, _) => - !url.endsWith(".sha1") && !url.endsWith(".md5") - } - .sortBy { case (url, _) => url } - - doneQueue.clear() - - val dw = downloads.toVector - .map { url => url -> infos.get(url) } - .sortBy { case (_, info) => -info.fraction.sum } - - (q, dw) - } - - for ((url, info) <- done0 ++ downloads0) { - assert(info != null, s"Incoherent state ($url)") - - truncatedPrintln(url) - out.clearLine(2) - out.write(s" ${info.display()}\n") - } - - val displayedCount = (done0 ++ downloads0).length - - if (displayedCount < lineCount) { - for (_ <- 1 to 2; _ <- displayedCount until lineCount) { - out.clearLine(2) - out.down(1) - } - - for (_ <- displayedCount until lineCount) - out.up(2) - } - - for (_ <- downloads0.indices) - out.up(2) - - out.left(10000) - - out.flush() - Thread.sleep(refreshInterval) - updateDisplayLoop(downloads0.length) - } - } - - @tailrec private def fallbackDisplayLoop(previous: Set[String]): Unit = - Option(q.poll(100L, TimeUnit.MILLISECONDS)) match { - case None => fallbackDisplayLoop(previous) - case Some(Message.Stop) => // poison pill - - // clean up display - for (_ <- 1 to 2; _ <- 0 until currentHeight) { - out.clearLine(2) - out.down(1) - } - for (_ <- 0 until currentHeight) { - out.up(2) - } - - case Some(Message.Update) => - val downloads0 = downloads.synchronized { - downloads.toVector - .map { url => url -> infos.get(url) } - .sortBy { case (_, info) => -info.fraction.sum } - } - - var displayedSomething = false - for ((url, info) <- downloads0 if previous(url)) { - assert(info != null, s"Incoherent state ($url)") - - val (url0, extra0) = reflowed(url, info) - - displayedSomething = true - out.write(s"$url0 $extra0\n") - } - - if (displayedSomething) - out.write("\n") - - out.flush() - Thread.sleep(fallbackRefreshInterval) - fallbackDisplayLoop(previous ++ downloads0.map { case (url, _) => - url - }) - } - - override def run(): Unit = { - - Terminal.consoleDim("cols") match { - case Some(cols) => - width = cols - out.clearLine(2) - case None => - fallbackMode = true - } - - if (fallbackMode) - fallbackDisplayLoop(Set.empty) - else - updateDisplayLoop(0) - } - } - -} - -object Cache { - trait Logger { - def foundLocally(url: String, f: File): Unit = {} - def startTask(url: String, file: File): Unit = {} - def taskProgress(url: String, downloaded: Long): Unit = {} - def completedTask(url: String, success: Boolean): Unit = {} - def checkingUpdates(url: String, currentTimeOpt: Option[Long]): Unit = {} - } -} - -class TermDisplay( - out: Writer, - val fallbackMode: Boolean = TermDisplay.defaultFallbackMode -) extends Cache.Logger { - - import TermDisplay._ - - private val updateThread = new UpdateDisplayThread(out, fallbackMode) - - def init(): Unit = { - updateThread.start() - } - - def stop(): Unit = { - updateThread.end() - } - - override def startTask(msg: String, file: File): Unit = - updateThread.newEntry( - msg, - DownloadInfo( - 0L, - 0L, - None, - System.currentTimeMillis(), - updateCheck = false - ), - s"$msg\n" - ) - - def taskLength( - url: String, - totalLength: Long, - alreadyDownloaded: Long - ): Unit = { - val info = updateThread.infos.get(url) - assert(info != null) - val newInfo = info match { - case info0: DownloadInfo => - info0.copy( - length = Some(totalLength), - previouslyDownloaded = alreadyDownloaded - ) - case _ => - throw new Exception(s"Incoherent display state for $url") - } - updateThread.infos.put(url, newInfo) - - updateThread.update() - } - override def taskProgress(url: String, downloaded: Long): Unit = { - val info = updateThread.infos.get(url) - if (info != null) { // We might not want the progress bar. - val newInfo = info match { - case info0: DownloadInfo => - info0.copy(downloaded = downloaded) - case _ => - throw new Exception(s"Incoherent display state for $url") - } - updateThread.infos.put(url, newInfo) - - updateThread.update() - } - } - - override def completedTask(url: String, success: Boolean): Unit = - updateThread.removeEntry(url, success, s"$url\n")(x => x) - - override def checkingUpdates( - url: String, - currentTimeOpt: Option[Long] - ): Unit = - updateThread.newEntry( - url, - CheckUpdateInfo(currentTimeOpt, None, isDone = false), - s"$url\n" - ) - -} diff --git a/scalafmt-core/shared/src/main/scala-2.11/org/scalafmt/CompatCollections.scala b/scalafmt-core/js/src/main/scala-2.11/org/scalafmt/CompatCollections.scala similarity index 100% rename from scalafmt-core/shared/src/main/scala-2.11/org/scalafmt/CompatCollections.scala rename to scalafmt-core/js/src/main/scala-2.11/org/scalafmt/CompatCollections.scala diff --git a/scalafmt-core/shared/src/main/scala-2.12/org/scalafmt/CompatCollections.scala b/scalafmt-core/js/src/main/scala-2.12/org/scalafmt/CompatCollections.scala similarity index 100% rename from scalafmt-core/shared/src/main/scala-2.12/org/scalafmt/CompatCollections.scala rename to scalafmt-core/js/src/main/scala-2.12/org/scalafmt/CompatCollections.scala diff --git a/scalafmt-core/shared/src/main/scala-2.13/org/scalafmt/CompatCollections.scala b/scalafmt-core/js/src/main/scala-2.13/org/scalafmt/CompatCollections.scala similarity index 100% rename from scalafmt-core/shared/src/main/scala-2.13/org/scalafmt/CompatCollections.scala rename to scalafmt-core/js/src/main/scala-2.13/org/scalafmt/CompatCollections.scala diff --git a/scalafmt-core/jvm/src/main/scala-2.11/org/scalafmt/CompatCollections.scala b/scalafmt-core/jvm/src/main/scala-2.11/org/scalafmt/CompatCollections.scala new file mode 100644 index 0000000000..540f8d8e20 --- /dev/null +++ b/scalafmt-core/jvm/src/main/scala-2.11/org/scalafmt/CompatCollections.scala @@ -0,0 +1,14 @@ +package org.scalafmt + +import java.util.concurrent._ + +object CompatCollections { + val JavaConverters = scala.jdk.CollectionConverters + object CompatParConverters { + implicit class XIterable[T](val col: Iterable[T]) extends AnyVal { + def compatPar = col.par + } + } + def concurrentMap[K, V] = new ConcurrentHashMap[K, V] + +} diff --git a/scalafmt-core/jvm/src/main/scala-2.12/org/scalafmt/CompatCollections.scala b/scalafmt-core/jvm/src/main/scala-2.12/org/scalafmt/CompatCollections.scala new file mode 100644 index 0000000000..540f8d8e20 --- /dev/null +++ b/scalafmt-core/jvm/src/main/scala-2.12/org/scalafmt/CompatCollections.scala @@ -0,0 +1,14 @@ +package org.scalafmt + +import java.util.concurrent._ + +object CompatCollections { + val JavaConverters = scala.jdk.CollectionConverters + object CompatParConverters { + implicit class XIterable[T](val col: Iterable[T]) extends AnyVal { + def compatPar = col.par + } + } + def concurrentMap[K, V] = new ConcurrentHashMap[K, V] + +} diff --git a/scalafmt-core/jvm/src/main/scala-2.13/org/scalafmt/CompatCollections.scala b/scalafmt-core/jvm/src/main/scala-2.13/org/scalafmt/CompatCollections.scala new file mode 100644 index 0000000000..851c5894fe --- /dev/null +++ b/scalafmt-core/jvm/src/main/scala-2.13/org/scalafmt/CompatCollections.scala @@ -0,0 +1,15 @@ +package org.scalafmt + +import scala.collection.parallel.CollectionConverters._ +import java.util.concurrent._ + +object CompatCollections { + val JavaConverters = scala.jdk.CollectionConverters + object CompatParConverters { + implicit class XIterable[T](val col: Iterable[T]) extends AnyVal { + def compatPar = col.par + } + } + def concurrentMap[K, V] = new ConcurrentHashMap[K, V] + +} diff --git a/scalafmt-core/jvm/src/main/scala/org/scalafmt/config/PlatformConfig.scala b/scalafmt-core/jvm/src/main/scala/org/scalafmt/config/PlatformConfig.scala index c644de99e8..e26288d39b 100644 --- a/scalafmt-core/jvm/src/main/scala/org/scalafmt/config/PlatformConfig.scala +++ b/scalafmt-core/jvm/src/main/scala/org/scalafmt/config/PlatformConfig.scala @@ -1,6 +1,7 @@ package org.scalafmt.config object PlatformConfig { + def isNative = false implicit val parser = metaconfig.typesafeconfig.typesafeConfigMetaconfigParser } diff --git a/scalafmt-core/jvm/src/main/scala/org/scalafmt/internal/PlatformCompat.scala b/scalafmt-core/jvm/src/main/scala/org/scalafmt/internal/PlatformCompat.scala new file mode 100644 index 0000000000..ccad662d5f --- /dev/null +++ b/scalafmt-core/jvm/src/main/scala/org/scalafmt/internal/PlatformCompat.scala @@ -0,0 +1,10 @@ +package org.scalafmt.internal + +import metaconfig._ + +import java.io.File + +object PlatformCompat { + @inline def metaconfigInputFromFile(input: File) = + Input.File(input) +} diff --git a/scalafmt-core/jvm/src/main/scala/org/scalafmt/internal/RegexCompat.scala b/scalafmt-core/jvm/src/main/scala/org/scalafmt/internal/RegexCompat.scala new file mode 100644 index 0000000000..b0fb56e812 --- /dev/null +++ b/scalafmt-core/jvm/src/main/scala/org/scalafmt/internal/RegexCompat.scala @@ -0,0 +1,98 @@ +package org.scalafmt.internal + +import java.util.regex.Pattern +import scala.util.matching.Regex + +object RegexCompat { + + @inline + val trailingSpace = + Pattern.compile("\\h++$", Pattern.MULTILINE) + + @inline + val srcLine = + Pattern.compile("^/\\/\\/*+\\h*+(.*?)\\h*+$") + + @inline + val slcDelim = Pattern.compile("\\h++") + + @inline + val mlcHeader = + Pattern.compile("^/\\*\\h*+(?:\n\\h*+[*]*+\\h*+)?") + + @inline + val mlcLineDelim = + Pattern.compile("\\h*+\n\\h*+[*]*+\\h*+") + + @inline + val mlcParagraphEnd = Pattern.compile("[.:!?=]$") + + @inline + val mlcParagraphBeg = Pattern.compile("^(?:[-*@=]|\\d++[.:])") + + @inline + val leadingAsteriskSpace = + Pattern.compile("(?<=\n)\\h*+(?=[*][^*])") + + @inline + val docstringLine = + Pattern.compile( + "^(?:\\h*+\\*)?(\\h*+)(.*?)\\h*+$", + Pattern.MULTILINE + ) + + @inline + val emptyLines = "\\h*+(\n\\h*+\\*?\\h*+)*" + + @inline + val emptyDocstring = Pattern.compile(s"^/\\*\\*$emptyLines\\*/$$") + + @inline + val onelineDocstring = { + val oneline = "[^*\n\\h](?:[^\n]*[^\n\\h])?" + Pattern.compile(s"^/\\*\\*$emptyLines($oneline)$emptyLines\\*/$$") + } + + @inline + val docstringLeadingSpace = Pattern.compile("^\\h++") + + @inline + def compileStripMarginPattern(pipe: Char) = + Pattern.compile(s"(?<=\n)\\h*+(?=\\${pipe})") + + // see: https://ammonite.io/#Save/LoadSession + @inline + private val ammonitePattern: Regex = "(?:\\s*\\n@(?=\\s))+".r + + @inline + val stripMarginPattern = + Pattern.compile("\n(\\h*+\\|)?([^\n]*+)") + + @inline + def replaceAllStripMargin( + stripMarginPattern: Pattern, + text: String, + spaces: String, + pipe: Char + ): String = + stripMarginPattern.matcher(text).replaceAll(spaces) + + @inline + def replaceAllLeadingAsterisk( + leadingAsteriskSpace: Pattern, + trimmed: String, + spaces: String + ): String = + leadingAsteriskSpace.matcher(trimmed).replaceAll(spaces) + + @inline + def splitByAmmonitePattern(code: String): Array[String] = + ammonitePattern.split(code) + + @inline + def splitByBeforeTextMatching( + baseText: String, + beforeText: String + ): Array[String] = + baseText.split(s"(?=${beforeText})") +} diff --git a/scalafmt-core/native/src/main/scala-2.11/org/scalafmt/CompatCollections.scala b/scalafmt-core/native/src/main/scala-2.11/org/scalafmt/CompatCollections.scala new file mode 100644 index 0000000000..f92299ab9b --- /dev/null +++ b/scalafmt-core/native/src/main/scala-2.11/org/scalafmt/CompatCollections.scala @@ -0,0 +1,22 @@ +package org.scalafmt + +import java.util.HashMap + +object CompatCollections { + val JavaConverters = scala.jdk.CollectionConverters + + // Scala native doesn't support concurrency + object CompatParConverters { + implicit class XIterable[T](val col: Iterable[T]) extends AnyVal { + def compatPar = col + } + } + + class ConcurrentMap[K, V] extends HashMap[K, V] { + def contains(key: K): Boolean = { + super.containsKey(key) + } + } + def concurrentMap[K, V] = new ConcurrentMap[K, V] + +} diff --git a/scalafmt-core/native/src/main/scala-2.12/org/scalafmt/CompatCollections.scala b/scalafmt-core/native/src/main/scala-2.12/org/scalafmt/CompatCollections.scala new file mode 100644 index 0000000000..f92299ab9b --- /dev/null +++ b/scalafmt-core/native/src/main/scala-2.12/org/scalafmt/CompatCollections.scala @@ -0,0 +1,22 @@ +package org.scalafmt + +import java.util.HashMap + +object CompatCollections { + val JavaConverters = scala.jdk.CollectionConverters + + // Scala native doesn't support concurrency + object CompatParConverters { + implicit class XIterable[T](val col: Iterable[T]) extends AnyVal { + def compatPar = col + } + } + + class ConcurrentMap[K, V] extends HashMap[K, V] { + def contains(key: K): Boolean = { + super.containsKey(key) + } + } + def concurrentMap[K, V] = new ConcurrentMap[K, V] + +} diff --git a/scalafmt-core/native/src/main/scala-2.13/org/scalafmt/CompatCollections.scala b/scalafmt-core/native/src/main/scala-2.13/org/scalafmt/CompatCollections.scala new file mode 100644 index 0000000000..f92299ab9b --- /dev/null +++ b/scalafmt-core/native/src/main/scala-2.13/org/scalafmt/CompatCollections.scala @@ -0,0 +1,22 @@ +package org.scalafmt + +import java.util.HashMap + +object CompatCollections { + val JavaConverters = scala.jdk.CollectionConverters + + // Scala native doesn't support concurrency + object CompatParConverters { + implicit class XIterable[T](val col: Iterable[T]) extends AnyVal { + def compatPar = col + } + } + + class ConcurrentMap[K, V] extends HashMap[K, V] { + def contains(key: K): Boolean = { + super.containsKey(key) + } + } + def concurrentMap[K, V] = new ConcurrentMap[K, V] + +} diff --git a/scalafmt-core/native/src/main/scala/org/scalafmt/config/PlatformConfig.scala b/scalafmt-core/native/src/main/scala/org/scalafmt/config/PlatformConfig.scala new file mode 100644 index 0000000000..041f975ac3 --- /dev/null +++ b/scalafmt-core/native/src/main/scala/org/scalafmt/config/PlatformConfig.scala @@ -0,0 +1,7 @@ +package org.scalafmt.config + +object PlatformConfig { + def isNative = true + implicit val parser = + metaconfig.sconfig.sConfigMetaconfigParser +} diff --git a/scalafmt-core/native/src/main/scala/org/scalafmt/internal/PlatformCompat.scala b/scalafmt-core/native/src/main/scala/org/scalafmt/internal/PlatformCompat.scala new file mode 100644 index 0000000000..94586ad6c0 --- /dev/null +++ b/scalafmt-core/native/src/main/scala/org/scalafmt/internal/PlatformCompat.scala @@ -0,0 +1,15 @@ +package org.scalafmt.internal + +import metaconfig._ +import scala.meta.internal.io.FileIO +import scala.meta.io.AbsolutePath + +import java.nio.charset.StandardCharsets +import java.io.File + +object PlatformCompat { + @inline def metaconfigInputFromFile(input: File) = + Input.String( + FileIO.slurp(AbsolutePath(input.toPath()), StandardCharsets.UTF_8) + ) +} diff --git a/scalafmt-core/native/src/main/scala/org/scalafmt/internal/RegexCompat.scala b/scalafmt-core/native/src/main/scala/org/scalafmt/internal/RegexCompat.scala new file mode 100644 index 0000000000..34ea4a91c0 --- /dev/null +++ b/scalafmt-core/native/src/main/scala/org/scalafmt/internal/RegexCompat.scala @@ -0,0 +1,253 @@ +package org.scalafmt.internal + +import scala.util.matching.Regex +import java.util.regex.Pattern + +/* Before text matching (?=re), after text matching (?<=re) + * and more are incompatible in Scala Native, so custom functions + * have to be used. + * Scala Native uses an implementation of: + * https://github.com/google/re2/wiki/Syntax + */ + +object RegexCompat { + + /* Replaces '\\h', which is incompatible in Scala Native. + * Does not check the correctness of the input regex string. + */ + private def fixHorizontalSpaceInRegex(reg: String) = { + + @inline + val replacingInsideClass = + "\t \u00A0\u1680\u180E\u2000-\u200A\u202F\u205F\u3000" + + @inline + val replacingOutsideClass = + s"[$replacingInsideClass]" + + val sb = new StringBuilder() + var isInClass = false + var isEscaped = false + + for (char <- reg) { + char match { + case '\\' if !isEscaped => + isEscaped = true + case 'h' if isEscaped => + sb.append( + if (isInClass) replacingInsideClass + else replacingOutsideClass + ) + isEscaped = false + case '[' if !isEscaped => + sb.append('[') + isInClass = true + case ']' if !isEscaped => + sb.append(']') + isInClass = false + case other => + if (isEscaped) { + isEscaped = false + sb.append('\\') + } + sb.append(other) + } + } + sb.toString() + } + + @inline + val trailingSpace = + Pattern.compile( + RegexCompat.fixHorizontalSpaceInRegex("\\h+$"), + Pattern.MULTILINE + ) + + @inline + val srcLine = + Pattern.compile( + RegexCompat.fixHorizontalSpaceInRegex("^/\\/\\/*\\h*(.*?)\\h*$") + ) + + @inline + val slcDelim = + Pattern.compile(RegexCompat.fixHorizontalSpaceInRegex("\\h+")) + + @inline + val mlcHeader = + Pattern.compile( + RegexCompat.fixHorizontalSpaceInRegex("^/\\*\\h*(?:\n\\h*[*]*\\h*)?") + ) + + @inline + val mlcLineDelim = + Pattern.compile( + RegexCompat.fixHorizontalSpaceInRegex("\\h*\n\\h*[*]*\\h*") + ) + + @inline + val mlcParagraphEnd = Pattern.compile("[.:!?=]$") + + @inline + val mlcParagraphBeg = Pattern.compile("^(?:[-*@=]|\\d+[.:])") + + @inline + val leadingAsteriskSpace = + Pattern.compile( + RegexCompat.fixHorizontalSpaceInRegex("\n\\h*[*][^*]"), + Pattern.MULTILINE + ) + + @inline + val docstringLine = + Pattern.compile( + RegexCompat.fixHorizontalSpaceInRegex("^(?:\\h*\\*)?(\\h*)(.*?)\\h*$"), + Pattern.MULTILINE + ) + + @inline + val emptyLines = + RegexCompat.fixHorizontalSpaceInRegex("\\h*(\n\\h*\\*?\\h*)*") + + @inline + val emptyDocstring = Pattern.compile(s"^/\\*\\*$emptyLines\\*/$$") + + @inline + val onelineDocstring = { + val oneline = + RegexCompat.fixHorizontalSpaceInRegex("[^*\n\\h](?:[^\n]*[^\n\\h])?") + Pattern.compile( + RegexCompat.fixHorizontalSpaceInRegex( + s"^/\\*\\*$emptyLines($oneline)$emptyLines\\*/$$" + ) + ) + } + + @inline + val docstringLeadingSpace = + Pattern.compile(RegexCompat.fixHorizontalSpaceInRegex("^\\h+")) + + @inline + def compileStripMarginPattern(pipe: Char) = + Pattern.compile( + RegexCompat.fixHorizontalSpaceInRegex(s"\n+\\h*?\\${pipe}"), + Pattern.MULTILINE + ) + + // see: https://ammonite.io/#Save/LoadSession + @inline + val ammonitePattern: Regex = + "(?:\\s*\\n@)".r + + @inline + val stripMarginPattern = + Pattern.compile( + RegexCompat.fixHorizontalSpaceInRegex("\n(\\h*\\|)?([^\n]*)"), + Pattern.MULTILINE + ) + + // startAfterPattern and endBeforePattern should be unique in basePattern + // basePattern = startAfterPattern + matched pattern + endBeforePattern + private def replaceAll( + basePattern: Pattern, + startAfterPattern: Pattern, + endBeforePattern: Pattern, + baseText: String, + replacingText: String + ): String = { + val sb = new StringBuilder() + val matcher = basePattern.matcher(baseText) + var currPosition = 0 + while (matcher.find()) { + val start = matcher.start() + val end = matcher.end() + + sb.append(baseText.substring(currPosition, start)) + + val subtext = baseText.substring(start, end) + val startAfterMatcher = startAfterPattern.matcher(subtext) + val endBeforeMatcher = endBeforePattern.matcher(subtext) + + startAfterMatcher.find() + endBeforeMatcher.find() + + sb.append(startAfterMatcher.group()) + sb.append(replacingText) + sb.append(endBeforeMatcher.group()) + + currPosition = end + } + + sb.append(baseText.substring(currPosition)) + sb.toString() + } + + def replaceAllStripMargin( + stripMarginPattern: Pattern, + text: String, + spaces: String, + pipe: Char + ): String = { + val startAfter = Pattern.compile("\n+") + val endBefore = Pattern.compile(s"\\${pipe}") + + replaceAll(stripMarginPattern, startAfter, endBefore, text, spaces) + } + + def replaceAllLeadingAsterisk( + leadingAsteriskSpace: Pattern, + trimmed: String, + spaces: String + ): String = { + val startAfter = Pattern.compile("\n") + val endBefore = Pattern.compile("([*][^*])") + + replaceAll(leadingAsteriskSpace, startAfter, endBefore, trimmed, spaces) + } + + def splitByAmmonitePattern(code: String): Array[String] = { + val whitespacePattern = Pattern.compile("\\s") + val actualMatches = ammonitePattern + .findAllMatchIn(code) + .filter(regexMatch => + regexMatch.end < code.length && whitespacePattern + .matcher(Character.toString(code.charAt(regexMatch.end))) + .find() + ) + .toArray + + val res = new scala.collection.mutable.ArrayBuffer[String]() + var currPosition = 0 + for (actualMatch <- actualMatches) { + if (currPosition != actualMatch.start) + res.append(code.substring(currPosition, actualMatch.start)) + currPosition = actualMatch.end + } + res.append(code.substring(currPosition)) + res.toArray + } + + // replaces baseText.split("(?={beforeText})") + def splitByBeforeTextMatching( + baseText: String, + beforeText: String + ): Array[String] = { + val beforeTextPattern = Pattern.compile(beforeText) + val matcher = beforeTextPattern + .matcher(baseText) + + val res = new scala.collection.mutable.ArrayBuffer[String]() + var currPosition = 0 + while (matcher.find()) { + val start = matcher.start() + val end = matcher.end() + if (start != 0) + res.append(baseText.substring(currPosition, start)) + + currPosition = start + } + res.append(baseText.substring(currPosition, baseText.size)) + + res.toArray + } +} diff --git a/scalafmt-core/native/src/main/scala/org/scalafmt/internal/package.scala b/scalafmt-core/native/src/main/scala/org/scalafmt/internal/package.scala new file mode 100644 index 0000000000..97e0bcf34f --- /dev/null +++ b/scalafmt-core/native/src/main/scala/org/scalafmt/internal/package.scala @@ -0,0 +1,5 @@ +package org.scalafmt + +package object internal { + type PriorityQueue[T] = scala.collection.mutable.PriorityQueue[T] +} 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 6007a45441..34d5a2357e 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/Scalafmt.scala @@ -10,7 +10,6 @@ import scala.meta.parsers.ParseException import scala.util.Failure import scala.util.Success import scala.util.Try -import scala.util.matching.Regex import org.scalafmt.config.Config import org.scalafmt.Error.PreciseIncomplete @@ -22,6 +21,7 @@ import org.scalafmt.internal.FormatOps import org.scalafmt.internal.FormatWriter import org.scalafmt.rewrite.Rewrite import org.scalafmt.util.{FileOps, MarkdownFile, MarkdownPart} +import org.scalafmt.internal.RegexCompat /** WARNING. This API is discouraged when integrating with Scalafmt from a build * tool or editor plugin. It is recommended to use the `scalafmt-dynamic` @@ -99,9 +99,6 @@ object Scalafmt { iter(Seq.empty) } - // see: https://ammonite.io/#Save/LoadSession - private val ammonitePattern: Regex = "(?:\\s*\\n@(?=\\s))+".r - private def doFormat( code: String, style: ScalafmtConfig, @@ -110,7 +107,7 @@ object Scalafmt { ): Try[String] = if (FileOps.isAmmonite(file)) { // XXX: we won't support ranges as we don't keep track of lines - val chunks = ammonitePattern.split(code) + val chunks = RegexCompat.splitByAmmonitePattern(code) if (chunks.length <= 1) doFormatOne(code, style, file, range) else flatMapAll(chunks.iterator)(doFormatOne(_, style, file)) diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Config.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Config.scala index 7be310472e..767c862194 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Config.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/Config.scala @@ -5,6 +5,7 @@ import java.io.File import metaconfig._ import org.scalafmt.config.PlatformConfig._ import org.scalafmt.Versions.{stable => stableVersion} +import org.scalafmt.internal.PlatformCompat // NOTE: these methods are intended for internal usage and are subject to // binary and source breaking changes between any release. For a stable API @@ -15,10 +16,13 @@ object Config { def hoconStringToConf(input: String, path: Option[String]): Configured[Conf] = Input.String(input).parse(path) - def hoconFileToConf(input: File, path: Option[String]): Configured[Conf] = + def hoconFileToConf(input: File, path: Option[String]): Configured[Conf] = { Configured - .fromExceptionThrowing(Input.File(input)) + .fromExceptionThrowing( + PlatformCompat.metaconfigInputFromFile(input) + ) .andThen(_.parse(path)) + } def fromHoconString( string: String, diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ProjectFiles.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ProjectFiles.scala index 94c64f68f6..1e33aa6b54 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ProjectFiles.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ProjectFiles.scala @@ -40,7 +40,7 @@ object ProjectFiles { val defaultIncludePaths = Seq("glob:**.scala", "glob:**.sbt", "glob:**.sc") - private sealed abstract class PathMatcher { + sealed abstract class PathMatcher { def matches(path: file.Path): Boolean } @@ -59,16 +59,99 @@ object ProjectFiles { ) } + def nio(glob: String) = { + if (PlatformConfig.isNative) + new Regex(createRegexFromGlob(glob)) + else + new Nio(glob) + } + private def create(seq: Seq[String], f: String => PathMatcher) = seq.map(_.asFilename).distinct.map(f) - private def nio(seq: Seq[String]) = create(seq, new Nio(_)) + private def nio(seq: Seq[String]) = { + if (PlatformConfig.isNative) + create(seq, p => new Regex(createRegexFromGlob(p))) + else + create(seq, new Nio(_)) + } private def regex(seq: Seq[String]) = create(seq, new Regex(_)) - private final class Nio(pattern: String) extends PathMatcher { + // Copy/pasted from https://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns + def createRegexFromGlob(glob: String): String = { + var line = glob.stripPrefix("glob:") + line = line.trim() + var strLen = line.length() + val sb = new StringBuilder + // Remove beginning and ending * globs because they're useless + // if (line.startsWith("*")) { + // line = line.substring(1); + // strLen -= 1; + // } + // if (line.endsWith("*")) { + // line = line.substring(0, strLen-1); + // strLen -= 1; + // } + var escaping = false + var inCurlies = 0 + for (currentChar <- line.toCharArray()) { + currentChar match { + case '*' => + if (escaping) sb.append("\\*") + else sb.append(".*") + escaping = false + case '?' => + if (escaping) + sb.append("\\?") + else + sb.append('.') + escaping = false + case '.' | '(' | ')' | '+' | '|' | '^' | '$' | '@' | '%' => + sb.append('\\') + sb.append(currentChar) + escaping = false + case '\\' => + if (escaping) { + sb.append("\\\\") + escaping = false + } else escaping = true + case '{' => + if (escaping) { + sb.append("\\{") + } else { + sb.append('(') + inCurlies += 1 + } + escaping = false + case '}' => + if (inCurlies > 0 && !escaping) { + sb.append(')') + inCurlies -= 1 + } else if (escaping) + sb.append("\\}") + else + sb.append("}") + escaping = false + case ',' => + if (inCurlies > 0 && !escaping) { + sb.append('|') + } else if (escaping) + sb.append("\\,") + else + sb.append(",") + case _ => + escaping = false + sb.append(currentChar) + } + } + sb.append('$') + return sb.toString() + } + + final class Nio(pattern: String) extends PathMatcher { private val matcher = fs.getPathMatcher(pattern) def matches(path: file.Path): Boolean = matcher.matches(path) } - private final class Regex(regex: String) extends PathMatcher { + final class Regex(regex: String) extends PathMatcher { private val pattern = java.util.regex.Pattern.compile(regex) def matches(path: file.Path): Boolean = pattern.matcher(path.toString).find() 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 5a72bff781..2a4d1e44a4 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 @@ -157,8 +157,11 @@ case class ScalafmtConfig( private lazy val expandedFileOverride = Try { val fs = file.FileSystems.getDefault fileOverride.values.map { case (pattern, conf) => + val regex = "regex:" + ProjectFiles.FileMatcher.createRegexFromGlob( + pattern.asFilename + ) val style = ScalafmtConfig.decoder.read(Some(this), conf).get - fs.getPathMatcher(pattern.asFilename) -> style + fs.getPathMatcher(regex) -> style } } def getConfigFor(filename: String): ScalafmtConfig = { diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala index 033bfc8222..44940c13e3 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/FormatWriter.scala @@ -492,7 +492,12 @@ class FormatWriter(formatOps: FormatOps) { } tupleOpt.fold(text) { case (pipe, indent) => val spaces = getIndentation(indent) - getStripMarginPattern(pipe).matcher(text).replaceAll(spaces) + RegexCompat.replaceAllStripMargin( + getStripMarginPattern(pipe), + text, + spaces, + pipe + ) } } @@ -681,7 +686,13 @@ class FormatWriter(formatOps: FormatOps) { sb.append(" */") } else { val trimmed = removeTrailingWhiteSpace(text) - sb.append(leadingAsteriskSpace.matcher(trimmed).replaceAll(spaces)) + sb.append( + RegexCompat.replaceAllLeadingAsterisk( + leadingAsteriskSpace, + trimmed, + spaces + ) + ) } } @@ -1650,7 +1661,7 @@ object FormatWriter { if (extra < extraNewlines.length) extraNewlines(extra) else "\n" * (1 + extra) - private val trailingSpace = Pattern.compile("\\h++$", Pattern.MULTILINE) + private val trailingSpace = RegexCompat.trailingSpace private def removeTrailingWhiteSpace(str: String): String = { trailingSpace.matcher(str).replaceAll("") } @@ -1659,33 +1670,25 @@ object FormatWriter { regex.splitAsStream(value).iterator().asScala // "slc" stands for single-line comment - private val slcDelim = Pattern.compile("\\h++") + private val slcDelim = RegexCompat.slcDelim // "mlc" stands for multi-line comment - private val mlcHeader = Pattern.compile("^/\\*\\h*+(?:\n\\h*+[*]*+\\h*+)?") - private val mlcLineDelim = Pattern.compile("\\h*+\n\\h*+[*]*+\\h*+") - private val mlcParagraphEnd = Pattern.compile("[.:!?=]$") - private val mlcParagraphBeg = Pattern.compile("^(?:[-*@=]|\\d++[.:])") - - private val leadingAsteriskSpace = Pattern.compile("(?<=\n)\\h*+(?=[*][^*])") - private val docstringLine = - Pattern.compile("^(?:\\h*+\\*)?(\\h*+)(.*?)\\h*+$", Pattern.MULTILINE) - private val emptyLines = "\\h*+(\n\\h*+\\*?\\h*+)*" - private val emptyDocstring = Pattern.compile(s"^/\\*\\*$emptyLines\\*/$$") - private val onelineDocstring = { - val oneline = "[^*\n\\h](?:[^\n]*[^\n\\h])?" - Pattern.compile(s"^/\\*\\*$emptyLines($oneline)$emptyLines\\*/$$") - } - private val docstringLeadingSpace = Pattern.compile("^\\h++") - @inline - private def getStripMarginPattern(pipe: Char) = - if (pipe == '|') leadingPipeSpace else compileStripMarginPattern(pipe) + private val mlcHeader = RegexCompat.mlcHeader + private val mlcLineDelim = RegexCompat.mlcLineDelim + private val mlcParagraphEnd = RegexCompat.mlcParagraphEnd + private val mlcParagraphBeg = RegexCompat.mlcParagraphBeg + + private val leadingAsteriskSpace = RegexCompat.leadingAsteriskSpace + private val docstringLine = RegexCompat.docstringLine + private val onelineDocstring = RegexCompat.onelineDocstring + private val docstringLeadingSpace = RegexCompat.docstringLeadingSpace @inline - private def compileStripMarginPattern(pipe: Char) = - Pattern.compile(s"(?<=\n)\\h*+(?=\\${pipe})") + private def getStripMarginPattern(pipe: Char) = + if (pipe == '|') leadingPipeSpace + else RegexCompat.compileStripMarginPattern(pipe) - private val leadingPipeSpace = compileStripMarginPattern('|') + private val leadingPipeSpace = RegexCompat.compileStripMarginPattern('|') /** [[https://dotty.epfl.ch/docs/reference/other-new-features/indentation.html#the-end-marker]] */ @@ -1745,6 +1748,6 @@ object FormatWriter { ): Int = getLineDiff(toks(beg.meta.idx), toks(end.meta.idx)) def isEmptyDocstring(text: String): Boolean = - emptyDocstring.matcher(text).matches() + RegexCompat.emptyDocstring.matcher(text).matches() } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala index 404a958f00..421219fe0e 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/internal/State.scala @@ -1,7 +1,6 @@ package org.scalafmt.internal import java.util.regex.Matcher -import java.util.regex.Pattern import scala.annotation.tailrec import scala.meta.tokens.Token @@ -315,10 +314,9 @@ object State { } } - private val stripMarginPattern = - Pattern.compile("\n(\\h*+\\|)?([^\n]*+)") + private val stripMarginPattern = RegexCompat.stripMarginPattern - private val slcLine = Pattern.compile("^/\\/\\/*+\\h*+(.*?)\\h*+$") + private val slcLine = RegexCompat.srcLine def getColumns( ft: FormatToken, diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/AbsoluteFile.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/AbsoluteFile.scala index f9b47a7090..ef8ad0184c 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/AbsoluteFile.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/AbsoluteFile.scala @@ -1,6 +1,7 @@ package org.scalafmt.util import java.io.File +import java.nio.file.Path /** Wrapper around java.io.File with an absolute path. */ sealed abstract case class AbsoluteFile(jfile: File) { @@ -22,10 +23,18 @@ object AbsoluteFile { def fromFile(file: File, workingDir: AbsoluteFile): AbsoluteFile = { new AbsoluteFile(FileOps.makeAbsolute(workingDir.jfile)(file)) {} } + def fromPath(path: String): Option[AbsoluteFile] = { val file = new File(path) if (file.isAbsolute) Some(new AbsoluteFile(file) {}) else None } + + def fromPath(path: Path): Option[AbsoluteFile] = { + val file = path.toFile + if (file.isAbsolute) Some(new AbsoluteFile(file) {}) + else None + } + def userDir = new AbsoluteFile(new File(System.getProperty("user.dir"))) {} } diff --git a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/GitOps.scala b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/GitOps.scala index 25d4314a2f..2ce74a07da 100644 --- a/scalafmt-core/shared/src/main/scala/org/scalafmt/util/GitOps.scala +++ b/scalafmt-core/shared/src/main/scala/org/scalafmt/util/GitOps.scala @@ -4,6 +4,8 @@ import scala.sys.process.ProcessLogger import scala.util.Try import scala.util.control.NonFatal import java.io.File +import java.io.BufferedReader +import java.io.InputStreamReader trait GitOps { def status: Seq[AbsoluteFile] @@ -20,10 +22,30 @@ class GitOpsImpl(private[util] val workingDirectory: AbsoluteFile) val lastError = new StringBuilder val swallowStderr = ProcessLogger(_ => (), err => lastError.append(err)) try { - sys.process - .Process(cmd, workingDirectory.jfile) - .!!(swallowStderr) - .trim + val processBuilder = new ProcessBuilder() + processBuilder.directory(workingDirectory.jfile) + val out = new StringBuilder + processBuilder.command(cmd: _*); + var process = processBuilder.start() + + val outputReader = + new BufferedReader(new InputStreamReader(process.getInputStream())) + + val errorReader = + new BufferedReader(new InputStreamReader(process.getErrorStream())) + + def pipe(src: BufferedReader, dst: StringBuilder): Unit = { + var line = outputReader.readLine() + while (line != null) { + out.append(line + "\n") + line = outputReader.readLine() + } + } + + pipe(outputReader, out) + pipe(errorReader, lastError) + + out.toString() } catch { case NonFatal(e) => throw new IllegalStateException( diff --git a/scalafmt-dynamic/src/main/resources/META-INF/services/org.scalafmt.interfaces.Scalafmt b/scalafmt-dynamic/shared/src/main/resources/META-INF/services/org.scalafmt.interfaces.Scalafmt similarity index 100% rename from scalafmt-dynamic/src/main/resources/META-INF/services/org.scalafmt.interfaces.Scalafmt rename to scalafmt-dynamic/shared/src/main/resources/META-INF/services/org.scalafmt.interfaces.Scalafmt diff --git a/scalafmt-dynamic/src/main/scala-2.11/org/scalafmt/dynamic/package.scala b/scalafmt-dynamic/shared/src/main/scala-2.11/org/scalafmt/dynamic/package.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala-2.11/org/scalafmt/dynamic/package.scala rename to scalafmt-dynamic/shared/src/main/scala-2.11/org/scalafmt/dynamic/package.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ConsoleScalafmtReporter.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ConsoleScalafmtReporter.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ConsoleScalafmtReporter.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ConsoleScalafmtReporter.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamic.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamic.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamic.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamic.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicDownloader.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicDownloader.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicDownloader.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicDownloader.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicError.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicError.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicError.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtDynamicError.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflect.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtReflect.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflect.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtReflect.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflectConfig.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtReflectConfig.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtReflectConfig.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtReflectConfig.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtVersion.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtVersion.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/ScalafmtVersion.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/ScalafmtVersion.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/PositionExceptionImpl.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/PositionExceptionImpl.scala similarity index 65% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/PositionExceptionImpl.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/PositionExceptionImpl.scala index ff4a31c59b..7319dc26b3 100644 --- a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/PositionExceptionImpl.scala +++ b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/PositionExceptionImpl.scala @@ -13,8 +13,8 @@ case class PositionExceptionImpl( ) extends PositionException(longMessage, cause) { def start: Int = pos.start def end: Int = pos.end - override def startLine: Int = pos.startLine - override def startCharacter: Int = pos.startCharacter - override def endLine: Int = pos.endLine - override def endCharacter: Int = pos.endCharacter + override def startLine(): Int = pos.startLine + override def startCharacter(): Int = pos.startCharacter + override def endLine(): Int = pos.endLine + override def endCharacter(): Int = pos.endCharacter } diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/RangePosition.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/RangePosition.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/RangePosition.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/RangePosition.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/ReflectionException.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/ReflectionException.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/ReflectionException.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/ReflectionException.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/ScalafmtException.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/ScalafmtException.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/ScalafmtException.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/ScalafmtException.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/VersionMismatch.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/VersionMismatch.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/exceptions/VersionMismatch.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/exceptions/VersionMismatch.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/utils/ReentrantCache.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/utils/ReentrantCache.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/utils/ReentrantCache.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/utils/ReentrantCache.scala diff --git a/scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/utils/ReflectUtils.scala b/scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/utils/ReflectUtils.scala similarity index 100% rename from scalafmt-dynamic/src/main/scala/org/scalafmt/dynamic/utils/ReflectUtils.scala rename to scalafmt-dynamic/shared/src/main/scala/org/scalafmt/dynamic/utils/ReflectUtils.scala diff --git a/scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/PositionException.java b/scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/PositionException.java similarity index 100% rename from scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/PositionException.java rename to scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/PositionException.java diff --git a/scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/Scalafmt.java b/scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/Scalafmt.java similarity index 100% rename from scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/Scalafmt.java rename to scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/Scalafmt.java diff --git a/scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/ScalafmtClassLoader.java b/scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/ScalafmtClassLoader.java similarity index 100% rename from scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/ScalafmtClassLoader.java rename to scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/ScalafmtClassLoader.java diff --git a/scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/ScalafmtReporter.java b/scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/ScalafmtReporter.java similarity index 100% rename from scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/ScalafmtReporter.java rename to scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/ScalafmtReporter.java diff --git a/scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/ScalafmtSession.java b/scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/ScalafmtSession.java similarity index 100% rename from scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/ScalafmtSession.java rename to scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/ScalafmtSession.java diff --git a/scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/ScalafmtSessionFactory.java b/scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/ScalafmtSessionFactory.java similarity index 100% rename from scalafmt-interfaces/src/main/java/org/scalafmt/interfaces/ScalafmtSessionFactory.java rename to scalafmt-interfaces/jvm/src/main/java/org/scalafmt/interfaces/ScalafmtSessionFactory.java diff --git a/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/PositionException.scala b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/PositionException.scala new file mode 100644 index 0000000000..d5812edb11 --- /dev/null +++ b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/PositionException.scala @@ -0,0 +1,39 @@ +package org.scalafmt.interfaces + +import java.nio.file.Path + +/** An exception that happened at a position in a source file such as a parse + * error. + */ +abstract class PositionException(message: String, cause: Throwable) + extends Exception(message, cause) { + + override def fillInStackTrace(): Throwable = synchronized { + return this; + } + + /** @return + * The file where the error occurred. + */ + def file(): Path; + + /** @return + * The text contents of the file being formatted. + */ + def code(): String; + + /** @return + * The fully formatted error message including line content and caret. + */ + def longMessage(): String + + /** @return + * Only the error message itself without line content and caret. + */ + def shortMessage(): String + + def startLine(): Int + def startCharacter(): Int + def endLine(): Int + def endCharacter(): Int +} diff --git a/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/Scalafmt.scala b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/Scalafmt.scala new file mode 100644 index 0000000000..2f79131b5a --- /dev/null +++ b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/Scalafmt.scala @@ -0,0 +1,104 @@ +package org.scalafmt.interfaces + +import java.nio.file.Path +import java.util +import java.util.NoSuchElementException +import java.util.ServiceLoader + +/** A stable public interface to run Scalafmt. + * + * This interface is designed for integrations such as editor plugins and build + * tools. An implementation of this interface is available in the module + * 'scalafmt-dynamic'. + * + * It is recommended to use this interface over the org.scalafmt.Scalafmt + * object for several reasons: + * + * - this API is guaranteed to be binary compatible with all future versions + * of Scalafmt. + * - it downloads the Scalafmt version matching the 'version' setting in + * .scalafmt.conf. All versions down to v1.2.0 are supported. + * - it respects the 'project.{excludeFilters,includeFilters}' setting in + * .scalafmt.conf. + * - it uses the correct parser for `*.sbt` and `*.sc` files. + * - it automatically caches parsing of configuration files avoiding + * redundant work when possible. + * - it has two external library dependencies (com.geirsson:coursier-small + * and com.typesafe:config), which is a smaller dependency footprint + * compared to scalafmt-core. + */ + +trait Scalafmt { + + /** Format a single file with the given .scalafmt.conf configuration. + * + * @param config + * the absolute path to the configuration file. This file must exist or an + * exception will be thrown. + * @param file + * relative or absolute path to the file being formatted. Used only for the + * path name, the file does not have to exist on disk. + * @param code + * the text contents to format. + * @return + * the formatted contents if formatting was successful, otherwise the + * original text contents. + */ + def format(config: Path, file: Path, code: String): String + + /** Whether to respect the 'project.{excludeFilters,includeFilters}' setting + * in .scalafmt.conf. + * + * @param respectExcludeFilters + * If true (default), returns the original file contents when formatting a + * file that does not matches the project settings in .scalafmt.conf. If + * false, formats every file that is passed to the {@link #format(Path, + * Path, String)} method regardless of .scalafmt.conf settings. + */ + def withRespectProjectFilters(respectExcludeFilters: Boolean): Scalafmt + + /** Whether to respect the 'version' setting in .scalafmt.conf. + * + * @param respectVersion + * If true (default), refuses to format files when the 'version' setting is + * missing in .scalafmt.conf and users must update .scalafmt.conf to format + * files. If false, falls back to the default version provided via {@link + * #withDefaultVersion(String)}. + */ + def withRespectVersion(respectVersion: Boolean): Scalafmt + + /** The fallback Scalafmt version to use when there is no 'version' setting in + * .scalafmt.conf. + * + * The default version is ignored when {@link #withRespectVersion(boolean)} + * is true. + */ + def withDefaultVersion(defaultVersion: String): Scalafmt + + /** Use this reporter to report errors and information messages. + */ + def withReporter(reporter: ScalafmtReporter): Scalafmt + + /** Use this repositories to resolve dependencies. + */ + def withMavenRepositories(repositories: String*): Scalafmt + + /** Clear internal caches such as classloaded Scalafmt instances. + */ + def clear(): Unit +} + +object Scalafmt { + + /** Classload a new instance of this Scalafmt instance. + * + * @param loader + * the classloader containing the 'scalafmt-dynamic' module. + * + * @throws NoSuchElementException + * if the classloader does not have the 'scalafmt-dynamic' module. + */ + def create(loader: ClassLoader): Scalafmt = throw new Exception( + "Can't use different version for native CLI" + ) +} diff --git a/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtClassLoader.scala b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtClassLoader.scala new file mode 100644 index 0000000000..2b796f3c0c --- /dev/null +++ b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtClassLoader.scala @@ -0,0 +1,17 @@ +package org.scalafmt.interfaces + +/** A classloader that shares only scalafmt-interfaces classes from the parent + * classloader. + * + * This classloader is intended to be used as a parent when class-loading + * scalafmt-dynamic. By using this classloader as a parent, it's possible to + * cast runtime instances from the scalafmt-dynamic classloader into + * `org.scalafmt.interfaces.Scalafmt` from this classloader. + */ +class ScalafmtClassLoader(var parent: ClassLoader) extends ClassLoader(null) { + @throws[ClassNotFoundException] + override protected def findClass(name: String) = if ( + name.startsWith("org.scalafmt.interfaces") + ) parent.loadClass(name) + else super.findClass(name) +} diff --git a/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtReporter.scala b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtReporter.scala new file mode 100644 index 0000000000..f36eb444f3 --- /dev/null +++ b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtReporter.scala @@ -0,0 +1,78 @@ +package org.scalafmt.interfaces + +import java.io.OutputStreamWriter +import java.io.PrintWriter +import java.nio.file.Path + +/** A reporter to handle error and information messages from Scalafmt. + */ +trait ScalafmtReporter { + + /** An error occurred while trying to process this file. + * + * @param file + * can be either a Scala source file or .scalafmt.conf. + * @param message + * the error message. + */ + def error(file: Path, message: String): Unit + + /** An exception occurred while trying to process this file. + * + * @param file + * can be either a Scala source file or .scalafmt.conf. + * @param e + * the exception that occurred, has type {@link PositionException} when the + * error appeared as a position. + */ + def error(file: Path, e: Throwable): Unit + + /** An exception occurred while trying to process this file. + * + * @param file + * can be either a Scala source file or .scalafmt.conf. + * @param message + * additional error message + * @param e + * the exception that occurred, has type {@link PositionException} when the + * error appeared as a position. + */ + def error(file: Path, message: String, e: Throwable): Unit = + error(file, new RuntimeException(message, e)) + + /** This file was not formatted because it's excluded by project settings from + * .scalafmt.conf. + * + * @param file + * the file path that was not formatted. + */ + def excluded(file: Path): Unit + + /** This .scalafmt.conf file is missing the 'version' setting. + * + * @param config + * the .scalafmt.conf file. + * @param defaultVersion + * the configured default Scalafmt version. + */ + def missingVersion(config: Path, defaultVersion: String): Unit = { + val message = String.format( + "missing setting 'version'. To fix this problem, add the following line to .scalafmt.conf: 'version=%s'.", + defaultVersion + ) + error(config, message) + } + + /** The .scalafmt.conf file was parsed with the given Scalafmt version. + */ + def parsedConfig(config: Path, scalafmtVersion: String): Unit + + /** Use {@link #downloadOutputStreamWriter} instead. + */ + @deprecated def downloadWriter(): PrintWriter + + /** Use this writer for printing progress while downloading new Scalafmt + * versions. + */ + def downloadOutputStreamWriter(): OutputStreamWriter +} diff --git a/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtSession.scala b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtSession.scala new file mode 100644 index 0000000000..b0a8ca03a4 --- /dev/null +++ b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtSession.scala @@ -0,0 +1,26 @@ +package org.scalafmt.interfaces + +import java.nio.file.Path + +/** A session based on a fixed configuration. + */ +trait ScalafmtSession { + + /** Format a single file with the given configuration. + * + * @param file + * relative or absolute path to the file being formatted. Used only for the + * path name, the file does not have to exist on disk. + * @param code + * the text contents to format. + * @return + * the formatted contents if formatting was successful, otherwise the + * original text contents. + */ + def format(file: Path, code: String): String + + /** Whether the path matches the 'project.{excludeFilters,includeFilters}' + * setting. + */ + def matchesProjectFilters(file: Path): Boolean +} diff --git a/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtSessionFactory.scala b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtSessionFactory.scala new file mode 100644 index 0000000000..327f35c805 --- /dev/null +++ b/scalafmt-interfaces/native/src/main/scala/org/scalafmt/interfaces/ScalafmtSessionFactory.scala @@ -0,0 +1,11 @@ +package org.scalafmt.interfaces + +import java.nio.file.Path + +trait ScalafmtSessionFactory { + + /** Create a ScalafmtSession to format a batch of files using fixed + * configuration. + */ + def createSession(config: Path): ScalafmtSession +} diff --git a/scalafmt-tests/src/main/scala/org/scalafmt/util/ScalaFile.scala b/scalafmt-tests/jvm/src/main/scala/org/scalafmt/util/ScalaFile.scala similarity index 100% rename from scalafmt-tests/src/main/scala/org/scalafmt/util/ScalaFile.scala rename to scalafmt-tests/jvm/src/main/scala/org/scalafmt/util/ScalaFile.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtProps.scala b/scalafmt-tests/jvm/src/test/scala/org/scalafmt/ScalafmtProps.scala similarity index 98% rename from scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtProps.scala rename to scalafmt-tests/jvm/src/test/scala/org/scalafmt/ScalafmtProps.scala index 57fd11748d..cbdb1d8975 100644 --- a/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtProps.scala +++ b/scalafmt-tests/jvm/src/test/scala/org/scalafmt/ScalafmtProps.scala @@ -4,7 +4,7 @@ import scala.collection.mutable import scala.meta._ import scala.meta.testkit._ -import org.scalafmt.CompatCollections.ParConverters._ +import org.scalafmt.CompatCollections.CompatParConverters._ import org.scalafmt.config.ScalafmtConfig import org.scalafmt.util.AbsoluteFile import org.scalafmt.util.FileOps @@ -28,7 +28,7 @@ class ScalafmtProps extends FunSuite with FormatAssertions { ) .take(count) .toBuffer - .par + .compatPar SyntaxAnalysis.run[Observation[Bug]](corpus) { file => val code = file.read try { diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/ExperimentResult.scala b/scalafmt-tests/jvm/src/test/scala/org/scalafmt/util/ExperimentResult.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/ExperimentResult.scala rename to scalafmt-tests/jvm/src/test/scala/org/scalafmt/util/ExperimentResult.scala diff --git a/scalafmt-tests/src/test/resources/Test.manual b/scalafmt-tests/shared/src/test/resources/Test.manual similarity index 100% rename from scalafmt-tests/src/test/resources/Test.manual rename to scalafmt-tests/shared/src/test/resources/Test.manual diff --git a/scalafmt-tests/src/test/resources/align/AlignByRightName.stat b/scalafmt-tests/shared/src/test/resources/align/AlignByRightName.stat similarity index 100% rename from scalafmt-tests/src/test/resources/align/AlignByRightName.stat rename to scalafmt-tests/shared/src/test/resources/align/AlignByRightName.stat diff --git a/scalafmt-tests/src/test/resources/align/ArrowEnumeratorGenerator.stat b/scalafmt-tests/shared/src/test/resources/align/ArrowEnumeratorGenerator.stat similarity index 100% rename from scalafmt-tests/src/test/resources/align/ArrowEnumeratorGenerator.stat rename to scalafmt-tests/shared/src/test/resources/align/ArrowEnumeratorGenerator.stat diff --git a/scalafmt-tests/src/test/resources/align/BeforeCommentWithinMethodChain.stat b/scalafmt-tests/shared/src/test/resources/align/BeforeCommentWithinMethodChain.stat similarity index 100% rename from scalafmt-tests/src/test/resources/align/BeforeCommentWithinMethodChain.stat rename to scalafmt-tests/shared/src/test/resources/align/BeforeCommentWithinMethodChain.stat diff --git a/scalafmt-tests/src/test/resources/align/DefaultWithAlign.stat b/scalafmt-tests/shared/src/test/resources/align/DefaultWithAlign.stat similarity index 100% rename from scalafmt-tests/src/test/resources/align/DefaultWithAlign.stat rename to scalafmt-tests/shared/src/test/resources/align/DefaultWithAlign.stat diff --git a/scalafmt-tests/src/test/resources/align/MixedTokens.stat b/scalafmt-tests/shared/src/test/resources/align/MixedTokens.stat similarity index 100% rename from scalafmt-tests/src/test/resources/align/MixedTokens.stat rename to scalafmt-tests/shared/src/test/resources/align/MixedTokens.stat diff --git a/scalafmt-tests/src/test/resources/align/NoSpace.stat b/scalafmt-tests/shared/src/test/resources/align/NoSpace.stat similarity index 100% rename from scalafmt-tests/src/test/resources/align/NoSpace.stat rename to scalafmt-tests/shared/src/test/resources/align/NoSpace.stat diff --git a/scalafmt-tests/src/test/resources/align/TrollAlignment.stat b/scalafmt-tests/shared/src/test/resources/align/TrollAlignment.stat similarity index 100% rename from scalafmt-tests/src/test/resources/align/TrollAlignment.stat rename to scalafmt-tests/shared/src/test/resources/align/TrollAlignment.stat diff --git a/scalafmt-tests/src/test/resources/align/addSbtPlugin.source b/scalafmt-tests/shared/src/test/resources/align/addSbtPlugin.source similarity index 100% rename from scalafmt-tests/src/test/resources/align/addSbtPlugin.source rename to scalafmt-tests/shared/src/test/resources/align/addSbtPlugin.source diff --git a/scalafmt-tests/src/test/resources/align/comment-wrapping.source b/scalafmt-tests/shared/src/test/resources/align/comment-wrapping.source similarity index 100% rename from scalafmt-tests/src/test/resources/align/comment-wrapping.source rename to scalafmt-tests/shared/src/test/resources/align/comment-wrapping.source diff --git a/scalafmt-tests/src/test/resources/align/defaultTokenOwners.stat b/scalafmt-tests/shared/src/test/resources/align/defaultTokenOwners.stat similarity index 100% rename from scalafmt-tests/src/test/resources/align/defaultTokenOwners.stat rename to scalafmt-tests/shared/src/test/resources/align/defaultTokenOwners.stat diff --git a/scalafmt-tests/src/test/resources/align/duplicateAlign.stat b/scalafmt-tests/shared/src/test/resources/align/duplicateAlign.stat similarity index 100% rename from scalafmt-tests/src/test/resources/align/duplicateAlign.stat rename to scalafmt-tests/shared/src/test/resources/align/duplicateAlign.stat diff --git a/scalafmt-tests/src/test/resources/binPack/LambdaParameterFalse.stat b/scalafmt-tests/shared/src/test/resources/binPack/LambdaParameterFalse.stat similarity index 100% rename from scalafmt-tests/src/test/resources/binPack/LambdaParameterFalse.stat rename to scalafmt-tests/shared/src/test/resources/binPack/LambdaParameterFalse.stat diff --git a/scalafmt-tests/src/test/resources/binPack/LiteralList.stat b/scalafmt-tests/shared/src/test/resources/binPack/LiteralList.stat similarity index 100% rename from scalafmt-tests/src/test/resources/binPack/LiteralList.stat rename to scalafmt-tests/shared/src/test/resources/binPack/LiteralList.stat diff --git a/scalafmt-tests/src/test/resources/binPack/LiteralListNoByte.stat b/scalafmt-tests/shared/src/test/resources/binPack/LiteralListNoByte.stat similarity index 100% rename from scalafmt-tests/src/test/resources/binPack/LiteralListNoByte.stat rename to scalafmt-tests/shared/src/test/resources/binPack/LiteralListNoByte.stat diff --git a/scalafmt-tests/src/test/resources/binPack/ParentConstructors.stat b/scalafmt-tests/shared/src/test/resources/binPack/ParentConstructors.stat similarity index 100% rename from scalafmt-tests/src/test/resources/binPack/ParentConstructors.stat rename to scalafmt-tests/shared/src/test/resources/binPack/ParentConstructors.stat diff --git a/scalafmt-tests/src/test/resources/binPack/TermNameList b/scalafmt-tests/shared/src/test/resources/binPack/TermNameList similarity index 100% rename from scalafmt-tests/src/test/resources/binPack/TermNameList rename to scalafmt-tests/shared/src/test/resources/binPack/TermNameList diff --git a/scalafmt-tests/src/test/resources/binPack/TermNameList.stat b/scalafmt-tests/shared/src/test/resources/binPack/TermNameList.stat similarity index 100% rename from scalafmt-tests/src/test/resources/binPack/TermNameList.stat rename to scalafmt-tests/shared/src/test/resources/binPack/TermNameList.stat diff --git a/scalafmt-tests/src/test/resources/default/Advanced.stat b/scalafmt-tests/shared/src/test/resources/default/Advanced.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Advanced.stat rename to scalafmt-tests/shared/src/test/resources/default/Advanced.stat diff --git a/scalafmt-tests/src/test/resources/default/Apply.stat b/scalafmt-tests/shared/src/test/resources/default/Apply.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Apply.stat rename to scalafmt-tests/shared/src/test/resources/default/Apply.stat diff --git a/scalafmt-tests/src/test/resources/default/ApplyInfix.stat b/scalafmt-tests/shared/src/test/resources/default/ApplyInfix.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/ApplyInfix.stat rename to scalafmt-tests/shared/src/test/resources/default/ApplyInfix.stat diff --git a/scalafmt-tests/src/test/resources/default/Case.case b/scalafmt-tests/shared/src/test/resources/default/Case.case similarity index 100% rename from scalafmt-tests/src/test/resources/default/Case.case rename to scalafmt-tests/shared/src/test/resources/default/Case.case diff --git a/scalafmt-tests/src/test/resources/default/Case.stat b/scalafmt-tests/shared/src/test/resources/default/Case.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Case.stat rename to scalafmt-tests/shared/src/test/resources/default/Case.stat diff --git a/scalafmt-tests/src/test/resources/default/Class.stat b/scalafmt-tests/shared/src/test/resources/default/Class.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Class.stat rename to scalafmt-tests/shared/src/test/resources/default/Class.stat diff --git a/scalafmt-tests/src/test/resources/default/Comment.stat b/scalafmt-tests/shared/src/test/resources/default/Comment.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Comment.stat rename to scalafmt-tests/shared/src/test/resources/default/Comment.stat diff --git a/scalafmt-tests/src/test/resources/default/DefDef.stat b/scalafmt-tests/shared/src/test/resources/default/DefDef.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/DefDef.stat rename to scalafmt-tests/shared/src/test/resources/default/DefDef.stat diff --git a/scalafmt-tests/src/test/resources/default/Fidelity.source b/scalafmt-tests/shared/src/test/resources/default/Fidelity.source similarity index 100% rename from scalafmt-tests/src/test/resources/default/Fidelity.source rename to scalafmt-tests/shared/src/test/resources/default/Fidelity.source diff --git a/scalafmt-tests/src/test/resources/default/Fidelity.stat b/scalafmt-tests/shared/src/test/resources/default/Fidelity.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Fidelity.stat rename to scalafmt-tests/shared/src/test/resources/default/Fidelity.stat diff --git a/scalafmt-tests/src/test/resources/default/For.stat b/scalafmt-tests/shared/src/test/resources/default/For.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/For.stat rename to scalafmt-tests/shared/src/test/resources/default/For.stat diff --git a/scalafmt-tests/src/test/resources/default/Idempotency.stat b/scalafmt-tests/shared/src/test/resources/default/Idempotency.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Idempotency.stat rename to scalafmt-tests/shared/src/test/resources/default/Idempotency.stat diff --git a/scalafmt-tests/src/test/resources/default/If.stat b/scalafmt-tests/shared/src/test/resources/default/If.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/If.stat rename to scalafmt-tests/shared/src/test/resources/default/If.stat diff --git a/scalafmt-tests/src/test/resources/default/Lambda.stat b/scalafmt-tests/shared/src/test/resources/default/Lambda.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Lambda.stat rename to scalafmt-tests/shared/src/test/resources/default/Lambda.stat diff --git a/scalafmt-tests/src/test/resources/default/SearchState.stat b/scalafmt-tests/shared/src/test/resources/default/SearchState.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/SearchState.stat rename to scalafmt-tests/shared/src/test/resources/default/SearchState.stat diff --git a/scalafmt-tests/src/test/resources/default/Select.stat b/scalafmt-tests/shared/src/test/resources/default/Select.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Select.stat rename to scalafmt-tests/shared/src/test/resources/default/Select.stat diff --git a/scalafmt-tests/src/test/resources/default/Semicolon.stat b/scalafmt-tests/shared/src/test/resources/default/Semicolon.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Semicolon.stat rename to scalafmt-tests/shared/src/test/resources/default/Semicolon.stat diff --git a/scalafmt-tests/src/test/resources/default/String.stat b/scalafmt-tests/shared/src/test/resources/default/String.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/String.stat rename to scalafmt-tests/shared/src/test/resources/default/String.stat diff --git a/scalafmt-tests/src/test/resources/default/Super.stat b/scalafmt-tests/shared/src/test/resources/default/Super.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Super.stat rename to scalafmt-tests/shared/src/test/resources/default/Super.stat diff --git a/scalafmt-tests/src/test/resources/default/TermParam.stat b/scalafmt-tests/shared/src/test/resources/default/TermParam.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/TermParam.stat rename to scalafmt-tests/shared/src/test/resources/default/TermParam.stat diff --git a/scalafmt-tests/src/test/resources/default/TestingClassNameDoNotEdit.scalafmt b/scalafmt-tests/shared/src/test/resources/default/TestingClassNameDoNotEdit.scalafmt similarity index 100% rename from scalafmt-tests/src/test/resources/default/TestingClassNameDoNotEdit.scalafmt rename to scalafmt-tests/shared/src/test/resources/default/TestingClassNameDoNotEdit.scalafmt diff --git a/scalafmt-tests/src/test/resources/default/Trait.stat b/scalafmt-tests/shared/src/test/resources/default/Trait.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Trait.stat rename to scalafmt-tests/shared/src/test/resources/default/Trait.stat diff --git a/scalafmt-tests/src/test/resources/default/Try.stat b/scalafmt-tests/shared/src/test/resources/default/Try.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Try.stat rename to scalafmt-tests/shared/src/test/resources/default/Try.stat diff --git a/scalafmt-tests/src/test/resources/default/TypeArguments.stat b/scalafmt-tests/shared/src/test/resources/default/TypeArguments.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/TypeArguments.stat rename to scalafmt-tests/shared/src/test/resources/default/TypeArguments.stat diff --git a/scalafmt-tests/src/test/resources/default/TypeWith.stat b/scalafmt-tests/shared/src/test/resources/default/TypeWith.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/TypeWith.stat rename to scalafmt-tests/shared/src/test/resources/default/TypeWith.stat diff --git a/scalafmt-tests/src/test/resources/default/Unicode.stat b/scalafmt-tests/shared/src/test/resources/default/Unicode.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Unicode.stat rename to scalafmt-tests/shared/src/test/resources/default/Unicode.stat diff --git a/scalafmt-tests/src/test/resources/default/Unindent.stat b/scalafmt-tests/shared/src/test/resources/default/Unindent.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Unindent.stat rename to scalafmt-tests/shared/src/test/resources/default/Unindent.stat diff --git a/scalafmt-tests/src/test/resources/default/Val.stat b/scalafmt-tests/shared/src/test/resources/default/Val.stat similarity index 100% rename from scalafmt-tests/src/test/resources/default/Val.stat rename to scalafmt-tests/shared/src/test/resources/default/Val.stat diff --git a/scalafmt-tests/src/test/resources/literals/Double.stat b/scalafmt-tests/shared/src/test/resources/literals/Double.stat similarity index 100% rename from scalafmt-tests/src/test/resources/literals/Double.stat rename to scalafmt-tests/shared/src/test/resources/literals/Double.stat diff --git a/scalafmt-tests/src/test/resources/literals/Float.stat b/scalafmt-tests/shared/src/test/resources/literals/Float.stat similarity index 100% rename from scalafmt-tests/src/test/resources/literals/Float.stat rename to scalafmt-tests/shared/src/test/resources/literals/Float.stat diff --git a/scalafmt-tests/src/test/resources/literals/Long.stat b/scalafmt-tests/shared/src/test/resources/literals/Long.stat similarity index 100% rename from scalafmt-tests/src/test/resources/literals/Long.stat rename to scalafmt-tests/shared/src/test/resources/literals/Long.stat diff --git a/scalafmt-tests/src/test/resources/literals/Unchanged.stat b/scalafmt-tests/shared/src/test/resources/literals/Unchanged.stat similarity index 100% rename from scalafmt-tests/src/test/resources/literals/Unchanged.stat rename to scalafmt-tests/shared/src/test/resources/literals/Unchanged.stat diff --git a/scalafmt-tests/src/test/resources/literals/hex.stat b/scalafmt-tests/shared/src/test/resources/literals/hex.stat similarity index 100% rename from scalafmt-tests/src/test/resources/literals/hex.stat rename to scalafmt-tests/shared/src/test/resources/literals/hex.stat diff --git a/scalafmt-tests/src/test/resources/literals/scientific.stat b/scalafmt-tests/shared/src/test/resources/literals/scientific.stat similarity index 100% rename from scalafmt-tests/src/test/resources/literals/scientific.stat rename to scalafmt-tests/shared/src/test/resources/literals/scientific.stat diff --git a/scalafmt-tests/src/test/resources/newdefault/CaseNoAlign.stat b/scalafmt-tests/shared/src/test/resources/newdefault/CaseNoAlign.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newdefault/CaseNoAlign.stat rename to scalafmt-tests/shared/src/test/resources/newdefault/CaseNoAlign.stat diff --git a/scalafmt-tests/src/test/resources/newdefault/ColumnWidth.stat b/scalafmt-tests/shared/src/test/resources/newdefault/ColumnWidth.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newdefault/ColumnWidth.stat rename to scalafmt-tests/shared/src/test/resources/newdefault/ColumnWidth.stat diff --git a/scalafmt-tests/src/test/resources/newdefault/SearchState.stat b/scalafmt-tests/shared/src/test/resources/newdefault/SearchState.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newdefault/SearchState.stat rename to scalafmt-tests/shared/src/test/resources/newdefault/SearchState.stat diff --git a/scalafmt-tests/src/test/resources/newlines/AlwaysBeforeCurlyBraceLambdaParams.stat b/scalafmt-tests/shared/src/test/resources/newlines/AlwaysBeforeCurlyBraceLambdaParams.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/AlwaysBeforeCurlyBraceLambdaParams.stat rename to scalafmt-tests/shared/src/test/resources/newlines/AlwaysBeforeCurlyBraceLambdaParams.stat diff --git a/scalafmt-tests/src/test/resources/newlines/OffByOneParameterlessMethod.stat b/scalafmt-tests/shared/src/test/resources/newlines/OffByOneParameterlessMethod.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/OffByOneParameterlessMethod.stat rename to scalafmt-tests/shared/src/test/resources/newlines/OffByOneParameterlessMethod.stat diff --git a/scalafmt-tests/src/test/resources/newlines/afterCurlyLambdaAlways.stat b/scalafmt-tests/shared/src/test/resources/newlines/afterCurlyLambdaAlways.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/afterCurlyLambdaAlways.stat rename to scalafmt-tests/shared/src/test/resources/newlines/afterCurlyLambdaAlways.stat diff --git a/scalafmt-tests/src/test/resources/newlines/afterCurlyLambdaNever.stat b/scalafmt-tests/shared/src/test/resources/newlines/afterCurlyLambdaNever.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/afterCurlyLambdaNever.stat rename to scalafmt-tests/shared/src/test/resources/newlines/afterCurlyLambdaNever.stat diff --git a/scalafmt-tests/src/test/resources/newlines/afterCurlyLambdaPreserve.stat b/scalafmt-tests/shared/src/test/resources/newlines/afterCurlyLambdaPreserve.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/afterCurlyLambdaPreserve.stat rename to scalafmt-tests/shared/src/test/resources/newlines/afterCurlyLambdaPreserve.stat diff --git a/scalafmt-tests/src/test/resources/newlines/afterCurlyLambdaSquash.stat b/scalafmt-tests/shared/src/test/resources/newlines/afterCurlyLambdaSquash.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/afterCurlyLambdaSquash.stat rename to scalafmt-tests/shared/src/test/resources/newlines/afterCurlyLambdaSquash.stat diff --git a/scalafmt-tests/src/test/resources/newlines/alwaysBeforeElseAfterCurlyIf.stat b/scalafmt-tests/shared/src/test/resources/newlines/alwaysBeforeElseAfterCurlyIf.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/alwaysBeforeElseAfterCurlyIf.stat rename to scalafmt-tests/shared/src/test/resources/newlines/alwaysBeforeElseAfterCurlyIf.stat diff --git a/scalafmt-tests/src/test/resources/newlines/alwaysBeforeMultilineDef.stat b/scalafmt-tests/shared/src/test/resources/newlines/alwaysBeforeMultilineDef.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/alwaysBeforeMultilineDef.stat rename to scalafmt-tests/shared/src/test/resources/newlines/alwaysBeforeMultilineDef.stat diff --git a/scalafmt-tests/src/test/resources/newlines/alwaysBeforeTopLevelStatements.source b/scalafmt-tests/shared/src/test/resources/newlines/alwaysBeforeTopLevelStatements.source similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/alwaysBeforeTopLevelStatements.source rename to scalafmt-tests/shared/src/test/resources/newlines/alwaysBeforeTopLevelStatements.source diff --git a/scalafmt-tests/src/test/resources/newlines/alwaysBeforeTopLevelStatements.stat b/scalafmt-tests/shared/src/test/resources/newlines/alwaysBeforeTopLevelStatements.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/alwaysBeforeTopLevelStatements.stat rename to scalafmt-tests/shared/src/test/resources/newlines/alwaysBeforeTopLevelStatements.stat diff --git a/scalafmt-tests/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingF.stat b/scalafmt-tests/shared/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingF.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingF.stat rename to scalafmt-tests/shared/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingF.stat diff --git a/scalafmt-tests/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingT.stat b/scalafmt-tests/shared/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingT.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingT.stat rename to scalafmt-tests/shared/src/test/resources/newlines/beforeConfigSingleArgParenLambdaParamsF_danglingT.stat diff --git a/scalafmt-tests/src/test/resources/newlines/newlineBetweenCurlyAndCatchFinally.stat b/scalafmt-tests/shared/src/test/resources/newlines/newlineBetweenCurlyAndCatchFinally.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/newlineBetweenCurlyAndCatchFinally.stat rename to scalafmt-tests/shared/src/test/resources/newlines/newlineBetweenCurlyAndCatchFinally.stat diff --git a/scalafmt-tests/src/test/resources/newlines/source.source b/scalafmt-tests/shared/src/test/resources/newlines/source.source similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/source.source rename to scalafmt-tests/shared/src/test/resources/newlines/source.source diff --git a/scalafmt-tests/src/test/resources/newlines/source_classic.stat b/scalafmt-tests/shared/src/test/resources/newlines/source_classic.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/source_classic.stat rename to scalafmt-tests/shared/src/test/resources/newlines/source_classic.stat diff --git a/scalafmt-tests/src/test/resources/newlines/source_fold.stat b/scalafmt-tests/shared/src/test/resources/newlines/source_fold.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/source_fold.stat rename to scalafmt-tests/shared/src/test/resources/newlines/source_fold.stat diff --git a/scalafmt-tests/src/test/resources/newlines/source_keep.stat b/scalafmt-tests/shared/src/test/resources/newlines/source_keep.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/source_keep.stat rename to scalafmt-tests/shared/src/test/resources/newlines/source_keep.stat diff --git a/scalafmt-tests/src/test/resources/newlines/source_unfold.stat b/scalafmt-tests/shared/src/test/resources/newlines/source_unfold.stat similarity index 100% rename from scalafmt-tests/src/test/resources/newlines/source_unfold.stat rename to scalafmt-tests/shared/src/test/resources/newlines/source_unfold.stat diff --git a/scalafmt-tests/src/test/resources/optIn/Annotation.stat b/scalafmt-tests/shared/src/test/resources/optIn/Annotation.stat similarity index 100% rename from scalafmt-tests/src/test/resources/optIn/Annotation.stat rename to scalafmt-tests/shared/src/test/resources/optIn/Annotation.stat diff --git a/scalafmt-tests/src/test/resources/optIn/AnnotationParam.stat b/scalafmt-tests/shared/src/test/resources/optIn/AnnotationParam.stat similarity index 100% rename from scalafmt-tests/src/test/resources/optIn/AnnotationParam.stat rename to scalafmt-tests/shared/src/test/resources/optIn/AnnotationParam.stat diff --git a/scalafmt-tests/src/test/resources/optIn/BreakChainOnFirstMethodDot.stat b/scalafmt-tests/shared/src/test/resources/optIn/BreakChainOnFirstMethodDot.stat similarity index 100% rename from scalafmt-tests/src/test/resources/optIn/BreakChainOnFirstMethodDot.stat rename to scalafmt-tests/shared/src/test/resources/optIn/BreakChainOnFirstMethodDot.stat diff --git a/scalafmt-tests/src/test/resources/optIn/SelectChain.stat b/scalafmt-tests/shared/src/test/resources/optIn/SelectChain.stat similarity index 100% rename from scalafmt-tests/src/test/resources/optIn/SelectChain.stat rename to scalafmt-tests/shared/src/test/resources/optIn/SelectChain.stat diff --git a/scalafmt-tests/src/test/resources/optIn/SelectChains.stat b/scalafmt-tests/shared/src/test/resources/optIn/SelectChains.stat similarity index 100% rename from scalafmt-tests/src/test/resources/optIn/SelectChains.stat rename to scalafmt-tests/shared/src/test/resources/optIn/SelectChains.stat diff --git a/scalafmt-tests/src/test/resources/optIn/SelfAnnotation.stat b/scalafmt-tests/shared/src/test/resources/optIn/SelfAnnotation.stat similarity index 100% rename from scalafmt-tests/src/test/resources/optIn/SelfAnnotation.stat rename to scalafmt-tests/shared/src/test/resources/optIn/SelfAnnotation.stat diff --git a/scalafmt-tests/src/test/resources/optIn/forceBlankLineBeforeDocstring.stat b/scalafmt-tests/shared/src/test/resources/optIn/forceBlankLineBeforeDocstring.stat similarity index 100% rename from scalafmt-tests/src/test/resources/optIn/forceBlankLineBeforeDocstring.stat rename to scalafmt-tests/shared/src/test/resources/optIn/forceBlankLineBeforeDocstring.stat diff --git a/scalafmt-tests/src/test/resources/readme.md b/scalafmt-tests/shared/src/test/resources/readme.md similarity index 100% rename from scalafmt-tests/src/test/resources/readme.md rename to scalafmt-tests/shared/src/test/resources/readme.md diff --git a/scalafmt-tests/src/test/resources/rewrite/AsciiSortImports.stat b/scalafmt-tests/shared/src/test/resources/rewrite/AsciiSortImports.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/AsciiSortImports.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/AsciiSortImports.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/AvoidInfix.stat b/scalafmt-tests/shared/src/test/resources/rewrite/AvoidInfix.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/AvoidInfix.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/AvoidInfix.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/AvoidInfix2.stat b/scalafmt-tests/shared/src/test/resources/rewrite/AvoidInfix2.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/AvoidInfix2.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/AvoidInfix2.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/AvoidInfix3.stat b/scalafmt-tests/shared/src/test/resources/rewrite/AvoidInfix3.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/AvoidInfix3.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/AvoidInfix3.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/ExpandImportSelectors.stat b/scalafmt-tests/shared/src/test/resources/rewrite/ExpandImportSelectors.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/ExpandImportSelectors.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/ExpandImportSelectors.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/Imports.source b/scalafmt-tests/shared/src/test/resources/rewrite/Imports.source similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/Imports.source rename to scalafmt-tests/shared/src/test/resources/rewrite/Imports.source diff --git a/scalafmt-tests/src/test/resources/rewrite/PreferCurlyForYields.stat b/scalafmt-tests/shared/src/test/resources/rewrite/PreferCurlyForYields.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/PreferCurlyForYields.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/PreferCurlyForYields.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/PreferCurlyFors.stat b/scalafmt-tests/shared/src/test/resources/rewrite/PreferCurlyFors.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/PreferCurlyFors.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/PreferCurlyFors.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-ParenLambdas.stat b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-ParenLambdas.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/RedundantBraces-ParenLambdas.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-ParenLambdas.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-case.stat b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-case.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/RedundantBraces-case.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-case.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-if.stat b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-if.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/RedundantBraces-if.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-if.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces-precedence.stat b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-precedence.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/RedundantBraces-precedence.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces-precedence.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces.stat b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/RedundantBraces.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces2.stat b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces2.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/RedundantBraces2.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces2.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBraces748.stat b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces748.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/RedundantBraces748.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/RedundantBraces748.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantBracesStrInterpolation.stat b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantBracesStrInterpolation.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/RedundantBracesStrInterpolation.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/RedundantBracesStrInterpolation.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/RedundantParens.stat b/scalafmt-tests/shared/src/test/resources/rewrite/RedundantParens.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/RedundantParens.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/RedundantParens.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/SortImports.stat b/scalafmt-tests/shared/src/test/resources/rewrite/SortImports.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/SortImports.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/SortImports.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/SortModifiers.stat b/scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/SortModifiers.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/SortModifiers1.source b/scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers1.source similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/SortModifiers1.source rename to scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers1.source diff --git a/scalafmt-tests/src/test/resources/rewrite/SortModifiersScala3.stat b/scalafmt-tests/shared/src/test/resources/rewrite/SortModifiersScala3.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/SortModifiersScala3.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/SortModifiersScala3.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/SortModifiers_Default.source b/scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers_Default.source similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/SortModifiers_Default.source rename to scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers_Default.source diff --git a/scalafmt-tests/src/test/resources/rewrite/SortModifiers_Mod_With_No_Token.source b/scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers_Mod_With_No_Token.source similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/SortModifiers_Mod_With_No_Token.source rename to scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers_Mod_With_No_Token.source diff --git a/scalafmt-tests/src/test/resources/rewrite/SortModifiers_bug_1148.stat b/scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers_bug_1148.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/SortModifiers_bug_1148.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers_bug_1148.stat diff --git a/scalafmt-tests/src/test/resources/rewrite/SortModifiers_bug_1148_implicit_first_in_order.stat b/scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers_bug_1148_implicit_first_in_order.stat similarity index 100% rename from scalafmt-tests/src/test/resources/rewrite/SortModifiers_bug_1148_implicit_first_in_order.stat rename to scalafmt-tests/shared/src/test/resources/rewrite/SortModifiers_bug_1148_implicit_first_in_order.stat diff --git a/scalafmt-tests/src/test/resources/scala3/ContextFunction.stat b/scalafmt-tests/shared/src/test/resources/scala3/ContextFunction.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/ContextFunction.stat rename to scalafmt-tests/shared/src/test/resources/scala3/ContextFunction.stat diff --git a/scalafmt-tests/src/test/resources/scala3/DependentFunction.stat b/scalafmt-tests/shared/src/test/resources/scala3/DependentFunction.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/DependentFunction.stat rename to scalafmt-tests/shared/src/test/resources/scala3/DependentFunction.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Derives.stat b/scalafmt-tests/shared/src/test/resources/scala3/Derives.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Derives.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Derives.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Dialect.stat b/scalafmt-tests/shared/src/test/resources/scala3/Dialect.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Dialect.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Dialect.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Enum.stat b/scalafmt-tests/shared/src/test/resources/scala3/Enum.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Enum.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Enum.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Export.stat b/scalafmt-tests/shared/src/test/resources/scala3/Export.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Export.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Export.stat diff --git a/scalafmt-tests/src/test/resources/scala3/ExportGivens.stat b/scalafmt-tests/shared/src/test/resources/scala3/ExportGivens.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/ExportGivens.stat rename to scalafmt-tests/shared/src/test/resources/scala3/ExportGivens.stat diff --git a/scalafmt-tests/src/test/resources/scala3/ExtendsComma.stat b/scalafmt-tests/shared/src/test/resources/scala3/ExtendsComma.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/ExtendsComma.stat rename to scalafmt-tests/shared/src/test/resources/scala3/ExtendsComma.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Extension.stat b/scalafmt-tests/shared/src/test/resources/scala3/Extension.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Extension.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Extension.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Given.stat b/scalafmt-tests/shared/src/test/resources/scala3/Given.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Given.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Given.stat diff --git a/scalafmt-tests/src/test/resources/scala3/GivenPatterns.stat b/scalafmt-tests/shared/src/test/resources/scala3/GivenPatterns.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/GivenPatterns.stat rename to scalafmt-tests/shared/src/test/resources/scala3/GivenPatterns.stat diff --git a/scalafmt-tests/src/test/resources/scala3/ImportingGivens.stat b/scalafmt-tests/shared/src/test/resources/scala3/ImportingGivens.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/ImportingGivens.stat rename to scalafmt-tests/shared/src/test/resources/scala3/ImportingGivens.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Infix.stat b/scalafmt-tests/shared/src/test/resources/scala3/Infix.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Infix.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Infix.stat diff --git a/scalafmt-tests/src/test/resources/scala3/InlineTransparent.stat b/scalafmt-tests/shared/src/test/resources/scala3/InlineTransparent.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/InlineTransparent.stat rename to scalafmt-tests/shared/src/test/resources/scala3/InlineTransparent.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Issues.stat b/scalafmt-tests/shared/src/test/resources/scala3/Issues.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Issues.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Issues.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Macro.stat b/scalafmt-tests/shared/src/test/resources/scala3/Macro.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Macro.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Macro.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Match.stat b/scalafmt-tests/shared/src/test/resources/scala3/Match.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Match.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Match.stat diff --git a/scalafmt-tests/src/test/resources/scala3/MatchType.stat b/scalafmt-tests/shared/src/test/resources/scala3/MatchType.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/MatchType.stat rename to scalafmt-tests/shared/src/test/resources/scala3/MatchType.stat diff --git a/scalafmt-tests/src/test/resources/scala3/OpaqueType.stat b/scalafmt-tests/shared/src/test/resources/scala3/OpaqueType.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/OpaqueType.stat rename to scalafmt-tests/shared/src/test/resources/scala3/OpaqueType.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Open.stat b/scalafmt-tests/shared/src/test/resources/scala3/Open.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Open.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Open.stat diff --git a/scalafmt-tests/src/test/resources/scala3/OptionalBraces.stat b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/OptionalBraces.stat rename to scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces.stat diff --git a/scalafmt-tests/src/test/resources/scala3/OptionalBraces_fold.stat b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_fold.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/OptionalBraces_fold.stat rename to scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_fold.stat diff --git a/scalafmt-tests/src/test/resources/scala3/OptionalBraces_keep.stat b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_keep.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/OptionalBraces_keep.stat rename to scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_keep.stat diff --git a/scalafmt-tests/src/test/resources/scala3/OptionalBraces_unfold.stat b/scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_unfold.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/OptionalBraces_unfold.stat rename to scalafmt-tests/shared/src/test/resources/scala3/OptionalBraces_unfold.stat diff --git a/scalafmt-tests/src/test/resources/scala3/PolyFunction.stat b/scalafmt-tests/shared/src/test/resources/scala3/PolyFunction.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/PolyFunction.stat rename to scalafmt-tests/shared/src/test/resources/scala3/PolyFunction.stat diff --git a/scalafmt-tests/src/test/resources/scala3/RenameImportExport.stat b/scalafmt-tests/shared/src/test/resources/scala3/RenameImportExport.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/RenameImportExport.stat rename to scalafmt-tests/shared/src/test/resources/scala3/RenameImportExport.stat diff --git a/scalafmt-tests/src/test/resources/scala3/StarImport.stat b/scalafmt-tests/shared/src/test/resources/scala3/StarImport.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/StarImport.stat rename to scalafmt-tests/shared/src/test/resources/scala3/StarImport.stat diff --git a/scalafmt-tests/src/test/resources/scala3/TraitParams.stat b/scalafmt-tests/shared/src/test/resources/scala3/TraitParams.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/TraitParams.stat rename to scalafmt-tests/shared/src/test/resources/scala3/TraitParams.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Type.stat b/scalafmt-tests/shared/src/test/resources/scala3/Type.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Type.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Type.stat diff --git a/scalafmt-tests/src/test/resources/scala3/TypeLambda.stat b/scalafmt-tests/shared/src/test/resources/scala3/TypeLambda.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/TypeLambda.stat rename to scalafmt-tests/shared/src/test/resources/scala3/TypeLambda.stat diff --git a/scalafmt-tests/src/test/resources/scala3/UnionIntersectionType.stat b/scalafmt-tests/shared/src/test/resources/scala3/UnionIntersectionType.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/UnionIntersectionType.stat rename to scalafmt-tests/shared/src/test/resources/scala3/UnionIntersectionType.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Using.stat b/scalafmt-tests/shared/src/test/resources/scala3/Using.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Using.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Using.stat diff --git a/scalafmt-tests/src/test/resources/scala3/Vararg.stat b/scalafmt-tests/shared/src/test/resources/scala3/Vararg.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scala3/Vararg.stat rename to scalafmt-tests/shared/src/test/resources/scala3/Vararg.stat diff --git a/scalafmt-tests/src/test/resources/scalajs/Advanced.stat b/scalafmt-tests/shared/src/test/resources/scalajs/Advanced.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scalajs/Advanced.stat rename to scalafmt-tests/shared/src/test/resources/scalajs/Advanced.stat diff --git a/scalafmt-tests/src/test/resources/scalajs/Apply.stat b/scalafmt-tests/shared/src/test/resources/scalajs/Apply.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scalajs/Apply.stat rename to scalafmt-tests/shared/src/test/resources/scalajs/Apply.stat diff --git a/scalafmt-tests/src/test/resources/scalajs/Class.stat b/scalafmt-tests/shared/src/test/resources/scalajs/Class.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scalajs/Class.stat rename to scalafmt-tests/shared/src/test/resources/scalajs/Class.stat diff --git a/scalafmt-tests/src/test/resources/scalajs/DefDef.stat b/scalafmt-tests/shared/src/test/resources/scalajs/DefDef.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scalajs/DefDef.stat rename to scalafmt-tests/shared/src/test/resources/scalajs/DefDef.stat diff --git a/scalafmt-tests/src/test/resources/scalajs/For.stat b/scalafmt-tests/shared/src/test/resources/scalajs/For.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scalajs/For.stat rename to scalafmt-tests/shared/src/test/resources/scalajs/For.stat diff --git a/scalafmt-tests/src/test/resources/scalajs/If.stat b/scalafmt-tests/shared/src/test/resources/scalajs/If.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scalajs/If.stat rename to scalafmt-tests/shared/src/test/resources/scalajs/If.stat diff --git a/scalafmt-tests/src/test/resources/scalajs/Import.source b/scalafmt-tests/shared/src/test/resources/scalajs/Import.source similarity index 100% rename from scalafmt-tests/src/test/resources/scalajs/Import.source rename to scalafmt-tests/shared/src/test/resources/scalajs/Import.source diff --git a/scalafmt-tests/src/test/resources/scalajs/TermApply.stat b/scalafmt-tests/shared/src/test/resources/scalajs/TermApply.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scalajs/TermApply.stat rename to scalafmt-tests/shared/src/test/resources/scalajs/TermApply.stat diff --git a/scalafmt-tests/src/test/resources/scalajs/Type.stat b/scalafmt-tests/shared/src/test/resources/scalajs/Type.stat similarity index 100% rename from scalafmt-tests/src/test/resources/scalajs/Type.stat rename to scalafmt-tests/shared/src/test/resources/scalajs/Type.stat diff --git a/scalafmt-tests/src/test/resources/spaces/AfterKeywordBeforeParen.stat b/scalafmt-tests/shared/src/test/resources/spaces/AfterKeywordBeforeParen.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/AfterKeywordBeforeParen.stat rename to scalafmt-tests/shared/src/test/resources/spaces/AfterKeywordBeforeParen.stat diff --git a/scalafmt-tests/src/test/resources/spaces/AfterSymbolicDefs.stat b/scalafmt-tests/shared/src/test/resources/spaces/AfterSymbolicDefs.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/AfterSymbolicDefs.stat rename to scalafmt-tests/shared/src/test/resources/spaces/AfterSymbolicDefs.stat diff --git a/scalafmt-tests/src/test/resources/spaces/BeforeContextBoundColonIfMultipleBounds.stat b/scalafmt-tests/shared/src/test/resources/spaces/BeforeContextBoundColonIfMultipleBounds.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/BeforeContextBoundColonIfMultipleBounds.stat rename to scalafmt-tests/shared/src/test/resources/spaces/BeforeContextBoundColonIfMultipleBounds.stat diff --git a/scalafmt-tests/src/test/resources/spaces/BeforeContextBoundColonTrue.stat b/scalafmt-tests/shared/src/test/resources/spaces/BeforeContextBoundColonTrue.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/BeforeContextBoundColonTrue.stat rename to scalafmt-tests/shared/src/test/resources/spaces/BeforeContextBoundColonTrue.stat diff --git a/scalafmt-tests/src/test/resources/spaces/Constructor.stat b/scalafmt-tests/shared/src/test/resources/spaces/Constructor.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/Constructor.stat rename to scalafmt-tests/shared/src/test/resources/spaces/Constructor.stat diff --git a/scalafmt-tests/src/test/resources/spaces/Hacking.stat b/scalafmt-tests/shared/src/test/resources/spaces/Hacking.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/Hacking.stat rename to scalafmt-tests/shared/src/test/resources/spaces/Hacking.stat diff --git a/scalafmt-tests/src/test/resources/spaces/ImportCurlyBraces.source b/scalafmt-tests/shared/src/test/resources/spaces/ImportCurlyBraces.source similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/ImportCurlyBraces.source rename to scalafmt-tests/shared/src/test/resources/spaces/ImportCurlyBraces.source diff --git a/scalafmt-tests/src/test/resources/spaces/InByNameTypes.stat b/scalafmt-tests/shared/src/test/resources/spaces/InByNameTypes.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/InByNameTypes.stat rename to scalafmt-tests/shared/src/test/resources/spaces/InByNameTypes.stat diff --git a/scalafmt-tests/src/test/resources/spaces/InParentheses.stat b/scalafmt-tests/shared/src/test/resources/spaces/InParentheses.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/InParentheses.stat rename to scalafmt-tests/shared/src/test/resources/spaces/InParentheses.stat diff --git a/scalafmt-tests/src/test/resources/spaces/InterpolatedStringCurlyBraces.stat b/scalafmt-tests/shared/src/test/resources/spaces/InterpolatedStringCurlyBraces.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/InterpolatedStringCurlyBraces.stat rename to scalafmt-tests/shared/src/test/resources/spaces/InterpolatedStringCurlyBraces.stat diff --git a/scalafmt-tests/src/test/resources/spaces/NeverAroundInfixTypes.stat b/scalafmt-tests/shared/src/test/resources/spaces/NeverAroundInfixTypes.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/NeverAroundInfixTypes.stat rename to scalafmt-tests/shared/src/test/resources/spaces/NeverAroundInfixTypes.stat diff --git a/scalafmt-tests/src/test/resources/spaces/Scalatest.stat b/scalafmt-tests/shared/src/test/resources/spaces/Scalatest.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/Scalatest.stat rename to scalafmt-tests/shared/src/test/resources/spaces/Scalatest.stat diff --git a/scalafmt-tests/src/test/resources/spaces/TrailingWhiteSpace.stat b/scalafmt-tests/shared/src/test/resources/spaces/TrailingWhiteSpace.stat similarity index 100% rename from scalafmt-tests/src/test/resources/spaces/TrailingWhiteSpace.stat rename to scalafmt-tests/shared/src/test/resources/spaces/TrailingWhiteSpace.stat diff --git a/scalafmt-tests/src/test/resources/test/BlocksInSingleArg.stat b/scalafmt-tests/shared/src/test/resources/test/BlocksInSingleArg.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/BlocksInSingleArg.stat rename to scalafmt-tests/shared/src/test/resources/test/BlocksInSingleArg.stat diff --git a/scalafmt-tests/src/test/resources/test/Class.stat b/scalafmt-tests/shared/src/test/resources/test/Class.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/Class.stat rename to scalafmt-tests/shared/src/test/resources/test/Class.stat diff --git a/scalafmt-tests/src/test/resources/test/ContinuationIndent.stat b/scalafmt-tests/shared/src/test/resources/test/ContinuationIndent.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/ContinuationIndent.stat rename to scalafmt-tests/shared/src/test/resources/test/ContinuationIndent.stat diff --git a/scalafmt-tests/src/test/resources/test/ContinuationIndentCaseSite0.stat b/scalafmt-tests/shared/src/test/resources/test/ContinuationIndentCaseSite0.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/ContinuationIndentCaseSite0.stat rename to scalafmt-tests/shared/src/test/resources/test/ContinuationIndentCaseSite0.stat diff --git a/scalafmt-tests/src/test/resources/test/ContinuationIndentCaseSite1.stat b/scalafmt-tests/shared/src/test/resources/test/ContinuationIndentCaseSite1.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/ContinuationIndentCaseSite1.stat rename to scalafmt-tests/shared/src/test/resources/test/ContinuationIndentCaseSite1.stat diff --git a/scalafmt-tests/src/test/resources/test/ContinuationIndentCaseSite5.stat b/scalafmt-tests/shared/src/test/resources/test/ContinuationIndentCaseSite5.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/ContinuationIndentCaseSite5.stat rename to scalafmt-tests/shared/src/test/resources/test/ContinuationIndentCaseSite5.stat diff --git a/scalafmt-tests/src/test/resources/test/ContinuationIndentHalfDanglingParens.stat b/scalafmt-tests/shared/src/test/resources/test/ContinuationIndentHalfDanglingParens.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/ContinuationIndentHalfDanglingParens.stat rename to scalafmt-tests/shared/src/test/resources/test/ContinuationIndentHalfDanglingParens.stat diff --git a/scalafmt-tests/src/test/resources/test/ContinuationIndentNoDanglingParens.stat b/scalafmt-tests/shared/src/test/resources/test/ContinuationIndentNoDanglingParens.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/ContinuationIndentNoDanglingParens.stat rename to scalafmt-tests/shared/src/test/resources/test/ContinuationIndentNoDanglingParens.stat diff --git a/scalafmt-tests/src/test/resources/test/Dangling.stat b/scalafmt-tests/shared/src/test/resources/test/Dangling.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/Dangling.stat rename to scalafmt-tests/shared/src/test/resources/test/Dangling.stat diff --git a/scalafmt-tests/src/test/resources/test/Dialect.source b/scalafmt-tests/shared/src/test/resources/test/Dialect.source similarity index 100% rename from scalafmt-tests/src/test/resources/test/Dialect.source rename to scalafmt-tests/shared/src/test/resources/test/Dialect.source diff --git a/scalafmt-tests/src/test/resources/test/DynamicStyle.stat b/scalafmt-tests/shared/src/test/resources/test/DynamicStyle.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/DynamicStyle.stat rename to scalafmt-tests/shared/src/test/resources/test/DynamicStyle.stat diff --git a/scalafmt-tests/src/test/resources/test/ImportSingleLine.source b/scalafmt-tests/shared/src/test/resources/test/ImportSingleLine.source similarity index 100% rename from scalafmt-tests/src/test/resources/test/ImportSingleLine.source rename to scalafmt-tests/shared/src/test/resources/test/ImportSingleLine.source diff --git a/scalafmt-tests/src/test/resources/test/IndentOperator.stat b/scalafmt-tests/shared/src/test/resources/test/IndentOperator.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/IndentOperator.stat rename to scalafmt-tests/shared/src/test/resources/test/IndentOperator.stat diff --git a/scalafmt-tests/src/test/resources/test/IndentOperatorFormatInfix.stat b/scalafmt-tests/shared/src/test/resources/test/IndentOperatorFormatInfix.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/IndentOperatorFormatInfix.stat rename to scalafmt-tests/shared/src/test/resources/test/IndentOperatorFormatInfix.stat diff --git a/scalafmt-tests/src/test/resources/test/IndentYieldKeyword.stat b/scalafmt-tests/shared/src/test/resources/test/IndentYieldKeyword.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/IndentYieldKeyword.stat rename to scalafmt-tests/shared/src/test/resources/test/IndentYieldKeyword.stat diff --git a/scalafmt-tests/src/test/resources/test/Issue1509.stat b/scalafmt-tests/shared/src/test/resources/test/Issue1509.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/Issue1509.stat rename to scalafmt-tests/shared/src/test/resources/test/Issue1509.stat diff --git a/scalafmt-tests/src/test/resources/test/JavaDoc.stat b/scalafmt-tests/shared/src/test/resources/test/JavaDoc.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/JavaDoc.stat rename to scalafmt-tests/shared/src/test/resources/test/JavaDoc.stat diff --git a/scalafmt-tests/src/test/resources/test/NoAlign.stat b/scalafmt-tests/shared/src/test/resources/test/NoAlign.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/NoAlign.stat rename to scalafmt-tests/shared/src/test/resources/test/NoAlign.stat diff --git a/scalafmt-tests/src/test/resources/test/OperatorSpray.stat b/scalafmt-tests/shared/src/test/resources/test/OperatorSpray.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/OperatorSpray.stat rename to scalafmt-tests/shared/src/test/resources/test/OperatorSpray.stat diff --git a/scalafmt-tests/src/test/resources/test/StripMargin.stat b/scalafmt-tests/shared/src/test/resources/test/StripMargin.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/StripMargin.stat rename to scalafmt-tests/shared/src/test/resources/test/StripMargin.stat diff --git a/scalafmt-tests/src/test/resources/test/Type747.stat b/scalafmt-tests/shared/src/test/resources/test/Type747.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/Type747.stat rename to scalafmt-tests/shared/src/test/resources/test/Type747.stat diff --git a/scalafmt-tests/src/test/resources/test/Unicode.stat b/scalafmt-tests/shared/src/test/resources/test/Unicode.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/Unicode.stat rename to scalafmt-tests/shared/src/test/resources/test/Unicode.stat diff --git a/scalafmt-tests/src/test/resources/test/UnindentTopLevelOperatorsOperatorSpray.stat b/scalafmt-tests/shared/src/test/resources/test/UnindentTopLevelOperatorsOperatorSpray.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/UnindentTopLevelOperatorsOperatorSpray.stat rename to scalafmt-tests/shared/src/test/resources/test/UnindentTopLevelOperatorsOperatorSpray.stat diff --git a/scalafmt-tests/src/test/resources/test/arityThreshold.source b/scalafmt-tests/shared/src/test/resources/test/arityThreshold.source similarity index 100% rename from scalafmt-tests/src/test/resources/test/arityThreshold.source rename to scalafmt-tests/shared/src/test/resources/test/arityThreshold.source diff --git a/scalafmt-tests/src/test/resources/test/i1116.stat b/scalafmt-tests/shared/src/test/resources/test/i1116.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/i1116.stat rename to scalafmt-tests/shared/src/test/resources/test/i1116.stat diff --git a/scalafmt-tests/src/test/resources/test/i1245.stat b/scalafmt-tests/shared/src/test/resources/test/i1245.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/i1245.stat rename to scalafmt-tests/shared/src/test/resources/test/i1245.stat diff --git a/scalafmt-tests/src/test/resources/test/i1300.stat b/scalafmt-tests/shared/src/test/resources/test/i1300.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/i1300.stat rename to scalafmt-tests/shared/src/test/resources/test/i1300.stat diff --git a/scalafmt-tests/src/test/resources/test/i1439.stat b/scalafmt-tests/shared/src/test/resources/test/i1439.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/i1439.stat rename to scalafmt-tests/shared/src/test/resources/test/i1439.stat diff --git a/scalafmt-tests/src/test/resources/test/i1527.stat b/scalafmt-tests/shared/src/test/resources/test/i1527.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/i1527.stat rename to scalafmt-tests/shared/src/test/resources/test/i1527.stat diff --git a/scalafmt-tests/src/test/resources/test/includeNoParensInSelectChains.stat b/scalafmt-tests/shared/src/test/resources/test/includeNoParensInSelectChains.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/includeNoParensInSelectChains.stat rename to scalafmt-tests/shared/src/test/resources/test/includeNoParensInSelectChains.stat diff --git a/scalafmt-tests/src/test/resources/test/poorMansTrailingCommasInConfigStyle.stat b/scalafmt-tests/shared/src/test/resources/test/poorMansTrailingCommasInConfigStyle.stat similarity index 100% rename from scalafmt-tests/src/test/resources/test/poorMansTrailingCommasInConfigStyle.stat rename to scalafmt-tests/shared/src/test/resources/test/poorMansTrailingCommasInConfigStyle.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlways.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlways.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlways.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlways.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysAlignMore.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysAlignMore.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysAlignMore.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysAlignMore.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysComments.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysComments.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysComments.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysComments.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysDanglingParens.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysDanglingParens.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysDanglingParens.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysDanglingParens.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysOwners.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysOwners.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysOwners.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysOwners.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysSingleLine.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysSingleLine.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysSingleLine.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysSingleLine.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysVerticalMultilineAtDefnSite.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysVerticalMultilineAtDefnSite.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasAlwaysVerticalMultilineAtDefnSite.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasAlwaysVerticalMultilineAtDefnSite.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasMultiple.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasMultiple.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasMultiple.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasMultiple.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasMultipleAlignMore.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasMultipleAlignMore.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasMultipleAlignMore.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasMultipleAlignMore.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasMultipleComments.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasMultipleComments.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasMultipleComments.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasMultipleComments.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasMultipleDanglingParens.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasMultipleDanglingParens.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasMultipleDanglingParens.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasMultipleDanglingParens.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasNever.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasNever.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasNever.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasNever.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasNeverComments.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasNeverComments.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasNeverComments.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasNeverComments.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasNeverCommentsAlignMore.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasNeverCommentsAlignMore.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasNeverCommentsAlignMore.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasNeverCommentsAlignMore.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasNeverSingleLine.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasNeverSingleLine.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasNeverSingleLine.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasNeverSingleLine.stat diff --git a/scalafmt-tests/src/test/resources/trailing-commas/trailingCommasPreserve.stat b/scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasPreserve.stat similarity index 100% rename from scalafmt-tests/src/test/resources/trailing-commas/trailingCommasPreserve.stat rename to scalafmt-tests/shared/src/test/resources/trailing-commas/trailingCommasPreserve.stat diff --git a/scalafmt-tests/src/test/resources/unit/Advanced.source b/scalafmt-tests/shared/src/test/resources/unit/Advanced.source similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Advanced.source rename to scalafmt-tests/shared/src/test/resources/unit/Advanced.source diff --git a/scalafmt-tests/src/test/resources/unit/Annotations.stat b/scalafmt-tests/shared/src/test/resources/unit/Annotations.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Annotations.stat rename to scalafmt-tests/shared/src/test/resources/unit/Annotations.stat diff --git a/scalafmt-tests/src/test/resources/unit/Apply.stat b/scalafmt-tests/shared/src/test/resources/unit/Apply.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Apply.stat rename to scalafmt-tests/shared/src/test/resources/unit/Apply.stat diff --git a/scalafmt-tests/src/test/resources/unit/ApplyInfix.stat b/scalafmt-tests/shared/src/test/resources/unit/ApplyInfix.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/ApplyInfix.stat rename to scalafmt-tests/shared/src/test/resources/unit/ApplyInfix.stat diff --git a/scalafmt-tests/src/test/resources/unit/Argument.source b/scalafmt-tests/shared/src/test/resources/unit/Argument.source similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Argument.source rename to scalafmt-tests/shared/src/test/resources/unit/Argument.source diff --git a/scalafmt-tests/src/test/resources/unit/Basic.source b/scalafmt-tests/shared/src/test/resources/unit/Basic.source similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Basic.source rename to scalafmt-tests/shared/src/test/resources/unit/Basic.source diff --git a/scalafmt-tests/src/test/resources/unit/Case.case b/scalafmt-tests/shared/src/test/resources/unit/Case.case similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Case.case rename to scalafmt-tests/shared/src/test/resources/unit/Case.case diff --git a/scalafmt-tests/src/test/resources/unit/Case.stat b/scalafmt-tests/shared/src/test/resources/unit/Case.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Case.stat rename to scalafmt-tests/shared/src/test/resources/unit/Case.stat diff --git a/scalafmt-tests/src/test/resources/unit/ColumnWidth.source b/scalafmt-tests/shared/src/test/resources/unit/ColumnWidth.source similarity index 100% rename from scalafmt-tests/src/test/resources/unit/ColumnWidth.source rename to scalafmt-tests/shared/src/test/resources/unit/ColumnWidth.source diff --git a/scalafmt-tests/src/test/resources/unit/Comment.stat b/scalafmt-tests/shared/src/test/resources/unit/Comment.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Comment.stat rename to scalafmt-tests/shared/src/test/resources/unit/Comment.stat diff --git a/scalafmt-tests/src/test/resources/unit/Cond.stat b/scalafmt-tests/shared/src/test/resources/unit/Cond.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Cond.stat rename to scalafmt-tests/shared/src/test/resources/unit/Cond.stat diff --git a/scalafmt-tests/src/test/resources/unit/DefDef.stat b/scalafmt-tests/shared/src/test/resources/unit/DefDef.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/DefDef.stat rename to scalafmt-tests/shared/src/test/resources/unit/DefDef.stat diff --git a/scalafmt-tests/src/test/resources/unit/Dialect.stat b/scalafmt-tests/shared/src/test/resources/unit/Dialect.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Dialect.stat rename to scalafmt-tests/shared/src/test/resources/unit/Dialect.stat diff --git a/scalafmt-tests/src/test/resources/unit/For.stat b/scalafmt-tests/shared/src/test/resources/unit/For.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/For.stat rename to scalafmt-tests/shared/src/test/resources/unit/For.stat diff --git a/scalafmt-tests/src/test/resources/unit/FormatOff.stat b/scalafmt-tests/shared/src/test/resources/unit/FormatOff.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/FormatOff.stat rename to scalafmt-tests/shared/src/test/resources/unit/FormatOff.stat diff --git a/scalafmt-tests/src/test/resources/unit/If.stat b/scalafmt-tests/shared/src/test/resources/unit/If.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/If.stat rename to scalafmt-tests/shared/src/test/resources/unit/If.stat diff --git a/scalafmt-tests/src/test/resources/unit/Import.source b/scalafmt-tests/shared/src/test/resources/unit/Import.source similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Import.source rename to scalafmt-tests/shared/src/test/resources/unit/Import.source diff --git a/scalafmt-tests/src/test/resources/unit/Interpolate.stat b/scalafmt-tests/shared/src/test/resources/unit/Interpolate.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Interpolate.stat rename to scalafmt-tests/shared/src/test/resources/unit/Interpolate.stat diff --git a/scalafmt-tests/src/test/resources/unit/Lambda.stat b/scalafmt-tests/shared/src/test/resources/unit/Lambda.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Lambda.stat rename to scalafmt-tests/shared/src/test/resources/unit/Lambda.stat diff --git a/scalafmt-tests/src/test/resources/unit/Lit.stat b/scalafmt-tests/shared/src/test/resources/unit/Lit.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Lit.stat rename to scalafmt-tests/shared/src/test/resources/unit/Lit.stat diff --git a/scalafmt-tests/src/test/resources/unit/Mod.stat b/scalafmt-tests/shared/src/test/resources/unit/Mod.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Mod.stat rename to scalafmt-tests/shared/src/test/resources/unit/Mod.stat diff --git a/scalafmt-tests/src/test/resources/unit/Package.source b/scalafmt-tests/shared/src/test/resources/unit/Package.source similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Package.source rename to scalafmt-tests/shared/src/test/resources/unit/Package.source diff --git a/scalafmt-tests/src/test/resources/unit/Symbol.stat b/scalafmt-tests/shared/src/test/resources/unit/Symbol.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Symbol.stat rename to scalafmt-tests/shared/src/test/resources/unit/Symbol.stat diff --git a/scalafmt-tests/src/test/resources/unit/Template.source b/scalafmt-tests/shared/src/test/resources/unit/Template.source similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Template.source rename to scalafmt-tests/shared/src/test/resources/unit/Template.source diff --git a/scalafmt-tests/src/test/resources/unit/TermApply.stat b/scalafmt-tests/shared/src/test/resources/unit/TermApply.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/TermApply.stat rename to scalafmt-tests/shared/src/test/resources/unit/TermApply.stat diff --git a/scalafmt-tests/src/test/resources/unit/TermUpdate.stat b/scalafmt-tests/shared/src/test/resources/unit/TermUpdate.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/TermUpdate.stat rename to scalafmt-tests/shared/src/test/resources/unit/TermUpdate.stat diff --git a/scalafmt-tests/src/test/resources/unit/Trait.source b/scalafmt-tests/shared/src/test/resources/unit/Trait.source similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Trait.source rename to scalafmt-tests/shared/src/test/resources/unit/Trait.source diff --git a/scalafmt-tests/src/test/resources/unit/Type.stat b/scalafmt-tests/shared/src/test/resources/unit/Type.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Type.stat rename to scalafmt-tests/shared/src/test/resources/unit/Type.stat diff --git a/scalafmt-tests/src/test/resources/unit/TypeArgument.stat b/scalafmt-tests/shared/src/test/resources/unit/TypeArgument.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/TypeArgument.stat rename to scalafmt-tests/shared/src/test/resources/unit/TypeArgument.stat diff --git a/scalafmt-tests/src/test/resources/unit/UnaryApply.stat b/scalafmt-tests/shared/src/test/resources/unit/UnaryApply.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/UnaryApply.stat rename to scalafmt-tests/shared/src/test/resources/unit/UnaryApply.stat diff --git a/scalafmt-tests/src/test/resources/unit/Val.stat b/scalafmt-tests/shared/src/test/resources/unit/Val.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Val.stat rename to scalafmt-tests/shared/src/test/resources/unit/Val.stat diff --git a/scalafmt-tests/src/test/resources/unit/Xml.stat b/scalafmt-tests/shared/src/test/resources/unit/Xml.stat similarity index 100% rename from scalafmt-tests/src/test/resources/unit/Xml.stat rename to scalafmt-tests/shared/src/test/resources/unit/Xml.stat diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat b/scalafmt-tests/shared/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat similarity index 100% rename from scalafmt-tests/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat rename to scalafmt-tests/shared/src/test/resources/vertical-multiline/VerticalMultilineDefnSite.stat diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/afterImplicitKW.stat b/scalafmt-tests/shared/src/test/resources/vertical-multiline/afterImplicitKW.stat similarity index 100% rename from scalafmt-tests/src/test/resources/vertical-multiline/afterImplicitKW.stat rename to scalafmt-tests/shared/src/test/resources/vertical-multiline/afterImplicitKW.stat diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/beforeImplicitKW.stat b/scalafmt-tests/shared/src/test/resources/vertical-multiline/beforeImplicitKW.stat similarity index 100% rename from scalafmt-tests/src/test/resources/vertical-multiline/beforeImplicitKW.stat rename to scalafmt-tests/shared/src/test/resources/vertical-multiline/beforeImplicitKW.stat diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/excludeDanglingInDef.stat b/scalafmt-tests/shared/src/test/resources/vertical-multiline/excludeDanglingInDef.stat similarity index 100% rename from scalafmt-tests/src/test/resources/vertical-multiline/excludeDanglingInDef.stat rename to scalafmt-tests/shared/src/test/resources/vertical-multiline/excludeDanglingInDef.stat diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/newlineAfterOpenParen.stat b/scalafmt-tests/shared/src/test/resources/vertical-multiline/newlineAfterOpenParen.stat similarity index 100% rename from scalafmt-tests/src/test/resources/vertical-multiline/newlineAfterOpenParen.stat rename to scalafmt-tests/shared/src/test/resources/vertical-multiline/newlineAfterOpenParen.stat diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/verticalAlignMultilineOperators.stat b/scalafmt-tests/shared/src/test/resources/vertical-multiline/verticalAlignMultilineOperators.stat similarity index 100% rename from scalafmt-tests/src/test/resources/vertical-multiline/verticalAlignMultilineOperators.stat rename to scalafmt-tests/shared/src/test/resources/vertical-multiline/verticalAlignMultilineOperators.stat diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/verticalMultiline.stat b/scalafmt-tests/shared/src/test/resources/vertical-multiline/verticalMultiline.stat similarity index 100% rename from scalafmt-tests/src/test/resources/vertical-multiline/verticalMultiline.stat rename to scalafmt-tests/shared/src/test/resources/vertical-multiline/verticalMultiline.stat diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/verticalMultilineDangling.source b/scalafmt-tests/shared/src/test/resources/vertical-multiline/verticalMultilineDangling.source similarity index 100% rename from scalafmt-tests/src/test/resources/vertical-multiline/verticalMultilineDangling.source rename to scalafmt-tests/shared/src/test/resources/vertical-multiline/verticalMultilineDangling.source diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/verticalMultilineDefnSiteNoDangling.stat b/scalafmt-tests/shared/src/test/resources/vertical-multiline/verticalMultilineDefnSiteNoDangling.stat similarity index 100% rename from scalafmt-tests/src/test/resources/vertical-multiline/verticalMultilineDefnSiteNoDangling.stat rename to scalafmt-tests/shared/src/test/resources/vertical-multiline/verticalMultilineDefnSiteNoDangling.stat diff --git a/scalafmt-tests/src/test/resources/vertical-multiline/withSpacesInParentheses.stat b/scalafmt-tests/shared/src/test/resources/vertical-multiline/withSpacesInParentheses.stat similarity index 100% rename from scalafmt-tests/src/test/resources/vertical-multiline/withSpacesInParentheses.stat rename to scalafmt-tests/shared/src/test/resources/vertical-multiline/withSpacesInParentheses.stat diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/CommentTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/CommentTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/CommentTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/CommentTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/Debug.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/Debug.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/Debug.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/Debug.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/EmptyFileTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/EmptyFileTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/EmptyFileTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/EmptyFileTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/FidelityTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/FidelityTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/FidelityTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/FidelityTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/FormatTests.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/FormatTests.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/FormatTests.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/FormatTests.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/LineEndingsTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/LineEndingsTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/LineEndingsTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/LineEndingsTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/ManualTests.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/ManualTests.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/ManualTests.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/ManualTests.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/RangeTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/RangeTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/RangeTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/RangeTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtConfigTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/ScalafmtConfigTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtConfigTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/ScalafmtConfigTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtRunnerTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/ScalafmtRunnerTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtRunnerTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/ScalafmtRunnerTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/ScalafmtTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/ScalafmtTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/ScalafmtTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/UnitTests.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/UnitTests.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/UnitTests.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/UnitTests.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/cli/CliOptionsTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/CliOptionsTest.scala similarity index 93% rename from scalafmt-tests/src/test/scala/org/scalafmt/cli/CliOptionsTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/CliOptionsTest.scala index d8d550edfe..fcd3011d4b 100644 --- a/scalafmt-tests/src/test/scala/org/scalafmt/cli/CliOptionsTest.scala +++ b/scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/CliOptionsTest.scala @@ -10,6 +10,7 @@ import org.scalafmt.Versions import metaconfig.Configured import munit.FunSuite +import java.io.FileNotFoundException class CliOptionsTest extends FunSuite { @@ -104,7 +105,13 @@ class CliOptionsTest extends FunSuite { val opt = baseCliOptions.copy(config = Some(configPath.toFile)) assert(opt.scalafmtConfig.isInstanceOf[Configured.NotOk]) val confError = opt.scalafmtConfig.asInstanceOf[Configured.NotOk].error - assert(confError.cause.exists(_.isInstanceOf[NoSuchFileException])) + // native - FileNotFoundException, JVM - NoSuchFileException + assert( + confError.cause.exists(err => + err.isInstanceOf[NoSuchFileException] || err + .isInstanceOf[FileNotFoundException] + ) + ) } test(".scalafmtConfig returns Configured.NotOk for invalid configuration") { diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/cli/CliTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/CliTest.scala similarity index 96% rename from scalafmt-tests/src/test/scala/org/scalafmt/cli/CliTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/CliTest.scala index 35f5aaec76..c386f355c4 100644 --- a/scalafmt-tests/src/test/scala/org/scalafmt/cli/CliTest.scala +++ b/scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/CliTest.scala @@ -17,6 +17,7 @@ import org.scalafmt.cli.FileTestOps._ import org.scalafmt.config.{Config, ProjectFiles, ScalafmtConfig} import org.scalafmt.util.{AbsoluteFile, FileOps} import org.scalafmt.util.OsSpecific._ +import org.scalafmt.config.PlatformConfig abstract class AbstractCliTest extends FunSuite { def mkArgs(str: String): Array[String] = @@ -103,10 +104,17 @@ abstract class AbstractCliTest extends FunSuite { trait CliTestBehavior { this: AbstractCliTest => def testCli(version: String) { - val label = if (version == Versions.version) "core" else "dynamic" + val (label, isDynamic) = + if (version == Versions.version) ("core", false) else ("dynamic", true) + def assumeNotDynamicOnNative(): Unit = + assume( + !(isDynamic && PlatformConfig.isNative), + "Dynamic module is incompatible with Scala Native" + ) val dialectError = if (version == Versions.version) " [dialect default]" else "" test(s"scalafmt tmpFile tmpFile2: $label") { + assumeNotDynamicOnNative() val originalTmpFile = Files.createTempFile("prefix", ".scala") val originalTmpFile2 = Files.createTempFile("prefix2", ".scala") val scalafmtConfig = Files.createTempFile("scalafmtConfig", ".scala") @@ -133,6 +141,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"scalafmt --stdout tmpFile prints to stdout: $label") { + assumeNotDynamicOnNative() val originalTmpFile = Files.createTempFile("prefix", ".scala") Files.write(originalTmpFile, unformatted.getBytes) val args = Array( @@ -154,6 +163,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"scalafmt --stdin --assume-filename: $label") { + assumeNotDynamicOnNative() val scalafmtConfig = Files.createTempFile(".scalafmt", ".conf") val config = s""" |version="$version" @@ -187,6 +197,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"scalafmt --test tmpFile is left unformatted: $label") { + assumeNotDynamicOnNative() val tmpFile = Files.createTempFile("prefix", ".scala") Files.write(tmpFile, unformatted.getBytes) val args = Array( @@ -202,6 +213,7 @@ trait CliTestBehavior { this: AbstractCliTest => assertNoDiff(str, unformatted) } test(s"scalafmt --test fails with non zero exit code $label") { + assumeNotDynamicOnNative() val tmpFile = Files.createTempFile("prefix", ".scala") Files.write(tmpFile, unformatted.getBytes) val args = Array( @@ -216,6 +228,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"scalafmt foo.randomsuffix is formatted: $label") { + assumeNotDynamicOnNative() val tmpFile = Files.createTempFile("prefix", "randomsuffix") Files.write(tmpFile, unformatted.getBytes) val args = Array( @@ -230,6 +243,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"handles .scala, .sbt, and .sc files: $label") { + assumeNotDynamicOnNative() val input = string2dir( s"""|/foobar.scala |object A { } @@ -262,6 +276,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"excludefilters are respected: $label") { + assumeNotDynamicOnNative() val input = string2dir( s"""|/foo.sbt |lazy val x = project @@ -313,6 +328,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"scalafmt doesnotexist.scala throws error: $label") { + assumeNotDynamicOnNative() def check(filename: String): Unit = { val args = Array( s"$filename.scala".asFilename, @@ -328,6 +344,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"scalafmt (no matching files) throws error: $label") { + assumeNotDynamicOnNative() val scalafmtConfig: Path = Files.createTempFile(".scalafmt", ".conf") val config: String = s""" |version="$version" @@ -342,6 +359,7 @@ trait CliTestBehavior { this: AbstractCliTest => test( s"scalafmt (no matching files) is okay with --mode diff and --stdin: $label" ) { + assumeNotDynamicOnNative() val diff = getConfig( Array( "--mode", @@ -364,6 +382,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"scalafmt (no arg) read config from git repo: $label") { + assumeNotDynamicOnNative() val input = string2dir( s"""|/foo.scala |object FormatMe { @@ -402,6 +421,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"scalafmt (no arg, no config): $label") { + assumeNotDynamicOnNative() noArgTest( string2dir( """|/foo.scala @@ -423,6 +443,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"config is read even from nested dir: $label") { + assumeNotDynamicOnNative() val original = "object a { val x = 1 }" val expected = """|object a { @@ -452,6 +473,7 @@ trait CliTestBehavior { this: AbstractCliTest => test( s"if project.includeFilters isn't modified (and files aren't passed manually), it should ONLY accept scala and sbt files: $label" ) { + assumeNotDynamicOnNative() val root = string2dir( s""" @@ -489,6 +511,7 @@ trait CliTestBehavior { this: AbstractCliTest => test( s"includeFilters are ignored for full paths but NOT test for passed directories: $label" ) { + assumeNotDynamicOnNative() val root = string2dir( s""" @@ -521,6 +544,7 @@ trait CliTestBehavior { this: AbstractCliTest => test( s"includeFilters are respected for full paths but NOT test for passed directories: $label" ) { + assumeNotDynamicOnNative() val root = string2dir( s""" @@ -552,6 +576,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"--config accepts absolute paths: $label") { + assumeNotDynamicOnNative() val root = string2dir( s"""/scalafmt.conf |version = "$version" @@ -574,6 +599,7 @@ trait CliTestBehavior { this: AbstractCliTest => // These are tests for deprecated flags test(s"scalafmt -i -f file1,file2,file3 should still work: $label") { + assumeNotDynamicOnNative() val file1 = Files.createTempFile("prefix", ".scala") val file2 = Files.createTempFile("prefix2", ".scala") val file3 = Files.createTempFile("prefix3", ".scala") @@ -599,6 +625,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"parse error is formatted nicely: $label") { + assumeNotDynamicOnNative() val input = """|/foo.scala |object A { foo( } @@ -625,6 +652,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"command line argument error: $label") { + assumeNotDynamicOnNative() val exit = Console.withErr(NoopOutputStream.printStream) { Cli.mainWithOptions( Array("--foobar"), @@ -635,6 +663,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"--test failure prints out unified diff: $label") { + assumeNotDynamicOnNative() val fooFile = "foo.scala" val input = s"""|/.scalafmt.conf @@ -666,6 +695,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"--test succeeds even with parse error: $label") { + assumeNotDynamicOnNative() val input = """|/foo.scala |object A { @@ -689,6 +719,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"--test fails with parse error if fatalWarnings=true: $label") { + assumeNotDynamicOnNative() val input = s"""|/.scalafmt.conf |runner.fatalWarnings = true @@ -714,6 +745,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"exception is thrown on invalid .scalafmt.conf: $label") { + assumeNotDynamicOnNative() val input = s"""/.scalafmt.conf |version="$version" @@ -737,6 +769,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"eof: $label") { + assumeNotDynamicOnNative() val in = Files.createTempFile("scalafmt", "Foo.scala") Files.write(in, "object A".getBytes(StandardCharsets.UTF_8)) val exit = Cli.mainWithOptions(Array(in.toString), baseCliOptions) @@ -746,6 +779,7 @@ trait CliTestBehavior { this: AbstractCliTest => } test(s"--config-str should be used if it is specified: $label") { + assumeNotDynamicOnNative() val expected = "This message should be shown" val unexpected = "This message should not be shown" val input = @@ -779,6 +813,7 @@ trait CliTestBehavior { this: AbstractCliTest => test( s"--list enable scalafmt to output a list of unformatted files with ExitCode.TestError: $label" ) { + assumeNotDynamicOnNative() val input = s"""|/.scalafmt.conf |version = "$version" diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/cli/FakeGitOps.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/FakeGitOps.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/cli/FakeGitOps.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/FakeGitOps.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/cli/FileTestOps.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/FileTestOps.scala similarity index 94% rename from scalafmt-tests/src/test/scala/org/scalafmt/cli/FileTestOps.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/FileTestOps.scala index b7ce4cd34e..50636ed6df 100644 --- a/scalafmt-tests/src/test/scala/org/scalafmt/cli/FileTestOps.scala +++ b/scalafmt-tests/shared/src/test/scala/org/scalafmt/cli/FileTestOps.scala @@ -6,6 +6,7 @@ import java.nio.file.Files import org.scalafmt.config.ScalafmtConfig import org.scalafmt.util.AbsoluteFile import org.scalafmt.util.FileOps +import org.scalafmt.internal.RegexCompat object FileTestOps { @@ -14,7 +15,7 @@ object FileTestOps { */ def string2dir(layout: String): AbsoluteFile = { val root = Files.createTempDirectory("root").toFile - layout.split("(?=\n/)").foreach { row => + RegexCompat.splitByBeforeTextMatching(layout, "\n/").foreach { row => val path :: contents :: Nil = row.stripPrefix("\n").split("\n", 2).toList val file = new File(root, path) diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/DynamicSuite.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/dynamic/DynamicSuite.scala similarity index 99% rename from scalafmt-tests/src/test/scala/org/scalafmt/dynamic/DynamicSuite.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/dynamic/DynamicSuite.scala index 837f071b93..cb5f8a1f92 100644 --- a/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/DynamicSuite.scala +++ b/scalafmt-tests/shared/src/test/scala/org/scalafmt/dynamic/DynamicSuite.scala @@ -14,8 +14,12 @@ import scala.reflect.ClassTag import scala.{meta => m} import munit.FunSuite import munit.Location +import org.scalafmt.config.PlatformConfig class DynamicSuite extends FunSuite { + + override def munitIgnore: Boolean = PlatformConfig.isNative + class Format(name: String, cfgFunc: ScalafmtDynamic => ScalafmtDynamic) { val download = new ByteArrayOutputStream() def downloadLogs: String = download.toString() diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/PositionSyntax.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/dynamic/PositionSyntax.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/dynamic/PositionSyntax.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/dynamic/PositionSyntax.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/dynamic/ScalafmtVersionSuite.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/dynamic/ScalafmtVersionSuite.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/dynamic/ScalafmtVersionSuite.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/dynamic/ScalafmtVersionSuite.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/stats/GitInfo.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/GitInfo.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/stats/GitInfo.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/GitInfo.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/stats/JavaInfo.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/JavaInfo.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/stats/JavaInfo.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/JavaInfo.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/stats/MachineStats.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/MachineStats.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/stats/MachineStats.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/MachineStats.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/stats/OsInfo.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/OsInfo.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/stats/OsInfo.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/OsInfo.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/stats/RuntimeInfo.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/RuntimeInfo.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/stats/RuntimeInfo.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/RuntimeInfo.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/stats/TestStats.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/TestStats.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/stats/TestStats.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/stats/TestStats.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/CanRunTests.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/CanRunTests.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/CanRunTests.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/CanRunTests.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/DeleteTree.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/DeleteTree.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/DeleteTree.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/DeleteTree.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/DiffTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/DiffTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/DiffTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/DiffTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/ErrorTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/ErrorTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/ErrorTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/ErrorTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/FormatAssertions.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/FormatAssertions.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/FormatAssertions.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/FormatAssertions.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/FormatException.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/FormatException.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/FormatException.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/FormatException.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/FormatOutput.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/FormatOutput.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/FormatOutput.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/FormatOutput.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/GitOpsTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/GitOpsTest.scala similarity index 99% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/GitOpsTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/GitOpsTest.scala index 4a0351c120..01b854c9a3 100644 --- a/scalafmt-tests/src/test/scala/org/scalafmt/util/GitOpsTest.scala +++ b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/GitOpsTest.scala @@ -70,9 +70,10 @@ class GitOpsTest extends FunSuite { dir.orElse(ops.rootDir).get.jfile.toPath, "dir_" ) + val destPath = destDir.resolve("new_file") val dest = Files.move( f.jfile.toPath, - destDir, + destPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING ) rm(f) diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/HasTests.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/HasTests.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/HasTests.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/HasTests.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/Report.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/Report.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/Report.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/Report.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/Result.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/Result.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/Result.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/Result.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/StyleMapTest.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/StyleMapTest.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/StyleMapTest.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/StyleMapTest.scala diff --git a/scalafmt-tests/src/test/scala/org/scalafmt/util/Tabulator.scala b/scalafmt-tests/shared/src/test/scala/org/scalafmt/util/Tabulator.scala similarity index 100% rename from scalafmt-tests/src/test/scala/org/scalafmt/util/Tabulator.scala rename to scalafmt-tests/shared/src/test/scala/org/scalafmt/util/Tabulator.scala