diff --git a/build.sbt b/build.sbt index db3ac273b9..9f2bd60edd 100644 --- a/build.sbt +++ b/build.sbt @@ -151,7 +151,7 @@ val cli = MultiScalaProject( "com.github.alexarchambault" %% "case-app" % "1.2.0", "org.typelevel" %% "paiges-core" % "0.2.0", "com.martiansoftware" % "nailgun-server" % "0.9.1", - "org.eclipse.jgit" % "org.eclipse.jgit" % "4.5.4.201711221230-r", + jgit, "ch.qos.logback" % "logback-classic" % "1.2.3" ) ) @@ -179,7 +179,7 @@ lazy val `scalafix-sbt` = project } }, sbtPlugin := true, - libraryDependencies ++= coursierDeps, + libraryDependencies ++= jgit +: coursierDeps, testQuick := {}, // these test are slow. // scripted tests needs scalafix 2.12 // semanticdb-scala will generate the semantic db for both scala 2.11 and scala 2.12 diff --git a/project/Dependencies.scala b/project/Dependencies.scala index e3bc952775..f66663b238 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -18,6 +18,8 @@ object Dependencies { def sbt013 = "0.13.6" def sbt1 = "1.0.4" + val jgit = "org.eclipse.jgit" % "org.eclipse.jgit" % "4.5.4.201711221230-r" + var testClasspath: String = "empty" def semanticdb: ModuleID = "org.scalameta" % "semanticdb-scalac" % scalametaV cross CrossVersion.full def metaconfig: ModuleID = "com.geirsson" %% "metaconfig-typesafe-config" % metaconfigV diff --git a/scalafix-sbt/src/main/scala/scalafix/internal/sbt/JGitCompletions.scala b/scalafix-sbt/src/main/scala/scalafix/internal/sbt/JGitCompletions.scala new file mode 100644 index 0000000000..5059da20a2 --- /dev/null +++ b/scalafix-sbt/src/main/scala/scalafix/internal/sbt/JGitCompletions.scala @@ -0,0 +1,32 @@ +package scalafix.internal.sbt + +import java.nio.file.Path +import org.eclipse.jgit.storage.file.FileRepositoryBuilder +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.RefDatabase +import org.eclipse.jgit.lib.Repository +import org.eclipse.jgit.util.GitDateFormatter +import scala.collection.JavaConverters._ + +class JGitCompletion(cwd: Path) { + val builder = new FileRepositoryBuilder() + val repo = builder.readEnvironment().setWorkTree(cwd.toFile).build() + val refList = repo.getRefDatabase().getRefs(RefDatabase.ALL).asScala + val branchAndTags = refList.map { + case (a, b) => Repository.shortenRefName(a) + } + val git = new Git(repo) + val refs = git.log().setMaxCount(20).call().asScala.toList + val dateFormatter = new GitDateFormatter(GitDateFormatter.Format.RELATIVE) + + val last20Commits = refs.map { ref => + val relativeCommitTime = + dateFormatter.formatDate(refs.head.getCommitterIdent) + val abrev = ref.abbreviate(8).name + val short = ref.getShortMessage + // TODO: figure out how to display the whole message but only auto-complete on abrev + s"$abrev -- $short ($relativeCommitTime)" + } + + def all: Iterable[String] = branchAndTags ++ last20Commits +} diff --git a/scalafix-sbt/src/main/scala/scalafix/internal/sbt/ScalafixCompletions.scala b/scalafix-sbt/src/main/scala/scalafix/internal/sbt/ScalafixCompletions.scala index b6cc057a6c..36a5c4f4ee 100644 --- a/scalafix-sbt/src/main/scala/scalafix/internal/sbt/ScalafixCompletions.scala +++ b/scalafix-sbt/src/main/scala/scalafix/internal/sbt/ScalafixCompletions.scala @@ -3,10 +3,15 @@ package scalafix.internal.sbt import java.io.File import java.nio.file.Path import java.nio.file.Paths +import java.util.regex.Pattern + +import scala.util.control.NonFatal + import sbt.complete.DefaultParsers import sbt.complete.DefaultParsers._ import sbt.complete.FileExamples import sbt.complete.Parser +import sbt.complete.FixedSetExamples object ScalafixCompletions { private val names = ScalafixRuleNames.all @@ -35,15 +40,6 @@ object ScalafixCompletions { } } - private def fileRule(cwd: Path): Parser[String] = - token("file:") ~> - StringBasic - .examples(new AbsolutePathExamples(cwd)) - .map { f => - val path = toAbsolutePath(Paths.get(f), cwd).toString - "file:" + path - } - private def uri(protocol: String) = token(protocol + ":") ~> NotQuoted.map(x => s"$protocol:$x") @@ -51,14 +47,132 @@ object ScalafixCompletions { names.map(literal).reduceLeft(_ | _) def parser(cwd: Path): Parser[Seq[String]] = { - val all = - namedRule | - fileRule(cwd) | - uri("github") | - uri("replace") | - uri("http") | - uri("https") | - uri("scala") - (token(Space) ~> token(all)).* <~ SpaceClass.* + + // like repsep but keep the sep and flatten everything back + def repsep[T](rep: Parser[String], sep: Parser[String]): Parser[String] = + (rep ~ (sep ~ rep).*).map { + case x ~ xs => + x + xs.map { case s ~ y => s + y }.mkString("") + } + + def single[T](p: Parser[T]): Parser[Seq[T]] = + p.map(Seq(_)) + + def join[T](a: Parser[T], b: Parser[T]): Parser[Seq[T]] = + (a ~ b).map { case (ra, rb) => Seq(ra, rb) } + + def joinSeq[T](a: Parser[Seq[T]], b: Parser[Seq[T]]): Parser[Seq[T]] = + (a ~ b).map { case (ra, rb) => ra ++ rb } + + def flatten[T](p: Parser[Seq[Seq[T]]]): Parser[Seq[T]] = + p.map(_.flatten) + + def mapOrFail[S, T](p: Parser[S])(f: S => T): Parser[T] = + p.flatMap(s => + try { success(f(s)) } catch { case NonFatal(e) => failure(e.toString) }) + + val string: Parser[String] = StringBasic + + // val pathExamples: Parser[String] = + // string + // .examples(new AbsolutePathExamples(cwd)) + // .map { f => + // toAbsolutePath(Paths.get(f), cwd).toString + // } + + // val regexPath: Parser[String] = mapOrFail(pathExamples) { regex => + // Pattern.compile(regex) + // regex + // } + + // val fileRule: Parser[String] = token("file:") ~> pathExamples.map( + // "file:" + _) + + // val classpathExamples: Parser[String] = + // repsep(pathExamples, ":") + + val jgitCompletion = new JGitCompletion(cwd) + val gitBase: Parser[String] = + string.examples(new FixedSetExamples(jgitCompletion.all)) + + type P = Parser[Seq[String]] + val space: P = single(" ") + val usage: P = single("--usage") + val help: P = single("--help") | single("-h") + val version: P = single("--version") | single("-v") + val verbose: P = single("--verbose") + // val config: P = join(("--config" | "-c"), pathExamples) + val configStr: P = join(("--config-str" | "-c"), string) + // val sourceroot: P = join(("--config-str" | "-c"), pathExamples) + // val classpath: P = join("--classpath", classpathExamples) + // val classpathAutoRoots: P = join("--classpath-auto-roots", pathExamples) + // val toolClasspath: P = join("--tool-classpath", classpathExamples) + val noStrictSemanticdb: P = single("--no-strict-semanticdb") + val rules: P = + flatten( + join( + "--rule" | "-r", + token( + namedRule | + // fileRule | + uri("github") | + uri("replace") | + uri("http") | + uri("https") | + uri("scala") + ) + ).* + ) + // val files: P = join("--files" | "-f", pathExamples) // extra + val stdout: P = single("--stdout") + val test: P = single("--test") + // val outFrom: P = join("--out-from", regexPath) + // val outTo: P = join("--out-to", regexPath) + // val exclude: P = join("exclude", regexPath) + val singleThread: P = single("--single-thread") + val noSysExit: P = single("--no-sys-exit") + val inPlace: P = single("-i" | "in-place") + val quietParseErrors: P = single("--quiet-parse-errors") + val bash: P = single("--bash") + val zsh: P = single("--zsh") + val nonInteractive: P = single("--non-interactive") + val projectId: P = join("--project-id", string) + val diff: P = single("--diff") + val diffBase: P = join("--diff-base", gitBase) + + val all = usage | help + + // | + // help | + // version | + // verbose | + // // config | + // configStr | + // // sourceroot | + // // classpath | + // // classpathAutoRoots | + // // toolClasspath | + // noStrictSemanticdb | + // rules | + // // files | + // stdout | + // test | + // // outFrom | + // // outTo | + // // exclude | + // singleThread | + // noSysExit | + // inPlace | + // quietParseErrors | + // bash | + // zsh | + // nonInteractive | + // projectId | + // diff | + // diffBase + + + + flatten(joinSeq(space, all).*) } } diff --git a/scalafix-sbt/src/main/scala/scalafix/sbt/ScalafixPlugin.scala b/scalafix-sbt/src/main/scala/scalafix/sbt/ScalafixPlugin.scala index 282afa509f..c9af9e2c73 100644 --- a/scalafix-sbt/src/main/scala/scalafix/sbt/ScalafixPlugin.scala +++ b/scalafix-sbt/src/main/scala/scalafix/sbt/ScalafixPlugin.scala @@ -270,16 +270,12 @@ object ScalafixPlugin extends AutoPlugin { scalafixConfig.value .map(x => "--config" :: x.getAbsolutePath :: Nil) .getOrElse(Nil) - val ruleArgs = - if (inputArgs.nonEmpty) - inputArgs.flatMap("-r" :: _ :: Nil) - else Nil + val sourceroot = scalafixSourceroot.value.getAbsolutePath // only fix unmanaged sources, skip code generated files. verbose ++ config ++ - ruleArgs ++ - baseArgs ++ + inputArgs ++ options ++ List( "--sourceroot",