diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 00000000..3c681f57 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,12 @@ +# Keep in sync with docs/.scalafmt.conf +align = true +assumeStandardLibraryStripMargin = true +danglingParentheses = true +docstrings = JavaDoc +maxColumn = 120 +project.git = true +rewrite.rules = [ AvoidInfix, ExpandImportSelectors, RedundantParens, SortModifiers, PreferCurlyFors ] +rewrite.sortModifiers.order = [ "private", "protected", "final", "sealed", "abstract", "implicit", "override", "lazy" ] +spaces.inImportCurlyBraces = true # more idiomatic to include whitepsace in import x.{ yyy } +trailingCommas = preserve +version = 2.0.0 diff --git a/.travis.yml b/.travis.yml index b66d6249..3729acaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ before_install: curl -Ls https://git.io/jabba | bash && . ~/.jabba/jabba.sh install: jabba install "adopt@~1.$TRAVIS_JDK.0-0" && jabba use "$_" && java -Xmx32m -version script: + - ./scripts/test-code.sh - ./scripts/validate-code.sh - ./scripts/validate-docs.sh diff --git a/api/shared/src/main/scala/play/twirl/api/BaseScalaTemplate.scala b/api/shared/src/main/scala/play/twirl/api/BaseScalaTemplate.scala index 94a3743c..60685755 100644 --- a/api/shared/src/main/scala/play/twirl/api/BaseScalaTemplate.scala +++ b/api/shared/src/main/scala/play/twirl/api/BaseScalaTemplate.scala @@ -13,32 +13,33 @@ 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: 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_(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 () => 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 None => format.empty case Some(v) => _display_(v) - case _ => format.empty + case _ => format.empty } - case xml: scala.xml.NodeSeq => format.raw(xml.toString()) - case escapeds: immutable.Seq[_] => format.fill(escapeds.map(_display_)) + case xml: scala.xml.NodeSeq => format.raw(xml.toString()) + case escapeds: immutable.Seq[_] => format.fill(escapeds.map(_display_)) case escapeds: TraversableOnce[_] => format.fill(escapeds.map(_display_).toList) - case escapeds: Array[_] => format.fill(escapeds.view.map(_display_).toList) - case escapeds: java.util.List[_] => format.fill(JavaConverters.collectionAsScalaIterableConverter(escapeds).asScala.map(_display_).toList) + case escapeds: Array[_] => format.fill(escapeds.view.map(_display_).toList) + case escapeds: java.util.List[_] => + format.fill(JavaConverters.collectionAsScalaIterableConverter(escapeds).asScala.map(_display_).toList) case string: String => format.escape(string) case v if v != null => format.escape(v.toString) - case _ => format.empty + case _ => format.empty } } } diff --git a/api/shared/src/main/scala/play/twirl/api/Content.scala b/api/shared/src/main/scala/play/twirl/api/Content.scala index d4a2ca05..19c06d53 100644 --- a/api/shared/src/main/scala/play/twirl/api/Content.scala +++ b/api/shared/src/main/scala/play/twirl/api/Content.scala @@ -32,7 +32,11 @@ trait Content { * @param text Formatted content * @tparam A self-type */ -abstract class BufferedContent[A <: BufferedContent[A]](protected val elements: immutable.Seq[A], protected val text: String) extends Appendable[A] with Content { this: A => +abstract class BufferedContent[A <: BufferedContent[A]]( + protected val elements: immutable.Seq[A], + protected val text: String +) extends Appendable[A] + with Content { this: A => protected def buildString(builder: StringBuilder): Unit = { if (!elements.isEmpty) { elements.foreach { e => @@ -59,7 +63,7 @@ abstract class BufferedContent[A <: BufferedContent[A]](protected val elements: override def equals(obj: Any): Boolean = obj match { case other: BufferedContent[_] if this.getClass == other.getClass => body == other.body - case _ => false + case _ => false } override def hashCode(): Int = this.getClass.hashCode() + body.hashCode() diff --git a/api/shared/src/main/scala/play/twirl/api/Formats.scala b/api/shared/src/main/scala/play/twirl/api/Formats.scala index 6c019103..6c3cc6b5 100644 --- a/api/shared/src/main/scala/play/twirl/api/Formats.scala +++ b/api/shared/src/main/scala/play/twirl/api/Formats.scala @@ -23,7 +23,8 @@ object Formats { * This has 3 states, either it's a tree of elements, or a leaf, if it's a leaf, it's either safe text, or unsafe text * that needs to be escaped when written out. */ -class Html private[api] (elements: immutable.Seq[Html], text: String, escape: Boolean) extends BufferedContent[Html](elements, text) { +class Html private[api] (elements: immutable.Seq[Html], text: String, escape: Boolean) + extends BufferedContent[Html](elements, text) { def this(text: String) = this(Nil, Formats.safe(text), false) def this(elements: immutable.Seq[Html]) = this(elements, "", false) @@ -36,7 +37,7 @@ class Html private[api] (elements: immutable.Seq[Html], text: String, escape: Bo * of Strings, if it doesn't, performance actually goes down (measured 10%), due to the fact that the JVM can't * optimise the invocation of buildString as well because there are two different possible implementations. */ - override protected def buildString(builder: StringBuilder): Unit = { + protected override def buildString(builder: StringBuilder): Unit = { if (elements.nonEmpty) { elements.foreach { e => e.buildString(builder) @@ -47,12 +48,12 @@ class Html private[api] (elements: immutable.Seq[Html], text: String, escape: Bo var i = 0 while (i < text.length) { text.charAt(i) match { - case '<' => builder.append("<") - case '>' => builder.append(">") - case '"' => builder.append(""") + case '<' => builder.append("<") + case '>' => builder.append(">") + case '"' => builder.append(""") case '\'' => builder.append("'") - case '&' => builder.append("&") - case c => builder += c + case '&' => builder.append("&") + case c => builder += c } i += 1 } @@ -80,8 +81,8 @@ object Html { } /** - * Creates an HTML fragment with initial content specified. Uses an empty String if None is passed. - */ + * Creates an HTML fragment with initial content specified. Uses an empty String if None is passed. + */ def apply(text: Option[String]): Html = { apply(text.getOrElse("")) } @@ -227,7 +228,8 @@ object XmlFormat extends Format[Xml] { /** * Type used in default JavaScript templates. */ -class JavaScript private (elements: immutable.Seq[JavaScript], text: String) extends BufferedContent[JavaScript](elements, text) { +class JavaScript private (elements: immutable.Seq[JavaScript], text: String) + extends BufferedContent[JavaScript](elements, text) { def this(text: String) = this(Nil, Formats.safe(text)) def this(elements: immutable.Seq[JavaScript]) = this(elements, "") @@ -241,6 +243,7 @@ class JavaScript private (elements: immutable.Seq[JavaScript], text: String) ext * Helper for JavaScript utility methods. */ object JavaScript { + /** * Creates a JavaScript fragment with initial content specified */ @@ -253,6 +256,7 @@ object JavaScript { * Formatter for JavaScript content. */ object JavaScriptFormat extends Format[JavaScript] { + /** * Integrate `text` without performing any escaping process. * @param text Text to integrate diff --git a/api/shared/src/main/scala/play/twirl/api/Template.scala b/api/shared/src/main/scala/play/twirl/api/Template.scala index 05d8fee6..0b5cdeb6 100644 --- a/api/shared/src/main/scala/play/twirl/api/Template.scala +++ b/api/shared/src/main/scala/play/twirl/api/Template.scala @@ -72,25 +72,113 @@ trait Template16[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Result] { } trait Template17[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, Result] { - def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q): Result + def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q) + : Result } trait Template18[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, Result] { - def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q, r: R): Result + def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q, r: R) + : Result } trait Template19[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, Result] { - def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q, r: R, s: S): Result + def render( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J, + k: K, + l: L, + m: M, + n: N, + o: O, + p: P, + q: Q, + r: R, + s: S + ): Result } trait Template20[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, Result] { - def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q, r: R, s: S, t: T): Result + def render( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J, + k: K, + l: L, + m: M, + n: N, + o: O, + p: P, + q: Q, + r: R, + s: S, + t: T + ): Result } trait Template21[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, Result] { - def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q, r: R, s: S, t: T, u: U): Result + def render( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J, + k: K, + l: L, + m: M, + n: N, + o: O, + p: P, + q: Q, + r: R, + s: S, + t: T, + u: U + ): Result } trait Template22[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, Result] { - def render(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q, r: R, s: S, t: T, u: U, v: V): Result + def render( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J, + k: K, + l: L, + m: M, + n: N, + o: O, + p: P, + q: Q, + r: R, + s: S, + t: T, + u: U, + v: V + ): Result } diff --git a/api/shared/src/main/scala/play/twirl/api/TemplateMagic.scala b/api/shared/src/main/scala/play/twirl/api/TemplateMagic.scala index b6a71f00..3bbe71ed 100644 --- a/api/shared/src/main/scala/play/twirl/api/TemplateMagic.scala +++ b/api/shared/src/main/scala/play/twirl/api/TemplateMagic.scala @@ -19,8 +19,8 @@ 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 optionToBoolean(x: Option[_]) = x != null && x.isDefined + implicit def stringToBoolean(x: String) = x != null && !x.isEmpty // --- JAVA @@ -33,12 +33,12 @@ object TemplateMagic { case class Default(default: Any) { def ?:(x: Any) = x match { - case "" => default - case Nil => default + case "" => default + case Nil => default case false => default - case 0 => default - case None => default - case _ => x + case 0 => default + case None => default + case _ => x } } @@ -62,7 +62,7 @@ object TemplateMagic { def when(predicate: => Boolean) = { predicate match { - case true => string + case true => string case false => "" } } diff --git a/api/shared/src/main/scala/play/twirl/api/TwirlFeatureImports.scala b/api/shared/src/main/scala/play/twirl/api/TwirlFeatureImports.scala index defd3a68..235fffe2 100644 --- a/api/shared/src/main/scala/play/twirl/api/TwirlFeatureImports.scala +++ b/api/shared/src/main/scala/play/twirl/api/TwirlFeatureImports.scala @@ -6,23 +6,23 @@ package play.twirl.api import scala.language.implicitConversions /** - * Imports that provide Twirl language features. - * - * This includes: - * - * - @defining - * - @using - * - iterable/option/string as boolean for if statements - * - default values (maybeFoo ? defaultFoo) - */ + * Imports that provide Twirl language features. + * + * This includes: + * + * - @defining + * - @using + * - iterable/option/string as boolean for if statements + * - default values (maybeFoo ? defaultFoo) + */ object TwirlFeatureImports { /** - * Provides the `@defining` language feature, that lets you set a local val that can be reused. - * - * @param t The defined val. - * @param handler The block to handle it. - */ + * Provides the `@defining` language feature, that lets you set a local val that can be reused. + * + * @param t The defined val. + * @param handler The block to handle it. + */ def defining[T](t: T)(handler: T => Any): Any = { handler(t) } @@ -32,23 +32,25 @@ object TwirlFeatureImports { /** Adds "truthiness" to iterables, making them false if they are empty. */ implicit def twirlIterableToBoolean(x: Iterable[_]): Boolean = x != null && !x.isEmpty + /** Adds "truthiness" to options, making them false if they are empty. */ implicit def twirlOptionToBoolean(x: Option[_]): Boolean = x != null && x.isDefined + /** Adds "truthiness" to strings, making them false if they are empty. */ implicit def twirlStringToBoolean(x: String): Boolean = x != null && !x.isEmpty /** - * Provides default values, such that an empty sequence, string, option, false boolean, or null will render - * the default value. - */ + * Provides default values, such that an empty sequence, string, option, false boolean, or null will render + * the default value. + */ implicit class TwirlDefaultValue(default: Any) { def ?:(x: Any): Any = x match { - case "" => default - case Nil => default + case "" => default + case Nil => default case false => default - case 0 => default - case None => default - case _ => x + case 0 => default + case None => default + case _ => x } } -} \ No newline at end of file +} diff --git a/api/shared/src/main/scala/play/twirl/api/TwirlHelperImports.scala b/api/shared/src/main/scala/play/twirl/api/TwirlHelperImports.scala index d366fe02..bdee1c68 100644 --- a/api/shared/src/main/scala/play/twirl/api/TwirlHelperImports.scala +++ b/api/shared/src/main/scala/play/twirl/api/TwirlHelperImports.scala @@ -6,8 +6,8 @@ package play.twirl.api import scala.language.implicitConversions /** - * Imports for useful Twirl helpers. - */ + * Imports for useful Twirl helpers. + */ object TwirlHelperImports { /** Allows Java collections to be used as if they were Scala collections. */ @@ -30,11 +30,11 @@ object TwirlHelperImports { def when(predicate: => Boolean): String = { predicate match { - case true => string + case true => string case false => "" } } } -} \ No newline at end of file +} diff --git a/api/shared/src/main/scala/play/twirl/api/package.scala b/api/shared/src/main/scala/play/twirl/api/package.scala index 5aa274be..89212565 100644 --- a/api/shared/src/main/scala/play/twirl/api/package.scala +++ b/api/shared/src/main/scala/play/twirl/api/package.scala @@ -22,17 +22,17 @@ package object api { * Three interpolators are available: `html`, `xml` and `js`. */ implicit class StringInterpolation(val sc: StringContext) extends AnyVal { - + def html(args: Any*): Html = interpolate(args, HtmlFormat) - + def xml(args: Any*): Xml = interpolate(args, XmlFormat) def js(args: Any*): JavaScript = interpolate(args, JavaScriptFormat) - def interpolate[A <: Appendable[A] : ClassTag](args: Seq[Any], format: Format[A]): A = { + def interpolate[A <: Appendable[A]: ClassTag](args: Seq[Any], format: Format[A]): A = { sc.checkLengths(args) - val array = Array.ofDim[Any](args.size + sc.parts.size) - val strings = sc.parts.iterator + val array = Array.ofDim[Any](args.size + sc.parts.size) + val strings = sc.parts.iterator val expressions = args.iterator array(0) = format.raw(strings.next()) var i = 1 @@ -43,7 +43,7 @@ package object api { } new BaseScalaTemplate[A, Format[A]](format)._display_(array) } - + } } diff --git a/api/shared/src/main/scala/play/twirl/api/utils/StringEscapeUtils.scala b/api/shared/src/main/scala/play/twirl/api/utils/StringEscapeUtils.scala index 78441053..1ad4f157 100644 --- a/api/shared/src/main/scala/play/twirl/api/utils/StringEscapeUtils.scala +++ b/api/shared/src/main/scala/play/twirl/api/utils/StringEscapeUtils.scala @@ -6,7 +6,7 @@ package play.twirl.api.utils object StringEscapeUtils { def escapeEcmaScript(input: String): String = { - val s = new StringBuilder() + val s = new StringBuilder() val len = input.length var pos = 0 while (pos < len) { @@ -15,7 +15,7 @@ object StringEscapeUtils { case '\'' => s.append("\\'") case '\"' => s.append("\\\"") case '\\' => s.append("\\\\") - case '/' => s.append("\\/") + case '/' => s.append("\\/") // JAVA_CTRL_CHARS case '\b' => s.append("\\b") case '\n' => s.append("\\n") @@ -36,21 +36,21 @@ object StringEscapeUtils { def escapeXml11(input: String): String = { // Implemented per XML spec: // http://www.w3.org/International/questions/qa-controls - val s = new StringBuilder() + val s = new StringBuilder() val len = input.length var pos = 0 while (pos < len) { input.charAt(pos) match { - case '<' => s.append("<") - case '>' => s.append(">") - case '&' => s.append("&") - case '"' => s.append(""") - case '\n' => s.append('\n') - case '\r' => s.append('\r') - case '\t' => s.append('\t') + case '<' => s.append("<") + case '>' => s.append(">") + case '&' => s.append("&") + case '"' => s.append(""") + case '\n' => s.append('\n') + case '\r' => s.append('\r') + case '\t' => s.append('\t') case c if c < ' ' => - case c => s.append(c) + case c => s.append(c) } pos += 1 } diff --git a/api/shared/src/test/scala/play/twirl/api/test/BufferedContentSpec.scala b/api/shared/src/test/scala/play/twirl/api/test/BufferedContentSpec.scala index bbd9eedd..c7ceb00d 100644 --- a/api/shared/src/test/scala/play/twirl/api/test/BufferedContentSpec.scala +++ b/api/shared/src/test/scala/play/twirl/api/test/BufferedContentSpec.scala @@ -3,7 +3,8 @@ */ package play.twirl.api.test -import org.scalatest.{ MustMatchers, WordSpec } +import org.scalatest.MustMatchers +import org.scalatest.WordSpec import play.twirl.api._ import scala.collection.immutable @@ -19,11 +20,17 @@ class BufferedContentSpec extends WordSpec with MustMatchers { } "return false for BufferedContents with different bodies but the same implementations" in { - HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) must not be HtmlFormat.fill(immutable.Seq(Html("fizz"), Html("buzz"))) + HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) must not be HtmlFormat.fill( + immutable.Seq(Html("fizz"), Html("buzz")) + ) HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) must not be Html("fizzbuzz") - XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) must not be XmlFormat.fill(immutable.Seq(Xml("fizz"), Xml("buzz"))) + XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) must not be XmlFormat.fill( + immutable.Seq(Xml("fizz"), Xml("buzz")) + ) XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) must not be Xml("fizzbuzz") - TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) must not be TxtFormat.fill(immutable.Seq(Txt("fizz"), Txt("buzz"))) + TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) must not be TxtFormat.fill( + immutable.Seq(Txt("fizz"), Txt("buzz")) + ) TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) must not be Txt("fizzbuzz") Html("hello") must not be Html("boom") Txt("hello") must not be Txt("boom") @@ -31,11 +38,17 @@ class BufferedContentSpec extends WordSpec with MustMatchers { } "return true for BufferedContents with the same body and the same implementation" in { - HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) mustEqual HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) + HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) mustEqual HtmlFormat.fill( + immutable.Seq(Html("foo"), Html("bar")) + ) HtmlFormat.fill(immutable.Seq(Html("foo"), Html("bar"))) mustEqual Html("foobar") - XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) mustEqual XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) + XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) mustEqual XmlFormat.fill( + immutable.Seq(Xml("foo"), Xml("bar")) + ) XmlFormat.fill(immutable.Seq(Xml("foo"), Xml("bar"))) mustEqual Xml("foobar") - TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) mustEqual TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) + TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) mustEqual TxtFormat.fill( + immutable.Seq(Txt("foo"), Txt("bar")) + ) TxtFormat.fill(immutable.Seq(Txt("foo"), Txt("bar"))) mustEqual Txt("foobar") Html("hello") mustEqual Html("hello") Txt("hello") mustEqual Txt("hello") diff --git a/api/shared/src/test/scala/play/twirl/api/test/FormatSpec.scala b/api/shared/src/test/scala/play/twirl/api/test/FormatSpec.scala index ae9ab1c6..6994545a 100644 --- a/api/shared/src/test/scala/play/twirl/api/test/FormatSpec.scala +++ b/api/shared/src/test/scala/play/twirl/api/test/FormatSpec.scala @@ -4,7 +4,8 @@ package play.twirl.api package test -import org.scalatest.{ MustMatchers, WordSpec } +import org.scalatest.MustMatchers +import org.scalatest.WordSpec class FormatSpec extends WordSpec with MustMatchers { diff --git a/api/shared/src/test/scala/play/twirl/api/test/StringInterpolationSpec.scala b/api/shared/src/test/scala/play/twirl/api/test/StringInterpolationSpec.scala index 090834a5..7a1417df 100644 --- a/api/shared/src/test/scala/play/twirl/api/test/StringInterpolationSpec.scala +++ b/api/shared/src/test/scala/play/twirl/api/test/StringInterpolationSpec.scala @@ -8,7 +8,8 @@ import java.util.ArrayList import java.util.Optional import java.util.{ List => JList } -import org.scalatest.{ MustMatchers, WordSpec } +import org.scalatest.MustMatchers +import org.scalatest.WordSpec class StringInterpolationSpec extends WordSpec with MustMatchers { @@ -19,11 +20,11 @@ class StringInterpolationSpec extends WordSpec with MustMatchers { } "escape interpolated arguments" in { val arg = "<" - val p = html"

