Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change design of sbt plugin, again. #23

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/testAll.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash
set -e
sbt clean test publishLocal scripted
sbt clean test scripted
6 changes: 6 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,12 @@ lazy val `scalafix-sbt` = project.settings(
allSettings,
ScriptedPlugin.scriptedSettings,
sbtPlugin := true,
scripted := scripted.dependsOn(
publishLocal in `scalafix-nsc`,
publishLocal in core
).evaluated,
scalaVersion := "2.10.5",
moduleName := "sbt-scalafix",
sources in Compile +=
baseDirectory.value / "../core/src/main/scala/scalafix/Versions.scala",
scriptedLaunchOpts := Seq(
Expand Down Expand Up @@ -234,3 +239,4 @@ def exposePaths(projectName: String,
}
)
}

1 change: 1 addition & 0 deletions core/src/main/scala/scalafix/Scalafix.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import scalafix.rewrite.RewriteCtx
import scalafix.rewrite.SemanticApi
import scalafix.util.Patch
import scalafix.util.TokenList
import scalafix.util.logger

object Scalafix {
def fix(code: Input,
Expand Down
22 changes: 21 additions & 1 deletion core/src/main/scala/scalafix/ScalafixConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,27 @@ import scala.meta.parsers.Parse
import scalafix.rewrite.Rewrite

case class ScalafixConfig(
rewrites: Seq[Rewrite] = Rewrite.syntaxRewrites,
rewrites: Seq[Rewrite] = Rewrite.allRewrites,
parser: Parse[_ <: Tree] = Parse.parseSource,
dialect: Dialect = Scala211
)

object ScalafixConfig {
def fromNames(names: List[String]): Either[String, ScalafixConfig] = {
names match {
case "all" :: Nil =>
Right(ScalafixConfig(rewrites = Rewrite.allRewrites))
case _ =>
val invalidNames =
names.filterNot(Rewrite.name2rewrite.contains)
if (invalidNames.nonEmpty) {
Left(
s"Invalid rewrite rule: ${invalidNames.mkString(",")}. " +
s"Valid rules are: ${Rewrite.name2rewrite.keys.mkString(",")}")
} else {
val rewrites = names.map(Rewrite.name2rewrite)
Right(ScalafixConfig(rewrites = rewrites))
}
}
}
}
2 changes: 1 addition & 1 deletion core/src/main/scala/scalafix/Versions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package scalafix

object Versions {
val nightly = "0.2.0-SNAPSHOT"
val stable = nightly
val stable: String = nightly
val scala = "2.11.8"
}
11 changes: 10 additions & 1 deletion core/src/main/scala/scalafix/rewrite/ExplicitImplicit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import scalafix.util.Patch
import scalafix.util.Whitespace

case object ExplicitImplicit extends Rewrite {
// Don't explicitly annotate vals when the right-hand body is a single call
// to `implicitly`. Prevents ambiguous implicit. Not annotating in such cases,
// this a common trick employed implicit-heavy code to workaround SI-2712.
// Context: https://gitter.im/typelevel/cats?at=584573151eb3d648695b4a50
private def isImplicitly(term: m.Term): Boolean = term match {
case m.Term.ApplyType(m.Term.Name("implicitly"), _) => true
case _ => false
}
override def rewrite(ast: m.Tree, ctx: RewriteCtx): Seq[Patch] = {
import scala.meta._
val semantic = getSemanticApi(ctx)
Expand All @@ -23,7 +31,8 @@ case object ExplicitImplicit extends Rewrite {
}.toSeq
ast.collect {
case t @ m.Defn.Val(mods, _, None, body)
if mods.exists(_.syntax == "implicit") =>
if mods.exists(_.syntax == "implicit") &&
!isImplicitly(body) =>
fix(t, body)
case t @ m.Defn.Def(mods, _, _, _, None, body)
if mods.exists(_.syntax == "implicit") =>
Expand Down
82 changes: 82 additions & 0 deletions core/src/test/resources/ExplicitImplicit/basic.source
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,85 @@ package foo {
class B {
implicit val x: foo.A[Int] = new foo.A(10)
}
<<< implicitly 2712 trick
class A {
implicit val s = "string"
implicit val x = implicitly[String]
}
>>>
class A {
implicit val s: String = "string"
implicit val x = implicitly[String]
}
<<< shorten imported name
import scala.collection.immutable.Map
class A {
implicit val x = Map(1 -> "")
}
>>>
import scala.collection.immutable.Map
class A {
implicit val x: Map[Int, String] = Map(1 -> "")
}
<<< shorten imported name 2
import scala.collection.immutable._
class A {
implicit val x = Map(1 -> "")
}
>>>
import scala.collection.immutable._
class A {
implicit val x: Map[Int, String] = Map(1 -> "")
}
<<< enclosing package strip is last
package b { class B }
package a {
import b.B
class A {
implicit val x = new B
}
}
>>>
package b { class B }
package a {
import b.B
class A {
implicit val x: B = new B
}
}
<<< inner inner object
object A {
object B {
class C
object C {
implicit val x = List(new C)
}
}
}
>>>
object A {
object B {
class C
object C {
implicit val x: List[C] = List(new C)
}
}
}
<<< sibling classes
object D {
class B
}
object A {
class C {
implicit val x = List(new D.B)
}
}
>>>
object D {
class B
}
object A {
class C {
implicit val x: List[D.B] = List(new D.B)
}
}
2 changes: 1 addition & 1 deletion scalafix-nsc/src/main/resources/scalac-plugin.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<plugin>
<name>scalafix</name>
<classname>scalafix.nsc.ScalafixNsc</classname>
<classname>scalafix.nsc.ScalafixNscPlugin</classname>
</plugin>
89 changes: 70 additions & 19 deletions scalafix-nsc/src/main/scala/scalafix/nsc/NscSemanticApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,101 @@ package scalafix.nsc

import scala.collection.mutable
import scala.meta.Dialect
import scala.meta.Type
import scala.reflect.internal.util.SourceFile
import scala.{meta => m}
import scalafix.Fixed
import scalafix.Scalafix
import scalafix.ScalafixConfig
import scalafix.rewrite.SemanticApi
import scalafix.util.logger

trait NscSemanticApi extends ReflectToolkit {
case class SemanticContext(enclosingPackage: String, inScope: List[String])

/** Removes redudant Foo.this.ActualType prefix from a type */
private def stripRedundantThis(typ: m.Type): m.Type =
typ.transform {
case m.Term.Select(m.Term.This(m.Name.Indeterminate(_)), qual) => qual
case m.Type.Select(m.Term.This(m.Name.Indeterminate(_)), qual) => qual
}.asInstanceOf[m.Type]
trait NscSemanticApi extends ReflectToolkit {

/** Returns a map from byte offset to type name at that offset. */
private def offsetToType(gtree: g.Tree,
dialect: Dialect): mutable.Map[Int, m.Type] = {
// TODO(olafur) Come up with more principled approach, this is hacky as hell.
// Operating on strings is definitely the wrong way to approach this
// problem. Ideally, this implementation uses the tree/symbol api. However,
// while I'm just trying to get something simple working I feel a bit more
// productive with this hack.
val builder = mutable.Map.empty[Int, m.Type]
def add(gtree: g.Tree, enclosingPackage: String): Unit = {
// TODO(olafur) Come up with more principled approach, this is hacky as hell.
val typename = gtree.toString().stripPrefix(enclosingPackage)
val parsed = dialect(typename).parse[m.Type]
def add(gtree: g.Tree, ctx: SemanticContext): Unit = {

/** Removes redudant Foo.this.ActualType prefix from a type */
val stripRedundantThis: m.Type => m.Type = _.transform {
case m.Term.Select(m.Term.This(m.Name.Indeterminate(_)), qual) =>
qual
case m.Type.Select(m.Term.This(m.Name.Indeterminate(_)), qual) =>
qual
}.asInstanceOf[m.Type]

val stripImportedPrefix: m.Type => m.Type = _.transform {
case prefix @ m.Type.Select(_, name)
if ctx.inScope.contains(prefix.syntax) =>
name
}.asInstanceOf[m.Type]

val stripEnclosingPackage: m.Type => m.Type = _.transform {
case typ: m.Type.Ref =>
import scala.meta._
// logger.elem(ctx.enclosingPackage, typ.syntax)
typ.syntax.stripPrefix(ctx.enclosingPackage).parse[m.Type].get
}.asInstanceOf[m.Type]

val cleanUp: (Type) => Type =
stripRedundantThis andThen
stripImportedPrefix andThen
stripEnclosingPackage

val typename = gtree.toString()
val toImport: String =
if (ctx.inScope.contains(typename)) typename.replace(".*\\.", "")
else typename
val parsed = dialect(toImport).parse[m.Type]
// logger.elem(g.showRaw(gtree), typename, toImport, ctx.inScope)
parsed match {
case m.Parsed.Success(ast) =>
builder(gtree.pos.point) = stripRedundantThis(ast)
builder(gtree.pos.point) = cleanUp(ast)
case _ =>
}
}
def foreach(gtree: g.Tree, enclosingPkg: String): Unit = {
def evaluate(ctx: SemanticContext, gtree: g.Tree): SemanticContext = {
gtree match {
case g.ValDef(_, _, tpt, _) if tpt.nonEmpty => add(tpt, enclosingPkg)
case g.DefDef(_, _, _, _, tpt, _) => add(tpt, enclosingPkg)
case g.ValDef(_, _, tpt, _) if tpt.nonEmpty => add(tpt, ctx)
case g.DefDef(_, _, _, _, tpt, _) => add(tpt, ctx)
case _ =>
}
gtree match {
case g.PackageDef(pid, _) =>
gtree.children.foreach(x => foreach(x, pid.symbol.fullName + "."))
case _ =>
gtree.children.foreach(x => foreach(x, enclosingPkg))
val newCtx = ctx.copy(enclosingPackage = pid.symbol.fullName + ".")
gtree.children.foldLeft(newCtx)(evaluate)
ctx // leaving pkg scope
case t: g.Template =>
val newCtx =
ctx.copy(inScope = t.symbol.owner.fullName :: ctx.inScope)
gtree.children.foldLeft(newCtx)(evaluate)
ctx
case g.Import(expr, selectors) =>
val newNames: Seq[String] = selectors.collect {
case g.ImportSelector(from, _, to, _) if from == to =>
Seq(s"${expr.symbol.fullName}.$from")
case g.ImportSelector(from, _, null, _) =>
expr.tpe.members.collect {
case x if !x.fullName.contains("$") =>
x.fullName
}
}.flatten
ctx.copy(inScope = ctx.inScope ++ newNames)
case els =>
// logger.elem(els, g.showRaw(els))
gtree.children.foldLeft(ctx)(evaluate)
}
}
foreach(gtree, "")
evaluate(SemanticContext("", Nil), gtree)
builder
}

Expand Down
14 changes: 0 additions & 14 deletions scalafix-nsc/src/main/scala/scalafix/nsc/ScalafixNsc.scala

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import scala.tools.nsc.plugins.Plugin
import scala.tools.nsc.plugins.PluginComponent
import scalafix.Fixed
import scalafix.ScalafixConfig
import scalafix.rewrite.Rewrite
import scalafix.util.FileOps
import scalafix.util.logger

class ScalafixNscComponent(plugin: Plugin, val global: Global)
import java.util

class ScalafixNscComponent(plugin: Plugin,
val global: Global,
getConfig: () => ScalafixConfig)
extends PluginComponent
with ReflectToolkit
with NscSemanticApi {
Expand All @@ -23,11 +25,11 @@ class ScalafixNscComponent(plugin: Plugin, val global: Global)
if (unit.source.file.exists &&
unit.source.file.file.isFile &&
!unit.isJava) {
// TODO(olafur) pull out rewrite rules from configuration flags.
val rewrites = Rewrite.semanticRewrites
fix(unit, ScalafixConfig(rewrites = rewrites)) match {
fix(unit, getConfig()) match {
case Fixed.Success(fixed) =>
FileOps.writeFile(unit.source.file.file, fixed)
if (fixed.nonEmpty && fixed != new String(unit.source.content)) {
FileOps.writeFile(unit.source.file.file, fixed)
}
case Fixed.Failed(e) =>
g.reporter.warning(unit.body.pos,
"Failed to run scalafix. " + e.getMessage)
Expand Down
28 changes: 28 additions & 0 deletions scalafix-nsc/src/main/scala/scalafix/nsc/ScalafixNscPlugin.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package scalafix.nsc

import scala.meta.Defn
import scala.meta.Type
import scala.tools.nsc.Global
import scala.tools.nsc.plugins.Plugin
import scala.tools.nsc.plugins.PluginComponent
import scalafix.ScalafixConfig
import scalafix.rewrite.Rewrite

class ScalafixNscPlugin(val global: Global) extends Plugin {
val name = "scalafix"
val description = "Refactoring tool."
var config: ScalafixConfig = ScalafixConfig(rewrites = Rewrite.allRewrites)
val components: List[PluginComponent] =
new ScalafixNscComponent(this, global, () => config) :: Nil

override def init(options: List[String], error: (String) => Unit): Boolean = {
ScalafixConfig.fromNames(options) match {
case Left(msg) =>
error(msg)
false
case Right(userConfig) =>
config = userConfig
true
}
}
}
Loading