Skip to content

Add support for Scala.js #118

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

Closed
wants to merge 1 commit 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
32 changes: 23 additions & 9 deletions project/Scoverage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import sbt._
import sbtrelease.ReleasePlugin
import sbtrelease.ReleasePlugin.ReleaseKeys
import com.typesafe.sbt.pgp.PgpKeys
import org.scalajs.sbtplugin.cross.CrossProject
import org.scalajs.sbtplugin.cross.CrossType
import org.scalajs.sbtplugin.ScalaJSPlugin
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._

object Scoverage extends Build {

val Org = "org.scoverage"
val Scala = "2.11.4"
val MockitoVersion = "1.9.5"
val ScalatestVersion = "2.2.2"
val ScalatestVersion = "3.0.0-SNAP5"

lazy val LocalTest = config("local") extend Test

Expand All @@ -25,10 +29,7 @@ object Scoverage extends Build {
resolvers := ("releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2") +: resolvers.value,
concurrentRestrictions in Global += Tags.limit(Tags.Test, 1),
javacOptions := Seq("-source", "1.6", "-target", "1.6"),
libraryDependencies ++= Seq(
"org.mockito" % "mockito-all" % MockitoVersion % "test",
"org.scalatest" %% "scalatest" % ScalatestVersion % "test"
),
javaOptions += "-XX:MaxMetaspaceSize=2048m",
publishTo <<= version {
(v: String) =>
val nexus = "https://oss.sonatype.org/"
Expand Down Expand Up @@ -70,17 +71,30 @@ object Scoverage extends Build {
.settings(name := "scalac-scoverage")
.settings(appSettings: _*)
.settings(publishArtifact := false)
.aggregate(plugin, runtime)
.aggregate(plugin, runtime.jvm, runtime.js)

lazy val runtime = Project("scalac-scoverage-runtime", file("scalac-scoverage-runtime"))
lazy val runtime = CrossProject("scalac-scoverage-runtime", file("scalac-scoverage-runtime"), CrossType.Full)
.settings(name := "scalac-scoverage-runtime")
.settings(appSettings: _*)
.jvmSettings(libraryDependencies ++= Seq(
"org.mockito" % "mockito-all" % MockitoVersion % "test",
"org.scalatest" %% "scalatest" % ScalatestVersion % "test"
))
.jsSettings(
libraryDependencies += "org.scalatest" %%% "scalatest" % ScalatestVersion,
scalaJSStage := FastOptStage
)

lazy val `scalac-scoverage-runtimeJVM` = runtime.jvm
lazy val `scalac-scoverage-runtimeJS` = runtime.js

lazy val plugin = Project("scalac-scoverage-plugin", file("scalac-scoverage-plugin"))
.dependsOn(runtime % "test")
.dependsOn(`scalac-scoverage-runtimeJVM` % "test")
.settings(name := "scalac-scoverage-plugin")
.settings(appSettings: _*)
.settings(libraryDependencies ++= Seq(
"org.mockito" % "mockito-all" % MockitoVersion % "test",
"org.scalatest" %% "scalatest" % ScalatestVersion % "test",
"org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided",
"org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided",
"org.joda" % "joda-convert" % "1.6" % "test",
Expand All @@ -96,4 +110,4 @@ object Scoverage extends Build {
Nil
}
})
}
}
131 changes: 131 additions & 0 deletions project/ScoveragePhantomJSEnv.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import org.scalajs.jsenv.phantomjs._
import org.scalajs.jsenv._

import org.scalajs.core.tools.classpath._
import org.scalajs.core.tools.io._
import org.scalajs.core.tools.logging._

import org.scalajs.core.ir.Utils.{escapeJS, fixFileURI}

import java.io._

class ScoveragePhantomJSEnv (
phantomjsPath: String = "phantomjs",
addArgs: Seq[String] = Seq.empty,
addEnv: Map[String, String] = Map.empty,
override val autoExit: Boolean = true,
jettyClassLoader: ClassLoader = null
) extends PhantomJSEnv (phantomjsPath, addArgs, addEnv, autoExit, jettyClassLoader) {
private[ScoveragePhantomJSEnv] trait WebsocketListener {
def onRunning(): Unit
def onOpen(): Unit
def onClose(): Unit
def onMessage(msg: String): Unit

def log(msg: String): Unit
}
override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile,
logger: Logger, console: JSConsole): JSRunner = {
new ScoveragePhantomRunner(classpath, code, logger, console)
}

override def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile,
logger: Logger, console: JSConsole): AsyncJSRunner = {
new AsyncScoveragePhantomRunner(classpath, code, logger, console)
}

override def comRunner(classpath: CompleteClasspath, code: VirtualJSFile,
logger: Logger, console: JSConsole): ComJSRunner = {
new ComScoveragePhantomRunner(classpath, code, logger, console)
}


protected class ScoveragePhantomRunner(classpath: CompleteClasspath,
code: VirtualJSFile, logger: Logger, console: JSConsole
) extends ExtRunner(classpath, code, logger, console)
with AbstractScoveragePhantomRunner

protected class AsyncScoveragePhantomRunner(classpath: CompleteClasspath,
code: VirtualJSFile, logger: Logger, console: JSConsole
) extends AsyncExtRunner(classpath, code, logger, console)
with AbstractScoveragePhantomRunner

protected class ComScoveragePhantomRunner(classpath: CompleteClasspath,
code: VirtualJSFile, logger: Logger, console: JSConsole
) extends ComPhantomRunner(classpath, code, logger, console)
with ComJSRunner with AbstractScoveragePhantomRunner with WebsocketListener

protected trait AbstractScoveragePhantomRunner extends AbstractPhantomRunner {
override protected def createTmpLauncherFile(): File = {
val webF = createTmpWebpage()

val launcherTmpF = File.createTempFile("phantomjs-launcher", ".js")
launcherTmpF.deleteOnExit()

val out = new FileWriter(launcherTmpF)

try {
out.write(
s"""// Scala.js Phantom.js launcher
|var page = require('webpage').create();
|var fs = require('fs');
|var url = "${escapeJS(fixFileURI(webF.toURI).toASCIIString)}";
|var autoExit = $autoExit;
|page.onConsoleMessage = function(msg) {
| console.log(msg);
|};
|page.onError = function(msg, trace) {
| console.error(msg);
| if (trace && trace.length) {
| console.error('');
| trace.forEach(function(t) {
| console.error(' ' + t.file + ':' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
| });
| }
|
| phantom.exit(2);
|};
|page.onCallback = function(data) {
| if (!data.action) {
| console.error('Called callback without action');
| phantom.exit(3);
| } else if (data.action === 'exit') {
| phantom.exit(data.returnValue || 0);
| } else if (data.action === 'setAutoExit') {
| if (typeof(data.autoExit) === 'boolean')
| autoExit = data.autoExit;
| else
| autoExit = true;
| } else if (data.action == 'require.fs') {
| if(data.method == 'separator') {
| return JSON.stringify(fs.separator);
| } else {
| var ret = fs[data.method].apply(this, data.args);
| return JSON.stringify(ret);
| }
| } else {
| console.error('Unknown callback action ' + data.action);
| phantom.exit(4);
| }
|};
|page.open(url, function (status) {
| if (autoExit || status !== 'success')
| phantom.exit(status !== 'success');
|});
|""".stripMargin)
} finally {
out.close()
}

logger.debug(
"PhantomJS using launcher at: " + launcherTmpF.getAbsolutePath())

launcherTmpF
}
}
}