$arg

" + val p = html"

$arg

" p.body mustBe "

<

" } "leave nested templates untouched" in { - val p = html"

" + val p = html"

" val div = html"
$p
" div.body mustBe "

" } @@ -38,4 +39,4 @@ class StringInterpolationSpec extends WordSpec with MustMatchers { } } -} \ No newline at end of file +} diff --git a/build.sbt b/build.sbt index 762bc610..721e8adf 100644 --- a/build.sbt +++ b/build.sbt @@ -7,15 +7,18 @@ val previousVersion: Option[String] = None def binaryCompatibilitySettings(org: String, moduleName: String, scalaBinVersion: String): Set[ModuleID] = { if (scalaBinVersion.equals(scala213)) Set.empty - else previousVersion match { - case None => Set.empty - case Some(pv) => Set(org % s"${moduleName}_${scalaBinVersion}" % pv) - } + else + previousVersion match { + case None => Set.empty + case Some(pv) => Set(org % s"${moduleName}_${scalaBinVersion}" % pv) + } } val javacParameters = Seq( - "-source", "1.8", - "-target", "1.8", + "-source", + "1.8", + "-target", + "1.8", "-Xlint:deprecation", "-Xlint:unchecked" ) @@ -27,7 +30,6 @@ val scalacBasicParams = Seq( val scalacExtraParams = scalacBasicParams ++ Seq( "-Ywarn-unused:imports", "-Xlint:nullary-unit", - "-Xlint", "-Ywarn-dead-code", ) @@ -37,18 +39,21 @@ val javaCompilerSettings = Seq( javacOptions in Test ++= javacParameters, ) -def scalacCompilerSettings(scalaVer: String) = if (scalaVer.equals(scala210)) { - scalacBasicParams -} else { - scalacExtraParams -} +def scalacCompilerSettings(scalaVer: String) = + if (scalaVer.equals(scala210)) { + scalacBasicParams + } else { + scalacExtraParams + } val headerSettings = Seq( headerLicense := { val currentYear = java.time.Year.now(java.time.Clock.systemUTC).getValue - Some(HeaderLicense.Custom( - s"Copyright (C) 2009-$currentYear Lightbend Inc. " - )) + Some( + HeaderLicense.Custom( + s"Copyright (C) 2009-$currentYear Lightbend Inc. " + ) + ) }, headerEmptyLine := false ) @@ -60,13 +65,15 @@ val commonSettings = javaCompilerSettings ++ headerSettings ++ Seq( ) lazy val twirl = project - .in(file(".")) - .enablePlugins(PlayRootProject) - .settings(commonSettings) - .settings(crossScalaVersions := Nil) // workaround so + uses project-defined variants - .settings(releaseCrossBuild := false) - .aggregate(apiJvm, apiJs, parser, compiler, plugin) - + .in(file(".")) + .enablePlugins(PlayRootProject) + .settings(commonSettings) + .settings( + crossScalaVersions := Nil, // workaround so + uses project-defined variants + releaseCrossBuild := false, + mimaFailOnNoPrevious := false + ) + .aggregate(apiJvm, apiJs, parser, compiler, plugin) lazy val nodeJs = { if (System.getProperty("NODE_PATH") != null) @@ -76,66 +83,79 @@ lazy val nodeJs = { } lazy val api = crossProject(JVMPlatform, JSPlatform) - .in(file("api")) - .enablePlugins(PlayLibrary, Playdoc) - .configs(Docs) - .settings(commonSettings) - .settings(mimaPreviousArtifacts := binaryCompatibilitySettings(organization.value, moduleName.value, scalaBinaryVersion.value)) - .settings( - name := "twirl-api", - jsEnv := nodeJs, - libraryDependencies ++= scalaXml.value, - libraryDependencies += "org.scalatest" %%% "scalatest" % scalatest(scalaVersion.value) % "test" - ) + .in(file("api")) + .enablePlugins(PlayLibrary, Playdoc) + .configs(Docs) + .settings(commonSettings) + .settings( + name := "twirl-api", + jsEnv := nodeJs, + libraryDependencies ++= scalaXml.value, + libraryDependencies += "org.scalatest" %%% "scalatest" % scalatest(scalaVersion.value) % "test", + mimaPreviousArtifacts := binaryCompatibilitySettings( + organization.value, + moduleName.value, + scalaBinaryVersion.value + ), + ) lazy val apiJvm = api.jvm -lazy val apiJs = api.js +lazy val apiJs = api.js lazy val parser = project - .in(file("parser")) - .enablePlugins(PlayLibrary) - .settings(commonSettings) - .settings(mimaPreviousArtifacts := binaryCompatibilitySettings(organization.value, moduleName.value, scalaBinaryVersion.value)) - .settings( - name := "twirl-parser", - libraryDependencies ++= scalaParserCombinators(scalaVersion.value), - libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test", - libraryDependencies += "org.scalatest" %%% "scalatest" % scalatest(scalaVersion.value) % "test" - ) + .in(file("parser")) + .enablePlugins(PlayLibrary) + .settings(commonSettings) + .settings( + name := "twirl-parser", + libraryDependencies ++= scalaParserCombinators(scalaVersion.value), + libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test", + libraryDependencies += "org.scalatest" %%% "scalatest" % scalatest(scalaVersion.value) % "test", + mimaPreviousArtifacts := binaryCompatibilitySettings(organization.value, moduleName.value, scalaBinaryVersion.value) + ) lazy val compiler = project - .in(file("compiler")) - .enablePlugins(PlayLibrary) - .dependsOn(apiJvm, parser % "compile;test->test") - .settings(commonSettings) - .settings(mimaPreviousArtifacts := binaryCompatibilitySettings(organization.value, moduleName.value, scalaBinaryVersion.value)) - .settings( - name := "twirl-compiler", - libraryDependencies += scalaCompiler(scalaVersion.value), - libraryDependencies ++= scalaParserCombinators(scalaVersion.value), - fork in run := true - ) + .in(file("compiler")) + .enablePlugins(PlayLibrary) + .dependsOn(apiJvm, parser % "compile;test->test") + .settings(commonSettings) + .settings( + name := "twirl-compiler", + libraryDependencies += scalaCompiler(scalaVersion.value), + libraryDependencies ++= scalaParserCombinators(scalaVersion.value), + fork in run := true, + mimaPreviousArtifacts := binaryCompatibilitySettings( + organization.value, + moduleName.value, + scalaBinaryVersion.value + ), + ) lazy val plugin = project - .in(file("sbt-twirl")) - .enablePlugins(PlaySbtPlugin, SbtPlugin) - .dependsOn(compiler) - .settings(javaCompilerSettings) - .settings(headerSettings) - .settings( - name := "sbt-twirl", - organization := "com.typesafe.sbt", - scalaVersion := scala212, - libraryDependencies += "org.scalatest" %%% "scalatest" % scalatest(scalaVersion.value) % "test", - resourceGenerators in Compile += generateVersionFile.taskValue, - scriptedDependencies := { - scriptedDependencies.value - publishLocal.all(ScopeFilter( - inDependencies(compiler) - )).value - }, - scalacOptions ++= scalacCompilerSettings(scalaVersion.value), - ) + .in(file("sbt-twirl")) + .enablePlugins(PlaySbtPlugin, SbtPlugin) + .dependsOn(compiler) + .settings(javaCompilerSettings) + .settings(headerSettings) + .settings( + name := "sbt-twirl", + organization := "com.typesafe.sbt", + scalaVersion := scala212, + libraryDependencies += "org.scalatest" %%% "scalatest" % scalatest(scalaVersion.value) % "test", + resourceGenerators in Compile += generateVersionFile.taskValue, + scriptedDependencies := { + scriptedDependencies.value + publishLocal + .all( + ScopeFilter( + inDependencies(compiler) + ) + ) + .value + }, + scalacOptions ++= scalacCompilerSettings(scalaVersion.value), + mimaFailOnNoPrevious := false, + ) playBuildRepoName in ThisBuild := "twirl" playBuildExtraTests := { @@ -149,7 +169,7 @@ playBuildExtraPublish := { def generateVersionFile = Def.task { val version = (Keys.version in apiJvm).value - val file = (resourceManaged in Compile).value / "twirl.version.properties" + val file = (resourceManaged in Compile).value / "twirl.version.properties" val content = s"twirl.api.version=$version" IO.write(file, content) Seq(file) @@ -165,9 +185,10 @@ def scalaCompiler(version: String) = "org.scala-lang" % "scala-compiler" % versi def scalaParserCombinators(scalaVersion: String): Seq[ModuleID] = scalaVersion match { case interplay.ScalaVersions.scala210 => Seq.empty - case _ => Seq( - "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" % "optional" - ) + case _ => + Seq( + "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2" % "optional" + ) } def scalaXml = Def.setting { @@ -179,4 +200,4 @@ def scalaXml = Def.setting { } } -addCommandAlias("validateCode", ";headerCheck;test:headerCheck") \ No newline at end of file +addCommandAlias("validateCode", ";headerCheck;test:headerCheck;scalafmtCheckAll;scalafmtSbtCheck") diff --git a/compiler/src/main/scala/play/twirl/compiler/TwirlCompiler.scala b/compiler/src/main/scala/play/twirl/compiler/TwirlCompiler.scala index 77908178..f3001f75 100644 --- a/compiler/src/main/scala/play/twirl/compiler/TwirlCompiler.scala +++ b/compiler/src/main/scala/play/twirl/compiler/TwirlCompiler.scala @@ -9,7 +9,8 @@ import java.time.LocalDateTime import scala.annotation.tailrec import scala.io.Codec import scala.reflect.internal.Flags -import play.twirl.parser.{TwirlIO, TwirlParser} +import play.twirl.parser.TwirlIO +import play.twirl.parser.TwirlParser object Hash { @@ -24,7 +25,8 @@ object Hash { } -case class TemplateCompilationError(source: File, message: String, line: Int, column: Int) extends RuntimeException(message) +case class TemplateCompilationError(source: File, message: String, line: Int, column: Int) + extends RuntimeException(message) object MaybeGeneratedSource { @@ -45,17 +47,22 @@ sealed trait AbstractGeneratedSource { def content: String lazy val meta: Map[String, String] = { - val Meta = """([A-Z]+): (.*)""".r + val Meta = """([A-Z]+): (.*)""".r val UndefinedMeta = """([A-Z]+):""".r Map.empty[String, String] ++ { try { - content.split("-- GENERATED --")(1).trim.split('\n').map { m => - m.trim match { - case Meta(key, value) => (key -> value) - case UndefinedMeta(key) => (key -> "") - case _ => ("UNDEFINED", "") + content + .split("-- GENERATED --")(1) + .trim + .split('\n') + .map { m => + m.trim match { + case Meta(key, value) => (key -> value) + case UndefinedMeta(key) => (key -> "") + case _ => ("UNDEFINED", "") + } } - }.toMap + .toMap } catch { case _: Exception => Map.empty[String, String] } @@ -65,19 +72,19 @@ sealed trait AbstractGeneratedSource { lazy val matrix: Seq[(Int, Int)] = { for (pos <- meta("MATRIX").split('|'); c = pos.split("->")) yield try { - Integer.parseInt(c(0)) -> Integer.parseInt(c(1)) - } catch { - case _: Exception => (0, 0) // Skip if MATRIX meta is corrupted - } + Integer.parseInt(c(0)) -> Integer.parseInt(c(1)) + } catch { + case _: Exception => (0, 0) // Skip if MATRIX meta is corrupted + } } lazy val lines: Seq[(Int, Int)] = { for (pos <- meta("LINES").split('|'); c = pos.split("->")) yield try { - Integer.parseInt(c(0)) -> Integer.parseInt(c(1)) - } catch { - case _: Exception => (0, 0) // Skip if LINES meta is corrupted - } + Integer.parseInt(c(0)) -> Integer.parseInt(c(1)) + } catch { + case _: Exception => (0, 0) // Skip if LINES meta is corrupted + } } def mapPosition(generatedPosition: Int): Int = { @@ -113,15 +120,16 @@ case class GeneratedSource(file: File, codec: Codec = TwirlIO.defaultCodec) exte def content = TwirlIO.readFileAsString(file, codec) - def needRecompilation(imports: collection.Seq[String]): Boolean = !file.exists || - // A generated source already exist but - source.isDefined && ((source.get.lastModified > file.lastModified) || // the source has been modified - (meta("HASH") != Hash(TwirlIO.readFile(source.get), imports))) // or the hash don't match + def needRecompilation(imports: collection.Seq[String]): Boolean = + !file.exists || + // A generated source already exist but + source.isDefined && ((source.get.lastModified > file.lastModified) || // the source has been modified + (meta("HASH") != Hash(TwirlIO.readFile(source.get), imports))) // or the hash don't match def toSourcePosition(marker: Int): (Int, Int) = { try { val targetMarker = mapPosition(marker) - val line = TwirlIO.readFileAsString(source.get, codec).substring(0, targetMarker).split('\n').size + val line = TwirlIO.readFileAsString(source.get, codec).substring(0, targetMarker).split('\n').size (line, targetMarker) } catch { case _: Exception => (0, 0) @@ -166,14 +174,31 @@ object TwirlCompiler { import play.twirl.parser.TreeNodes._ - def compile(source: File, sourceDirectory: File, generatedDirectory: File, formatterType: String, - additionalImports: collection.Seq[String] = Nil, constructorAnnotations: collection.Seq[String] = Nil, codec: Codec = TwirlIO.defaultCodec, - inclusiveDot: Boolean = false) = { + def compile( + source: File, + sourceDirectory: File, + generatedDirectory: File, + formatterType: String, + additionalImports: collection.Seq[String] = Nil, + constructorAnnotations: collection.Seq[String] = Nil, + codec: Codec = TwirlIO.defaultCodec, + inclusiveDot: Boolean = false + ) = { val resultType = formatterType + ".Appendable" - val (templateName, generatedSource) = generatedFile(source, codec, sourceDirectory, generatedDirectory, inclusiveDot) + val (templateName, generatedSource) = + generatedFile(source, codec, sourceDirectory, generatedDirectory, inclusiveDot) if (generatedSource.needRecompilation(additionalImports)) { - val generated = parseAndGenerateCode(templateName, TwirlIO.readFile(source), codec, source.getAbsolutePath, - resultType, formatterType, additionalImports, constructorAnnotations, inclusiveDot) + val generated = parseAndGenerateCode( + templateName, + TwirlIO.readFile(source), + codec, + source.getAbsolutePath, + resultType, + formatterType, + additionalImports, + constructorAnnotations, + inclusiveDot + ) TwirlIO.writeStringToFile(generatedSource.file, generated.toString, codec) Some(generatedSource.file) } else { @@ -181,23 +206,49 @@ object TwirlCompiler { } } - def compileVirtual(content: String, source: File, sourceDirectory: File, resultType: String, formatterType: String, - additionalImports: collection.Seq[String] = Nil, constructorAnnotations: collection.Seq[String] = Nil, - codec: Codec = TwirlIO.defaultCodec, inclusiveDot: Boolean = false) = { + def compileVirtual( + content: String, + source: File, + sourceDirectory: File, + resultType: String, + formatterType: String, + additionalImports: collection.Seq[String] = Nil, + constructorAnnotations: collection.Seq[String] = Nil, + codec: Codec = TwirlIO.defaultCodec, + inclusiveDot: Boolean = false + ) = { val (templateName, generatedSource) = generatedFileVirtual(source, sourceDirectory, inclusiveDot) - val generated = parseAndGenerateCode(templateName, content.getBytes(codec.charSet), codec, source.getAbsolutePath, - resultType, formatterType, additionalImports, constructorAnnotations, inclusiveDot) + val generated = parseAndGenerateCode( + templateName, + content.getBytes(codec.charSet), + codec, + source.getAbsolutePath, + resultType, + formatterType, + additionalImports, + constructorAnnotations, + inclusiveDot + ) generatedSource.setContent(generated) generatedSource } - def parseAndGenerateCode(templateName: Array[String], content: Array[Byte], codec: Codec, absolutePath: String, - resultType: String, formatterType: String, additionalImports: collection.Seq[String], constructorAnnotations: collection.Seq[String], - inclusiveDot: Boolean) = { + def parseAndGenerateCode( + templateName: Array[String], + content: Array[Byte], + codec: Codec, + absolutePath: String, + resultType: String, + formatterType: String, + additionalImports: collection.Seq[String], + constructorAnnotations: collection.Seq[String], + inclusiveDot: Boolean + ) = { val templateParser = new TwirlParser(inclusiveDot) templateParser.parse(new String(content, codec.charSet)) match { case templateParser.Success(parsed: Template, rest) if rest.atEnd => { - generateFinalTemplate(absolutePath, + generateFinalTemplate( + absolutePath, content, templateName.dropRight(1).mkString("."), templateName.takeRight(1).mkString, @@ -213,14 +264,26 @@ object TwirlCompiler { } case templateParser.Error(_, rest, errors) => { val firstError = errors.head - throw new TemplateCompilationError(new File(absolutePath), firstError.str, firstError.pos.line, firstError.pos.column) + throw new TemplateCompilationError( + new File(absolutePath), + firstError.str, + firstError.pos.line, + firstError.pos.column + ) } } } - def generatedFile(template: File, codec: Codec, sourceDirectory: File, generatedDirectory: File, inclusiveDot: Boolean) = { + def generatedFile( + template: File, + codec: Codec, + sourceDirectory: File, + generatedDirectory: File, + inclusiveDot: Boolean + ) = { val templateName = { - val name = source2TemplateName(template, sourceDirectory, template.getName.split('.').takeRight(1).head).split('.') + val name = + source2TemplateName(template, sourceDirectory, template.getName.split('.').takeRight(1).head).split('.') if (inclusiveDot) addInclusiveDotName(name) else name } templateName -> GeneratedSource(new File(generatedDirectory, templateName.mkString("/") + ".template.scala"), codec) @@ -228,7 +291,8 @@ object TwirlCompiler { def generatedFileVirtual(template: File, sourceDirectory: File, inclusiveDot: Boolean) = { val templateName = { - val name = source2TemplateName(template, sourceDirectory, template.getName.split('.').takeRight(1).head).split('.') + val name = + source2TemplateName(template, sourceDirectory, template.getName.split('.').takeRight(1).head).split('.') if (inclusiveDot) addInclusiveDotName(name) else name } templateName -> GeneratedSourceVirtual(templateName.mkString("/") + ".template.scala") @@ -242,19 +306,37 @@ object TwirlCompiler { } @tailrec - def source2TemplateName(f: File, sourceDirectory: File, ext: String, suffix: String = "", topDirectory: String = "views", setExt: Boolean = true): String = { + def source2TemplateName( + f: File, + sourceDirectory: File, + ext: String, + suffix: String = "", + topDirectory: String = "views", + setExt: Boolean = true + ): String = { val Name = """([a-zA-Z0-9_]+)[.]scala[.]([a-z]+)""".r (f, f.getName) match { case (f, _) if f == sourceDirectory => { if (setExt) { val parts = suffix.split('.') - Option(parts.dropRight(1).mkString(".")).filterNot(_.isEmpty).map(_ + ".").getOrElse("") + ext + "." + parts.takeRight(1).mkString + Option(parts.dropRight(1).mkString(".")).filterNot(_.isEmpty).map(_ + ".").getOrElse("") + ext + "." + parts + .takeRight(1) + .mkString } else suffix } - case (f, name) if name == topDirectory => source2TemplateName(f.getParentFile, sourceDirectory, ext, name + "." + ext + "." + suffix, topDirectory, false) - case (f, Name(name, _)) if f.isFile => source2TemplateName(f.getParentFile, sourceDirectory, ext, name, topDirectory, setExt) - case (f, name) if !f.isFile => source2TemplateName(f.getParentFile, sourceDirectory, ext, name + "." + suffix, topDirectory, setExt) - case (f, name) => throw TemplateCompilationError(f, "Invalid template name [" + name + "], filenames must only consist of alphanumeric characters and underscores or periods.", 0, 0) + case (f, name) if name == topDirectory => + source2TemplateName(f.getParentFile, sourceDirectory, ext, name + "." + ext + "." + suffix, topDirectory, false) + case (f, Name(name, _)) if f.isFile => + source2TemplateName(f.getParentFile, sourceDirectory, ext, name, topDirectory, setExt) + case (f, name) if !f.isFile => + source2TemplateName(f.getParentFile, sourceDirectory, ext, name + "." + suffix, topDirectory, setExt) + case (f, name) => + throw TemplateCompilationError( + f, + "Invalid template name [" + name + "], filenames must only consist of alphanumeric characters and underscores or periods.", + 0, + 0 + ) } } @@ -270,9 +352,9 @@ object TwirlCompiler { // Scala doesn't offer a way to escape triple quoted strings inside triple quoted strings (to my knowledge), so we // have to escape them in this rather crude way // We need to double escape slashes, since it's a regex replacement - private val escapedTripleQuote = "\\\"" * 3 + private val escapedTripleQuote = "\\\"" * 3 private val doubleEscapedTripleQuote = "\\\\\"" * 3 - private val tripleQuoteReplacement = escapedTripleQuote + " + \\\"" + doubleEscapedTripleQuote + "\\\" + " + escapedTripleQuote + private val tripleQuoteReplacement = escapedTripleQuote + " + \\\"" + doubleEscapedTripleQuote + "\\\" + " + escapedTripleQuote private def quoteAndEscape(text: String): collection.Seq[String] = { Seq(tripleQuote, text.replaceAll(tripleQuote, tripleQuoteReplacement), tripleQuote) } @@ -280,24 +362,34 @@ object TwirlCompiler { def visit(elem: collection.Seq[TemplateTree], previous: collection.Seq[Any]): collection.Seq[Any] = { elem.toList match { case head :: tail => - visit(tail, head match { - case p @ Plain(text) => - - // String literals may not be longer than 65536 bytes. They are encoded as UTF-8 in the classfile, each - // UTF-16 2 byte char could end up becoming up to 3 bytes, so that puts an upper limit of somewhere - // over 20000 characters. 20000 characters is a nice round number, use that. - val grouped = StringGrouper(text, 20000) - (if (previous.isEmpty) Nil else previous :+ ",") :+ - "format.raw" :+ Source("(", p.pos) :+ quoteAndEscape(grouped.head) :+ ")" :+ - grouped.tail.flatMap { t => Seq(",\nformat.raw(", quoteAndEscape(t), ")") } - case Comment(msg) => previous - case Display(exp) => (if (previous.isEmpty) Nil else previous :+ ",") :+ displayVisitedChildren(visit(Seq(exp), Nil)) - case ScalaExp(parts) => previous :+ parts.map { - case s @ Simple(code) => Source(code, s.pos) - case b @ Block(whitespace, args, content) if (content.forall(_.isInstanceOf[ScalaExp])) => Nil :+ Source(whitespace + "{" + args.getOrElse(""), b.pos) :+ visit(content, Nil) :+ "}" - case b @ Block(whitespace, args, content) => Nil :+ Source(whitespace + "{" + args.getOrElse(""), b.pos) :+ displayVisitedChildren(visit(content, Nil)) :+ "}" + visit( + tail, + head match { + case p @ Plain(text) => + // String literals may not be longer than 65536 bytes. They are encoded as UTF-8 in the classfile, each + // UTF-16 2 byte char could end up becoming up to 3 bytes, so that puts an upper limit of somewhere + // over 20000 characters. 20000 characters is a nice round number, use that. + val grouped = StringGrouper(text, 20000) + (if (previous.isEmpty) Nil else previous :+ ",") :+ + "format.raw" :+ Source("(", p.pos) :+ quoteAndEscape(grouped.head) :+ ")" :+ + grouped.tail.flatMap { t => + Seq(",\nformat.raw(", quoteAndEscape(t), ")") + } + case Comment(msg) => previous + case Display(exp) => + (if (previous.isEmpty) Nil else previous :+ ",") :+ displayVisitedChildren(visit(Seq(exp), Nil)) + case ScalaExp(parts) => + previous :+ parts.map { + case s @ Simple(code) => Source(code, s.pos) + case b @ Block(whitespace, args, content) if (content.forall(_.isInstanceOf[ScalaExp])) => + Nil :+ Source(whitespace + "{" + args.getOrElse(""), b.pos) :+ visit(content, Nil) :+ "}" + case b @ Block(whitespace, args, content) => + Nil :+ Source(whitespace + "{" + args.getOrElse(""), b.pos) :+ displayVisitedChildren( + visit(content, Nil) + ) :+ "}" + } } - }) + ) case Nil => previous } } @@ -307,10 +399,16 @@ object TwirlCompiler { val defs = (template.sub ++ template.defs).map { case t: Template if t.name.toString == "" => templateCode(t, resultType) case t: Template => { - Nil :+ (if (t.name.str.startsWith("implicit")) "implicit def " else "def ") :+ Source(t.name.str, t.name.pos) :+ Source(t.params.str, t.params.pos) :+ ":" :+ resultType :+ " = {_display_(" :+ templateCode(t, resultType) :+ ")};" + Nil :+ (if (t.name.str.startsWith("implicit")) "implicit def " else "def ") :+ Source(t.name.str, t.name.pos) :+ Source( + t.params.str, + t.params.pos + ) :+ ":" :+ resultType :+ " = {_display_(" :+ templateCode(t, resultType) :+ ")};" } case Def(name, params, block) => { - Nil :+ (if (name.str.startsWith("implicit")) "implicit def " else "def ") :+ Source(name.str, name.pos) :+ Source(params.str, params.pos) :+ " = {" :+ block.code :+ "};" + Nil :+ (if (name.str.startsWith("implicit")) "implicit def " else "def ") :+ Source(name.str, name.pos) :+ Source( + params.str, + params.pos + ) :+ " = {" :+ block.code :+ "};" } } @@ -319,15 +417,19 @@ object TwirlCompiler { Nil :+ imports :+ "\n" :+ defs :+ "\n" :+ "Seq[Any](" :+ visit(template.content, Nil) :+ ")" } - def generateCode(packageName: String, name: String, root: Template, resultType: String, formatterType: String, - additionalImports: collection.Seq[String], constructorAnnotations: collection.Seq[String]): collection.Seq[Any] = { - val (renderCall, f, templateType) = TemplateAsFunctionCompiler.getFunctionMapping( - root.params.str, - resultType) + def generateCode( + packageName: String, + name: String, + root: Template, + resultType: String, + formatterType: String, + additionalImports: collection.Seq[String], + constructorAnnotations: collection.Seq[String] + ): collection.Seq[Any] = { + val (renderCall, f, templateType) = TemplateAsFunctionCompiler.getFunctionMapping(root.params.str, resultType) // Get the imports that we need to include, filtering out empty imports - val imports: Seq[Any] = Seq(additionalImports.map(i => Seq("import ", i, "\n")), - formatImports(root.topImports)) + val imports: Seq[Any] = Seq(additionalImports.map(i => Seq("import ", i, "\n")), formatImports(root.topImports)) val classDeclaration = root.constructor.fold[Seq[Any]]( Seq("object ", name) @@ -370,11 +472,19 @@ package """ :+ packageName :+ """ imports.map(i => Seq(Source(i.code, i.pos), "\n")) } - def generateFinalTemplate(absolutePath: String, contents: Array[Byte], packageName: String, name: String, - root: Template, resultType: String, formatterType: String, additionalImports: collection.Seq[String], - constructorAnnotations: collection.Seq[String]): String = { - val generated = generateCode(packageName, name, root, resultType, formatterType, additionalImports, - constructorAnnotations) + def generateFinalTemplate( + absolutePath: String, + contents: Array[Byte], + packageName: String, + name: String, + root: Template, + resultType: String, + formatterType: String, + additionalImports: collection.Seq[String], + constructorAnnotations: collection.Seq[String] + ): String = { + val generated = + generateCode(packageName, name, root, resultType, formatterType, additionalImports, constructorAnnotations) Source.finalSource(absolutePath, contents, generated, Hash(contents, additionalImports)) } @@ -388,20 +498,23 @@ package """ :+ packageName :+ """ import java.io.File import scala.tools.nsc.interactive.Global - import scala.reflect.internal.util.{ SourceFile, Position, BatchSourceFile } + import scala.reflect.internal.util.SourceFile + import scala.reflect.internal.util.Position + import scala.reflect.internal.util.BatchSourceFile import scala.tools.nsc.Settings import scala.tools.nsc.reporters.ConsoleReporter - type Tree = PresentationCompiler.global.Tree - type DefDef = PresentationCompiler.global.DefDef + type Tree = PresentationCompiler.global.Tree + type DefDef = PresentationCompiler.global.DefDef type TypeDef = PresentationCompiler.global.TypeDef - type ValDef = PresentationCompiler.global.ValDef + type ValDef = PresentationCompiler.global.ValDef // For some reason they got rid of mods.isByNameParam object ByNameParam { - def unapply(param: ValDef): Option[(String, String)] = if (param.mods.hasFlag(Flags.BYNAMEPARAM)) { - Some((param.name.toString, param.tpt.children(1).toString)) - } else None + def unapply(param: ValDef): Option[(String, String)] = + if (param.mods.hasFlag(Flags.BYNAMEPARAM)) { + Some((param.name.toString, param.tpt.children(1).toString)) + } else None } /** The maximum time in milliseconds to wait for a compiler response to finish. */ @@ -409,54 +522,89 @@ package """ :+ packageName :+ """ def getFunctionMapping(signature: String, returnType: String): (String, String, String) = synchronized { - def filterType(t: String) = t - .replace("_root_.scala.", "Array") - .replace("", "") + def filterType(t: String) = + t.replace("_root_.scala.", "Array") + .replace("", "") def findSignature(tree: Tree): Option[DefDef] = { tree match { case t: DefDef if t.name.toString == "signature" => Some(t) - case t: Tree => t.children.flatMap(findSignature).headOption + case t: Tree => t.children.flatMap(findSignature).headOption } } - val params = findSignature( - PresentationCompiler.treeFrom("object FT { def signature" + signature + " }")).get.vparamss - - val resp = PresentationCompiler.global.askForResponse { () => - - val functionType = "(" + params.map(group => "(" + group.map { - case ByNameParam(_, paramType) => " => " + paramType - case a => filterType(a.tpt.toString) - }.mkString(",") + ")").mkString(" => ") + " => " + returnType + ")" - - val renderCall = "def render%s: %s = apply%s".format( - "(" + params.flatten.map { - case ByNameParam(name, paramType) => name + ":" + paramType - case a => a.name.toString + ":" + filterType(a.tpt.toString) - }.mkString(",") + ")", - returnType, - params.map(group => "(" + group.map { p => - p.name.toString + Option(p.tpt.toString).filter(_.startsWith("_root_.scala.")).map(_ => ":_*").getOrElse("") - }.mkString(",") + ")").mkString) - - val templateType = "_root_.play.twirl.api.Template%s[%s%s]".format( - params.flatten.size, - params.flatten.map { - case ByNameParam(_, paramType) => paramType - case a => filterType(a.tpt.toString) - }.mkString(","), - (if (params.flatten.isEmpty) "" else ",") + returnType) - - val f = "def f:%s = %s => apply%s".format( - functionType, - params.map(group => "(" + group.map(_.name.toString).mkString(",") + ")").mkString(" => "), - params.map(group => "(" + group.map { p => - p.name.toString + Option(p.tpt.toString).filter(_.startsWith("_root_.scala.")).map(_ => ":_*").getOrElse("") - }.mkString(",") + ")").mkString) - - (renderCall, f, templateType) - }.get(Timeout) + val params = + findSignature(PresentationCompiler.treeFrom("object FT { def signature" + signature + " }")).get.vparamss + + val resp = PresentationCompiler.global + .askForResponse { () => + val functionType = "(" + params + .map( + group => + "(" + group + .map { + case ByNameParam(_, paramType) => " => " + paramType + case a => filterType(a.tpt.toString) + } + .mkString(",") + ")" + ) + .mkString(" => ") + " => " + returnType + ")" + + val renderCall = "def render%s: %s = apply%s".format( + "(" + params.flatten + .map { + case ByNameParam(name, paramType) => name + ":" + paramType + case a => a.name.toString + ":" + filterType(a.tpt.toString) + } + .mkString(",") + ")", + returnType, + params + .map( + group => + "(" + group + .map { p => + p.name.toString + Option(p.tpt.toString) + .filter(_.startsWith("_root_.scala.")) + .map(_ => ":_*") + .getOrElse("") + } + .mkString(",") + ")" + ) + .mkString + ) + + val templateType = "_root_.play.twirl.api.Template%s[%s%s]".format( + params.flatten.size, + params.flatten + .map { + case ByNameParam(_, paramType) => paramType + case a => filterType(a.tpt.toString) + } + .mkString(","), + (if (params.flatten.isEmpty) "" else ",") + returnType + ) + + val f = "def f:%s = %s => apply%s".format( + functionType, + params.map(group => "(" + group.map(_.name.toString).mkString(",") + ")").mkString(" => "), + params + .map( + group => + "(" + group + .map { p => + p.name.toString + Option(p.tpt.toString) + .filter(_.startsWith("_root_.scala.")) + .map(_ => ":_*") + .getOrElse("") + } + .mkString(",") + ")" + ) + .mkString + ) + + (renderCall, f, templateType) + } + .get(Timeout) resp match { case None => @@ -487,20 +635,24 @@ package """ :+ packageName :+ """ if (scalaPredefSource != null) { import java.net.URL import java.security.CodeSource - def urlToFile(url: URL): File = try { - val file = new File(url.toURI) - if (file.exists) file else new File(url.getPath) // assume malformed URL - } catch { - case _: java.net.URISyntaxException => - // malformed URL: fallback to using the URL path directly - new File(url.getPath) - } + def urlToFile(url: URL): File = + try { + val file = new File(url.toURI) + if (file.exists) file else new File(url.getPath) // assume malformed URL + } catch { + case _: java.net.URISyntaxException => + // malformed URL: fallback to using the URL path directly + new File(url.getPath) + } def toAbsolutePath(cs: CodeSource): String = urlToFile(cs.getLocation).getAbsolutePath - val compilerPath = toAbsolutePath(Class.forName("scala.tools.nsc.Interpreter").getProtectionDomain.getCodeSource) - val libPath = toAbsolutePath(scalaPredefSource) - val pathList = List(compilerPath, libPath) + val compilerPath = toAbsolutePath( + Class.forName("scala.tools.nsc.Interpreter").getProtectionDomain.getCodeSource + ) + val libPath = toAbsolutePath(scalaPredefSource) + val pathList = List(compilerPath, libPath) val origBootclasspath = settings.bootclasspath.value - settings.bootclasspath.value = ((origBootclasspath :: pathList) ::: additionalClassPathEntry.toList) mkString File.pathSeparator + settings.bootclasspath.value = + ((origBootclasspath :: pathList) ::: additionalClassPathEntry.toList).mkString(File.pathSeparator) } val compiler = new Global(settings, new ConsoleReporter(settings) { @@ -558,7 +710,9 @@ package """ :+ packageName :+ """ /* ------- */ -import scala.util.parsing.input.{ Position, OffsetPosition, NoPosition } +import scala.util.parsing.input.Position +import scala.util.parsing.input.OffsetPosition +import scala.util.parsing.input.NoPosition case class Source(code: String, pos: Position = NoPosition) @@ -566,10 +720,15 @@ object Source { import scala.collection.mutable.ListBuffer - def finalSource(absolutePath: String, contents: Array[Byte], generatedTokens: collection.Seq[Any], hash: String): String = { + def finalSource( + absolutePath: String, + contents: Array[Byte], + generatedTokens: collection.Seq[Any], + hash: String + ): String = { val scalaCode = new StringBuilder val positions = ListBuffer.empty[(Int, Int)] - val lines = ListBuffer.empty[(Int, Int)] + val lines = ListBuffer.empty[(Int, Int)] serialize(generatedTokens, scalaCode, positions, lines) scalaCode.toString + s""" /* @@ -584,17 +743,22 @@ object Source { """ } - private def serialize(parts: collection.Seq[Any], source: StringBuilder, positions: ListBuffer[(Int, Int)], lines: ListBuffer[(Int, Int)]): Unit = { + private def serialize( + parts: collection.Seq[Any], + source: StringBuilder, + positions: ListBuffer[(Int, Int)], + lines: ListBuffer[(Int, Int)] + ): Unit = { parts.foreach { case s: String => source.append(s) case Source(code, pos @ OffsetPosition(_, offset)) => { source.append("/*" + pos + "*/") - positions += (source.length -> offset) + positions += (source.length -> offset) lines += (source.toString.split('\n').size -> pos.line) source.append(code) } case Source(code, NoPosition) => source.append(code) - case s: collection.Seq[any] => serialize(s, source, positions, lines) + case s: collection.Seq[any] => serialize(s, source, positions, lines) } } diff --git a/compiler/src/test/scala/play/twirl/compiler/test/Benchmark.scala b/compiler/src/test/scala/play/twirl/compiler/test/Benchmark.scala index 5fcb2d7a..a61480b3 100644 --- a/compiler/src/test/scala/play/twirl/compiler/test/Benchmark.scala +++ b/compiler/src/test/scala/play/twirl/compiler/test/Benchmark.scala @@ -15,11 +15,10 @@ import play.twirl.parser.TwirlIO */ object Benchmark extends App { - val sourceDir = new File("src/test/resources") - val generatedDir = new File("target/test/benchmark-templates") + val sourceDir = new File("src/test/resources") + val generatedDir = new File("target/test/benchmark-templates") val generatedClasses = new File("target/test/benchmark-classes") - TwirlIO.deleteRecursively(generatedDir) TwirlIO.deleteRecursively(generatedClasses) generatedClasses.mkdirs() @@ -28,7 +27,7 @@ object Benchmark extends App { println("Compiling template...") val template = helper.compile[((String, List[String]) => (Int) => Html)]("real.scala.html", "html.real").static - val input = (1 to 100).map(_.toString).toList + val input = (1 to 100).map(_.toString).toList val text = "world " * 100 diff --git a/compiler/src/test/scala/play/twirl/compiler/test/CompilerSpec.scala b/compiler/src/test/scala/play/twirl/compiler/test/CompilerSpec.scala index cc12d6b6..7bec582d 100644 --- a/compiler/src/test/scala/play/twirl/compiler/test/CompilerSpec.scala +++ b/compiler/src/test/scala/play/twirl/compiler/test/CompilerSpec.scala @@ -6,7 +6,8 @@ package test import java.io._ -import org.scalatest.{ MustMatchers, WordSpec } +import org.scalatest.MustMatchers +import org.scalatest.WordSpec import play.twirl.api.Html import play.twirl.parser.TwirlIO @@ -19,8 +20,8 @@ class CompilerSpec extends WordSpec with MustMatchers { val sourceDir = new File("compiler/src/test/resources") def newCompilerHelper = { - val dirName = "twirl-parser" - val generatedDir = new File("compiler/target/test/" + dirName + "/generated-templates") + val dirName = "twirl-parser" + val generatedDir = new File("compiler/target/test/" + dirName + "/generated-templates") val generatedClasses = new File("compiler/target/test/" + dirName + "/generated-classes") TwirlIO.deleteRecursively(generatedDir) TwirlIO.deleteRecursively(generatedClasses) @@ -32,23 +33,30 @@ class CompilerSpec extends WordSpec with MustMatchers { "compile successfully (real)" in { val helper = newCompilerHelper - val tmpl = helper.compile[((String, List[String]) => (Int) => Html)]("real.scala.html", "html.real") - .static("World", List("A", "B"))(4).toString.trim + val tmpl = helper + .compile[((String, List[String]) => (Int) => Html)]("real.scala.html", "html.real") + .static("World", List("A", "B"))(4) + .toString + .trim - tmpl must (include("

Hello World

") and include("You have 2 items") and include("EA") and include("EB")) + tmpl must (include("

Hello World

").and(include("You have 2 items")).and(include("EA")).and(include("EB"))) } "compile successfully (static)" in { val helper = newCompilerHelper helper.compile[(() => Html)]("static.scala.html", "html.static").static().toString.trim must be( - "

It works

") + "

It works

" + ) } "compile successfully (patternMatching)" in { val testParam = "12345" - val helper = newCompilerHelper - helper.compile[((String) => Html)]("patternMatching.scala.html", "html.patternMatching").static(testParam).toString.trim must be( - """@test + val helper = newCompilerHelper + helper + .compile[((String) => Html)]("patternMatching.scala.html", "html.patternMatching") + .static(testParam) + .toString + .trim must be("""@test @test.length @test.length.toInt @@ -62,81 +70,110 @@ class CompilerSpec extends WordSpec with MustMatchers { "compile successfully (hello)" in { val helper = newCompilerHelper - val hello = helper.compile[((String) => Html)]("hello.scala.html", "html.hello").static("World").toString.trim + val hello = helper.compile[((String) => Html)]("hello.scala.html", "html.hello").static("World").toString.trim hello must be("

Hello World!

xml

") } "compile successfully (helloNull)" in { val helper = newCompilerHelper - val hello = helper.compile[((String) => Html)]("helloNull.scala.html", "html.helloNull").static(null).toString.trim + val hello = + helper.compile[((String) => Html)]("helloNull.scala.html", "html.helloNull").static(null).toString.trim hello must be("

Hello !

") } "compile successfully (zipWithIndex)" in { val helper = newCompilerHelper - val output = helper.compile[((Seq[String]) => Html)]("zipWithIndex.scala.html", "html.zipWithIndex").static(Seq("Alice", "Bob", "Charlie")).toString.trim.replace("\n", "") + val output = helper + .compile[((Seq[String]) => Html)]("zipWithIndex.scala.html", "html.zipWithIndex") + .static(Seq("Alice", "Bob", "Charlie")) + .toString + .trim + .replace("\n", "") output must be("

0 Hello Alice!

1 Hello Bob!

2 Hello Charlie!

") } "compile successfully (set)" in { val helper = newCompilerHelper - val set = helper.compile[((collection.immutable.Set[String]) => Html)]("set.scala.html", "html.set").static(Set("first", "second", "third")).toString.trim.replace("\n", "").replaceAll("\\s+", "") + val set = helper + .compile[((collection.immutable.Set[String]) => Html)]("set.scala.html", "html.set") + .static(Set("first", "second", "third")) + .toString + .trim + .replace("\n", "") + .replaceAll("\\s+", "") set must be("firstsecondthird") } "compile successfully (arg imports)" in { val helper = newCompilerHelper - val result = helper.compile[((java.io.File, java.net.URL) => Html)]("argImports.scala.html", "html.argImports").static(new java.io.File("example"), new java.net.URL("http://example.org")).toString.trim + val result = helper + .compile[((java.io.File, java.net.URL) => Html)]("argImports.scala.html", "html.argImports") + .static(new java.io.File("example"), new java.net.URL("http://example.org")) + .toString + .trim result must be("

file: example, url: http://example.org

") } "compile successfully (default imports)" in { val helper = newCompilerHelper - val result = helper.compile[(() => Html)]("importsDefault.scala.html", "html.importsDefault").static().toString.trim + val result = + helper.compile[(() => Html)]("importsDefault.scala.html", "html.importsDefault").static().toString.trim result must be("foo") } "compile successfully (escape closing brace)" in { val helper = newCompilerHelper - val result = helper.compile[(Option[String] => Html)]("escapebrace.scala.html", "html.escapebrace").static(Some("foo")).toString.trim + val result = helper + .compile[(Option[String] => Html)]("escapebrace.scala.html", "html.escapebrace") + .static(Some("foo")) + .toString + .trim result must be("foo: }") } "compile successfully (utf8)" in { val helper = newCompilerHelper - val text = helper.compile[(() => Html)]("utf8.scala.html", "html.utf8").static().toString.trim + val text = helper.compile[(() => Html)]("utf8.scala.html", "html.utf8").static().toString.trim text must be("€, ö, or ü") } "compile successfully (existential)" in { val helper = newCompilerHelper - val text = helper.compile[(List[_] => Html)]("existential.scala.html", "html.existential").static(List(1, 2, 3)).toString.trim + val text = helper + .compile[(List[_] => Html)]("existential.scala.html", "html.existential") + .static(List(1, 2, 3)) + .toString + .trim text must be("123") } "compile successfully (triple quotes)" in { val helper = newCompilerHelper - val out = helper.compile[(() => Html)]("triplequotes.scala.html", "html.triplequotes").static().toString.trim + val out = helper.compile[(() => Html)]("triplequotes.scala.html", "html.triplequotes").static().toString.trim out must be("\"\"\"\n\n\"\"\"\"\"\n\n\"\"\"\"\"\"") } "compile successfully (var args existential)" in { val helper = newCompilerHelper - val text = helper.compile[(Array[List[_]] => Html)]("varArgsExistential.scala.html", "html.varArgsExistential").static(Array(List(1, 2, 3), List(4, 5, 6))).toString.trim + val text = helper + .compile[(Array[List[_]] => Html)]("varArgsExistential.scala.html", "html.varArgsExistential") + .static(Array(List(1, 2, 3), List(4, 5, 6))) + .toString + .trim text must be("123456") } "fail compilation for error.scala.html" in { val helper = newCompilerHelper the[CompilationError] thrownBy helper.compile[(() => Html)]("error.scala.html", "html.error") must have( - Symbol("line") (2), - Symbol("column") (12) + Symbol("line")(2), + Symbol("column")(12) ) } "compile templates that have contiguous strings > than 64k" in { val helper = newCompilerHelper - val input = TwirlIO.readFileAsString(new File(sourceDir, "long.scala.html")) + val input = TwirlIO.readFileAsString(new File(sourceDir, "long.scala.html")) val result = helper.compile[(() => Html)]("long.scala.html", "html.long").static().toString result.length mustBe input.length result mustBe input @@ -144,7 +181,9 @@ class CompilerSpec extends WordSpec with MustMatchers { "allow rendering a template twice" in { val helper = newCompilerHelper - val inner = helper.compile[((String, List[String]) => (Int) => Html)]("htmlInner.scala.html", "html.htmlInner").static("World", List("A", "B"))(4) + val inner = helper + .compile[((String, List[String]) => (Int) => Html)]("htmlInner.scala.html", "html.htmlInner") + .static("World", List("A", "B"))(4) val outer = helper.compile[Html => Html]("htmlParam.scala.html", "html.htmlParam").static @@ -155,30 +194,32 @@ class CompilerSpec extends WordSpec with MustMatchers { "support injectable templates" when { "plain injected template" in { - val helper = newCompilerHelper + val helper = newCompilerHelper val template = helper.compile[String => Html]("inject.scala.html", "html.inject").inject("Hello", 10) template("world").body.trim mustBe "Hello 10 world" } "with no args" in { - val helper = newCompilerHelper + val helper = newCompilerHelper val template = helper.compile[String => Html]("injectNoArgs.scala.html", "html.injectNoArgs").inject() template("Hello").body.trim mustBe "Hello" } "with parameter groups" in { val helper = newCompilerHelper - val template = helper.compile[String => Html]("injectParamGroups.scala.html", "html.injectParamGroups").inject("Hello", 10, "my") + val template = helper + .compile[String => Html]("injectParamGroups.scala.html", "html.injectParamGroups") + .inject("Hello", 10, "my") template("world").body.trim mustBe "Hello 10 my world" } "with comments" in { val helper = newCompilerHelper - val template = helper.compile[String => Html]("injectComments.scala.html", "html.injectComments").inject("Hello", 10) + val template = + helper.compile[String => Html]("injectComments.scala.html", "html.injectComments").inject("Hello", 10) template("world").body.trim mustBe "Hello 10 world" } - } } @@ -187,53 +228,54 @@ class CompilerSpec extends WordSpec with MustMatchers { val line = "abcde" + beer + "fg" "split before a surrogate pair" in { - StringGrouper(line, 5) must contain allOf ("abcde", beer + "fg") + (StringGrouper(line, 5) must contain).allOf("abcde", beer + "fg") } "not split a surrogate pair" in { - StringGrouper(line, 6) must contain allOf("abcde" + beer, "fg") + (StringGrouper(line, 6) must contain).allOf("abcde" + beer, "fg") } "split after a surrogate pair" in { - StringGrouper(line, 7) must contain allOf("abcde" + beer, "fg") + (StringGrouper(line, 7) must contain).allOf("abcde" + beer, "fg") } } "compile successfully (elseIf)" when { "input is in if clause" in { val helper = newCompilerHelper - val hello = helper.compile[((Int) => Html)]("elseIf.scala.html", "html.elseIf").static(0).toString.trim + val hello = helper.compile[((Int) => Html)]("elseIf.scala.html", "html.elseIf").static(0).toString.trim hello must be("hello") } "input is in else if clause" in { val helper = newCompilerHelper - val hello = helper.compile[((Int) => Html)]("elseIf.scala.html", "html.elseIf").static(1).toString.trim + val hello = helper.compile[((Int) => Html)]("elseIf.scala.html", "html.elseIf").static(1).toString.trim hello must be("world") } "input is in else clause" in { val helper = newCompilerHelper - val hello = helper.compile[((Int) => Html)]("elseIf.scala.html", "html.elseIf").static(25).toString.trim + val hello = helper.compile[((Int) => Html)]("elseIf.scala.html", "html.elseIf").static(25).toString.trim hello must be("fail!") } } "compile successfully (if without brackets)" in { val helper = newCompilerHelper - val hello = helper.compile[((String, String) => Html)]("ifWithoutBrackets.scala.html", "html.ifWithoutBrackets") + val hello = helper.compile[((String, String) => Html)]("ifWithoutBrackets.scala.html", "html.ifWithoutBrackets") hello.static("twirl", "play").toString.trim must be("twirl-play") hello.static("twirl", "something-else").toString.trim must be("twirl") } "compile successfully (complex if without brackets)" in { val helper = newCompilerHelper - val hello = helper.compile[((String, String) => Html)]("ifWithoutBracketsComplex.scala.html", "html.ifWithoutBracketsComplex") + val hello = + helper.compile[((String, String) => Html)]("ifWithoutBracketsComplex.scala.html", "html.ifWithoutBracketsComplex") hello.static("twirl", "play").toString.trim must include("""
""") hello.static("twirl", "something-else").toString.trim must include("""
""") } "compile successfully (block with tuple)" in { val helper = newCompilerHelper - val hello = helper.compile[(Seq[(String, String)] => Html)]("blockWithTuple.scala.html", "html.blockWithTuple") + val hello = helper.compile[(Seq[(String, String)] => Html)]("blockWithTuple.scala.html", "html.blockWithTuple") val args = Seq[(String, String)]( "the-key" -> "the-value" @@ -243,7 +285,8 @@ class CompilerSpec extends WordSpec with MustMatchers { "compile successfully (block with nested tuples)" in { val helper = newCompilerHelper - val hello = helper.compile[(Seq[(String, String)] => Html)]("blockWithNestedTuple.scala.html", "html.blockWithNestedTuple") + val hello = + helper.compile[(Seq[(String, String)] => Html)]("blockWithNestedTuple.scala.html", "html.blockWithNestedTuple") val args = Seq[(String, String)]( "the-key" -> "the-value" @@ -264,30 +307,43 @@ object Helper { import scala.collection.mutable import scala.reflect.internal.util.Position import scala.tools.nsc.reporters.ConsoleReporter - import scala.tools.nsc.{ Global, Settings } + import scala.tools.nsc.Global + import scala.tools.nsc.Settings val twirlCompiler = TwirlCompiler - val classloader = new URLClassLoader(Array(generatedClasses.toURI.toURL), Class.forName("play.twirl.compiler.TwirlCompiler").getClassLoader) + val classloader = new URLClassLoader( + Array(generatedClasses.toURI.toURL), + Class.forName("play.twirl.compiler.TwirlCompiler").getClassLoader + ) // A list of the compile errors from the most recent compiler run val compileErrors = new mutable.ListBuffer[CompilationError] val compiler = { - def additionalClassPathEntry: Option[String] = Some( - Class.forName("play.twirl.compiler.TwirlCompiler").getClassLoader.asInstanceOf[URLClassLoader].getURLs.map(url => new File(url.toURI)).mkString(":")) - - val settings = new Settings + def additionalClassPathEntry: Option[String] = + Some( + Class + .forName("play.twirl.compiler.TwirlCompiler") + .getClassLoader + .asInstanceOf[URLClassLoader] + .getURLs + .map(url => new File(url.toURI)) + .mkString(":") + ) + + val settings = new Settings val scalaObjectSource = Class.forName("scala.Option").getProtectionDomain.getCodeSource // is null in Eclipse/OSGI but luckily we don't need it there if (scalaObjectSource != null) { - val compilerPath = Class.forName("scala.tools.nsc.Interpreter").getProtectionDomain.getCodeSource.getLocation - val libPath = scalaObjectSource.getLocation - val pathList = List(compilerPath, libPath) + val compilerPath = Class.forName("scala.tools.nsc.Interpreter").getProtectionDomain.getCodeSource.getLocation + val libPath = scalaObjectSource.getLocation + val pathList = List(compilerPath, libPath) val origBootclasspath = settings.bootclasspath.value - settings.bootclasspath.value = ((origBootclasspath :: pathList) ::: additionalClassPathEntry.toList) mkString File.pathSeparator + settings.bootclasspath.value = + ((origBootclasspath :: pathList) ::: additionalClassPathEntry.toList).mkString(File.pathSeparator) settings.outdir.value = generatedClasses.getAbsolutePath } @@ -295,7 +351,7 @@ object Helper { override def display(pos: Position, msg: String, severity: Severity): Unit = { pos match { case scala.reflect.internal.util.NoPosition => // do nothing - case _ => compileErrors.append(CompilationError(msg, pos.line, pos.point)) + case _ => compileErrors.append(CompilationError(msg, pos.line, pos.point)) } } }) @@ -316,15 +372,25 @@ object Helper { def inject(constructorArgs: Any*): T = { classloader.loadClass(className).getConstructors match { case Array(single) => getF(single.newInstance(constructorArgs.asInstanceOf[Seq[AnyRef]]: _*)) - case other => throw new IllegalStateException(className + " does not declare exactly one constructor: " + other) + case other => + throw new IllegalStateException(className + " does not declare exactly one constructor: " + other) } } } - def compile[T](templateName: String, className: String, additionalImports: Seq[String] = Nil): CompiledTemplate[T] = { + def compile[T]( + templateName: String, + className: String, + additionalImports: Seq[String] = Nil + ): CompiledTemplate[T] = { val templateFile = new File(sourceDir, templateName) - val Some(generated) = twirlCompiler.compile(templateFile, sourceDir, generatedDir, "play.twirl.api.HtmlFormat", - additionalImports = TwirlCompiler.DefaultImports ++ additionalImports) + val Some(generated) = twirlCompiler.compile( + templateFile, + sourceDir, + generatedDir, + "play.twirl.api.HtmlFormat", + additionalImports = TwirlCompiler.DefaultImports ++ additionalImports + ) val mapper = GeneratedSource(generated) diff --git a/compiler/src/test/scala/play/twirl/compiler/test/TemplateUtilsSpec.scala b/compiler/src/test/scala/play/twirl/compiler/test/TemplateUtilsSpec.scala index d8ddb829..f06b8782 100644 --- a/compiler/src/test/scala/play/twirl/compiler/test/TemplateUtilsSpec.scala +++ b/compiler/src/test/scala/play/twirl/compiler/test/TemplateUtilsSpec.scala @@ -4,7 +4,8 @@ package play.twirl.compiler package test -import org.scalatest.{ MustMatchers, WordSpec } +import org.scalatest.MustMatchers +import org.scalatest.WordSpec import play.twirl.api._ import scala.collection.immutable @@ -26,9 +27,9 @@ class TemplateUtilsSpec extends WordSpec with MustMatchers { } object HtmlFormat extends Format[Html] { - def raw(text: String) = Html(text) - def escape(text: String) = Html(text.replace("<", "<")) - def empty = Html("") + def raw(text: String) = Html(text) + def escape(text: String) = Html(text.replace("<", "<")) + def empty = Html("") def fill(elements: immutable.Seq[Html]) = Html("") } @@ -44,9 +45,9 @@ class TemplateUtilsSpec extends WordSpec with MustMatchers { } object TextFormat extends Format[Text] { - def raw(text: String) = Text(text) - def escape(text: String) = Text(text) - def empty = Text("") + def raw(text: String) = Text(text) + def escape(text: String) = Text(text) + def empty = Text("") def fill(elements: immutable.Seq[Text]) = Text("") } diff --git a/compiler/src/test/scala/play/twirl/compiler/test/imports/bar/Default.scala b/compiler/src/test/scala/play/twirl/compiler/test/imports/bar/Default.scala index 9adb1ca6..040f5273 100644 --- a/compiler/src/test/scala/play/twirl/compiler/test/imports/bar/Default.scala +++ b/compiler/src/test/scala/play/twirl/compiler/test/imports/bar/Default.scala @@ -4,4 +4,4 @@ package play.twirl.compiler.test.imports.bar // Used to test import priorities -object Default \ No newline at end of file +object Default diff --git a/compiler/src/test/scala/play/twirl/compiler/test/imports/foo/Default.scala b/compiler/src/test/scala/play/twirl/compiler/test/imports/foo/Default.scala index 066c9108..e3e47842 100644 --- a/compiler/src/test/scala/play/twirl/compiler/test/imports/foo/Default.scala +++ b/compiler/src/test/scala/play/twirl/compiler/test/imports/foo/Default.scala @@ -4,4 +4,4 @@ package play.twirl.compiler.test.imports.foo // Used to test import priorities -object Default \ No newline at end of file +object Default diff --git a/docs/build.sbt b/docs/build.sbt index 0a3ed71b..4517ef02 100644 --- a/docs/build.sbt +++ b/docs/build.sbt @@ -7,30 +7,34 @@ lazy val docs = project // use special snapshot play version for now resolvers ++= DefaultOptions.resolvers(snapshot = true), resolvers += Resolver.typesafeRepo("releases"), - libraryDependencies += component("play-test") % "test", + libraryDependencies += component("play-test") % "test", libraryDependencies += component("play-specs2") % "test", PlayDocsKeys.javaManualSourceDirectories := (baseDirectory.value / "manual" / "working" / "javaGuide" ** "code").get, PlayDocsKeys.scalaManualSourceDirectories := (baseDirectory.value / "manual" / "working" / "scalaGuide" ** "code").get, headerLicense := { val currentYear = java.time.Year.now(java.time.Clock.systemUTC).getValue - Some(HeaderLicense.Custom( - s"Copyright (C) 2009-$currentYear Lightbend Inc. " - )) + Some( + HeaderLicense.Custom( + s"Copyright (C) 2009-$currentYear Lightbend Inc. " + ) + ) }, headerEmptyLine := false - ).settings(overrideTwirlSettings: _*) + ) + .settings(overrideTwirlSettings: _*) .dependsOn(twirlApi) // The changes in Twirl imports cause a problem with the PlayDocsPlugin, which defines its own twirl compile tasks // and doesn't use the default imports provided by Twirl but defines its own by scratch, and since the defaults // have changed, this breaks. So, first we need to set all source generators in test to Nil, then we can redefine the // twirl settings. -def overrideTwirlSettings: Seq[Setting[_]] = Seq( - sourceGenerators in Test := Nil -) ++ inConfig(Test)(SbtTwirl.twirlSettings) ++ SbtTwirl.defaultSettings ++ SbtTwirl.positionSettings ++ Seq( - sourceDirectories in (Test, TwirlKeys.compileTemplates) ++= - (PlayDocsKeys.javaManualSourceDirectories.value ++ PlayDocsKeys.scalaManualSourceDirectories.value) -) +def overrideTwirlSettings: Seq[Setting[_]] = + Seq( + sourceGenerators in Test := Nil + ) ++ inConfig(Test)(SbtTwirl.twirlSettings) ++ SbtTwirl.defaultSettings ++ SbtTwirl.positionSettings ++ Seq( + sourceDirectories in (Test, TwirlKeys.compileTemplates) ++= + (PlayDocsKeys.javaManualSourceDirectories.value ++ PlayDocsKeys.scalaManualSourceDirectories.value) + ) // the twirl plugin automatically adds this dependency, but this overrides it so // it can be an interproject dependency, rather than requiring it to be published diff --git a/docs/manual/working/scalaGuide/main/templates/code/ScalaTemplates.scala b/docs/manual/working/scalaGuide/main/templates/code/ScalaTemplates.scala index 3289a976..d8953422 100644 --- a/docs/manual/working/scalaGuide/main/templates/code/ScalaTemplates.scala +++ b/docs/manual/working/scalaGuide/main/templates/code/ScalaTemplates.scala @@ -33,8 +33,7 @@ package html.utils { object ScalaTemplatesSpec extends Specification { val customer = Customer("mr customer") - val orders = List(Order("foo"), Order("bar")) - + val orders = List(Order("foo"), Order("bar")) "Scala templates" should { "support an example template" in { @@ -57,7 +56,7 @@ object ScalaTemplatesSpec extends Specification { import play.twirl.api.StringInterpolation val name = "Martin" - val p = html"

Hello $name

" + val p = html"

Hello $name

" //#string-interpolation p.body must_== "

Hello Martin

" @@ -100,13 +99,17 @@ object ScalaTemplatesSpec extends Specification { "allow comments on the first line" in { val body = html.firstLineComment("blah").body body must contain("blah") - body must not contain("Home page") + body must not contain ("Home page") } { val body = html.snippets(Seq(Product("p1", "1"), Product("p2", "2")), User("John", "Doe"), Article("")).body def segment(name: String) = { - body.linesIterator.dropWhile(_ != "").drop(1).takeWhile(_ != "").mkString("\n") + body.linesIterator + .dropWhile(_ != "") + .drop(1) + .takeWhile(_ != "") + .mkString("\n") } "allow escaping the @ character" in { @@ -140,7 +143,7 @@ object ScalaTemplatesSpec extends Specification { } "allow comments" in { - body must not contain("This is a comment") + body must not contain ("This is a comment") } "allow intering raw HTML" in { @@ -153,10 +156,10 @@ object ScalaTemplatesSpec extends Specification { body must contain("User(Foo,Bar)") body must contain("value inside option") - body must not contain("Option(value inside option)") + body must not contain ("Option(value inside option)") body must contain("firstlast") - body must not contain("List") + body must not contain ("List") body must contain("helloUser(Foo,Bar)value inside optionfirstlast") } } -} \ No newline at end of file +} diff --git a/docs/project/plugins.sbt b/docs/project/plugins.sbt index 870c912c..0d9c95ed 100644 --- a/docs/project/plugins.sbt +++ b/docs/project/plugins.sbt @@ -5,5 +5,5 @@ lazy val sbtTwirl = ProjectRef(Path.fileProperty("user.dir").getParentFile, "plu resolvers ++= DefaultOptions.resolvers(snapshot = true) addSbtPlugin("com.typesafe.play" % "play-docs-sbt-plugin" % sys.props.getOrElse("play.version", "2.8.0-M3")) - -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0") \ No newline at end of file +addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.0") diff --git a/parser/src/main/scala/play/twirl/parser/TreeNodes.scala b/parser/src/main/scala/play/twirl/parser/TreeNodes.scala index ebcfff0b..bdbe938d 100644 --- a/parser/src/main/scala/play/twirl/parser/TreeNodes.scala +++ b/parser/src/main/scala/play/twirl/parser/TreeNodes.scala @@ -11,17 +11,28 @@ object TreeNodes { case class Params(code: String) extends Positional case class Constructor(comment: Option[Comment], params: PosString) - case class Template(name: PosString, constructor: Option[Constructor], comment: Option[Comment], params: PosString, topImports: collection.Seq[Simple], - imports: collection.Seq[Simple], defs: collection.Seq[Def], sub: collection.Seq[Template], content: collection.Seq[TemplateTree]) extends Positional + case class Template( + name: PosString, + constructor: Option[Constructor], + comment: Option[Comment], + params: PosString, + topImports: collection.Seq[Simple], + imports: collection.Seq[Simple], + defs: collection.Seq[Def], + sub: collection.Seq[Template], + content: collection.Seq[TemplateTree] + ) extends Positional case class PosString(str: String) extends Positional { override def toString: String = str } case class Def(name: PosString, params: PosString, code: Simple) extends Positional - case class Plain(text: String) extends TemplateTree with Positional - case class Display(exp: ScalaExp) extends TemplateTree with Positional - case class Comment(msg: String) extends TemplateTree with Positional - case class ScalaExp(parts: collection.Seq[ScalaExpPart]) extends TemplateTree with Positional - case class Simple(code: String) extends ScalaExpPart with Positional - case class Block(whitespace: String, args: Option[PosString], content: collection.Seq[TemplateTree]) extends ScalaExpPart with Positional + case class Plain(text: String) extends TemplateTree with Positional + case class Display(exp: ScalaExp) extends TemplateTree with Positional + case class Comment(msg: String) extends TemplateTree with Positional + case class ScalaExp(parts: collection.Seq[ScalaExpPart]) extends TemplateTree with Positional + case class Simple(code: String) extends ScalaExpPart with Positional + case class Block(whitespace: String, args: Option[PosString], content: collection.Seq[TemplateTree]) + extends ScalaExpPart + with Positional case class Value(ident: PosString, block: Block) extends Positional } diff --git a/parser/src/main/scala/play/twirl/parser/TwirlIO.scala b/parser/src/main/scala/play/twirl/parser/TwirlIO.scala index c12788dd..ba12aa8b 100644 --- a/parser/src/main/scala/play/twirl/parser/TwirlIO.scala +++ b/parser/src/main/scala/play/twirl/parser/TwirlIO.scala @@ -25,8 +25,8 @@ private[twirl] object TwirlIO { */ def readStream(stream: InputStream): Array[Byte] = { val buffer = new Array[Byte](8192) - var len = stream.read(buffer) - val out = new ByteArrayOutputStream() + var len = stream.read(buffer) + val out = new ByteArrayOutputStream() while (len != -1) { out.write(buffer, 0, len) len = stream.read(buffer) diff --git a/parser/src/main/scala/play/twirl/parser/TwirlParser.scala b/parser/src/main/scala/play/twirl/parser/TwirlParser.scala index ff5e5d00..663613e8 100644 --- a/parser/src/main/scala/play/twirl/parser/TwirlParser.scala +++ b/parser/src/main/scala/play/twirl/parser/TwirlParser.scala @@ -88,20 +88,19 @@ import scala.util.parsing.input.OffsetPosition * - Missing blocks after case and match statements * - Invalid ("alone") '@' symbols. */ - class TwirlParser(val shouldParseInclusiveDot: Boolean) { import play.twirl.parser.TreeNodes._ import scala.util.parsing.input.Positional sealed abstract class ParseResult - case class Success(template: Template, input: Input) extends ParseResult + case class Success(template: Template, input: Input) extends ParseResult case class Error(template: Template, input: Input, errors: List[PosString]) extends ParseResult case class Input() { - private var offset_ = 0 - private var source_ = "" - private var length_ = 1 + private var offset_ = 0 + private var source_ = "" + private var length_ = 1 val regressionStatistics = new collection.mutable.HashMap[String, (Int, Int)] /** Peek at the current input. Does not check for EOF. */ @@ -137,7 +136,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { /** Backtrack to a known offset */ def regressTo(offset: Int): Unit = offset_ = offset - def isPastEOF(len: Int): Boolean = (offset_ + len-1) >= length_ + def isPastEOF(len: Int): Boolean = (offset_ + len - 1) >= length_ def isEOF: Boolean = isPastEOF(1) @@ -158,7 +157,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { } } - private val input: Input = new Input + private val input: Input = new Input private val errorStack: ListBuffer[PosString] = ListBuffer() /** @@ -197,7 +196,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { */ def check(str: String): Boolean = { val len = str.length - if (!input.isPastEOF(len) && input.matches(str)){ + if (!input.isPastEOF(len) && input.matches(str)) { input.advance(len) true } else false @@ -207,7 +206,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { errorStack += position(PosString(message), offset) } - /** Consume/Advance `length` characters, and return the consumed characters. Returns "" if at EOF. */ + /** Consume/Advance `length` characters, and return the consumed characters. Returns "" if at EOF. */ def any(length: Int = 1): String = { if (input.isEOF) { error("Expected more input but found 'EOF'") @@ -227,8 +226,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { */ def anyUntil(stop: String, inclusive: Boolean): String = { val sb = new StringBuilder - while (!input.isPastEOF(stop.length) && !input.matches(stop)) - sb.append(any()) + while (!input.isPastEOF(stop.length) && !input.matches(stop)) sb.append(any()) if (inclusive && !input.isPastEOF(stop.length)) sb.append(any(stop.length)) sb.toString() @@ -242,8 +240,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { */ def anyUntil(f: Char => Boolean, inclusive: Boolean): String = { val sb = new StringBuilder - while (!input.isEOF && !f(input())) - sb.append(any()) + while (!input.isEOF && !f(input())) sb.append(any()) if (inclusive && !input.isEOF) sb.append(any()) sb.toString @@ -263,7 +260,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { def recursiveTag(prefix: String, suffix: String, allowStringLiterals: Boolean = false): String = { if (check(prefix)) { var stack = 1 - val sb = new StringBuffer + val sb = new StringBuffer sb.append(prefix) while (stack > 0) { if (check(prefix)) { @@ -278,7 +275,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { } else if (allowStringLiterals) { stringLiteral("\"", "\\") match { case null => sb.append(any()) - case s => sb.append(s) + case s => sb.append(s) } } else { sb.append(any()) @@ -295,7 +292,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { def stringLiteral(quote: String, escape: String): String = { if (check(quote)) { var within = true - val sb = new StringBuffer + val sb = new StringBuffer sb.append(quote) while (within) { if (check(quote)) { // end of string literal @@ -320,8 +317,10 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { } /** Match zero or more `parser` */ - def several[T, BufferType <: mutable.Buffer[T]](parser: () => T, provided: BufferType = null)(implicit manifest: Manifest[BufferType]): BufferType = { - val ab = if (provided != null) provided else manifest.runtimeClass.newInstance().asInstanceOf[BufferType] + def several[T, BufferType <: mutable.Buffer[T]](parser: () => T, provided: BufferType = null)( + implicit manifest: Manifest[BufferType] + ): BufferType = { + val ab = if (provided != null) provided else manifest.runtimeClass.newInstance().asInstanceOf[BufferType] var parsed = parser() while (parsed != null) { ab += parsed @@ -349,8 +348,8 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { } /** - * Parse a comment. - */ + * Parse a comment. + */ def comment(): Comment = { val pos = input.offset() if (check("@*")) { @@ -361,8 +360,8 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { } /** - * Parses comments and/or whitespace, ignoring both until the last comment is reached, and returning that (if found) - */ + * Parses comments and/or whitespace, ignoring both until the last comment is reached, and returning that (if found) + */ def lastComment(): Comment = { @tailrec def tryNext(last: Comment): Comment = { @@ -380,14 +379,14 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { def importExpression(): Simple = { val p = input.offset() if (check("@import ")) - position(Simple("import " + anyUntil("\n", inclusive = true).trim), p+1) // don't include position of @ + position(Simple("import " + anyUntil("\n", inclusive = true).trim), p + 1) // don't include position of @ else null } def localDef(): Def = { - var result: Def = null + var result: Def = null val resetPosition = input.offset() - val templDecl = templateDeclaration() + val templDecl = templateDeclaration() if (templDecl != null) { anyUntil(c => c != ' ' && c != '\t', inclusive = false) if (check("=")) { @@ -410,7 +409,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { val p = input.offset() brackets() match { case null => null - case b => position(Simple(b), p) + case b => position(Simple(b), p) } } else null } @@ -428,19 +427,23 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { def opt1(): ListBuffer[TemplateTree] = { val t = comment() match { - case null => scalaBlockDisplayed() match { - case null => forExpression() match { - case null => matchExpOrSafeExpOrExpr() match { - case null => caseExpression() match { - case null => plain() + case null => + scalaBlockDisplayed() match { + case null => + forExpression() match { + case null => + matchExpOrSafeExpOrExpr() match { + case null => + caseExpression() match { + case null => plain() + case x => x + } + case x => x + } case x => x } - case x => x - } case x => x } - case x => x - } case x => x } if (t != null) ListBuffer(t) @@ -453,7 +456,9 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { if (check("{")) { var buffer = new ListBuffer[TemplateTree] buffer += position(Plain("{"), lbracepos) - for (m <- several[ListBuffer[TemplateTree], ListBuffer[ListBuffer[TemplateTree]]]{ () => mixed() }) + for (m <- several[ListBuffer[TemplateTree], ListBuffer[ListBuffer[TemplateTree]]] { () => + mixed() + }) buffer = buffer ++ m // creates a new object, but is constant in time, as opposed to buffer ++= m which is linear (proportional to size of m) val rbracepos = input.offset() if (check("}")) @@ -466,7 +471,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { opt1() match { case null => opt2() - case x => x + case x => x } } @@ -484,12 +489,12 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { val noContainsOpenParenthesis = !result.contains("(") val startsWithParenthesis = result.trim.startsWith("(") || result.trim.stripPrefix("case").trim.startsWith("(") - val endsWithParenthesis = result.stripSuffix("=>").trim.endsWith(")") + val endsWithParenthesis = result.stripSuffix("=>").trim.endsWith(")") noContainsOpenParenthesis || (startsWithParenthesis && endsWithParenthesis) } - val p = input.offset() + val p = input.offset() val result = anyUntil("=>", inclusive = true) if (result.endsWith("=>") && !result.contains("\n") && noCurlyBraces(result) && noOpeningParenthesis(result)) position(PosString(result), p) @@ -501,11 +506,13 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { def block(blockArgsAllowed: Boolean): Block = { var result: Block = null - val p = input.offset() - val ws = whitespaceNoBreak() + val p = input.offset() + val ws = whitespaceNoBreak() if (check("{")) { - val blkArgs = if(blockArgsAllowed) Option(blockArgs()) else None - val mixeds = several[ListBuffer[TemplateTree], ListBuffer[ListBuffer[TemplateTree]]] { () => mixed() } + val blkArgs = if (blockArgsAllowed) Option(blockArgs()) else None + val mixeds = several[ListBuffer[TemplateTree], ListBuffer[ListBuffer[TemplateTree]]] { () => + mixed() + } accept("}") // TODO - not use flatten here (if it's a performance problem) result = position(Block(ws, blkArgs, mixeds.flatten), p) @@ -520,11 +527,11 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { var result: TemplateTree = null val wspos = input.offset() - val ws = whitespace() - val p = input.offset() + val ws = whitespace() + val p = input.offset() if (check("case ")) { val pattern = position(Simple("case " + anyUntil("=>", inclusive = true)), p) - val blk = block(blockArgsAllowed = true) + val blk = block(blockArgsAllowed = true) if (blk != null) { result = ScalaExp(ListBuffer(pattern, blk)) whitespace() @@ -546,15 +553,15 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { val result = expression() match { case null => safeExpression() - case x => x + case x => x } if (result != null) { val exprs = result.exp.parts.asInstanceOf[ListBuffer[ScalaExpPart]] - val mpos = input.offset() - val ws = whitespaceNoBreak() + val mpos = input.offset() + val ws = whitespaceNoBreak() if (check("match")) { - val m = position(Simple(ws + "match"), mpos) + val m = position(Simple(ws + "match"), mpos) val blk = block(blockArgsAllowed = false) if (blk != null) { exprs.append(m) @@ -573,13 +580,13 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { def forExpression(): Display = { var result: Display = null - val p = input.offset() + val p = input.offset() if (check("@for")) { val parens = parentheses() if (parens != null) { val blk = block(blockArgsAllowed = true) if (blk != null) { - result = Display(ScalaExp(ListBuffer(position(Simple("for" + parens + " yield "), p+1), blk))) // don't include pos of @ + result = Display(ScalaExp(ListBuffer(position(Simple("for" + parens + " yield "), p + 1), blk))) // don't include pos of @ } } } @@ -605,9 +612,9 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { else if (!input.isEOF && input() != '@' && input() != '}' && input() != '{') any() else null } - val p = input.offset() + val p = input.offset() var result: Plain = null - var part = single() + var part = single() if (part != null) { val sb = new StringBuffer while (part != null) { @@ -623,10 +630,12 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { def expression(): Display = { var result: Display = null if (check("@")) { - val pos = input.offset() + val pos = input.offset() val code = methodCall() if (code != null) { - val parts = several[ScalaExpPart, ListBuffer[ScalaExpPart]] { () => expressionPart(blockArgsAllowed = true) } + val parts = several[ScalaExpPart, ListBuffer[ScalaExpPart]] { () => + expressionPart(blockArgsAllowed = true) + } parts.prepend(position(Simple(code), pos)) result = Display(ScalaExp(parts)) } else input.regressTo(pos - 1) // don't consume the @ @@ -639,15 +648,15 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { val name = identifier() if (name != null) { val sb = new StringBuffer(name) - sb.append(Option(squareBrackets()) getOrElse "") - sb.append(Option(parentheses()) getOrElse "") + sb.append(Option(squareBrackets()).getOrElse("")) + sb.append(Option(parentheses()).getOrElse("")) sb.toString } else null } def expressionPart(blockArgsAllowed: Boolean): ScalaExpPart = { def simpleParens() = { - val p = input.offset() + val p = input.offset() val parens = parentheses() if (parens != null) position(Simple(parens), p) else null @@ -662,19 +671,23 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { } chainedMethods() match { - case null => block(blockArgsAllowed) match { - case null => wsThenScalaBlockChained() match { - case null => elseIfCall() match { - case null => elseCall() match { - case null =>simpleParens() + case null => + block(blockArgsAllowed) match { + case null => + wsThenScalaBlockChained() match { + case null => + elseIfCall() match { + case null => + elseCall() match { + case null => simpleParens() + case x => x + } + case x => x + } case x => x } - case x => x - } case x => x } - case x => x - } case x => x } } @@ -694,7 +707,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { val sb = new StringBuffer(".") // Simply alternate between matching a methodCall and a dot until one fails. - var done = false + var done = false var matchMethodCall = true // represent: "should I try to match a method call or a dot? while (!done) { if (matchMethodCall) { @@ -717,20 +730,20 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { // We know we must start with a methodCall, so try to parse one. // If it exceeds, enter a loop trying to parse a dot and methodcall in each iteration. def exclusiveDot(): Simple = { - val p = input.offset() + val p = input.offset() var result: Simple = null if (check(".")) { val firstMethodCall = methodCall() if (firstMethodCall != null) { - val sb = new StringBuffer("." + firstMethodCall) + val sb = new StringBuffer("." + firstMethodCall) var done = false while (!done) { - val reset = input.offset() + val reset = input.offset() var nextLink: String = null if (check(".")) { methodCall() match { case m: String => nextLink = m - case _ => + case _ => } } @@ -763,7 +776,9 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { val p = input.offset() if (check("else if")) { whitespaceNoBreak() - val args = several[String, ArrayBuffer[String]] { () => parentheses() } + val args = several[String, ArrayBuffer[String]] { () => + parentheses() + } position(Simple("else if" + args.mkString(",")), p) } else { input.regressTo(reset) @@ -786,10 +801,10 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { def template(): Template = { var result: Template = null - val resetPosition = input.offset() - val templDecl = templateDeclaration() + val resetPosition = input.offset() + val templDecl = templateDeclaration() if (templDecl != null) { - anyUntil(c => c != ' ' && c != '\t', inclusive = false) + anyUntil(c => c != ' ' && c != '\t', inclusive = false) if (check("=")) { anyUntil(c => c != ' ' && c != '\t', inclusive = false) if (check("{")) { @@ -810,13 +825,15 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { val namepos = input.offset() val name = identifier() match { case null => null - case id => position(PosString(id), namepos) + case id => position(PosString(id), namepos) } if (name != null) { val paramspos = input.offset() - val types = Option(squareBrackets()) getOrElse PosString("") - val args = several[String, ArrayBuffer[String]] { () => parentheses() } + val types = Option(squareBrackets()).getOrElse(PosString("")) + val args = several[String, ArrayBuffer[String]] { () => + parentheses() + } val params = position(PosString(types.toString + args.mkString), paramspos) if (params != null) return (name, params) @@ -826,11 +843,12 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { null } - def templateContent(): (collection.Seq[Simple], collection.Seq[Def], collection.Seq[Template], collection.Seq[TemplateTree]) = { - val imports = new ArrayBuffer[Simple] + def templateContent() + : (collection.Seq[Simple], collection.Seq[Def], collection.Seq[Template], collection.Seq[TemplateTree]) = { + val imports = new ArrayBuffer[Simple] val localDefs = new ArrayBuffer[Def] val templates = new ArrayBuffer[Template] - val mixeds = new ArrayBuffer[TemplateTree] + val mixeds = new ArrayBuffer[TemplateTree] var done = false while (!done) { @@ -861,7 +879,7 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { def extraImports(): collection.Seq[Simple] = { var resetPosition = input.offset() - val imports = new ArrayBuffer[Simple] + val imports = new ArrayBuffer[Simple] lastComment() @@ -883,10 +901,12 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { } /** - * Parse the template arguments. - */ + * Parse the template arguments. + */ private def templateArgs(): String = { - val result = several[String, ArrayBuffer[String]] { () => parentheses() } + val result = several[String, ArrayBuffer[String]] { () => + parentheses() + } if (result.nonEmpty) result.mkString else @@ -894,12 +914,12 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { } /** - * Parse the template arguments, if they exist - */ + * Parse the template arguments, if they exist + */ private def maybeTemplateArgs(): Option[PosString] = { if (check("@(")) { input.regress(1) - val p = input.offset() + val p = input.offset() val args = templateArgs() if (args != null) { val result = position(PosString(args), p) @@ -912,12 +932,12 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { } /** - * Parse the template arguments, if they exist - */ + * Parse the template arguments, if they exist + */ private def constructorArgs(): PosString = { if (check("@this(")) { input.regress(1) - val p = input.offset() + val p = input.offset() val args = templateArgs() if (args != null) position(PosString(args), p) else null @@ -944,10 +964,20 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) { (None, constructorComment) } } - val args = maybeTemplateArgs() + val args = maybeTemplateArgs() val (imports, localDefs, templates, mixeds) = templateContent() - val template = Template(PosString(""), constructor, argsComment, args.getOrElse(PosString("()")), topImports, imports, localDefs, templates, mixeds) + val template = Template( + PosString(""), + constructor, + argsComment, + args.getOrElse(PosString("()")), + topImports, + imports, + localDefs, + templates, + mixeds + ) if (errorStack.isEmpty) Success(template, input) diff --git a/parser/src/test/scala/play/twirl/parser/test/ParserSpec.scala b/parser/src/test/scala/play/twirl/parser/test/ParserSpec.scala index e3f645ec..3c2cdc7b 100644 --- a/parser/src/test/scala/play/twirl/parser/test/ParserSpec.scala +++ b/parser/src/test/scala/play/twirl/parser/test/ParserSpec.scala @@ -4,7 +4,9 @@ package play.twirl.parser package test -import org.scalatest.{Inside, MustMatchers, WordSpec} +import org.scalatest.Inside +import org.scalatest.MustMatchers +import org.scalatest.WordSpec import play.twirl.parser.TreeNodes._ class ParserSpec extends WordSpec with MustMatchers with Inside { @@ -70,23 +72,25 @@ class ParserSpec extends WordSpec with MustMatchers with Inside { } "elseIf.scala.html" in { - val template = parseTemplate("elseIf.scala.html") - val node = template.content(1) + val template = parseTemplate("elseIf.scala.html") + val node = template.content(1) val expressions = node.asInstanceOf[Display].exp.parts - expressions.head must be (Simple("if(input == 5)")) + expressions.head must be(Simple("if(input == 5)")) expressions(1).asInstanceOf[Block] - expressions(2) must be (Simple("else if(input == 6)")) + expressions(2) must be(Simple("else if(input == 6)")) expressions(3).asInstanceOf[Block] - expressions(4) must be (Simple("else if(input == 8)")) + expressions(4) must be(Simple("else if(input == 8)")) expressions(5).asInstanceOf[Block] - expressions(6) must be (Simple("else")) + expressions(6) must be(Simple("else")) } "imports.scala.html" in { - parseTemplate("imports.scala.html").topImports must be (Seq( - Simple("import java.io.File"), - Simple("import java.net.URL") - )) + parseTemplate("imports.scala.html").topImports must be( + Seq( + Simple("import java.io.File"), + Simple("import java.net.URL") + ) + ) } "case.scala.js" in { @@ -94,48 +98,48 @@ class ParserSpec extends WordSpec with MustMatchers with Inside { } "import expressions" in { - parseTemplateString("@import identifier").topImports must be (Seq(Simple("import identifier"))) + parseTemplateString("@import identifier").topImports must be(Seq(Simple("import identifier"))) parseTemplateString("@importIdentifier").topImports mustBe empty } "code block containing => of another statement with curly braces in first line" in { - val tmpl = parseTemplateString("""@if(attrs!=null){@attrs.map{ v => @v._1 }}""") // "@attrs.map{ v =>" should not be handled as block args + val tmpl = parseTemplateString("""@if(attrs!=null){@attrs.map{ v => @v._1 }}""") // "@attrs.map{ v =>" should not be handled as block args val ifExpressions = tmpl.content(0).asInstanceOf[Display].exp.parts - ifExpressions.head must be (Simple("if(attrs!=null)")) + ifExpressions.head must be(Simple("if(attrs!=null)")) val ifBlockBody = ifExpressions(1).asInstanceOf[Block].content(0).asInstanceOf[Display].exp.parts - ifBlockBody.head must be (Simple("attrs")) - ifBlockBody(1) must be (Simple(".map")) + ifBlockBody.head must be(Simple("attrs")) + ifBlockBody(1) must be(Simple(".map")) val mapBlock = ifBlockBody(2).asInstanceOf[Block] mapBlock.args.map(_.toString) mustBe Some(" v =>") val mapBlockBody = ifBlockBody(2).asInstanceOf[Block].content(1).asInstanceOf[Display].exp.parts - mapBlockBody.head must be (Simple("v")) - mapBlockBody(1) must be (Simple("._1")) + mapBlockBody.head must be(Simple("v")) + mapBlockBody(1) must be(Simple("._1")) } "code block containing => of another statement with parentheses in first line" in { - val tmpl = parseTemplateString("""@if(attrs!=null){@attrs.map( v => @v._1 )}""") // "@attrs.map( v =>" should not be handled as block args + val tmpl = parseTemplateString("""@if(attrs!=null){@attrs.map( v => @v._1 )}""") // "@attrs.map( v =>" should not be handled as block args val ifExpressions = tmpl.content(0).asInstanceOf[Display].exp.parts - ifExpressions.head must be (Simple("if(attrs!=null)")) + ifExpressions.head must be(Simple("if(attrs!=null)")) val ifBlockBody = ifExpressions(1).asInstanceOf[Block].content(0).asInstanceOf[Display].exp.parts - ifBlockBody.head must be (Simple("attrs")) - ifBlockBody(1) must be (Simple(".map( v => @v._1 )")) + ifBlockBody.head must be(Simple("attrs")) + ifBlockBody(1) must be(Simple(".map( v => @v._1 )")) } "code block containing (...) => in first line" in { - val tmpl = parseTemplateString("""@if(attrs!=null){( arg1, arg2 ) => @arg1.toString }""") // "( arg1, arg2 ) =>" should be handled as block args + val tmpl = parseTemplateString("""@if(attrs!=null){( arg1, arg2 ) => @arg1.toString }""") // "( arg1, arg2 ) =>" should be handled as block args val ifExpressions = tmpl.content(0).asInstanceOf[Display].exp.parts - ifExpressions.head must be (Simple("if(attrs!=null)")) + ifExpressions.head must be(Simple("if(attrs!=null)")) val ifBlock = ifExpressions(1).asInstanceOf[Block] ifBlock.args.map(_.toString) mustBe Some("( arg1, arg2 ) =>") val ifBlockBody = ifBlock.content(1).asInstanceOf[Display].exp.parts - ifBlockBody.head must be (Simple("arg1")) - ifBlockBody(1) must be (Simple(".toString")) + ifBlockBody.head must be(Simple("arg1")) + ifBlockBody(1) must be(Simple(".toString")) } "text outside of code block on same line containing =>" in { - val tmpl = parseTemplateString("""@if(attrs!=null){blockbody}Some plain text with => inside""") // "blockbody}Some plain text with =>" should not be handled as block args + val tmpl = parseTemplateString("""@if(attrs!=null){blockbody}Some plain text with => inside""") // "blockbody}Some plain text with =>" should not be handled as block args val ifExpressions = tmpl.content(0).asInstanceOf[Display].exp.parts - ifExpressions.head must be (Simple("if(attrs!=null)")) + ifExpressions.head must be(Simple("if(attrs!=null)")) val ifBlockBody = ifExpressions(1).asInstanceOf[Block].content(0).asInstanceOf[Plain] ifBlockBody.text mustBe "blockbody" val outsideIf = tmpl.content(1).asInstanceOf[Plain] @@ -143,20 +147,22 @@ class ParserSpec extends WordSpec with MustMatchers with Inside { } "match statement not allowed to have block arguments" in { - val tmpl = parseTemplateString("""@fooVariable match { case x: String => { Nice string } case _ => { Not a nice string } }""") // " case x: String =>" should not be handled as block args of the match block + val tmpl = parseTemplateString( + """@fooVariable match { case x: String => { Nice string } case _ => { Not a nice string } }""" + ) // " case x: String =>" should not be handled as block args of the match block val matchExpressions = tmpl.content(0).asInstanceOf[Display].exp.parts - matchExpressions.head must be (Simple("fooVariable")) - matchExpressions(1) must be (Simple(" match")) + matchExpressions.head must be(Simple("fooVariable")) + matchExpressions(1) must be(Simple(" match")) val matchBlock = matchExpressions(2).asInstanceOf[Block].content val firstCaseBlock = matchBlock.head.asInstanceOf[ScalaExp].parts - firstCaseBlock.head must be (Simple("case x: String =>")) + firstCaseBlock.head must be(Simple("case x: String =>")) val firstCaseBlockBody = firstCaseBlock(1).asInstanceOf[Block] firstCaseBlockBody.content(1).asInstanceOf[Plain].text mustBe "Nice string " val secondCaseBlock = matchBlock(1).asInstanceOf[ScalaExp].parts - secondCaseBlock.head must be (Simple("case _ =>")) + secondCaseBlock.head must be(Simple("case _ =>")) val secondCaseBlockBody = secondCaseBlock(1).asInstanceOf[Block] secondCaseBlockBody.content(1).asInstanceOf[Plain].text mustBe "Not a nice string " } @@ -197,4 +203,3 @@ class ParserSpec extends WordSpec with MustMatchers with Inside { } } - diff --git a/project/plugins.sbt b/project/plugins.sbt index b4a1370a..9f67a8ab 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -5,5 +5,6 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.28") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.1") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.5.0") -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.5.0") +addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.0") diff --git a/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/SbtTwirlCompat.scala b/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/SbtTwirlCompat.scala index be6962bf..453a7822 100644 --- a/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/SbtTwirlCompat.scala +++ b/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/SbtTwirlCompat.scala @@ -8,7 +8,7 @@ import sbt.Keys._ import play.twirl.sbt.Import.TwirlKeys._ object SbtTwirlCompat { - def watchSourcesSettings: Seq[Setting[_]] = Seq( + def watchSourcesSettings: Seq[Setting[_]] = Seq( watchSources in Defaults.ConfigGlobal += WatchSource( (sourceDirectory in compileTemplates).value, diff --git a/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/TemplateCompilerErrorHandler.scala b/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/TemplateCompilerErrorHandler.scala index 0a129199..2ee7e27d 100644 --- a/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/TemplateCompilerErrorHandler.scala +++ b/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/TemplateCompilerErrorHandler.scala @@ -14,8 +14,10 @@ trait TemplateCompilerErrorHandler { def handleError(log: Logger, codec: Codec): PartialFunction[Throwable, Nothing] = { case TemplateCompilationError(source, message, line, column) => val exception = TemplateProblem.exception(source, codec, message, line, column) - val reporter = new LoggedReporter(10, log) - exception.problems foreach { p => reporter.log(p) } + val reporter = new LoggedReporter(10, log) + exception.problems.foreach { p => + reporter.log(p) + } throw exception case e => throw e } diff --git a/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/TemplateProblem.scala b/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/TemplateProblem.scala index 375676dd..4efd4be1 100644 --- a/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/TemplateProblem.scala +++ b/sbt-twirl/src/main/scala-sbt-1.0/play/twirl/sbt/TemplateProblem.scala @@ -3,34 +3,38 @@ */ package play.twirl.sbt -import play.twirl.compiler.{ GeneratedSource, MaybeGeneratedSource } +import play.twirl.compiler.GeneratedSource +import play.twirl.compiler.MaybeGeneratedSource import play.twirl.parser.TwirlIO import sbt._ -import xsbti.{ CompileFailed, Position, Problem, Severity } +import xsbti.CompileFailed +import xsbti.Position +import xsbti.Problem +import xsbti.Severity import scala.io.Codec object TemplateProblem { def positionMapper(codec: Codec): Position => Option[Position] = position => { - toScala(position.sourceFile).flatMap(f => MaybeGeneratedSource(f, codec)) map { - generated => TemplatePosition(generated, position) + toScala(position.sourceFile).flatMap(f => MaybeGeneratedSource(f, codec)).map { generated => + TemplatePosition(generated, position) } } def exception(source: File, codec: Codec, message: String, line: Int, column: Int) = { - val column0 = 0 max (column - 1) // convert to 0-based column + val column0 = 0.max(column - 1) // convert to 0-based column new ProblemException(TemplateProblem(message, TemplatePosition(source, codec, line, column0))) } class ProblemException(issues: Problem*) extends CompileFailed with FeedbackProvidedException { def arguments(): Array[String] = Array.empty def problems(): Array[Problem] = issues.toArray - override def toString = "Twirl compilation failed" + override def toString = "Twirl compilation failed" } case class TemplateProblem(message: String, position: Position) extends Problem { - def category: String = "undefined" + def category: String = "undefined" def severity: Severity = Severity.Error } @@ -41,28 +45,34 @@ object TemplateProblem { } def apply(generated: GeneratedSource, position: Position): TemplatePosition = { - val offset = toScala(position.offset) map { o => generated.mapPosition(o) } - val location = offset flatMap { o => TemplateMapping(generated.source, generated.codec).location(o) } + val offset = toScala(position.offset).map { o => + generated.mapPosition(o) + } + val location = offset.flatMap { o => + TemplateMapping(generated.source, generated.codec).location(o) + } new TemplatePosition(generated.source, location) } } class TemplatePosition(source: Option[File], location: Option[TemplateMapping.Location]) extends Position { - val line: java.util.Optional[Integer] = toJava { location map (_.line) } + val line: java.util.Optional[Integer] = toJava { location.map(_.line) } val lineContent: String = location.fold("")(_.content) - val offset: java.util.Optional[Integer] = toJava { location map (_.offset) } + val offset: java.util.Optional[Integer] = toJava { location.map(_.offset) } - val pointer: java.util.Optional[Integer] = toJava { location map (_.column) } + val pointer: java.util.Optional[Integer] = toJava { location.map(_.column) } val pointerSpace: java.util.Optional[String] = toJava { - location.map { l => lineContent.take(l.column) map { case '\t' => '\t'; case _ => ' ' } } + location.map { l => + lineContent.take(l.column).map { case '\t' => '\t'; case _ => ' ' } + } } val sourceFile: java.util.Optional[File] = toJava(source) - val sourcePath: java.util.Optional[String] = toJava { source map (_.getCanonicalPath) } + val sourcePath: java.util.Optional[String] = toJava { source.map(_.getCanonicalPath) } override def toString: String = { val stringBuilder = new StringBuilder @@ -85,21 +95,21 @@ object TemplateProblem { } else if (l > line) { Location(line, content.length, end, content) } else { - val column = 0 max c min content.length + val column = 0.max(c).min(content.length) val offset = start + column Location(line, column, offset, content) } } def location(o: Int): Location = { - val offset = start max o min end + val offset = start.max(o).min(end) val column = offset - start Location(line, column, offset, content) } } def apply(source: Option[File], codec: Codec): TemplateMapping = { - val lines = source.toSeq flatMap { file => + val lines = source.toSeq.flatMap { file => TwirlIO.readFileAsString(file, codec.charSet).stripSuffix("\n").split("\n") } TemplateMapping(lines) @@ -107,17 +117,20 @@ object TemplateProblem { } case class TemplateMapping(sourceLines: Seq[String]) { - import TemplateMapping.{Line, Location} + import TemplateMapping.Line + import TemplateMapping.Location - val lines: Seq[Line] = sourceLines.scanLeft(Line(0, -1, -1, "")) { (previous, content) => - Line(previous.line + 1, previous.end + 1, previous.end + 1 + content.length, content.stripSuffix("\r")) - }.drop(1) + val lines: Seq[Line] = sourceLines + .scanLeft(Line(0, -1, -1, "")) { (previous, content) => + Line(previous.line + 1, previous.end + 1, previous.end + 1 + content.length, content.stripSuffix("\r")) + } + .drop(1) def location(offset: Int): Option[Location] = { if (lines.isEmpty) { None } else { - val index = 0 max lines.lastIndexWhere(_.start <= offset) + val index = 0.max(lines.lastIndexWhere(_.start <= offset)) Some(lines(index).location(offset)) } } @@ -126,7 +139,7 @@ object TemplateProblem { if (lines.isEmpty) { None } else { - val index = 0 max (line - 1) min (lines.length - 1) + val index = 0.max(line - 1).min(lines.length - 1) Some(lines(index).location(line, column)) } } @@ -134,7 +147,7 @@ object TemplateProblem { def toJava[A](o: Option[A]): java.util.Optional[A] = o match { case Some(v) => java.util.Optional.ofNullable(v) - case None => java.util.Optional.empty() + case None => java.util.Optional.empty() } def toScala[A](o: java.util.Optional[A]): Option[A] = { diff --git a/sbt-twirl/src/main/scala/play/twirl/sbt/SbtTwirl.scala b/sbt-twirl/src/main/scala/play/twirl/sbt/SbtTwirl.scala index 265ceaa4..af1df5cb 100644 --- a/sbt-twirl/src/main/scala/play/twirl/sbt/SbtTwirl.scala +++ b/sbt-twirl/src/main/scala/play/twirl/sbt/SbtTwirl.scala @@ -11,14 +11,19 @@ import scala.io.Codec object Import { object TwirlKeys { - val twirlVersion = SettingKey[String]("twirl-version", "Twirl version used for twirl-api dependency") + val twirlVersion = SettingKey[String]("twirl-version", "Twirl version used for twirl-api dependency") val templateFormats = SettingKey[Map[String, String]]("twirl-template-formats", "Defined twirl template formats") val templateImports = SettingKey[Seq[String]]("twirl-template-imports", "Extra imports for twirl templates") - val constructorAnnotations = SettingKey[Seq[String]]("twirl-constructor-annotations", "Annotations added to constructors in injectable templates") + val constructorAnnotations = SettingKey[Seq[String]]( + "twirl-constructor-annotations", + "Annotations added to constructors in injectable templates" + ) @deprecated("No longer supported", "1.2.0") val useOldParser = SettingKey[Boolean]("twirl-use-old-parser", "No longer supported") - val sourceEncoding = TaskKey[String]("twirl-source-encoding", "Source encoding for template files and generated scala files") - val compileTemplates = TaskKey[Seq[File]]("twirl-compile-templates", "Compile twirl templates into scala source files") + val sourceEncoding = + TaskKey[String]("twirl-source-encoding", "Source encoding for template files and generated scala files") + val compileTemplates = + TaskKey[Seq[File]]("twirl-compile-templates", "Compile twirl templates into scala source files") } } @@ -34,26 +39,24 @@ object SbtTwirl extends AutoPlugin { override def projectSettings: Seq[Setting[_]] = inConfig(Compile)(twirlSettings) ++ - inConfig(Test)(twirlSettings) ++ - defaultSettings ++ - positionSettings ++ - dependencySettings + inConfig(Test)(twirlSettings) ++ + defaultSettings ++ + positionSettings ++ + dependencySettings def twirlSettings: Seq[Setting[_]] = SbtTwirlCompat.watchSourcesSettings ++ Seq( includeFilter in compileTemplates := "*.scala.*", excludeFilter in compileTemplates := HiddenFileFilter, sourceDirectories in compileTemplates := Seq(sourceDirectory.value / "twirl"), - - sources in compileTemplates := Defaults.collectFiles( - sourceDirectories in compileTemplates, - includeFilter in compileTemplates, - excludeFilter in compileTemplates - ).value, - + sources in compileTemplates := Defaults + .collectFiles( + sourceDirectories in compileTemplates, + includeFilter in compileTemplates, + excludeFilter in compileTemplates + ) + .value, target in compileTemplates := crossTarget.value / "twirl" / Defaults.nameForSrc(configuration.value.name), - compileTemplates := compileTemplatesTask.value, - sourceGenerators += compileTemplates.taskValue, managedSourceDirectories += (target in compileTemplates).value ) @@ -75,11 +78,11 @@ object SbtTwirl extends AutoPlugin { val crossVer = crossVersion.value val isScalaJS = CrossVersion(crossVer, scalaVersion.value, scalaBinaryVersion.value) match { case Some(f) => f("").contains("_sjs0.6") // detect ScalaJS CrossVersion - case None => false + case None => false } // TODO: use %%% from sbt-crossproject when we add support for scalajs 1.0 val baseModuleID = "com.typesafe.play" %% "twirl-api" % twirlVersion.value - if (isScalaJS) baseModuleID cross crossVer else baseModuleID + if (isScalaJS) baseModuleID.cross(crossVer) else baseModuleID } ) @@ -90,9 +93,9 @@ object SbtTwirl extends AutoPlugin { def defaultFormats = Map( "html" -> "play.twirl.api.HtmlFormat", - "txt" -> "play.twirl.api.TxtFormat", - "xml" -> "play.twirl.api.XmlFormat", - "js" -> "play.twirl.api.JavaScriptFormat" + "txt" -> "play.twirl.api.TxtFormat", + "xml" -> "play.twirl.api.XmlFormat", + "js" -> "play.twirl.api.JavaScriptFormat" ) def compileTemplatesTask = Def.task { @@ -110,15 +113,13 @@ object SbtTwirl extends AutoPlugin { } def readResourceProperty(resource: String, property: String): String = { - val props = new java.util.Properties + val props = new java.util.Properties val stream = getClass.getClassLoader.getResourceAsStream(resource) try { props.load(stream) - } - catch { + } catch { case e: Exception => - } - finally { + } finally { if (stream ne null) stream.close } props.getProperty(property) diff --git a/sbt-twirl/src/main/scala/play/twirl/sbt/TemplateCompiler.scala b/sbt-twirl/src/main/scala/play/twirl/sbt/TemplateCompiler.scala index bfbca4c5..33ec45dc 100644 --- a/sbt-twirl/src/main/scala/play/twirl/sbt/TemplateCompiler.scala +++ b/sbt-twirl/src/main/scala/play/twirl/sbt/TemplateCompiler.scala @@ -10,36 +10,55 @@ import scala.io.Codec object TemplateCompiler extends TemplateCompilerErrorHandler { @deprecated("Use other compile method", "1.2.0") def compile( - sourceDirectories: Seq[File], - targetDirectory: File, - templateFormats: Map[String, String], - templateImports: Seq[String], - includeFilter: FileFilter, - excludeFilter: FileFilter, - codec: Codec, - useOldParser: Boolean, - log: Logger): Seq[File] = - compile(sourceDirectories, targetDirectory, templateFormats, templateImports, Nil, includeFilter, excludeFilter, - codec, log) + sourceDirectories: Seq[File], + targetDirectory: File, + templateFormats: Map[String, String], + templateImports: Seq[String], + includeFilter: FileFilter, + excludeFilter: FileFilter, + codec: Codec, + useOldParser: Boolean, + log: Logger + ): Seq[File] = + compile( + sourceDirectories, + targetDirectory, + templateFormats, + templateImports, + Nil, + includeFilter, + excludeFilter, + codec, + log + ) def compile( - sourceDirectories: Seq[File], - targetDirectory: File, - templateFormats: Map[String, String], - templateImports: Seq[String], - constructorAnnotations: Seq[String], - includeFilter: FileFilter, - excludeFilter: FileFilter, - codec: Codec, - log: Logger): Seq[File] = { + sourceDirectories: Seq[File], + targetDirectory: File, + templateFormats: Map[String, String], + templateImports: Seq[String], + constructorAnnotations: Seq[String], + includeFilter: FileFilter, + excludeFilter: FileFilter, + codec: Codec, + log: Logger + ): Seq[File] = { try { syncGenerated(targetDirectory, codec) val templates = collectTemplates(sourceDirectories, templateFormats, includeFilter, excludeFilter) for ((template, sourceDirectory, extension, format) <- templates) { val imports = formatImports(templateImports, extension) - TwirlCompiler.compile(template, sourceDirectory, targetDirectory, format, imports, constructorAnnotations, - codec, inclusiveDot = false) + TwirlCompiler.compile( + template, + sourceDirectory, + targetDirectory, + format, + imports, + constructorAnnotations, + codec, + inclusiveDot = false + ) } generatedFiles(targetDirectory).map(_.getAbsoluteFile) } catch handleError(log, codec) @@ -53,9 +72,14 @@ object TemplateCompiler extends TemplateCompilerErrorHandler { generatedFiles(targetDirectory).map(GeneratedSource(_, codec)).foreach(_.sync) } - def collectTemplates(sourceDirectories: Seq[File], templateFormats: Map[String, String], includeFilter: FileFilter, excludeFilter: FileFilter): Seq[(File, File, String, String)] = { - sourceDirectories flatMap { sourceDirectory => - (sourceDirectory ** includeFilter).get flatMap { file => + def collectTemplates( + sourceDirectories: Seq[File], + templateFormats: Map[String, String], + includeFilter: FileFilter, + excludeFilter: FileFilter + ): Seq[(File, File, String, String)] = { + sourceDirectories.flatMap { sourceDirectory => + (sourceDirectory ** includeFilter).get.flatMap { file => val ext = file.name.split('.').last if (!excludeFilter.accept(file) && templateFormats.contains(ext)) Some((file, sourceDirectory, ext, templateFormats(ext))) diff --git a/sbt-twirl/src/test/scala/play/twirl/sbt/test/TemplateMappingSpec.scala b/sbt-twirl/src/test/scala/play/twirl/sbt/test/TemplateMappingSpec.scala index 1ac396cb..bdfa91a6 100644 --- a/sbt-twirl/src/test/scala/play/twirl/sbt/test/TemplateMappingSpec.scala +++ b/sbt-twirl/src/test/scala/play/twirl/sbt/test/TemplateMappingSpec.scala @@ -4,7 +4,9 @@ package play.twirl.sbt package test -import org.scalatest.{ Inspectors, MustMatchers, WordSpec } +import org.scalatest.Inspectors +import org.scalatest.MustMatchers +import org.scalatest.WordSpec import play.twirl.sbt.TemplateProblem.TemplateMapping import play.twirl.sbt.TemplateProblem.TemplateMapping.Location @@ -33,7 +35,7 @@ class TemplateMappingSpec extends WordSpec with MustMatchers with Inspectors { Location(4, 1, 7, "d") ) - forAll (testLocations) { location => + forAll(testLocations) { location => mapping.location(location.offset) mustBe Some(location) mapping.location(location.line, location.column) mustBe Some(location) } @@ -47,8 +49,9 @@ class TemplateMappingSpec extends WordSpec with MustMatchers with Inspectors { 10 -> Location(4, 1, 7, "d") ) - forAll(testOffsets) { case (offset, location) => - mapping.location(offset) mustBe Some(location) + forAll(testOffsets) { + case (offset, location) => + mapping.location(offset) mustBe Some(location) } val testPositions = Seq( @@ -61,8 +64,9 @@ class TemplateMappingSpec extends WordSpec with MustMatchers with Inspectors { (5, 0) -> Location(4, 1, 7, "d") ) - forAll(testPositions) { case ((line, column), location) => - mapping.location(line, column) mustBe Some(location) + forAll(testPositions) { + case ((line, column), location) => + mapping.location(line, column) mustBe Some(location) } } } diff --git a/sbt-twirl/src/test/scala/play/twirl/sbt/test/TemplatePositionSpec.scala b/sbt-twirl/src/test/scala/play/twirl/sbt/test/TemplatePositionSpec.scala index e8f3aad0..ce5585db 100644 --- a/sbt-twirl/src/test/scala/play/twirl/sbt/test/TemplatePositionSpec.scala +++ b/sbt-twirl/src/test/scala/play/twirl/sbt/test/TemplatePositionSpec.scala @@ -5,8 +5,11 @@ package play.twirl.sbt.test import java.io.File -import play.twirl.sbt.TemplateProblem.{TemplateMapping, TemplatePosition} -import org.scalatest.{Inspectors, MustMatchers, WordSpec} +import play.twirl.sbt.TemplateProblem.TemplateMapping +import play.twirl.sbt.TemplateProblem.TemplatePosition +import org.scalatest.Inspectors +import org.scalatest.MustMatchers +import org.scalatest.WordSpec class TemplatePositionSpec extends WordSpec with MustMatchers with Inspectors { @@ -15,54 +18,54 @@ class TemplatePositionSpec extends WordSpec with MustMatchers with Inspectors { "toString" should { "have the source path" in { - val file = new File("/some/path/file.scala.html") + val file = new File("/some/path/file.scala.html") val location = TemplateMapping.Location(line = 10, column = 2, offset = 22, content = "some content") - val tp = new TemplatePosition(Option(file), Option(location)) + val tp = new TemplatePosition(Option(file), Option(location)) tp.toString mustBe "/some/path/file.scala.html:10\nsome content" } "not have the source path when it is empty" in { val location = TemplateMapping.Location(line = 10, column = 2, offset = 22, content = "some content") - val tp = new TemplatePosition(None, Option(location)) + val tp = new TemplatePosition(None, Option(location)) tp.toString mustNot include("/some/path/file.scala.html") } "have line if present" in { - val file = new File("/some/path/file.scala.html") + val file = new File("/some/path/file.scala.html") val location = TemplateMapping.Location(line = 10, column = 2, offset = 22, content = "some content") - val tp = new TemplatePosition(Option(file), Option(location)) + val tp = new TemplatePosition(Option(file), Option(location)) tp.toString must include("10") } "not have line when it is empty" in { val file = new File("/some/path/file.scala.html") - val tp = new TemplatePosition(Option(file), None /* means no location for the error, then no line */) + val tp = new TemplatePosition(Option(file), None /* means no location for the error, then no line */ ) tp.toString mustBe "/some/path/file.scala.html" } "have line content if present" in { - val file = new File("/some/path/file.scala.html") + val file = new File("/some/path/file.scala.html") val location = TemplateMapping.Location(line = 10, column = 2, offset = 22, content = "some content") - val tp = new TemplatePosition(Option(file), Option(location)) + val tp = new TemplatePosition(Option(file), Option(location)) tp.toString must include("some content") } "not have line content when it is missing" in { val file = new File("/some/path/file.scala.html") - val tp = new TemplatePosition(Option(file), None /* means no location for the error, then no offset */) + val tp = new TemplatePosition(Option(file), None /* means no location for the error, then no offset */ ) tp.toString mustBe "/some/path/file.scala.html" } "not have line content when it is empty" in { - val file = new File("/some/path/file.scala.html") + val file = new File("/some/path/file.scala.html") val location = TemplateMapping.Location(line = 10, column = 2, offset = 22, content = "") - val tp = new TemplatePosition(Option(file), Option(location)) + val tp = new TemplatePosition(Option(file), Option(location)) tp.toString mustBe "/some/path/file.scala.html:10" } diff --git a/scripts/test-code.sh b/scripts/test-code.sh new file mode 100755 index 00000000..f24fdb53 --- /dev/null +++ b/scripts/test-code.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +sbt "++ $TRAVIS_SCALA_VERSION test" || travis_terminate 1 +sbt +publishLocal plugin/test plugin/scripted || travis_terminate 1 \ No newline at end of file diff --git a/scripts/validate-code.sh b/scripts/validate-code.sh index 749773d9..49e47eec 100755 --- a/scripts/validate-code.sh +++ b/scripts/validate-code.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash -sbt ++$TRAVIS_SCALA_VERSION validateCode || travis_terminate 1 -sbt "++ $TRAVIS_SCALA_VERSION test" || travis_terminate 1 -sbt +publishLocal plugin/test plugin/scripted || travis_terminate 1 \ No newline at end of file +sbt ++$TRAVIS_SCALA_VERSION validateCode || travis_terminate 1 +sbt "++$TRAVIS_SCALA_VERSION mimaReportBinaryIssues" || travis_terminate 1