diff --git a/core/src/main/scala/scalafix/config/ScalafixMetaconfigReaders.scala b/core/src/main/scala/scalafix/config/ScalafixMetaconfigReaders.scala index 57ac808c8..58cdb234b 100644 --- a/core/src/main/scala/scalafix/config/ScalafixMetaconfigReaders.scala +++ b/core/src/main/scala/scalafix/config/ScalafixMetaconfigReaders.scala @@ -9,6 +9,7 @@ import scala.util.matching.Regex import scalafix.Failure.UnknownRewrite import scalafix.rewrite.ScalafixRewrite import scalafix.rewrite.ScalafixRewrites +import scalafix.util.ClassloadObject import scalafix.util.TreePatch._ import metaconfig.Reader @@ -26,7 +27,13 @@ trait ScalafixMetaconfigReaders { } implicit lazy val rewriteReader: Reader[ScalafixRewrite] = - ReaderUtil.fromMap(ScalafixRewrites.name2rewrite) + Reader.instance[ScalafixRewrite] { + case fqn: String if fqn.startsWith("_root_.") => + val suffix = if (fqn.endsWith("$")) "" else "$" + ClassloadObject[ScalafixRewrite](fqn.stripPrefix("_root_.") + suffix) + case els => + ReaderUtil.fromMap(ScalafixRewrites.name2rewrite).read(els) + } implicit val RegexReader: Reader[Regex] = Reader.instance[Regex] { case str: String => Right(FilterMatcher.mkRegexp(List(str))) diff --git a/core/src/main/scala/scalafix/util/ClassloadObject.scala b/core/src/main/scala/scalafix/util/ClassloadObject.scala new file mode 100644 index 000000000..ce9831c72 --- /dev/null +++ b/core/src/main/scala/scalafix/util/ClassloadObject.scala @@ -0,0 +1,48 @@ +package scalafix.util + +import scala.collection.immutable.Seq +import scala.reflect.ClassTag +import scala.util.Failure +import scala.util.Success +import scala.util.Try + +import java.lang.reflect.InvocationTargetException + +// Helper to classload object or no argument class. +class ClassloadObject[T](classLoader: ClassLoader)(implicit ev: ClassTag[T]) { + private val t = ev.runtimeClass + + private def getClassFor(fqcn: String): Try[Class[_ <: T]] = + Try[Class[_ <: T]]({ + val c = + Class.forName(fqcn, false, classLoader).asInstanceOf[Class[_ <: T]] + if (t.isAssignableFrom(c)) c + else throw new ClassCastException(s"$t is not assignable from $c") + }) + + private def createInstanceFor(clazz: Class[_]): Try[T] = + Try { + val constructor = clazz.getDeclaredConstructor() + constructor.setAccessible(true) + val obj = constructor.newInstance() + if (t.isInstance(obj)) obj.asInstanceOf[T] + else + throw new ClassCastException( + s"${clazz.getName} is not a subtype of $t") + } recover { + case i: InvocationTargetException if i.getTargetException ne null ⇒ + throw i.getTargetException + } + + def createInstanceFor(fqcn: String): Try[T] = + getClassFor(fqcn).flatMap(c => createInstanceFor(c)) +} + +object ClassloadObject { + def apply[T: ClassTag](fqn: String): Either[Throwable, T] = + new ClassloadObject(this.getClass.getClassLoader) + .createInstanceFor(fqn) match { + case Success(e) => Right(e) + case Failure(e) => Left(e) + } +} diff --git a/scalafix-nsc/src/test/resources/checkSyntax/FqnRewrite.source b/scalafix-nsc/src/test/resources/checkSyntax/FqnRewrite.source new file mode 100644 index 000000000..f6fbc5f59 --- /dev/null +++ b/scalafix-nsc/src/test/resources/checkSyntax/FqnRewrite.source @@ -0,0 +1,9 @@ +rewrites = [ + _root_.scalafix.test.FqnRewrite +] +<<< NOWRAP add import +import scala._ +object FqnMe +>>> +import scala.meta._ +object FqnMe diff --git a/scalafix-nsc/src/test/scala/scalafix/test/FqnRewrite.scala b/scalafix-nsc/src/test/scala/scalafix/test/FqnRewrite.scala new file mode 100644 index 000000000..26296ddd1 --- /dev/null +++ b/scalafix-nsc/src/test/scala/scalafix/test/FqnRewrite.scala @@ -0,0 +1,13 @@ +package scalafix.test + +import scala.meta._ +import scala.collection.immutable.Seq +import scalafix.rewrite.Rewrite +import scalafix.rewrite.RewriteCtx +import scalafix.util.Patch +import scalafix.util.TreePatch.AddGlobalImport + +case object FqnRewrite extends Rewrite[Any] { + override def rewrite[B <: Any](ctx: RewriteCtx[B]): Seq[Patch] = + Seq(AddGlobalImport(importer"scala.meta._")) +}