Skip to content

Commit

Permalink
feat(scala3): WIP working on scala 3 cross compilation. playframework…
Browse files Browse the repository at this point in the history
  • Loading branch information
brbrown25 committed Nov 19, 2021
1 parent 4440d3b commit 0a30cc5
Show file tree
Hide file tree
Showing 16 changed files with 1,035 additions and 44 deletions.
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ jobs:
- TRAVIS_SCALA_VERSION=2.13.6
- ADOPTOPENJDK=11

- name: "Run tests with Scala 3 and AdoptOpenJDK 11"
script: scripts/test-code.sh
env:
- TRAVIS_SCALA_VERSION=3.1.0
- ADOPTOPENJDK=11

- name: "Run tests with Scala 2.12 and AdoptOpenJDK 8"
script: scripts/test-code.sh
env:
Expand All @@ -40,6 +46,12 @@ jobs:
- TRAVIS_SCALA_VERSION=2.13.6
- ADOPTOPENJDK=8

- name: "Run tests with Scala 3 and AdoptOpenJDK 8"
script: scripts/test-code.sh
env:
- TRAVIS_SCALA_VERSION=3.1.0
- ADOPTOPENJDK=8

- stage: docs
script: scripts/validate-docs.sh
name: "Validate documentation"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/*
* Copyright (C) Lightbend Inc. <https://www.lightbend.com>
*/
package play.twirl.api

import scala.language.implicitConversions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) Lightbend Inc. <https://www.lightbend.com>
*/
package play.twirl.api

import java.util.Optional
import scala.collection.immutable
import scala.jdk.CollectionConverters._
import scala.reflect.ClassTag

