diff options
author | Simeon H.K. Fitch <fitch@datamininglab.com> | 2015-02-05 05:57:45 +0000 |
---|---|---|
committer | Christopher Vogt <oss.nsp@cvogt.org> | 2016-11-07 02:08:39 -0500 |
commit | 44b6e6845c931aff4a69f98a0b44c8c69128369b (patch) | |
tree | 58b95a9814b5f989d6a5b91c18809cbafe840a3b | |
parent | 623cfdd2a0b26ef2d1e0a78025b950f2958b67b7 (diff) | |
download | cbt-44b6e6845c931aff4a69f98a0b44c8c69128369b.tar.gz cbt-44b6e6845c931aff4a69f98a0b44c8c69128369b.tar.bz2 cbt-44b6e6845c931aff4a69f98a0b44c8c69128369b.zip |
Added ability to override `Reporter` used during compilation. See issue #89
Signed-off-by: Moses Nakamura <mnakamura@twitter.com>
RB_ID=572344
-rw-r--r-- | libraries/eval/Eval.scala | 67 | ||||
-rw-r--r-- | libraries/eval/test/EvalTest.scala | 54 |
2 files changed, 101 insertions, 20 deletions
diff --git a/libraries/eval/Eval.scala b/libraries/eval/Eval.scala index e9c95fd..7e5fb71 100644 --- a/libraries/eval/Eval.scala +++ b/libraries/eval/Eval.scala @@ -27,7 +27,7 @@ import scala.collection.mutable import scala.io.Source import scala.tools.nsc.interpreter.AbstractFileClassLoader import scala.tools.nsc.io.{AbstractFile, VirtualDirectory} -import scala.tools.nsc.reporters.AbstractReporter +import scala.tools.nsc.reporters.{Reporter, AbstractReporter} import scala.tools.nsc.util.{BatchSourceFile, Position} import scala.tools.nsc.{Global, Settings} import scala.util.matching.Regex @@ -108,8 +108,14 @@ class Eval(target: Option[File]) { ) ) - private[this] val STYLE_INDENT = 2 - private[this] lazy val compiler = new StringCompiler(STYLE_INDENT, target) + // For derived classes to provide an alternate compiler message handler. + protected lazy val compilerMessageHandler: Option[Reporter] = None + + // For derived classes do customize or override the default compiler settings. + protected lazy val compilerSettings: Settings = new EvalSettings(target) + + // Primary encapsulation around native Scala compiler + private[this] lazy val compiler = new StringCompiler(codeWrapperLineOffset, target, compilerSettings, compilerMessageHandler) /** * run preprocessors on our string, returning a String that is the processed source @@ -290,9 +296,10 @@ class Eval(target: Option[File]) { } /* - * Wrap source code in a new class with an apply method. + * Wraps source code in a new class with an apply method. + * NB: If this method is changed, make sure `codeWrapperLineOffset` is correct. */ - private def wrapCodeInClass(className: String, code: String) = { + private[this] def wrapCodeInClass(className: String, code: String) = { "class " + className + " extends (() => Any) {\n" + " def apply() = {\n" + code + "\n" + @@ -301,6 +308,13 @@ class Eval(target: Option[File]) { } /* + * Defines the number of code lines that proceed evaluated code. + * Used to ensure compile error messages report line numbers aligned with user's code. + * NB: If `wrapCodeInClass(String,String)` is changed, make sure this remains correct. + */ + private[this] val codeWrapperLineOffset = 2 + + /* * For a given FQ classname, trick the resource finder into telling us the containing jar. */ private def classPathOfClass(className: String) = { @@ -429,27 +443,34 @@ class Eval(target: Option[File]) { } } + lazy val compilerOutputDir = target match { + case Some(dir) => AbstractFile.getDirectory(dir) + case None => new VirtualDirectory("(memory)", None) + } + + class EvalSettings(targetDir: Option[File]) extends Settings { + nowarnings.value = true // warnings are exceptions, so disable + outputDirs.setSingleOutput(compilerOutputDir) + private[this] val pathList = compilerPath ::: libPath + bootclasspath.value = pathList.mkString(File.pathSeparator) + classpath.value = (pathList ::: impliedClassPath).mkString(File.pathSeparator) + } + /** * Dynamic scala compiler. Lots of (slow) state is created, so it may be advantageous to keep * around one of these and reuse it. */ - private class StringCompiler(lineOffset: Int, targetDir: Option[File]) { - val target = targetDir match { - case Some(dir) => AbstractFile.getDirectory(dir) - case None => new VirtualDirectory("(memory)", None) - } + private class StringCompiler( + lineOffset: Int, targetDir: Option[File], settings: Settings, messageHandler: Option[Reporter]) { val cache = new mutable.HashMap[String, Class[_]]() + val target = compilerOutputDir - val settings = new Settings - settings.nowarnings.value = true // warnings are exceptions, so disable - settings.outputDirs.setSingleOutput(target) - - val pathList = compilerPath ::: libPath - settings.bootclasspath.value = pathList.mkString(File.pathSeparator) - settings.classpath.value = (pathList ::: impliedClassPath).mkString(File.pathSeparator) + trait MessageCollector { + val messages: Seq[List[String]] + } - val reporter = new AbstractReporter { + val reporter = messageHandler getOrElse new AbstractReporter with MessageCollector { val settings = StringCompiler.this.settings val messages = new mutable.ListBuffer[List[String]] @@ -509,7 +530,7 @@ class Eval(target: Option[File]) { } } cache.clear() - reporter.reset + reporter.reset() classLoader = new AbstractFileClassLoader(target, this.getClass.getClassLoader) } @@ -557,7 +578,13 @@ class Eval(target: Option[File]) { compiler.compileSources(sourceFiles) if (reporter.hasErrors || reporter.WARNING.count > 0) { - throw new CompilerException(reporter.messages.toList) + val msgs: List[List[String]] = reporter match { + case collector: MessageCollector => + collector.messages.toList + case _ => + List(List(reporter.toString)) + } + throw new CompilerException(msgs) } } diff --git a/libraries/eval/test/EvalTest.scala b/libraries/eval/test/EvalTest.scala index 2f80894..e53dc47 100644 --- a/libraries/eval/test/EvalTest.scala +++ b/libraries/eval/test/EvalTest.scala @@ -5,10 +5,14 @@ import java.io.{File, FileWriter} import scala.io.Source import org.junit.runner.RunWith +import org.scalatest.DiagrammedAssertions._ import org.scalatest.WordSpec import org.scalatest.junit.JUnitRunner import com.twitter.io.TempFile +import scala.tools.nsc.reporters.{AbstractReporter, Reporter} +import scala.tools.nsc.Settings +import scala.tools.nsc.util.Position @RunWith(classOf[JUnitRunner]) class EvalTest extends WordSpec { @@ -166,5 +170,55 @@ class EvalTest extends WordSpec { // with crazy things assert(e.fileToClassName(new File("foo$! -@@@")) == "foo$24$21$20$2d$40$40$40") } + + "allow custom error reporting" when { + class Ctx { + val eval = new Eval { + @volatile var errors: Seq[(String, String)] = Nil + + override lazy val compilerMessageHandler: Option[Reporter] = Some(new AbstractReporter { + override val settings: Settings = compilerSettings + override def displayPrompt(): Unit = () + override def display(pos: Position, msg: String, severity: this.type#Severity): Unit = { + errors = errors :+ (msg, severity.toString) + } + override def reset() = { + super.reset() + errors = Nil + } + }) + } + } + + "not report errors on success" in { + val ctx = new Ctx + import ctx._ + + assert(eval[Int]("val a = 3; val b = 2; a + b", true) == 5) + assert(eval.errors.isEmpty) + } + + "report errors on bad code" in { + val ctx = new Ctx + import ctx._ + + intercept[Throwable] { + eval[Int]("val a = 3; val b = q; a + b", true) + } + assert(eval.errors.nonEmpty) + } + + "reset reporter between invocations" in { + val ctx = new Ctx + import ctx._ + + intercept[Throwable] { + eval[Int]("val a = 3; val b = q; a + b", true) + } + assert(eval.errors.nonEmpty) + assert(eval[Int]("val d = 3; val e = 2; d + e", true) == 5) + assert(eval.errors.isEmpty) + } + } } } |