object ScoveragePhantomJSEnv extends PhantomJSEnv {
}


2 changes: 2 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.3.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3")

addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.8.5")

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.3")
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ object ScoverageCompiler {
dir
}

private def runtimeClasses: File = new File("./scalac-scoverage-runtime/target/scala-2.11/classes")
private def runtimeClasses: File = new File("./scalac-scoverage-runtime/shared/target/scala-2.11/classes")

private def findScalaJar(artifactId: String): File = findIvyJar("org.scala-lang", artifactId, ScalaVersion)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package scalajssupport

import scala.scalajs.js
import js.Dynamic.{ global => g }

/**
* This wraps RhinoFile, NodeFile, or PhantomFile depending on which javascript
* environment is being used, and emulates a subset of the java.io.File API.
*/
class File(path: String) {
import File._

val _file = jsFile(path)

def this(path: String, child: String) = {
this(File.pathJoin(path, child))
}

def delete(): Unit = {
_file.delete()
}
def getAbsolutePath(): String = {
_file.getAbsolutePath()
}

def getName(): String = {
_file.getName()
}

def getPath(): String = {
_file.getPath()
}

def isDirectory(): Boolean = {
_file.isDirectory()
}

def mkdirs(): Unit = {
_file.mkdirs()
}

def listFiles(): Array[File] = {
_file.listFiles().toArray
}

def listFiles(filter: FileFilter): Array[File] = {
_file.listFiles().filter(filter.accept).toArray
}

def readFile(): String = {
_file.readFile()
}

override def toString: String = {
getPath()
}
}

object File {
val jsFile: JsFileObject = if (js.Dynamic.global.hasOwnProperty("Packages").asInstanceOf[Boolean])
RhinoFile
else if (!js.Dynamic.global.hasOwnProperty("window").asInstanceOf[Boolean])
NodeFile
else
PhantomFile
// Factorize this

def pathJoin(path: String, child: String): String =
jsFile.pathJoin(path, child)

def write(path: String, data: String, mode: String = "a") =
jsFile.write(path, data, mode)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package scalajssupport

/**
* Emulates a subset of the java.io.FileWriter API required for scoverage to work.
*/
class FileWriter(file: File, append: Boolean) {
def this(file: File) = this(file, false)
def this(file: String) = this(new File(file), false)
def this(file: String, append: Boolean) = this(new File(file), append)

def append(csq: CharSequence) = {
File.write(file.getPath, csq.toString)
this
}

def close(): Unit = {
// do nothing as we don't open a FD to the file, as phantomJS does not use FDs
}

override def finalize(): Unit = close()

def flush() = {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package scalajssupport

trait JsFile {
def delete(): Unit
def getAbsolutePath(): String

def getName(): String

def getPath(): String

def isDirectory(): Boolean

def mkdirs(): Unit

def listFiles(): Array[File]

def listFiles(filter: FileFilter): Array[File] = {
listFiles().filter(filter.accept)
}

def readFile(): String
}

trait FileFilter {
def accept(file: File): Boolean
}

trait JsFileObject {
def write(path: String, data: String, mode: String = "a")
def pathJoin(path: String, child: String): String
def apply(path: String): JsFile
}
Loading