Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Scalatest to develop a compiler-plugin in Scala

Tags:

scala

Actually I'm developing a compiler plugin for Scala according to the article on http://www.scala-lang.org/node/140.

Here is the code of the plugin:

package localhost

import scala.tools.nsc
import nsc.Global
import nsc.Phase
import nsc.plugins.Plugin
import nsc.plugins.PluginComponent

class DivByZero(val global: Global) extends Plugin {
  import global._

  val name = "divbyzero"
  val description = "checks for division by zero"
  val components = List[PluginComponent](Component)

  private object Component extends PluginComponent {
    val global: DivByZero.this.global.type = DivByZero.this.global
    val runsAfter = "refchecks"
    // Using the Scala Compiler 2.8.x the runsAfter should be written as below
    // val runsAfter = List[String]("refchecks");
    val phaseName = DivByZero.this.name
    def newPhase(_prev: Phase) = new DivByZeroPhase(_prev)    

    class DivByZeroPhase(prev: Phase) extends StdPhase(prev) {
      override def name = DivByZero.this.name
      def apply(unit: CompilationUnit) {
        for ( tree @ Apply(Select(rcvr, nme.DIV), List(Literal(Constant(0)))) <- unit.body;
             if rcvr.tpe <:< definitions.IntClass.tpe) 
          {
            unit.error(tree.pos, "definitely division by zero")
          }
      }
    }
  }
}

I'm doing the things mentioned there and wrote some makefile which compiles everything and then creates a jar-file. Then I'm loading the the plugin jar file with the testfile with the following command:

scalac -Xplugin:myplugin.jar test.scala

and see what the output is. I don't like this way because I knew from ruby how to do tdd and bdd. I installed Scalatest http://www.scalatest.org/. Is it in someway possible to test the jar-file or the class divbyzero? I know the plugin will first be load when executes with a file. I'm very wired in my head and don't know if it is possible to directly test the plugin class without creating the jar file (or is it even possible to test some functions and classes of the jar file)?

If no one can help me, I can keep on developing like in the good old days

Thanks for your time and help Matthias

like image 354
Matthias Guenther Avatar asked Nov 29 '22 03:11

Matthias Guenther


1 Answers

You can invoke the Scala compiler, plus plugins, programmatically with code like the following:

import scala.tools.nsc.{Settings, Global}
import scala.tools.nsc.io.VirtualDirectory
import scala.tools.nsc.reporters.ConsoleReporter
import scala.tools.nsc.util.BatchSourceFile

// prepare the code you want to compile
val code = "object Foo extends Application { println(42 / 0) }"
val sources = List(new BatchSourceFile("<test>", code))

val settings = new Settings
// save class files to a virtual directory in memory
settings.outputDirs.setSingleOutput(new VirtualDirectory("(memory)", None))

val compiler = new Global(settings, new ConsoleReporter(settings)) {
  override protected def computeInternalPhases () {
    super.computeInternalPhases
    for (phase <- new DivByZero(this).components)
      phasesSet += phase
  }
}
new compiler.Run() compileSources(sources)

Note that this code requires that scala-compiler.jar and scala-library.jar be on the classpath when executing the code. If you are running your tests from within something like SBT, this will unfortunately not be the case.

To get things running from within SBT, you have to do some hoop jumping:

val settings = new Settings
val loader = getClass.getClassLoader.asInstanceOf[URLClassLoader]
val entries = loader.getURLs map(_.getPath)
// annoyingly, the Scala library is not in our classpath, so we have to add it manually
val sclpath = entries find(_.endsWith("scala-compiler.jar")) map(
  _.replaceAll("scala-compiler.jar", "scala-library.jar"))
settings.classpath.value = ClassPath.join((entries ++ sclpath) : _*)

If you are running from within some other build environment, you might find that scala-library.jar is already on the classpath, or if you're really lucky, then everything you need is on the standard Java classpath, in which case you can replace the above with:

val settings = new Settings
settings.usejavacp.value = true

You can print out the value of the Java classpath with System.getProperty("java.class.path") and you can of course print out entries from the above code to see the classpath used by the class loader that is loading your test code.

like image 172
samskivert Avatar answered Dec 22 '22 06:12

samskivert