如何使用 Scalatest 在 Scala 中开发编译器插件

发布于 2024-10-12 09:15:55 字数 1797 浏览 1 评论 0 原文

实际上,我正在根据 http://www.scala- 上的文章为 Scala 开发一个编译器插件lang.org/node/140

这是插件的代码:

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")
          }
      }
    }
  }
}

我正在做那里提到的事情,并编写了一些 makefile 来编译所有内容,然后创建一个 jar 文件。然后我使用以下命令加载带有测试文件的插件 jar 文件:

scalac -Xplugin:myplugin.jar test.scala

并查看输出是什么。我不喜欢这种方式,因为我从 ruby​​ 知道如何做 tdd 和 bdd。我安装了 Scalatest http://www.scalatest.org/。是否可以以某种方式测试 jar 文件或 divbyzero 类?我知道该插件在使用文件执行时将首先加载。我脑子里很乱,不知道是否可以在不创建 jar 文件的情况下直接测试插件类(或者甚至可以测试 jar 文件的某些函数和类)?

如果没有人可以帮助我,我可以像过去的美好时光一样继续发展

感谢您的时间和帮助 马蒂亚斯

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

终难遇 2024-10-19 09:15:55

您可以使用如下代码以编程方式调用 Scala 编译器和插件:

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)

请注意,此代码要求 scala-compiler.jarscala-library.jar 打开执行代码时的类路径。如果您在 SBT 之类的内部运行测试,不幸的是情况并非如此。

为了让东西在 SBT 中运行,你必须做一些跳跃:

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) : _*)

如果你在其他构建环境中运行,你可能会发现 scala-library.jar 已经在类路径上,或者如果你真的很幸运,那么你需要的一切都在标准 Java 类路径上,在这种情况下,你可以将上面的内容替换为:

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

你可以使用 System.getProperty("java.class" 打印出 Java 类路径的值.path") 并且您当然可以从上面的代码中打印出 entries 来查看正在加载测试代码的类加载器所使用的类路径。

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.

晚雾 2024-10-19 09:15:55

我想补充一下 samskivert 的答案。重写computeInternalPhases会强制将插件的阶段注入到整个phaseSet中,但是编译器不会将它们视为插件的一部分。例如,如果您想使用以下方式将选项传递给插件 "-P:divbyzero:someoption"

settings.pluginOptions.appendToValue("divbyzero:someoption")

您将收到以下编译错误:

error: bad option: -P:divbyzero:someoption

这是因为编译器不知道有关名为 divbyzero 的插件的任何信息。

添加插件的更合适的方法是重写 loadRoughPluginsList 方法并在其中添加插件,而不是手动将插件的每个阶段注入编译phaseSet:

override protected def loadRoughPluginsList: List[Plugin] =
  new DivByZero(this) :: super.loadRoughPluginsList

I'd like to add to samskivert's answer. Overriding computeInternalPhases forces to inject the phases of the plugin into the whole phaseSet, however, the compiler doesn't treat them as part of the plugin. So for instance, if you want to pass an option to your plugin "-P:divbyzero:someoption" using:

settings.pluginOptions.appendToValue("divbyzero:someoption")

you will get the following compilation error:

error: bad option: -P:divbyzero:someoption

and that is because compiler doesn't know anything about a plugin named divbyzero.

The more appropriate way of adding plugin would be to override loadRoughPluginsList method and add plugins there, rather than manually inject every phase of the plugin into compilation phaseSet:

override protected def loadRoughPluginsList: List[Plugin] =
  new DivByZero(this) :: super.loadRoughPluginsList
逐鹿 2024-10-19 09:15:55

我认为您正在研究模拟对象(我喜欢 EasyMock,但还有很多其他对象)和一些重构。

当您参考您的 makefile 时,我的印象是您正在使用良好的旧 make。如果是这样,我可以建议您看看SBTGradle,或者,当您来自 Ruby 世界时,BuildR。它们都内置了对各种 Scala 测试框架的支持。

I think you're looking at mock objects (I like EasyMock, but there are many others) and some refactoring.

When you refer to your makefile, I get the impression you're using good old make. If so, might I suggest you look at something like SBT, Gradle, or, as you're coming from the Ruby world, BuildR. All of them have built in support for various scala test frameworks.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文