Skip to content

Commit

Permalink
Merge pull request #270 from olafurpg/replace-syntax
Browse files Browse the repository at this point in the history
Harden sbt-scalafix
  • Loading branch information
olafurpg authored Aug 8, 2017
2 parents 2529af4 + e6f1eed commit 5406b4b
Show file tree
Hide file tree
Showing 19 changed files with 234 additions and 140 deletions.
8 changes: 8 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ lazy val `scalafix-sbt` = project
buildInfoSettings,
Defaults.itSettings,
ScriptedPlugin.scriptedSettings,
commands += Command.command(
"installCompletions",
"Code generates names of scalafix rewrites.",
"") { s =>
"cli/run --sbt scalafix-sbt/src/main/scala/scalafix/internal/sbt/ScalafixRewriteNames.scala" ::
s
},
sbtPlugin := true,
libraryDependencies ++= Seq(
"io.get-coursier" %% "coursier" % "1.0.0-RC6",
Expand Down Expand Up @@ -245,6 +252,7 @@ lazy val testsDeps = List(
// integration property tests
"org.renucci" %% "scala-xml-quote" % "0.1.4",
"org.typelevel" %% "catalysts-platform" % "0.0.5",
"org.typelevel" %% "cats" % "0.9.0",
"com.typesafe.slick" %% "slick" % "3.2.0-M2",
"com.chuusai" %% "shapeless" % "2.3.2",
"org.scalacheck" %% "scalacheck" % "1.13.4"
Expand Down
15 changes: 4 additions & 11 deletions scalafix-cli/src/main/scala/scalafix/cli/Cli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -147,20 +147,13 @@ return 0
}

def sbtCompletions: String = {
s"""package scalafix.internal.sbt
s"""// DO NOT EDIT: this file is generated by build.sbt
package scalafix.internal.sbt

import sbt.complete.DefaultParsers._
import sbt.complete.Parser

object ScalafixCompletions {
val names = List(
object ScalafixRewriteNames {
def all: List[String] = List(
$sbtNames
)
val parser = {
val rewrite: Parser[String] =
names.map(literal).reduceLeft(_ | _)
(token(Space) ~> token(rewrite)).* <~ SpaceClass.*
}
}

"""
Expand Down
8 changes: 6 additions & 2 deletions scalafix-cli/src/main/scala/scalafix/cli/CliRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli

import java.io.File
import java.io.OutputStreamWriter
import java.io.PrintStream
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
Expand Down Expand Up @@ -51,10 +52,13 @@ sealed abstract case class CliRunner(
implicit val workingDirectory: AbsolutePath = common.workingPath

def run(): ExitStatus = {
val display = new TermDisplay(new OutputStreamWriter(System.out))
val display = new TermDisplay(
new OutputStreamWriter(
if (cli.stdout) cli.common.err else cli.common.out),
fallbackMode = cli.nonInteractive || TermDisplay.defaultFallbackMode)
if (inputs.length > 10) display.init()
if (inputs.isEmpty) common.err.println("Running scalafix on 0 files.")
val msg = s"Running scalafix rewrite ${rewrite.name}..."
val msg = cli.projectIdPrefix + s"Running ${rewrite.name}"
display.startTask(msg, common.workingDirectoryFile)
display.taskLength(msg, inputs.length, 0)
val exitCode = new AtomicReference(ExitStatus.Ok)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,14 @@ case class ScalafixOptions(
"""Print out zsh completion file for scalafix. To install:
| scalafix --zsh > /usr/local/share/zsh/site-functions/_scalafix""".stripMargin)
zsh: Boolean = false,
@HelpMessage("Don't use fancy progress bar.")
nonInteractive: Boolean = false,
@HelpMessage("String ID to prefix reported messages with")
projectId: Option[String] = None,
@HelpMessage("""Print out sbt completion parser to argument.""".stripMargin)
@ValueDescription("scalafix-sbt/src/main/scala/Completions.scala")
@Hidden
sbt: Option[String] = None
)
) {
def projectIdPrefix: String = projectId.fold("")(id => s"[$id] ")
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import metaconfig.ConfError
import metaconfig.Configured
import metaconfig.Configured.Ok
import scalafix.config.MetaconfigParser.{parser => hoconParser}
import scalafix.patch.TreePatch
import scalafix.rewrite.ConfigRewrite
import org.scalameta.logger

object ScalafixMetaconfigReaders extends ScalafixMetaconfigReaders
// A collection of metaconfig.Reader instances that are shared across
Expand All @@ -47,6 +47,12 @@ trait ScalafixMetaconfigReaders {
}
}

object UriRewriteString {
def unapply(arg: Conf.Str): Option[(String, String)] =
UriRewrite.unapply(arg).map {
case (a, b) => a -> b.getSchemeSpecificPart
}
}
object UriRewrite {
def unapply(arg: Conf.Str): Option[(String, URI)] =
for {
Expand Down Expand Up @@ -99,16 +105,32 @@ trait ScalafixMetaconfigReaders {
private lazy val semanticRewriteClass = classOf[SemanticRewrite]

def classloadRewrite(mirror: LazyMirror): Class[_] => Seq[Mirror] = { cls =>
val semanticRewrite =
cls.getClassLoader.loadClass("scalafix.rewrite.SemanticRewrite")
val kind =
if (cls.isAssignableFrom(semanticRewriteClass)) RewriteKind.Semantic
if (semanticRewriteClass.isAssignableFrom(cls)) RewriteKind.Semantic
else RewriteKind.Syntactic
mirror(kind).toList
}

private lazy val ReplaceSymbolR = "([^/]+)/(.*)".r

def classloadRewriteDecoder(mirror: LazyMirror): ConfDecoder[Rewrite] =
ConfDecoder.instance[Rewrite] {
case FromClassloadRewrite(fqn) =>
case UriRewriteString("scala", fqn) =>
ClassloadRewrite(fqn, classloadRewrite(mirror))
case UriRewriteString("replace", replace @ ReplaceSymbolR(from, to)) =>
mirror(RewriteKind.Semantic) match {
case Some(m) =>
(
symbolGlobalReader.read(Conf.Str(from)) |@|
symbolGlobalReader.read(Conf.Str(to))
).map(TreePatch.ReplaceSymbol.tupled).map { p =>
Rewrite.constant(replace, p, m)
}
case _ =>
Configured.error(s"$replace requires semantic API.")
}
}

def baseSyntacticRewriteDecoder: ConfDecoder[Rewrite] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class ClassloadRewrite[T](classLoader: ClassLoader)(implicit ev: ClassTag[T]) {
else s" or constructor matching arguments $args"
throw new IllegalArgumentException(
s"""No suitable constructor on $clazz.
|Expected : zeroargument constructor$argsMsg
|Expected : zero argument constructor $argsMsg
|Found : $constructors
""".stripMargin)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ private[scalafix] object TreePatch {
case class AddGlobalSymbol(symbol: Symbol) extends ImportPatch
case class ReplaceSymbol(from: Symbol.Global, to: Symbol.Global)
extends TreePatch
object ReplaceSymbol {}

}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package scalafix.internal.reflect

import java.io.File
import java.io.FileNotFoundException
import java.net.URL
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.attribute.FileAttribute
import java.util.concurrent.ConcurrentHashMap
import scala.meta.Input
import scalafix.Rewrite
import scalafix.rewrite.Rewrite
import scalafix.config.LazyMirror
import scalafix.config.ScalafixMetaconfigReaders.UriRewrite
import scalafix.internal.util.FileOps
Expand All @@ -13,6 +19,7 @@ import metaconfig.ConfError
import metaconfig.Configured
import metaconfig.Configured.NotOk
import metaconfig.Configured.Ok
import org.scalameta.logger

object ScalafixCompilerDecoder {
def baseCompilerDecoder(mirror: LazyMirror): ConfDecoder[Rewrite] =
Expand Down Expand Up @@ -87,15 +94,31 @@ object ScalafixCompilerDecoder {
}

object FromSourceRewrite {
private val scalafixRoot = Files.createTempDirectory("scalafix")
scalafixRoot.toFile.deleteOnExit()
private val fileCache = scala.collection.concurrent.TrieMap.empty[Int, Path]
private def getTempFile(url: URL, code: String): Path =
fileCache.getOrElseUpdate(
code.hashCode, {
val filename = Paths.get(url.getPath).getFileName.toString
val tmp = Files.createTempFile(scalafixRoot, filename, ".scala")
Files.write(tmp, code.getBytes)
tmp
}
)
def unapply(arg: Conf.Str): Option[Configured[Input]] = arg match {
case FileRewrite(file) =>
// NOgg
Option(Ok(Input.File(file)))
case UrlRewrite(Ok(url)) =>
val code = FileOps.readURL(url)
val file = File.createTempFile(url.toString, ".scala")
FileOps.writeFile(file, code)
Option(Ok(Input.File(file)))
try {
val code = FileOps.readURL(url)
val file = getTempFile(url, code)
Option(Ok(Input.File(file)))
} catch {
case e: FileNotFoundException =>
Option(Configured.error(s"404 - not found $url"))
}
case _ => None
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package scalafix.internal.reflect
import java.io.File
import java.net.URLClassLoader
import java.net.URLDecoder
import java.net.URLEncoder
import scala.collection.mutable
import scala.meta.inputs.Input
import scala.reflect.internal.util.AbstractFileClassLoader
import scala.reflect.internal.util.BatchSourceFile
Expand All @@ -19,38 +17,40 @@ import scalafix.internal.util.ClassloadRewrite
import scalafix.rewrite.Rewrite
import metaconfig.ConfError
import metaconfig.Configured
import org.scalameta.logger

object ScalafixToolbox {
private val rewriteCache: mutable.WeakHashMap[Input, Configured.Ok[Rewrite]] =
mutable.WeakHashMap.empty
object ScalafixToolbox extends ScalafixToolbox
class ScalafixToolbox {
private val rewriteCache =
new java.util.concurrent.ConcurrentHashMap[Input, Configured[Rewrite]]()
private val compiler = new Compiler()

def getRewrite(code: Input, mirror: LazyMirror): Configured[Rewrite] =
rewriteCache.getOrElse(code, {
rewriteCache.getOrDefault(code, {
val uncached = getRewriteUncached(code, mirror)
uncached match {
case toCache @ Configured.Ok(_) => rewriteCache(code) = toCache
case toCache @ Configured.Ok(_) =>
rewriteCache.put(code, toCache)
case _ =>
}
uncached
})

def getRewriteUncached(code: Input, mirror: LazyMirror): Configured[Rewrite] = {
(
compiler.compile(code) |@|
RewriteInstrumentation.getRewriteFqn(code)
).andThen {
case (classloader, names) =>
names.foldLeft(Configured.ok(Rewrite.empty)) {
case (rewrite, fqn) =>
rewrite
.product(
ClassloadRewrite(fqn, classloadRewrite(mirror), classloader))
.map { case (a, b) => a.andThen(b) }
}
def getRewriteUncached(code: Input, mirror: LazyMirror): Configured[Rewrite] =
synchronized {
(
compiler.compile(code) |@|
RewriteInstrumentation.getRewriteFqn(code)
).andThen {
case (classloader, names) =>
names.foldLeft(Configured.ok(Rewrite.empty)) {
case (rewrite, fqn) =>
val args = classloadRewrite(mirror)
rewrite
.product(ClassloadRewrite(fqn, args, classloader))
.map { case (a, b) => a.andThen(b) }
}
}
}
}
}

class Compiler() {
Expand Down Expand Up @@ -87,7 +87,10 @@ class Compiler() {
case reporter.Info(pos, msg, reporter.ERROR) =>
ConfError
.msg(msg)
.atPos(m.Position.Range(input, pos.start, pos.end))
.atPos(
if (pos.isDefined) m.Position.Range(input, pos.start, pos.end)
else m.Position.None
)
.notOk
}
ConfError
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
package scalafix.internal.sbt

import java.io.File
import sbt.complete.DefaultParsers
import sbt.complete.DefaultParsers._
import sbt.complete.FileExamples
import sbt.complete.Parser

object ScalafixCompletions {
val names = List(
"NoValInForComprehension",
"RemoveXmlLiterals",
"VolatileLazyVal",
"ProcedureSyntax",
"ExplicitUnit",
"DottyVarArgPattern",
"ExplicitReturnTypes",
"RemoveUnusedImports",
"NoAutoTupling"
)
val parser = {
val rewrite: Parser[String] =
names.map(literal).reduceLeft(_ | _)
(token(Space) ~> token(rewrite)).* <~ SpaceClass.*
private val names = ScalafixRewriteNames.all

private def uri(protocol: String) =
token(protocol + ":") ~> NotQuoted.map(x => s"$protocol:$x")

private def fileRewrite(base: File): Parser[String] =
token("file:") ~>
StringBasic
.examples(new FileExamples(base))
.map(f => s"file:${new File(base, f).getAbsolutePath}")

private val namedRewrite: Parser[String] =
names.map(literal).reduceLeft(_ | _)

def parser(base: File): Parser[Seq[String]] = {
val all =
namedRewrite |
fileRewrite(base) |
uri("github") |
uri("replace") |
uri("http") |
uri("https") |
uri("scala")
(token(Space) ~> token(all)).* <~ SpaceClass.*
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// DO NOT EDIT: this file is generated by build.sbt
package scalafix.internal.sbt

object ScalafixRewriteNames {
def all: List[String] = List(
"DottyKeywords",
"NoValInForComprehension",
"RemoveXmlLiterals",
"VolatileLazyVal",
"ProcedureSyntax",
"ExplicitUnit",
"DottyVarArgPattern",
"ExplicitReturnTypes",
"RemoveUnusedImports",
"NoAutoTupling"
)
}
Loading

0 comments on commit 5406b4b

Please sign in to comment.