case class BaseScalaTemplate[T <: Appendable[T], F <: Format[T]](format: F) {
// The overloaded methods are here for speed. The compiled templates
// can take advantage of them for a 12% performance boost
def _display_(x: AnyVal): T = format.escape(x.toString)
def _display_(x: String): T = if (x eq null) format.empty else format.escape(x)
def _display_(x: Unit): T = format.empty
def _display_(x: scala.xml.NodeSeq): T = if (x eq null) format.empty else format.raw(x.toString())
def _display_(x: T): T = if (x eq null) format.empty else x

def _display_(o: Any)(implicit m: ClassTag[T]): T = {
o match {
case escaped if escaped != null && escaped.getClass == m.runtimeClass => escaped.asInstanceOf[T]
case () => format.empty
case None => format.empty
case Some(v) => _display_(v)
case key: Optional[_] =>
(if (key.isPresent) Some(key.get) else None) match {
case None => format.empty
case Some(v) => _display_(v)
case _ => format.empty
}
case xml: scala.xml.NodeSeq => format.raw(xml.toString())
case escapeds: immutable.Seq[_] => format.fill(escapeds.map(_display_))
case escapeds: TraversableOnce[_] => format.fill(escapeds.iterator.map(_display_).toList)
case escapeds: Array[_] => format.fill(escapeds.view.map(_display_).toList)
case escapeds: java.util.List[_] =>
format.fill(escapeds.asScala.map(_display_).toList)
case string: String => format.escape(string)
case v if v != null => format.escape(v.toString)
case _ => format.empty
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package play.twirl.api

import scala.language.implicitConversions

/**
* Imports for useful Twirl helpers.
*/
object TwirlHelperImports {

/** Allows Java collections to be used as if they were Scala collections. */
implicit def twirlJavaCollectionToScala[T](x: java.lang.Iterable[T]): Iterable[T] = {
import scala.jdk.CollectionConverters._
x.asScala
}

/** Allows inline formatting of java.util.Date */
implicit class TwirlRichDate(date: java.util.Date) {
def format(pattern: String): String = {
new java.text.SimpleDateFormat(pattern).format(date)
}
}

/** Adds a when method to Strings to control when they are rendered. */
implicit class TwirlRichString(string: String) {
def when(predicate: => Boolean): String = {
predicate match {
case true => string
case false => ""
}
}
}
}
43 changes: 43 additions & 0 deletions api/shared/src/main/scala-3/play/twirl/api/BaseScalaTemplate.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) Lightbend Inc. <https://www.lightbend.com>
*/
package play.twirl.api

import java.util.Optional
import scala.collection.immutable
import scala.jdk.CollectionConverters._
import scala.reflect.ClassTag

case class BaseScalaTemplate[T <: Appendable[T], F <: Format[T]](format: F) {
// The overloaded methods are here for speed. The compiled templates
// can take advantage of them for a 12% performance boost
def _display_(x: AnyVal): T = format.escape(x.toString)
def _display_(x: String): T = if (x eq null) format.empty else format.escape(x)
def _display_(x: Unit): T = format.empty
def _display_(x: scala.xml.NodeSeq): T = if (x eq null) format.empty else format.raw(x.toString())
def _display_(x: T): T = if (x eq null) format.empty else x

def _display_(o: Any)(implicit m: ClassTag[T]): T = {
o match {
case escaped if escaped != null && escaped.getClass == m.runtimeClass => escaped.asInstanceOf[T]
case () => format.empty
case None => format.empty
case Some(v) => _display_(v)
case key: Optional[_] =>
(if (key.isPresent) Some(key.get) else None) match {
case None => format.empty
case Some(v) => _display_(v)
case null => format.empty
}
case xml: scala.xml.NodeSeq => format.raw(xml.toString())
case escapeds: immutable.Seq[_] => format.fill(escapeds.map(_display_))
case escapeds: TraversableOnce[_] => format.fill(escapeds.iterator.map(_display_).toList)
case escapeds: Array[_] => format.fill(escapeds.view.map(_display_).toList)
case escapeds: java.util.List[_] =>
format.fill(escapeds.asScala.map(_display_).toList)
case string: String => format.escape(string)
case v if v != null => format.escape(v.toString)
case null => format.empty
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package play.twirl.api

import scala.language.implicitConversions

/**
* Imports for useful Twirl helpers.
*/
object TwirlHelperImports {

/** Allows Java collections to be used as if they were Scala collections. */
implicit def twirlJavaCollectionToScala[T](x: java.lang.Iterable[T]): Iterable[T] = {
import scala.jdk.CollectionConverters._
x.asScala
}

/** Allows inline formatting of java.util.Date */
implicit class TwirlRichDate(date: java.util.Date) {
def format(pattern: String): String = {
new java.text.SimpleDateFormat(pattern).format(date)
}
}

/** Adds a when method to Strings to control when they are rendered. */
implicit class TwirlRichString(string: String) {
def when(predicate: => Boolean): String = {
predicate match {
case true => string
case false => ""
}
}
}
}
14 changes: 7 additions & 7 deletions api/shared/src/main/scala/play/twirl/api/TemplateMagic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ object TemplateMagic {

// --- IF

implicit def iterableToBoolean(x: Iterable[_]) = x != null && !x.isEmpty
implicit def optionToBoolean(x: Option[_]) = x != null && x.isDefined
implicit def stringToBoolean(x: String) = x != null && !x.isEmpty
implicit def iterableToBoolean(x: Iterable[_]): Boolean = x != null && !x.isEmpty
implicit def optionToBoolean(x: Option[_]): Boolean = x != null && x.isDefined
implicit def stringToBoolean(x: String): Boolean = x != null && !x.isEmpty

// --- JAVA

implicit def javaCollectionToScala[T](x: java.lang.Iterable[T]) = {
implicit def javaCollectionToScala[T](x: java.lang.Iterable[T]): Iterable[T] = {
import scala.collection.JavaConverters._
x.asScala
}
Expand All @@ -42,7 +42,7 @@ object TemplateMagic {
}
}

implicit def anyToDefault(x: Any) = Default(x)
implicit def anyToDefault(x: Any): Default = Default(x)

// --- DATE

Expand All @@ -52,7 +52,7 @@ object TemplateMagic {
}
}

implicit def richDate(date: java.util.Date) = new RichDate(date)
implicit def richDate(date: java.util.Date): RichDate = new RichDate(date)

// --- STRING

Expand All @@ -65,5 +65,5 @@ object TemplateMagic {
}
}

implicit def richString(string: String) = new RichString(string)
implicit def richString(string: String): RichString = new RichString(string)
}
56 changes: 40 additions & 16 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import Dependencies._

import sbtcrossproject.crossProject
import sbtcrossproject.CrossPlugin.autoImport.crossProject
import org.scalajs.jsenv.nodejs.NodeJSEnv

Global / onChangedBuildSource := ReloadOnSourceChanges

// Binary compatibility is this version
val previousVersion: Option[String] = Some("1.5.0")

val ScalaTestVersion = "3.2.10"

// Do NOT upgrade these dependencies to 2.x or newer! twirl is a sbt-plugin
// and gets published with Scala 2.12, therefore we need to stay at the same major version
// like the 2.12.x Scala compiler, otherwise we run into conflicts when using sbt 1.5+
// See https://github.com/scala/scala/pull/9743
val ScalaParserCombinatorsVersion = "1.1.2" // Do not upgrade beyond 1.x
val ScalaXmlVersion = "1.3.0" // Do not upgrade beyond 1.x
def parserCombinators(version: String) = {
CrossVersion.partialVersion(version) match {
case Some((2, _)) =>
Seq("org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" % Optional)
case _ => Seq("org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.0" % Optional)
}
}

val mimaSettings = Seq(
mimaPreviousArtifacts := previousVersion.map(organization.value %% name.value % _).toSet
Expand Down Expand Up @@ -56,6 +59,8 @@ lazy val api = crossProject(JVMPlatform, JSPlatform)
.enablePlugins(Common, Playdoc, Omnidoc)
.configs(Docs)
.settings(
// scalaVersion := Scala3,
// crossScalaVersions := ScalaVersions,
mimaSettings,
name := "twirl-api",
jsEnv := nodeJs,
Expand All @@ -67,8 +72,14 @@ lazy val api = crossProject(JVMPlatform, JSPlatform)
"org.scalatest.tools.ScalaTestFramework"
)
),
libraryDependencies += "org.scala-lang.modules" %%% "scala-xml" % ScalaXmlVersion,
libraryDependencies += "org.scalatest" %%% "scalatest" % ScalaTestVersion % Test,
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, _)) =>
Seq("org.scala-lang.modules" %%% "scala-xml" % "1.3.0" % Optional)
case _ => Seq("org.scala-lang.modules" %%% "scala-xml" % "2.0.1" % Optional)
}
},
libraryDependencies += "org.scalatest" %%% "scalatest" % ScalaTestVersion % Test,
)

