From a4c2a3d055113a94a350a39c11df6f9dcd4aaf3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Mon, 3 Apr 2017 13:05:05 +0200 Subject: [PATCH 1/7] Clean up cli. - Consistent handling of --rewrites. - Consistent error handling. - No default rewrites by default. - imports.organize=false by default. --- build.sbt | 10 +++---- cli/src/main/scala/scalafix/cli/Cli.scala | 26 ++++++++++--------- cli/src/test/scala/scalafix/cli/CliTest.scala | 13 ++++++++-- .../scala/scalafix/config/ImportsConfig.scala | 3 ++- .../scalafix/config/ScalafixConfig.scala | 14 +++++----- .../main/scala/scalafix/syntax/package.scala | 10 ++++++- project/Dependencies.scala | 12 ++++----- .../scalafix/nsc/ScalafixNscPlugin.scala | 4 ++- .../checkSyntax/DemandJsGlobal.source | 1 + .../test/scala/scalafix/SemanticTests.scala | 1 + 10 files changed, 58 insertions(+), 36 deletions(-) diff --git a/build.sbt b/build.sbt index 2c98391da..73078d1c8 100644 --- a/build.sbt +++ b/build.sbt @@ -168,7 +168,7 @@ lazy val core = project "com.lihaoyi" %% "sourcecode" % "0.1.3", metaconfig, scalameta, - scalahost(scalaVersion.value), + scalahost, "org.scala-lang" % "scala-reflect" % scalaVersion.value ) ) @@ -180,7 +180,7 @@ lazy val `scalafix-nsc` = project allSettings, libraryDependencies ++= Seq( "org.scala-lang" % "scala-compiler" % scalaVersion.value, - scalahostNsc(scalaVersion.value), + scalahostNsc, ammonite % Test, // integration property tests "org.typelevel" %% "catalysts-platform" % "0.0.5" % Test, @@ -229,10 +229,6 @@ lazy val cli = project .settings( allSettings, packSettings, - libraryDependencies ++= Seq( - // NB: The Mirror has an undeclared dependency on the scalahost-nsc package. - scalahostNsc(scalaVersion.value) - ), moduleName := "scalafix-cli", packJvmOpts := Map( "scalafix" -> jvmOptions, @@ -290,7 +286,7 @@ lazy val `scalafix-tests` = project .settings( allSettings, noPublish, - testQuick := {}, + testQuick := {}, // these tests are slow. parallelExecution in Test := true, libraryDependencies ++= Seq( ammonite diff --git a/cli/src/main/scala/scalafix/cli/Cli.scala b/cli/src/main/scala/scalafix/cli/Cli.scala index 921ad02f2..33e7e56d7 100644 --- a/cli/src/main/scala/scalafix/cli/Cli.scala +++ b/cli/src/main/scala/scalafix/cli/Cli.scala @@ -4,6 +4,7 @@ import scala.collection.GenSeq import scala.meta.inputs.Input import scala.util.control.NonFatal import scalafix.Failure +import scalafix.syntax._ import scalafix.Fixed import scalafix.Scalafix import scalafix.cli.termdisplay.TermDisplay @@ -94,18 +95,23 @@ case class ScalafixOptions( /** Returns ScalafixConfig from .scalafix.conf, it exists and --config was not passed. */ lazy val resolvedConfig: ScalafixConfig = config match { - case None => ScalafixConfig.auto(common.workingDirectoryFile) + case None => + ScalafixConfig + .auto(common.workingDirectoryFile) + .getOrElse( + ScalafixConfig.default + ) + .withRewrites(_ ++ rewrites) case Some(x) => x } - lazy val resolvedMirror: Option[ScalafixMirror] = + lazy val resolvedMirror: Either[String, Option[ScalafixMirror]] = (classpath, sourcepath) match { case (Some(cp), Some(sp)) => - Some(ScalafixMirror.fromMirror(scala.meta.Mirror(cp, sp))) - case (None, None) => None + Right(Some(ScalafixMirror.fromMirror(scala.meta.Mirror(cp, sp)))) + case (None, None) => Right(None) case _ => - // FIXME: improve during review. - throw new Exception( + Left( "The semantic API was partially configured: both a classpath and sourcepath are required.") } @@ -153,7 +159,7 @@ object Cli { def handleFile(file: File, options: ScalafixOptions): ExitStatus = { val fixed = Scalafix.fix(Input.File(file), options.resolvedConfig, - options.resolvedMirror) + options.resolvedMirror.get) fixed match { case Fixed.Success(code) => if (options.inPlace) { @@ -212,11 +218,7 @@ object Cli { OptionsParser.withHelp.detailedParse(args) match { case Right((help, extraFiles, ls)) => Right( - help.map(c => - c.copy( - files = help.base.files ++ extraFiles, - rewrites = c.rewrites ++ c.config.map(_.rewrites).getOrElse(Nil) - )) + help.map(_.copy(files = help.base.files ++ extraFiles)) ) case Left(x) => Left(x) } diff --git a/cli/src/test/scala/scalafix/cli/CliTest.scala b/cli/src/test/scala/scalafix/cli/CliTest.scala index 70d0c1fce..2beffe1aa 100644 --- a/cli/src/test/scala/scalafix/cli/CliTest.scala +++ b/cli/src/test/scala/scalafix/cli/CliTest.scala @@ -2,6 +2,7 @@ package scalafix.cli import scalafix.config.ScalafixConfig import scalafix.rewrite.ExplicitImplicit +import scalafix.rewrite.ProcedureSyntax import scalafix.rewrite.VolatileLazyVal import scalafix.util.DiffAssertions import scalafix.util.FileOps @@ -67,7 +68,11 @@ class CliTest extends FunSuite with DiffAssertions { val file = File.createTempFile("prefix", ".scala") FileOps.writeFile(file, original) Cli.runOn( - ScalafixOptions(files = List(file.getAbsolutePath), inPlace = true)) + ScalafixOptions( + rewrites = List(ProcedureSyntax), + files = List(file.getAbsolutePath), + inPlace = true + )) assertNoDiff(FileOps.readFile(file), expected) } @@ -106,8 +111,12 @@ class CliTest extends FunSuite with DiffAssertions { } test("--rewrites") { - assert(Cli.parse(Seq("--rewrites", "VolatileLazyVal")).isRight) + val Right(WithHelp(_, _, obtained)) = + Cli.parse(Seq("--rewrites", "VolatileLazyVal")) + assert(obtained.rewrites == List(VolatileLazyVal)) assert(Cli.parse(Seq("--rewrites", "Foobar")).isLeft) + assert( + Cli.parse(Seq("--rewrites", "file:rewrites/MyRewrite.scala")).isRight) } test("error returns failure exit code") { diff --git a/core/src/main/scala/scalafix/config/ImportsConfig.scala b/core/src/main/scala/scalafix/config/ImportsConfig.scala index a0d3248d5..2d3e2c82b 100644 --- a/core/src/main/scala/scalafix/config/ImportsConfig.scala +++ b/core/src/main/scala/scalafix/config/ImportsConfig.scala @@ -13,7 +13,8 @@ case class ImportsConfig( // imports, see https://github.com/scalacenter/scalafix/issues/83 expandRelative: Boolean = false, spaceAroundCurlyBrace: Boolean = false, - organize: Boolean = true, + // Disabled since users should explicitly opt into it. + organize: Boolean = false, removeUnused: Boolean = true, alwaysUsed: List[Ref] = List(), groups: List[FilterMatcher] = List( diff --git a/core/src/main/scala/scalafix/config/ScalafixConfig.scala b/core/src/main/scala/scalafix/config/ScalafixConfig.scala index 5606d0a4e..47b2c108f 100644 --- a/core/src/main/scala/scalafix/config/ScalafixConfig.scala +++ b/core/src/main/scala/scalafix/config/ScalafixConfig.scala @@ -1,12 +1,11 @@ package scalafix.config -import scalafix.syntax._ import scala.collection.immutable.Seq import scala.meta._ import scala.meta.dialects.Scala211 import scala.meta.parsers.Parse import scalafix.rewrite.ScalafixRewrite -import scalafix.rewrite.ScalafixRewrites +import scalafix.syntax._ import scalafix.util.FileOps import java.io.File @@ -16,7 +15,7 @@ import metaconfig.hocon.Hocon2Class @metaconfig.ConfigReader case class ScalafixConfig( - rewrites: List[ScalafixRewrite] = ScalafixRewrites.default, + rewrites: List[ScalafixRewrite] = Nil, parser: Parse[_ <: Tree] = Parse.parseSource, imports: ImportsConfig = ImportsConfig(), patches: PatchConfig = PatchConfig(), @@ -24,6 +23,9 @@ case class ScalafixConfig( fatalWarnings: Boolean = true, dialect: Dialect = Scala211 ) { + def withRewrites( + f: List[ScalafixRewrite] => List[ScalafixRewrite]): ScalafixConfig = + copy(rewrites = f(rewrites).distinct) implicit val importsConfigReader: Reader[ImportsConfig] = imports.reader implicit val patchConfigReader: Reader[PatchConfig] = patches.reader implicit val debugConfigReader: Reader[DebugConfig] = debug.reader @@ -34,10 +36,10 @@ object ScalafixConfig { val default = ScalafixConfig() /** Returns config from current working directory, if .scalafix.conf exists. */ - def auto(workingDir: File): ScalafixConfig = { + def auto(workingDir: File): Option[ScalafixConfig] = { val file = new File(workingDir, ".scalafix.conf") - if (file.isFile && file.exists()) ScalafixConfig.fromFile(file).get - else ScalafixConfig.default + if (file.isFile && file.exists()) Some(ScalafixConfig.fromFile(file).get) + else None } def fromFile(file: File): Either[Throwable, ScalafixConfig] = diff --git a/core/src/main/scala/scalafix/syntax/package.scala b/core/src/main/scala/scalafix/syntax/package.scala index f7c987677..210e6bb3a 100644 --- a/core/src/main/scala/scalafix/syntax/package.scala +++ b/core/src/main/scala/scalafix/syntax/package.scala @@ -31,12 +31,20 @@ package object syntax { } } - implicit class XtensionEither[B](either: Either[Throwable, B]) { + implicit class XtensionEitherThrowable[B](either: Either[Throwable, B]) { def get: B = either match { case Right(b) => b case Left(e) => throw e } } + + implicit class XtensionEitherString[B](either: Either[String, B]) { + def get: B = either match { + case Right(b) => b + case Left(e) => throw new Exception(e) + } + } + implicit class XtensionTermRef(ref: Term.Ref) { def toTypeRef: Type.Ref = ref match { case Term.Name(name) => Type.Name(name) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 71a937158..35e40e0fa 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,10 +8,10 @@ object Dependencies { val scalametaV = "1.7.0-485-086c4ac9" val paradiseV = "3.0.0-M7" var testClasspath: String = "empty" - def scalahost(scalaVersion: String): ModuleID = "org.scalameta" % s"scalahost_$scalaVersion" % scalametaV - def scalahostNsc(scalaVersion: String): ModuleID = "org.scalameta" % s"scalahost-nsc_$scalaVersion" % scalametaV - def scalameta: ModuleID = "org.scalameta" %% "contrib" % scalametaV - def scalatest: ModuleID = "org.scalatest" %% "scalatest" % "3.0.0" - def metaconfig: ModuleID = "com.geirsson" %% "metaconfig-hocon" % "0.1.2" - def ammonite = "com.lihaoyi" %% "ammonite-ops" % "0.8.2" + def scalahost: ModuleID = "org.scalameta" % s"scalahost" % scalametaV cross CrossVersion.full + def scalahostNsc: ModuleID = "org.scalameta" % s"scalahost-nsc" % scalametaV cross CrossVersion.full + def scalameta: ModuleID = "org.scalameta" %% "contrib" % scalametaV + def scalatest: ModuleID = "org.scalatest" %% "scalatest" % "3.0.0" + def metaconfig: ModuleID = "com.geirsson" %% "metaconfig-hocon" % "0.1.2" + def ammonite = "com.lihaoyi" %% "ammonite-ops" % "0.8.2" } diff --git a/scalafix-nsc/src/main/scala/scalafix/nsc/ScalafixNscPlugin.scala b/scalafix-nsc/src/main/scala/scalafix/nsc/ScalafixNscPlugin.scala index 5ac352c41..9f8cfc298 100644 --- a/scalafix-nsc/src/main/scala/scalafix/nsc/ScalafixNscPlugin.scala +++ b/scalafix-nsc/src/main/scala/scalafix/nsc/ScalafixNscPlugin.scala @@ -13,7 +13,9 @@ import java.io.File class ScalafixNscPlugin(val global: Global) extends Plugin { var config: ScalafixConfig = - ScalafixConfig.auto(new File(sys.props("user.dir"))) + ScalafixConfig + .auto(new File(sys.props("user.dir"))) + .getOrElse(ScalafixConfig.default) private val scalafixComponent = new ScalafixNscComponent(this, global, () => config) // IMPORTANT. This needs to happen before we create ScalahostPlugin in order to hijack the diff --git a/scalafix-nsc/src/test/resources/checkSyntax/DemandJsGlobal.source b/scalafix-nsc/src/test/resources/checkSyntax/DemandJsGlobal.source index 7fedc70fb..71a94ce87 100644 --- a/scalafix-nsc/src/test/resources/checkSyntax/DemandJsGlobal.source +++ b/scalafix-nsc/src/test/resources/checkSyntax/DemandJsGlobal.source @@ -1,4 +1,5 @@ rewrites = [DemandJSGlobal] +imports.organize=true <<< pull request example import scala.scalajs.js import scala.scalajs.js.annotation.JSName diff --git a/scalafix-nsc/src/test/scala/scalafix/SemanticTests.scala b/scalafix-nsc/src/test/scala/scalafix/SemanticTests.scala index a4ae36ab4..24cbed8ea 100644 --- a/scalafix-nsc/src/test/scala/scalafix/SemanticTests.scala +++ b/scalafix-nsc/src/test/scala/scalafix/SemanticTests.scala @@ -34,6 +34,7 @@ class SemanticTests extends FunSuite with DiffAssertions { self => def fail(msg: String) = sys.error(s"ReflectToMeta initialization failed: $msg") val classpath = System.getProperty("sbt.paths.scalafixNsc.test.classes") + logger.elem(classpath) val scalacOptions = Seq( "-cp", classpath, From 06b5605991cdb2ecff62745a1c6d1125407b2fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Mon, 3 Apr 2017 14:52:02 +0200 Subject: [PATCH 2/7] Make scalafix full cross-version. Scalafix depends on scala-compiler/reflect both for runtime compilation of rewrites and scalahost's online mirror. Different versions of scala-compiler and scala-reflect should not be mixed on the same classpath, even for patch versions. However, we'll try to only publish scalafix for the latest patch version to simplify the release step. I'm happy to reconsider publishing scalafix for multiple patch version, if there is demand for it --- build.sbt | 23 +++++++---------------- project/plugins.sbt | 1 - 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/build.sbt b/build.sbt index 73078d1c8..d6f0f5c89 100644 --- a/build.sbt +++ b/build.sbt @@ -9,13 +9,6 @@ lazy val crossVersions = Seq( "2.12.1" ) -lazy val buildSettings = Seq( - ) - -lazy val jvmOptions = Seq( - "-Xss4m" -) - lazy val compilerOptions = Seq( "-deprecation", "-encoding", @@ -161,6 +154,7 @@ lazy val core = project allSettings, buildInfoSettings, metaconfigSettings, + isFullCrossVersion, moduleName := "scalafix-core", dependencyOverrides += scalameta, libraryDependencies ++= Seq( @@ -178,6 +172,7 @@ lazy val core = project lazy val `scalafix-nsc` = project .settings( allSettings, + isFullCrossVersion, libraryDependencies ++= Seq( "org.scala-lang" % "scala-compiler" % scalaVersion.value, scalahostNsc, @@ -228,17 +223,9 @@ lazy val `scalafix-nsc` = project lazy val cli = project .settings( allSettings, - packSettings, + isFullCrossVersion, moduleName := "scalafix-cli", - packJvmOpts := Map( - "scalafix" -> jvmOptions, - "scalafix_ng_server" -> jvmOptions - ), mainClass in assembly := Some("scalafix.cli.Cli"), - packMain := Map( - "scalafix" -> "scalafix.cli.Cli", - "scalafix_ng_server" -> "com.martiansoftware.nailgun.NGServer" - ), libraryDependencies ++= Seq( "com.github.scopt" %% "scopt" % "3.5.0", "com.github.alexarchambault" %% "case-app" % "1.1.3", @@ -340,3 +327,7 @@ def exposePaths(projectName: String, } ) } + +lazy val isFullCrossVersion = Seq( + crossVersion := CrossVersion.full +) diff --git a/project/plugins.sbt b/project/plugins.sbt index 23e385b84..44dd76a01 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,7 +4,6 @@ addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3") addSbtPlugin("com.lihaoyi" % "scalatex-sbt-plugin" % "0.3.7") addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-M15") addSbtPlugin("org.wartremover" % "sbt-wartremover" % "1.2.1") -addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.8.0") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1") From 4b7911cec986b0d2c0613dbd9d70b8b2361225a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Mon, 3 Apr 2017 16:24:23 +0200 Subject: [PATCH 3/7] Test scala.meta.Mirror cli options --- build.sbt | 24 ++++++++++++++++++ cli/src/main/scala/scalafix/cli/Cli.scala | 25 ++++++++++++++++--- cli/src/test/scala/scalafix/cli/CliTest.scala | 23 +++++++++++++++++ .../main/scala/scalafix/syntax/package.scala | 19 ++++++++++++-- 4 files changed, 85 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index d6f0f5c89..348b61d4a 100644 --- a/build.sbt +++ b/build.sbt @@ -225,6 +225,10 @@ lazy val cli = project allSettings, isFullCrossVersion, moduleName := "scalafix-cli", + fork.in(Test, test) := true, + baseDirectory.in(test) := file("."), + javaOptions.in(test) += + s"-Dscalafix.scalahost.pluginpath=${scalahostNscPluginPath.value}", mainClass in assembly := Some("scalafix.cli.Cli"), libraryDependencies ++= Seq( "com.github.scopt" %% "scopt" % "3.5.0", @@ -331,3 +335,23 @@ def exposePaths(projectName: String, lazy val isFullCrossVersion = Seq( crossVersion := CrossVersion.full ) + +lazy val scalahostNscPluginPath = Def.task { + val files = update + .in(dummyScalahostProject) + .value + .allFiles + val path = files.find { file => + val path = file.getAbsolutePath + path.endsWith(s"scalahost-nsc_${scalaVersion.value}.jar") + }.get + path.getAbsolutePath +} + +lazy val dummyScalahostProject = project + .in(file("target/dummy")) + .settings( + allSettings, + description := "Just a project that has scalahost-nsc on the classpath.", + libraryDependencies += scalahostNsc + ) diff --git a/cli/src/main/scala/scalafix/cli/Cli.scala b/cli/src/main/scala/scalafix/cli/Cli.scala index 33e7e56d7..22b6adfa2 100644 --- a/cli/src/main/scala/scalafix/cli/Cli.scala +++ b/cli/src/main/scala/scalafix/cli/Cli.scala @@ -1,7 +1,11 @@ package scalafix.cli import scala.collection.GenSeq +import scala.meta.Mirror +import scala.meta.internal.scalahost.v1.offline import scala.meta.inputs.Input +import scala.util.Success +import scala.util.Try import scala.util.control.NonFatal import scalafix.Failure import scalafix.syntax._ @@ -63,6 +67,14 @@ case class ScalafixOptions( ) @ValueDescription( "File2.scala:File1.scala:src/main/scala" ) sourcepath: Option[String] = None, + @HelpMessage( + "java.io.File.pathSeparator separated list of" + ) @ValueDescription( + "File path to the scalahost-nsc compiler plugin fatjar, " + + "the same path that is passed in `-Xplugin:/scalahost.jar`. " + + "(optional) skip this option by adding org.scalameta:scalafix-nsc:x.y.z " + + "to the classpath of scalafix-cli, for example with coursier bootstrap. " + ) scalahostNscPluginPath: Option[String] = None, @HelpMessage( s"Additional rewrite rules to run. NOTE. rewrite.rules = [ .. ] from --config will also run." ) @ValueDescription( @@ -108,7 +120,12 @@ case class ScalafixOptions( lazy val resolvedMirror: Either[String, Option[ScalafixMirror]] = (classpath, sourcepath) match { case (Some(cp), Some(sp)) => - Right(Some(ScalafixMirror.fromMirror(scala.meta.Mirror(cp, sp)))) + val tryMirror = for { + pluginPath <- scalahostNscPluginPath.fold( + Try(offline.Mirror.autodetectScalahostNscPluginPath))(Success(_)) + mirror <- Try(ScalafixMirror.fromMirror(Mirror(cp, sp, pluginPath))) + } yield Option(mirror) + tryMirror.asEither.leftAsString case (None, None) => Right(None) case _ => Left( @@ -217,9 +234,9 @@ object Cli { def parse(args: Seq[String]): Either[String, WithHelp[ScalafixOptions]] = OptionsParser.withHelp.detailedParse(args) match { case Right((help, extraFiles, ls)) => - Right( - help.map(_.copy(files = help.base.files ++ extraFiles)) - ) + for { + _ <- help.base.resolvedMirror // validate + } yield help.map(_.copy(files = help.base.files ++ extraFiles)) case Left(x) => Left(x) } diff --git a/cli/src/test/scala/scalafix/cli/CliTest.scala b/cli/src/test/scala/scalafix/cli/CliTest.scala index 2beffe1aa..85b6b5cdd 100644 --- a/cli/src/test/scala/scalafix/cli/CliTest.scala +++ b/cli/src/test/scala/scalafix/cli/CliTest.scala @@ -119,6 +119,29 @@ class CliTest extends FunSuite with DiffAssertions { Cli.parse(Seq("--rewrites", "file:rewrites/MyRewrite.scala")).isRight) } + test("--sourcepath --classpath") { + assert(Cli.parse(List("--sourcepath", "foo.scala")).isLeft) + assert(Cli.parse(List("--classpath", "foo")).isLeft) + assert( // missing --scalahost-nsc-plugin-path + Cli + .parse(List("--sourcepath", "foo.scala", "--classpath", "bar")) + .isLeft) + // injected by javaOptions in build.sbt + val path = sys.props("scalafix.scalahost.pluginpath") + assert( + Cli + .parse( + List( + "--sourcepath", + "foo.scala", + "--classpath", + "bar", + "--scalahost-nsc-plugin-path", + path + )) + .isRight) + } + test("error returns failure exit code") { val file = File.createTempFile("prefix", ".scala") FileOps.writeFile(file, "object a { implicit val x = ??? }") diff --git a/core/src/main/scala/scalafix/syntax/package.scala b/core/src/main/scala/scalafix/syntax/package.scala index 210e6bb3a..2f54ad776 100644 --- a/core/src/main/scala/scalafix/syntax/package.scala +++ b/core/src/main/scala/scalafix/syntax/package.scala @@ -8,6 +8,7 @@ import scala.meta.semantic.v1.Completed import scala.meta.semantic.v1.Signature import scala.meta.semantic.v1.Symbol import scala.meta.tokens.Token +import scala.util.Success import scala.util.Try import scalafix.util.CanonicalImport import scalafix.util.ImportPatch @@ -31,8 +32,22 @@ package object syntax { } } - implicit class XtensionEitherThrowable[B](either: Either[Throwable, B]) { - def get: B = either match { + implicit class XtensionTryToEither[A](e: Try[A]) { + def asEither: Either[Throwable, A] = e match { + case Success(x) => Right(x) + case scala.util.Failure(x) => Left(x) + } + } + + implicit class XtensionEither[A, B](either: Either[A, B]) { + def map[C](f: B => C): Either[A, C] = either.right.map(f) + def flatMap[C >: A, D](f: B => Either[C, D]): Either[C, D] = + either.right.flatMap(f) + } + + implicit class XtensionEitherThrowable[A](either: Either[Throwable, A]) { + def leftAsString: Either[String, A] = either.left.map(_.getMessage) + def get: A = either match { case Right(b) => b case Left(e) => throw e } From 8347f6658dc2593a383321e1a2065568368b2718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Mon, 3 Apr 2017 17:07:10 +0200 Subject: [PATCH 4/7] Document scalafix-cli. - Add examples to help message. - Add installation instructions on website. - Add fatcli module to avoid --scalahost-nsc-plugin-path - Fix minor UI issues in cli --- build.sbt | 9 +++ cli/src/main/scala/scalafix/cli/Cli.scala | 61 ++++++++++++------- .../test/scala/scalafix/cli/FatCliTest.scala | 10 +++ readme/Installation.scalatex | 58 ++++++++++++++---- .../test/scala/scalafix/SemanticTests.scala | 1 - 5 files changed, 105 insertions(+), 34 deletions(-) create mode 100644 fatcli/src/test/scala/scalafix/cli/FatCliTest.scala diff --git a/build.sbt b/build.sbt index 348b61d4a..e89d5b020 100644 --- a/build.sbt +++ b/build.sbt @@ -237,6 +237,14 @@ lazy val cli = project ) ) .dependsOn(core, `scalafix-testutils` % Test) +lazy val fatcli = project + .settings( + allSettings, + isFullCrossVersion, + moduleName := "scalafix-fatcli", + libraryDependencies += scalahostNsc + ) + .dependsOn(cli) lazy val publishedArtifacts = Seq( publishLocal in `scalafix-nsc`, @@ -348,6 +356,7 @@ lazy val scalahostNscPluginPath = Def.task { path.getAbsolutePath } +// sbt makes it hard to do simple stuff like get the jar of a dependency. lazy val dummyScalahostProject = project .in(file("target/dummy")) .settings( diff --git a/cli/src/main/scala/scalafix/cli/Cli.scala b/cli/src/main/scala/scalafix/cli/Cli.scala index 22b6adfa2..63daa2db9 100644 --- a/cli/src/main/scala/scalafix/cli/Cli.scala +++ b/cli/src/main/scala/scalafix/cli/Cli.scala @@ -29,6 +29,7 @@ import java.util.regex.Pattern import caseapp._ import caseapp.core.WithHelp import com.martiansoftware.nailgun.NGContext +import org.scalameta.logger case class CommonOptions( @Hidden workingDirectory: String = System.getProperty("user.dir"), @@ -50,35 +51,38 @@ case class ScalafixOptions( ".scalafix.conf OR imports.organize=false" ) @ExtraName("c") config: Option[ScalafixConfig] = None, @HelpMessage( - "java.io.File.pathSeparator separated list of jar files" + - "or directories containing classfiles and `semanticdb` files." + - "The `semanticdb` files are emitted by the scalahost-nsc" + - "compiler plugin and are necessary for the semantic API to" + - "function. The classfiles + jar files are necessary for" + - "runtime compilation of quasiquotes when extracting" + - """symbols (that is, `q"scala.Predef".symbol`).""" + """java.io.File.pathSeparator separated list of jar files or directories + | containing classfiles and `semanticdb` files. The `semanticdb` + | files are emitted by the scalahost-nsc compiler plugin and + | are necessary for the semantic API to function. The + | classfiles + jar files are necessary forruntime compilation + | of quasiquotes when extracting symbols (that is, + | `q"scala.Predef".symbol`).""".stripMargin ) @ValueDescription( "entry1.jar:entry2.jar" ) classpath: Option[String] = None, @HelpMessage( - "java.io.File.pathSeparator separated list of" + - "Scala source files OR directories containing Scala" + - "source files." + """java.io.File.pathSeparator separated list of Scala source files OR + | directories containing Scala source files.""".stripMargin ) @ValueDescription( "File2.scala:File1.scala:src/main/scala" ) sourcepath: Option[String] = None, @HelpMessage( "java.io.File.pathSeparator separated list of" ) @ValueDescription( - "File path to the scalahost-nsc compiler plugin fatjar, " + - "the same path that is passed in `-Xplugin:/scalahost.jar`. " + - "(optional) skip this option by adding org.scalameta:scalafix-nsc:x.y.z " + - "to the classpath of scalafix-cli, for example with coursier bootstrap. " + """File path to the scalahost-nsc compiler plugin fatjar, the same path + | that is passed in `-Xplugin:/scalahost.jar`. + | (optional) skip this option by building the "scalafix-fatcli" + | module instead of "scalafix-cli."""".stripMargin ) scalahostNscPluginPath: Option[String] = None, @HelpMessage( - s"Additional rewrite rules to run. NOTE. rewrite.rules = [ .. ] from --config will also run." + s"""Rewrite rules to run. + | NOTE. rewrite.rules = [ .. ] from --config will also run.""".stripMargin ) @ValueDescription( - s"$ProcedureSyntax OR file:LocalFile.scala OR scala:full.Name OR https://gist.com/.../Rewrite.scala" + s"""$ProcedureSyntax OR + | file:LocalFile.scala OR + | scala:full.Name OR + | https://gist.com/.../Rewrite.scala""".stripMargin ) rewrites: List[ScalafixRewrite] = ScalafixRewrites.syntax, @HelpMessage( "Files to fix. Runs on all *.scala files if given a directory." @@ -89,12 +93,12 @@ case class ScalafixOptions( "If true, writes changes to files instead of printing to stdout." ) @ExtraName("i") inPlace: Boolean = false, @HelpMessage( - "Regex that is passed as first argument to " + - "fileToFix.replaceAll(outFrom, outTo)." + """Regex that is passed as first argument to + | fileToFix.replaceAll(outFrom, outTo).""".stripMargin ) @ValueDescription("/shared/") outFrom: String = "", @HelpMessage( - "Replacement string that is passed as second " + - "argument to fileToFix.replaceAll(outFrom, outTo)" + """Replacement string that is passed as second argument to + | fileToFix.replaceAll(outFrom, outTo)""".stripMargin ) @ValueDescription("/custom/") outTo: String = "", @HelpMessage( "If true, run on single thread. If false (default), use all available cores." @@ -105,6 +109,12 @@ case class ScalafixOptions( @Recurse common: CommonOptions = CommonOptions() ) { + lazy val absoluteFiles: List[File] = files.map { f => + val file = new File(f) + if (file.isAbsolute) file + else new File(new File(common.workingDirectory), f) + } + /** Returns ScalafixConfig from .scalafix.conf, it exists and --config was not passed. */ lazy val resolvedConfig: ScalafixConfig = config match { case None => @@ -142,6 +152,12 @@ object Cli { private val withHelp = OptionsMessages.withHelp val helpMessage: String = withHelp.helpMessage + s"""| + |Examples: + | $$ scalafix --rewrites ProcedureSyntax Code.scala # print fixed file to stdout + | $$ cat .scalafix.conf + | rewrites = [ProcedureSyntax] + | $$ scalafix Code.scala + | scalafix -i --rewrites ProcedureSyntax Code.scala # write fixed file in-place |Exit status codes: | ${ExitStatus.all.mkString("\n ")} |""".stripMargin @@ -185,9 +201,10 @@ object Cli { } else options.common.out.write(code.getBytes) ExitStatus.Ok case Fixed.Failed(e: Failure.ParseError) => - if (options.files.contains(file.getPath)) { + if (options.absoluteFiles.exists( + _.getAbsolutePath == file.getAbsolutePath)) { // Only log if user explicitly specified that file. - options.common.err.write(e.toString.getBytes()) + options.common.err.write((e.exception.getMessage + "\n").getBytes) } ExitStatus.ParseError case Fixed.Failed(failure) => diff --git a/fatcli/src/test/scala/scalafix/cli/FatCliTest.scala b/fatcli/src/test/scala/scalafix/cli/FatCliTest.scala new file mode 100644 index 000000000..9aaf7d4ac --- /dev/null +++ b/fatcli/src/test/scala/scalafix/cli/FatCliTest.scala @@ -0,0 +1,10 @@ +package scalafix.cli + +class FatCliTest extends org.scalatest.FunSuite { + test("--scalahost-nsc-plugin-path is not necessary") { + assert( + Cli + .parse(List("--sourcepath", "foo.scala", "--classpath", "bar")) + .isRight) + } +} diff --git a/readme/Installation.scalatex b/readme/Installation.scalatex index a0e2e4d13..0b25e5763 100644 --- a/readme/Installation.scalatex +++ b/readme/Installation.scalatex @@ -1,6 +1,9 @@ @import Main._ @import scalafix.Readme._ @import scalafix.rewrite._ +@import scalafix.{Versions => V} +@import scalafix.cli.Cli + @sect{Installation} @@ -109,17 +112,50 @@ Feedback is very welcome. Be prepared for breaking changes. - @sect{scalafix-nsc} - Scalafix can be used directly as a compiler plugin: + @sect{scalafix-cli} + The command line interface can be installed with + @lnk("coursier", "https://github.com/coursier/coursier#command-line"). + + Once you have the @code{coursier} command-line installed, run + @hl.scala - // download - https/repo1.maven.org/maven2/ch/epfl/scala/scalafix-nsc_2.12/@scalafix.Versions.version/scalafix-nsc_2.12-@(scalafix.Versions.version + ".jar") - // compile - scalac -Xplugin:/path/to/scalafix-nsc_2.12-@(scalafix.Versions.version + ".jar") mycode.scala + coursier bootstrap ch.epfl.scala:scalafix-cli_@(V.scalaVersion):@(V.version) --main scalafix.cli.Cli -o scalafix - @ul - @li - The compiler plugin writes fixes directly to source files during compilation. - @li - To specify a configuration file @code{-P:scalafix:/full/path/to/.scalafix.conf}. + This creates a small bootstrap script (<20kb) that downloads necessary + dependencies on the first run. + + @p + If you run semantic rewrites with the cli, you can optionally use the + @code{fatcli} module instead of the @code{cli} module. + + @hl.scala + coursier bootstrap ch.epfl.scala:scalafix-fatcli_@(V.scalaVersion):@(V.version) --main scalafix.cli.Cli -o scalafix + + @p + The @code{fatcli} module depends on the @code{scalahost-nsc} compiler plugin + fatjar, making it unnecessary to pass in the @code{--scalahost-nsc-plugin-path} + flag. + + @p + Once the scalafix cli is installed, consult the help page for further usage + instructions + + @hl.scala(Cli.helpMessage) + + @sect{scalafix-nsc} + @b{Note.} scalafix-nsc is being phased out in favor of scalahost-nsc. + + @p + Scalafix can be used directly as a compiler plugin: + @hl.scala + // download + https/repo1.maven.org/maven2/ch/epfl/scala/scalafix-nsc_2.12/@scalafix.Versions.version/scalafix-nsc_2.12-@(scalafix.Versions.version + ".jar") + // compile + scalac -Xplugin:/path/to/scalafix-nsc_2.12-@(scalafix.Versions.version + ".jar") mycode.scala + + @ul + @li + The compiler plugin writes fixes directly to source files during compilation. + @li + To specify a configuration file @code{-P:scalafix:/full/path/to/.scalafix.conf}. See @sect.ref{Configuration} for more details. diff --git a/scalafix-nsc/src/test/scala/scalafix/SemanticTests.scala b/scalafix-nsc/src/test/scala/scalafix/SemanticTests.scala index 24cbed8ea..a4ae36ab4 100644 --- a/scalafix-nsc/src/test/scala/scalafix/SemanticTests.scala +++ b/scalafix-nsc/src/test/scala/scalafix/SemanticTests.scala @@ -34,7 +34,6 @@ class SemanticTests extends FunSuite with DiffAssertions { self => def fail(msg: String) = sys.error(s"ReflectToMeta initialization failed: $msg") val classpath = System.getProperty("sbt.paths.scalafixNsc.test.classes") - logger.elem(classpath) val scalacOptions = Seq( "-cp", classpath, From 7a8bd691d6937aa443a4d8e1bfe920fd1895485d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Mon, 3 Apr 2017 17:21:07 +0200 Subject: [PATCH 5/7] Support fixing .sbt files. --- cli/src/main/scala/scalafix/cli/Cli.scala | 19 +++++++++++++------ cli/src/test/scala/scalafix/cli/CliTest.scala | 11 +++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/cli/src/main/scala/scalafix/cli/Cli.scala b/cli/src/main/scala/scalafix/cli/Cli.scala index 63daa2db9..e36355ded 100644 --- a/cli/src/main/scala/scalafix/cli/Cli.scala +++ b/cli/src/main/scala/scalafix/cli/Cli.scala @@ -2,6 +2,7 @@ package scalafix.cli import scala.collection.GenSeq import scala.meta.Mirror +import scala.meta.dialects import scala.meta.internal.scalahost.v1.offline import scala.meta.inputs.Input import scala.util.Success @@ -126,6 +127,8 @@ case class ScalafixOptions( .withRewrites(_ ++ rewrites) case Some(x) => x } + lazy val resolvedSbtConfig: ScalafixConfig = + resolvedConfig.copy(dialect = dialects.Sbt0137) lazy val resolvedMirror: Either[String, Option[ScalafixMirror]] = (classpath, sourcepath) match { @@ -153,10 +156,10 @@ object Cli { val helpMessage: String = withHelp.helpMessage + s"""| |Examples: - | $$ scalafix --rewrites ProcedureSyntax Code.scala # print fixed file to stdout + | $$ scalafix --rewrites ProcedureSyntax Code.scala # print fixed file to stdout | $$ cat .scalafix.conf | rewrites = [ProcedureSyntax] - | $$ scalafix Code.scala + | $$ scalafix Code.scala # Same as --rewrites ProcedureSyntax | scalafix -i --rewrites ProcedureSyntax Code.scala # write fixed file in-place |Exit status codes: | ${ExitStatus.all.mkString("\n ")} @@ -190,9 +193,11 @@ object Cli { } def handleFile(file: File, options: ScalafixOptions): ExitStatus = { - val fixed = Scalafix.fix(Input.File(file), - options.resolvedConfig, - options.resolvedMirror.get) + val config = + if (file.getAbsolutePath.endsWith(".sbt")) options.resolvedSbtConfig + else options.resolvedConfig + val fixed = + Scalafix.fix(Input.File(file), config, options.resolvedMirror.get) fixed match { case Fixed.Success(code) => if (options.inPlace) { @@ -223,7 +228,9 @@ object Cli { if (realPath.isDirectory) { val filesToFix: GenSeq[String] = { val files = - FileOps.listFiles(realPath).filter(x => x.endsWith(".scala")) + FileOps + .listFiles(realPath) + .filter(x => x.endsWith(".scala") || x.endsWith(".sbt")) if (config.singleThread) files else files.par } diff --git a/cli/src/test/scala/scalafix/cli/CliTest.scala b/cli/src/test/scala/scalafix/cli/CliTest.scala index 85b6b5cdd..7722ad7b0 100644 --- a/cli/src/test/scala/scalafix/cli/CliTest.scala +++ b/cli/src/test/scala/scalafix/cli/CliTest.scala @@ -152,4 +152,15 @@ class CliTest extends FunSuite with DiffAssertions { common = devNull)) assert(code == ExitStatus.ScalafixError) } + test(".sbt files get fixed with sbt dialect") { + val file = File.createTempFile("prefix", ".sbt") + FileOps.writeFile(file, "def foo { println(1) }\n") + val code = Cli.runOn( + ScalafixOptions(rewrites = List(ProcedureSyntax), + files = List(file.getAbsolutePath), + inPlace = true, + common = devNull)) + assert(code == ExitStatus.Ok) + assert(FileOps.readFile(file) == "def foo: Unit = { println(1) }\n") + } } From 3c0dc854ed3387af924aa0beeb898691e923bcf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Mon, 3 Apr 2017 17:40:15 +0200 Subject: [PATCH 6/7] Add changelog for 0.3.3 --- cli/src/main/scala/scalafix/cli/Cli.scala | 10 +++++----- readme/Changelog.scalatex | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/cli/src/main/scala/scalafix/cli/Cli.scala b/cli/src/main/scala/scalafix/cli/Cli.scala index e36355ded..ecc47ab42 100644 --- a/cli/src/main/scala/scalafix/cli/Cli.scala +++ b/cli/src/main/scala/scalafix/cli/Cli.scala @@ -68,14 +68,14 @@ case class ScalafixOptions( ) @ValueDescription( "File2.scala:File1.scala:src/main/scala" ) sourcepath: Option[String] = None, - @HelpMessage( - "java.io.File.pathSeparator separated list of" - ) @ValueDescription( + @HelpMessage( """File path to the scalahost-nsc compiler plugin fatjar, the same path | that is passed in `-Xplugin:/scalahost.jar`. - | (optional) skip this option by building the "scalafix-fatcli" + | (optional) skip this option by using the "scalafix-fatcli" | module instead of "scalafix-cli."""".stripMargin - ) scalahostNscPluginPath: Option[String] = None, + ) @ValueDescription( + "$HOME/.ivy2/cache/.../scalahost-nsc_2.11.8.jar" + ) scalahostNscPluginPath: Option[String] = None, @HelpMessage( s"""Rewrite rules to run. | NOTE. rewrite.rules = [ .. ] from --config will also run.""".stripMargin diff --git a/readme/Changelog.scalatex b/readme/Changelog.scalatex index 3a53da19b..68f8f4c83 100644 --- a/readme/Changelog.scalatex +++ b/readme/Changelog.scalatex @@ -2,6 +2,15 @@ @import scalafix.Readme._ @sect{Changelog} + @sect{0.3.3} + @ul + @li + scalafix-cli new supports running semantic rewrites with the + @code{--classpath/--sourcepath} options. + @li + scalafix-cli now supports @code{.sbt} files. + @li + New documentation for @sect.ref{scalafix-cli}. @sect{0.3.2} See @lnk("merged PRs", "https://github.com/scalacenter/scalafix/milestone/2?closed=1"). @ul From 3beeabcfce7740be0e8701be1638c879a1c72c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Mon, 3 Apr 2017 17:59:28 +0200 Subject: [PATCH 7/7] Fix CI. --- .drone.yml | 15 ++-- .drone.yml.sig | 2 +- build.sbt | 74 +++++++++---------- cli/src/main/scala/scalafix/cli/Cli.scala | 6 +- project/RunSbtCommand.scala | 26 +++++++ .../scala/scalafix/sbt/ScalafixPlugin.scala | 21 +++--- .../sbt-test/sbt-scalafix/config/build.sbt | 4 +- .../sbt-scalafix/config/myscalafix.conf | 2 +- .../sbt-test/sbt-scalafix/noconfig/build.sbt | 8 +- version.sbt | 2 +- 10 files changed, 93 insertions(+), 67 deletions(-) create mode 100644 project/RunSbtCommand.scala diff --git a/.drone.yml b/.drone.yml index ba91563f6..29a533e8f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,6 +8,8 @@ pipeline: - /drone/.coursier-cache - /drone/.sbt - /drone/.git + when: + event: [push, pull_request, tag, deployment] tests: image: scalaplatform/scala:0.6 @@ -20,7 +22,7 @@ pipeline: - git log | head -n 20 - ./scalafmt --test - ./bin/copy_cache.sh /drone /root - - /usr/bin/sbt $TEST + - /usr/bin/sbt $CI_TEST - ./bin/before_cache.sh - ./bin/copy_cache.sh /root /drone notify: @@ -47,9 +49,10 @@ pipeline: - /drone/.git matrix: include: - - TEST: ci-fast - SCALA_VERSION: 2.11.8 + - CI_TEST: ci-fast + CI_SCALA_VERSION: 2.11.8 # See https://github.com/scalacenter/scalafix/issues/101 - # - TEST: ci-fast - # SCALA_VERSION: 2.12.1 - - TEST: ci-slow + # - CI_TEST: ci-fast + # CI_SCALA_VERSION: 2.12.1 + - CI_TEST: ci-slow + CI_SCALA_VERSION: 2.11.8 diff --git a/.drone.yml.sig b/.drone.yml.sig index 09a558b60..730c66e73 100644 --- a/.drone.yml.sig +++ b/.drone.yml.sig @@ -1 +1 @@ -eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgIyBGZXRjaCBmb2xkZXJzIGZyb20gZGlzdHJpYnV0ZWQgY2FjaGUKICBzZnRwX2NhY2hlX3Jlc3RvcmU6CiAgICBpbWFnZTogcGx1Z2lucy9zZnRwLWNhY2hlCiAgICByZXN0b3JlOiB0cnVlCiAgICBtb3VudDoKICAgICAgLSAvZHJvbmUvLml2eTIKICAgICAgLSAvZHJvbmUvLmNvdXJzaWVyLWNhY2hlCiAgICAgIC0gL2Ryb25lLy5zYnQKICAgICAgLSAvZHJvbmUvLmdpdAoKICB0ZXN0czoKICAgIGltYWdlOiBzY2FsYXBsYXRmb3JtL3NjYWxhOjAuNgogICAgcHVsbDogdHJ1ZQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gQ09VUlNJRVJfQ0FDSEU9L2Ryb25lL2NhY2hlL2NvdXJzaWVyCiAgICB2b2x1bWVzOgogICAgICAtIC9wbGF0Zm9ybTova2V5cwogICAgY29tbWFuZHM6CiAgICAgIC0gZ2l0IGxvZyB8IGhlYWQgLW4gMjAKICAgICAgLSAuL3NjYWxhZm10IC0tdGVzdAogICAgICAtIC4vYmluL2NvcHlfY2FjaGUuc2ggL2Ryb25lIC9yb290CiAgICAgIC0gL3Vzci9iaW4vc2J0ICRURVNUCiAgICAgIC0gLi9iaW4vYmVmb3JlX2NhY2hlLnNoCiAgICAgIC0gLi9iaW4vY29weV9jYWNoZS5zaCAvcm9vdCAvZHJvbmUKICBub3RpZnk6CiAgICBpbWFnZTogZHJpbGxzdGVyL2Ryb25lLWVtYWlsCiAgICBob3N0OiBzbXRwLm1haWxndW4ub3JnCiAgICB1c2VybmFtZTogbm9yZXBseUBkcm9uZS5nZWlyc3Nvbi5jb20KICAgIHBhc3N3b3JkOiAke01BSUxHVU5fUEFTU1dPUkR9CiAgICBmcm9tOiBub3JlcGx5QGRyb25lLmdlaXJzc29uLmNvbQogICAgcmVjaXBpZW50czoKICAgICAgLSBvbGFmdXJwZ0BnbWFpbC5jb20KICAgIHdoZW46CiAgICAgIGV2ZW50OiBwdXNoCiAgICAgIGJyYW5jaDogW21hc3Rlcl0KICAgICAgc3RhdHVzOiBbY2hhbmdlZCwgZmFpbHVyZV0KCiAgIyBTYXZlIGZvbGRlcnMgaW4gZGlzdHJpYnV0ZWQgY2FjaGUKICBzZnRwX2NhY2hlX3JlYnVpbGQ6CiAgICBpbWFnZTogcGx1Z2lucy9zZnRwLWNhY2hlCiAgICByZWJ1aWxkOiB0cnVlCiAgICBtb3VudDoKICAgICAgLSAvZHJvbmUvLml2eTIKICAgICAgLSAvZHJvbmUvLmNvdXJzaWVyLWNhY2hlCiAgICAgIC0gL2Ryb25lLy5zYnQKICAgICAgLSAvZHJvbmUvLmdpdAptYXRyaXg6CiAgaW5jbHVkZToKICAgIC0gVEVTVDogY2ktZmFzdAogICAgICBTQ0FMQV9WRVJTSU9OOiAyLjExLjgKICAgICMgU2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9zY2FsYWNlbnRlci9zY2FsYWZpeC9pc3N1ZXMvMTAxCiAgICAjIC0gVEVTVDogY2ktZmFzdAogICAgIyAgIFNDQUxBX1ZFUlNJT046IDIuMTIuMQogICAgLSBURVNUOiBjaS1zbG93Cg.UsXt-SEnpsyKykJFpgdtbWNM8pRsngd2X5jShelSyEY \ No newline at end of file +eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgIyBGZXRjaCBmb2xkZXJzIGZyb20gZGlzdHJpYnV0ZWQgY2FjaGUKICBzZnRwX2NhY2hlX3Jlc3RvcmU6CiAgICBpbWFnZTogcGx1Z2lucy9zZnRwLWNhY2hlCiAgICByZXN0b3JlOiB0cnVlCiAgICBtb3VudDoKICAgICAgLSAvZHJvbmUvLml2eTIKICAgICAgLSAvZHJvbmUvLmNvdXJzaWVyLWNhY2hlCiAgICAgIC0gL2Ryb25lLy5zYnQKICAgICAgLSAvZHJvbmUvLmdpdAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFtwdXNoLCBwdWxsX3JlcXVlc3QsIHRhZywgZGVwbG95bWVudF0KCiAgdGVzdHM6CiAgICBpbWFnZTogc2NhbGFwbGF0Zm9ybS9zY2FsYTowLjYKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICAtIENPVVJTSUVSX0NBQ0hFPS9kcm9uZS9jYWNoZS9jb3Vyc2llcgogICAgdm9sdW1lczoKICAgICAgLSAvcGxhdGZvcm06L2tleXMKICAgIGNvbW1hbmRzOgogICAgICAtIGdpdCBsb2cgfCBoZWFkIC1uIDIwCiAgICAgIC0gLi9zY2FsYWZtdCAtLXRlc3QKICAgICAgLSAuL2Jpbi9jb3B5X2NhY2hlLnNoIC9kcm9uZSAvcm9vdAogICAgICAtIC91c3IvYmluL3NidCAkQ0lfVEVTVAogICAgICAtIC4vYmluL2JlZm9yZV9jYWNoZS5zaAogICAgICAtIC4vYmluL2NvcHlfY2FjaGUuc2ggL3Jvb3QgL2Ryb25lCiAgbm90aWZ5OgogICAgaW1hZ2U6IGRyaWxsc3Rlci9kcm9uZS1lbWFpbAogICAgaG9zdDogc210cC5tYWlsZ3VuLm9yZwogICAgdXNlcm5hbWU6IG5vcmVwbHlAZHJvbmUuZ2VpcnNzb24uY29tCiAgICBwYXNzd29yZDogJHtNQUlMR1VOX1BBU1NXT1JEfQogICAgZnJvbTogbm9yZXBseUBkcm9uZS5nZWlyc3Nvbi5jb20KICAgIHJlY2lwaWVudHM6CiAgICAgIC0gb2xhZnVycGdAZ21haWwuY29tCiAgICB3aGVuOgogICAgICBldmVudDogcHVzaAogICAgICBicmFuY2g6IFttYXN0ZXJdCiAgICAgIHN0YXR1czogW2NoYW5nZWQsIGZhaWx1cmVdCgogICMgU2F2ZSBmb2xkZXJzIGluIGRpc3RyaWJ1dGVkIGNhY2hlCiAgc2Z0cF9jYWNoZV9yZWJ1aWxkOgogICAgaW1hZ2U6IHBsdWdpbnMvc2Z0cC1jYWNoZQogICAgcmVidWlsZDogdHJ1ZQogICAgbW91bnQ6CiAgICAgIC0gL2Ryb25lLy5pdnkyCiAgICAgIC0gL2Ryb25lLy5jb3Vyc2llci1jYWNoZQogICAgICAtIC9kcm9uZS8uc2J0CiAgICAgIC0gL2Ryb25lLy5naXQKbWF0cml4OgogIGluY2x1ZGU6CiAgICAtIENJX1RFU1Q6IGNpLWZhc3QKICAgICAgQ0lfU0NBTEFfVkVSU0lPTjogMi4xMS44CiAgICAjIFNlZSBodHRwczovL2dpdGh1Yi5jb20vc2NhbGFjZW50ZXIvc2NhbGFmaXgvaXNzdWVzLzEwMQogICAgIyAtIENJX1RFU1Q6IGNpLWZhc3QKICAgICMgICBDSV9TQ0FMQV9WRVJTSU9OOiAyLjEyLjEKICAgIC0gQ0lfVEVTVDogY2ktc2xvdwogICAgICBDSV9TQ0FMQV9WRVJTSU9OOiAyLjExLjgK.kALWXJUcY8qVvXdeTWwbRhT82ykVHGFHNHM_mZXzyJE \ No newline at end of file diff --git a/build.sbt b/build.sbt index e89d5b020..3e33b1012 100644 --- a/build.sbt +++ b/build.sbt @@ -4,10 +4,7 @@ import sbt.ScriptedPlugin._ import Dependencies._ organization in ThisBuild := "ch.epfl.scala" -lazy val crossVersions = Seq( - "2.11.8", - "2.12.1" -) +lazy val crossVersions = Seq(scala211, scala212) lazy val compilerOptions = Seq( "-deprecation", @@ -36,13 +33,13 @@ commands += Command.command("release") { s => commands += Command.command("ci-fast") { s => "clean" :: - "testQuick" :: + s"plz $ciScalaVersion testQuick" :: s } commands += Command.command("ci-slow") { s => "very publishLocal" :: - "wow 2.11.8 scalafix-tests/test" :: + s"wow ${ciScalaVersion.get} scalafix-tests/test" :: "very scalafix-sbt/scripted" :: s } @@ -90,6 +87,8 @@ lazy val buildInfoSettings: Seq[Def.Setting[_]] = Seq( "stableVersion" -> "0.3.1", "scalameta" -> scalametaV, scalaVersion, + "scala211" -> scala211, + "scala212" -> scala212, sbtVersion ), buildInfoPackage := "scalafix", @@ -105,40 +104,22 @@ lazy val allSettings = List( libraryDependencies += scalatest % Test, testOptions in Test += Tests.Argument("-oD"), assemblyJarName in assembly := "scalafix.jar", - scalaVersion := sys.env.getOrElse("SCALA_VERSION", "2.11.8"), + scalaVersion := ciScalaVersion.getOrElse(scala211), crossScalaVersions := crossVersions, updateOptions := updateOptions.value.withCachedResolution(true) ) ++ publishSettings -lazy val `scalafix-root` = project - .in(file(".")) - .settings( - moduleName := "scalafix", - allSettings, - noPublish, - gitPushTag := { - val tag = s"v${version.value}" - assert(!tag.endsWith("SNAPSHOT")) - import sys.process._ - Seq("git", "tag", "-a", tag, "-m", tag).!! - Seq("git", "push", "--tags").!! - }, - initialCommands in console := - """ - |import scala.meta._ - |import scalafix._ - """.stripMargin - ) - .aggregate( - `scalafix-nsc`, - `scalafix-tests`, - `scalafix-testutils`, - core, - cli, - readme, - `scalafix-sbt` - ) - .dependsOn(core) +allSettings + +noPublish + +gitPushTag := { + val tag = s"v${version.value}" + assert(!tag.endsWith("SNAPSHOT")) + import sys.process._ + Seq("git", "tag", "-a", tag, "-m", tag).!! + Seq("git", "push", "--tags").!! +} // settings to projects using @metaconfig.ConfigReader annotation. lazy val metaconfigSettings: Seq[Def.Setting[_]] = Seq( @@ -259,8 +240,20 @@ lazy val `scalafix-sbt` = project sbtPlugin := true, // Doesn't work because we need to publish 2.11 and 2.12. // scripted := scripted.dependsOn(publishedArtifacts: _*).evaluated, - scalaVersion := "2.10.6", - crossScalaVersions := Seq("2.10.6"), + testQuick := { + RunSbtCommand( + s"; very publishLocal " + + "; very scalafix-sbt/scripted sbt-scalafix/config" + )(state.value) + }, + test := { + RunSbtCommand( + "; very publishLocal " + + "; very scalafix-sbt/scripted" + )(state.value) + }, + scalaVersion := scala210, + crossScalaVersions := Seq(scala210), moduleName := "sbt-scalafix", scriptedLaunchOpts ++= Seq( "-Dplugin.version=" + version.value, @@ -364,3 +357,8 @@ lazy val dummyScalahostProject = project description := "Just a project that has scalahost-nsc on the classpath.", libraryDependencies += scalahostNsc ) + +lazy val ciScalaVersion = sys.env.get("CI_SCALA_VERSION") +lazy val scala210 = "2.10.6" +lazy val scala211 = "2.11.8" +lazy val scala212 = "2.12.1" diff --git a/cli/src/main/scala/scalafix/cli/Cli.scala b/cli/src/main/scala/scalafix/cli/Cli.scala index ecc47ab42..4057ad2f4 100644 --- a/cli/src/main/scala/scalafix/cli/Cli.scala +++ b/cli/src/main/scala/scalafix/cli/Cli.scala @@ -68,14 +68,14 @@ case class ScalafixOptions( ) @ValueDescription( "File2.scala:File1.scala:src/main/scala" ) sourcepath: Option[String] = None, - @HelpMessage( + @HelpMessage( """File path to the scalahost-nsc compiler plugin fatjar, the same path | that is passed in `-Xplugin:/scalahost.jar`. | (optional) skip this option by using the "scalafix-fatcli" | module instead of "scalafix-cli."""".stripMargin ) @ValueDescription( - "$HOME/.ivy2/cache/.../scalahost-nsc_2.11.8.jar" - ) scalahostNscPluginPath: Option[String] = None, + "$HOME/.ivy2/cache/.../scalahost-nsc_2.11.8.jar" + ) scalahostNscPluginPath: Option[String] = None, @HelpMessage( s"""Rewrite rules to run. | NOTE. rewrite.rules = [ .. ] from --config will also run.""".stripMargin diff --git a/project/RunSbtCommand.scala b/project/RunSbtCommand.scala new file mode 100644 index 000000000..cd9143ca0 --- /dev/null +++ b/project/RunSbtCommand.scala @@ -0,0 +1,26 @@ +import sbt._ +import Keys._ +import sbt.complete.Parser + +// Helper to execute command from tasks. +// Note: This was copied from https://github.com/sbt/sbt-release/blob/663cfd426361484228a21a1244b2e6b0f7656bdf/src/main/scala/ReleasePlugin.scala#L99-L115 +object RunSbtCommand { + def apply(command: String): State => State = { st: State => + @annotation.tailrec + def runCommand(command: String, state: State): State = { + val nextState = Parser.parse(command, state.combinedParser) match { + case Right(cmd) => cmd() + case Left(msg) => throw sys.error(s"Invalid programmatic input:\n$msg") + } + nextState.remainingCommands.toList match { + case Nil => nextState + case head :: tail => + runCommand(head, nextState.copy(remainingCommands = tail)) + } + } + runCommand(command, + st.copy(remainingCommands = Nil, + onFailure = Some(s"Failed to run $command"))) + .copy(remainingCommands = st.remainingCommands) + } +} diff --git a/scalafix-sbt/src/main/scala/scalafix/sbt/ScalafixPlugin.scala b/scalafix-sbt/src/main/scala/scalafix/sbt/ScalafixPlugin.scala index 80ea84459..9bfdc347f 100644 --- a/scalafix-sbt/src/main/scala/scalafix/sbt/ScalafixPlugin.scala +++ b/scalafix-sbt/src/main/scala/scalafix/sbt/ScalafixPlugin.scala @@ -1,5 +1,7 @@ package scalafix.sbt +import scalafix.Versions + import sbt.Keys._ import sbt._ import sbt.plugins.JvmPlugin @@ -16,7 +18,6 @@ trait ScalafixKeys { object ScalafixPlugin extends AutoPlugin with ScalafixKeys { object autoImport extends ScalafixKeys - private val Version = "2\\.(\\d\\d)\\..*".r private val scalafixVersion = _root_.scalafix.Versions.version private val disabled = sys.props.contains("scalafix.disable") private def jar(report: UpdateReport): File = @@ -26,7 +27,7 @@ object ScalafixPlugin extends AutoPlugin with ScalafixKeys { // publishLocal produces jars with name `VERSION/scalafix-nsc_2.11.jar` // while the jars published with publishSigned to Maven are named // `scalafix-nsc_2.11-VERSION.jar` - s".*scalafix-nsc_2.1[12](-$scalafixVersion)?.jar$$") + s".*scalafix-nsc_2.1[12].(\\d+)(-$scalafixVersion)?.jar$$") } .getOrElse { throw new IllegalStateException( @@ -35,7 +36,7 @@ object ScalafixPlugin extends AutoPlugin with ScalafixKeys { } private def stub(version: String) = { - val Version(id) = version + val id = version.replace(".", "-") Project(id = s"scalafix-$id", base = file(s"project/scalafix/$id")) .settings( description := @@ -51,11 +52,13 @@ object ScalafixPlugin extends AutoPlugin with ScalafixKeys { scalaVersion := version, libraryDependencies := Nil, // remove injected dependencies from random sbt plugins. libraryDependencies += - ("ch.epfl.scala" %% "scalafix-nsc" % scalafixVersion).intransitive() + ("ch.epfl.scala" % "scalafix-nsc" % scalafixVersion) + .cross(CrossVersion.full) + .intransitive() ) } - private val scalafix211 = stub("2.11.8") - private val scalafix212 = stub("2.12.1") + private val scalafix211 = stub(Versions.scala211) + private val scalafix212 = stub(Versions.scala212) override def extraProjects: Seq[Project] = Seq(scalafix211, scalafix212) @@ -79,10 +82,10 @@ object ScalafixPlugin extends AutoPlugin with ScalafixKeys { scalafixInternalJar := Def .taskDyn[Option[File]] { - scalaVersion.value match { - case Version("11") => + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, 11)) => Def.task(Option(jar((update in scalafix211).value))) - case Version("12") => + case Some((2, 12)) => Def.task(Option(jar((update in scalafix212).value))) case _ => Def.task(None) diff --git a/scalafix-sbt/src/sbt-test/sbt-scalafix/config/build.sbt b/scalafix-sbt/src/sbt-test/sbt-scalafix/config/build.sbt index ac0b3f99b..0d4085b8c 100644 --- a/scalafix-sbt/src/sbt-test/sbt-scalafix/config/build.sbt +++ b/scalafix-sbt/src/sbt-test/sbt-scalafix/config/build.sbt @@ -3,8 +3,6 @@ scalaVersion := "2.11.8" scalafixConfig in ThisBuild := Some(file("myscalafix.conf")) TaskKey[Unit]("check") := { - val assertContentMatches: ((String, String) => Boolean) = - ScalafixTestUtility.assertContentMatches(streams.value) _ val expected = """object Main { | implicit val x = 2 @@ -14,7 +12,7 @@ TaskKey[Unit]("check") := { | } |}""".stripMargin assert( - assertContentMatches( + ScalafixTestUtility.assertContentMatches(streams.value)( "src/main/scala/Test.scala", expected ) diff --git a/scalafix-sbt/src/sbt-test/sbt-scalafix/config/myscalafix.conf b/scalafix-sbt/src/sbt-test/sbt-scalafix/config/myscalafix.conf index 7062903ac..b8e7471f0 100644 --- a/scalafix-sbt/src/sbt-test/sbt-scalafix/config/myscalafix.conf +++ b/scalafix-sbt/src/sbt-test/sbt-scalafix/config/myscalafix.conf @@ -1 +1 @@ -rewrites = [VolatileLazyVal] \ No newline at end of file +rewrites = [VolatileLazyVal] diff --git a/scalafix-sbt/src/sbt-test/sbt-scalafix/noconfig/build.sbt b/scalafix-sbt/src/sbt-test/sbt-scalafix/noconfig/build.sbt index 63b2a6495..0e1fb7d92 100644 --- a/scalafix-sbt/src/sbt-test/sbt-scalafix/noconfig/build.sbt +++ b/scalafix-sbt/src/sbt-test/sbt-scalafix/noconfig/build.sbt @@ -1,18 +1,16 @@ scalaVersion := "2.11.8" TaskKey[Unit]("check") := { - val assertContentMatches: ((String, String) => Boolean) = - ScalafixTestUtility.assertContentMatches(streams.value) _ val expected = """object Main { - | implicit val x: Int = 2 + | implicit val x = 2 | lazy val y = 2 - | def main(args: Array[String]): Unit = { + | def main(args: Array[String]) { | println("hello") | } |}""".stripMargin assert( - assertContentMatches( + ScalafixTestUtility.assertContentMatches(streams.value)( "src/main/scala/Test.scala", expected ) diff --git a/version.sbt b/version.sbt index f45dd2d11..42b4e6eee 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.3.3-SNAPSHOT" +version in ThisBuild := "0.3.3-ASNAPSHOT"