lazy val apiJvm = api.jvm
Expand All @@ -78,9 +89,11 @@ lazy val parser = project
.in(file("parser"))
.enablePlugins(Common, Omnidoc)
.settings(
// scalaVersion := Scala3,
// crossScalaVersions := ScalaVersions,
mimaSettings,
name := "twirl-parser",
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % ScalaParserCombinatorsVersion % Optional,
libraryDependencies ++= parserCombinators(scalaVersion.value),
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test,
libraryDependencies += "org.scalatest" %%% "scalatest" % ScalaTestVersion % Test,
)
Expand All @@ -90,10 +103,20 @@ lazy val compiler = project
.enablePlugins(Common, Omnidoc)
.dependsOn(apiJvm, parser % "compile;test->test")
.settings(
// scalaVersion := Scala3,
// crossScalaVersions := ScalaVersions,
mimaSettings,
name := "twirl-compiler",
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value,
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % ScalaParserCombinatorsVersion % "optional",
name := "twirl-compiler",
libraryDependencies ++= {
//todo commonize this
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, _)) =>
//only for scala < 3
Seq("org.scala-lang" % "scala-compiler" % scalaVersion.value)
case _ => Seq("org.scala-lang" %% "scala3-compiler" % scalaVersion.value)
}
},
libraryDependencies ++= parserCombinators(scalaVersion.value),
run / fork := true,
)

Expand All @@ -102,9 +125,10 @@ lazy val plugin = project
.enablePlugins(SbtPlugin)
.dependsOn(compiler)
.settings(
name := "sbt-twirl",
organization := "com.typesafe.sbt",
scalaVersion := Scala212,
name := "sbt-twirl",
organization := "com.typesafe.sbt",
scalaVersion := Scala212,
// crossScalaVersions := ScalaVersions,
libraryDependencies += "org.scalatest" %%% "scalatest" % ScalaTestVersion % Test,
Compile / resourceGenerators += generateVersionFile.taskValue,
scriptedLaunchOpts += version.apply { v => s"-Dproject.version=$v" }.value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ sealed trait AbstractGeneratedSource {

lazy val matrix: Seq[(Int, Int)] = {
for {
pos <- meta("MATRIX").split('|')
pos <- meta("MATRIX").split('|').toIndexedSeq
c = pos.split("->")
} yield try {
Integer.parseInt(c(0)) -> Integer.parseInt(c(1))
Expand All @@ -79,7 +79,7 @@ sealed trait AbstractGeneratedSource {

lazy val lines: Seq[(Int, Int)] = {
for {
pos <- meta("LINES").split('|')
pos <- meta("LINES").split('|').toIndexedSeq
c = pos.split("->")
} yield try {
Integer.parseInt(c(0)) -> Integer.parseInt(c(1))
Expand Down Expand Up @@ -247,7 +247,7 @@ object TwirlCompiler {
) = {
val templateParser = new TwirlParser(inclusiveDot)
templateParser.parse(new String(content, codec.charSet)) match {
case templateParser.Success(parsed: Template, rest) if rest.atEnd => {
case templateParser.Success(parsed: Template, rest) if rest.atEnd() => {
generateFinalTemplate(
relativePath,
content,
Expand All @@ -261,7 +261,7 @@ object TwirlCompiler {
)
}
case templateParser.Success(_, rest) => {
throw new TemplateCompilationError(new File(relativePath), "Not parsed?", rest.pos.line, rest.pos.column)
throw new TemplateCompilationError(new File(relativePath), "Not parsed?", rest.pos().line, rest.pos().column)
}
case templateParser.Error(_, rest, errors) => {
val firstError = errors.head
Expand Down
Loading

0 comments on commit 0a30cc5

Please sign in to comment.