Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Scala3Renderer #198

Merged
merged 2 commits into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/scala/sbtbuildinfo/BuildInfoPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,6 @@ object BuildInfoPlugin extends AutoPlugin {
buildInfoKeys := Seq(name, version, scalaVersion, sbtVersion),
buildInfoBuildNumber := buildNumberTask(baseDirectory.value, 1),
buildInfoOptions := Seq(),
buildInfoRenderFactory := ScalaCaseObjectRenderer.apply
buildInfoRenderFactory := (if(scalaVersion.value.startsWith("3")) Scala3CaseObjectRenderer.apply else ScalaCaseObjectRenderer.apply)
)
}
66 changes: 66 additions & 0 deletions src/main/scala/sbtbuildinfo/Scala3CaseClassRenderer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package sbtbuildinfo

case class Scala3CaseClassRenderer(options: Seq[BuildInfoOption], pkg: String, obj: String) extends Scala3Renderer {
override def fileType = BuildInfoType.Source
override def extension = "scala"

val traitNames = options.collect{case BuildInfoOption.Traits(ts @ _*) => ts}.flatten
val objTraits = if (traitNames.isEmpty) "" else "extends " ++ traitNames.mkString(" with ")

// It is safe to add `import scala.Predef` even though we need to keep `-Ywarn-unused-import` in mind
// because we always generate code that has a reference to `String`. If the "base" generated code were to be
// changed and no longer contain a reference to `String`, we would need to remove `import scala.Predef` and
// fully qualify every reference. Note it is NOT safe to use `import scala._` because of the possibility of
// the project using `-Ywarn-unused-import` because we do not always generated references that are part of
// `scala` such as `scala.Option`.
val importScalaPredef = options.contains(BuildInfoOption.ImportScalaPredef)
def header = List(
"// $COVERAGE-OFF$",
s"package $pkg",
""
)
val imports = if (importScalaPredef) List(
"import scala.Predef.*",
""
) else Nil
val generateComment = List(
s"/** This file was generated by sbt-buildinfo. */"
)

def footer = List("// $COVERAGE-ON$")

override def renderKeys(buildInfoResults: Seq[BuildInfoResult]) =
header ++ imports ++ generateComment ++
caseClassDefinitionBegin ++
buildInfoResults.flatMap(caseClassParameter).mkString(",\n").split("\n") ++
caseClassDefinitionEnd ++
toMapLines(buildInfoResults) ++
toJsonLines ++
caseClassEnd ++
List("") ++
caseObjectLine(buildInfoResults) ++
footer

private def caseClassDefinitionBegin = List(
withPkgPriv(s"case class $obj(")
)

private def caseClassParameter(r: BuildInfoResult): Seq[String] = {
val typeDecl = getType(r.typeExpr) getOrElse "scala.Any"

List(
s" ${r.identifier}: $typeDecl"
)
}

private def caseClassDefinitionEnd = List(s") $objTraits {", "")
private def caseClassEnd = List("}")

private def caseObjectLine(buildInfoResults: Seq[BuildInfoResult]) = List(
withPkgPriv(s"case object $obj {"),
s" def apply(): $obj = new $obj(${buildInfoResults.map(r => s"\n ${r.identifier} = ${quote(r.value)}").mkString(",")})",
s" val get = apply()",
s" val value = apply()",
s"}"
)
}
71 changes: 71 additions & 0 deletions src/main/scala/sbtbuildinfo/Scala3CaseObjectRenderer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package sbtbuildinfo

case class Scala3CaseObjectRenderer(options: Seq[BuildInfoOption], pkg: String, obj: String) extends Scala3Renderer {

override def fileType = BuildInfoType.Source
override def extension = "scala"
val traitNames = options.collect{case BuildInfoOption.Traits(ts @ _*) => ts}.flatten
val objTraits = if (traitNames.isEmpty) "" else " extends " ++ traitNames.mkString(" with ")
val constantValue = options.contains(BuildInfoOption.ConstantValue)

// It is safe to add `import scala.Predef` even though we need to keep `-Ywarn-unused-import` in mind
// because we always generate code that has a reference to `String`. If the "base" generated code were to be
// changed and no longer contain a reference to `String`, we would need to remove `import scala.Predef` and
// fully qualify every reference. Note it is NOT safe to use `import scala._` because of the possibility of
// the project using `-Ywarn-unused-import` because we do not always generated references that are part of
// `scala` such as `scala.Option`.
val importScalaPredef = options.contains(BuildInfoOption.ImportScalaPredef)
def header = List(
"// $COVERAGE-OFF$",
s"package $pkg",
""
)
val imports = if (importScalaPredef) List(
"import scala.Predef.*",
""
) else Nil
val objectHeader = List(
s"/** This object was generated by sbt-buildinfo. */",
withPkgPriv(s"case object $obj$objTraits {")
)

def footer = List("}", "// $COVERAGE-ON$")

override def renderKeys(buildInfoResults: Seq[BuildInfoResult]) =
header ++ imports ++ objectHeader ++
buildInfoResults.flatMap(line) ++
Seq(toStringLines(buildInfoResults)) ++
toMapLines(buildInfoResults) ++
toJsonLines ++
footer

private val constantTypes = Set("scala.Int", "scala.Long", "scala.Double", "scala.Boolean", "scala.Symbol", "String")

private def line(result: BuildInfoResult): Seq[String] = {
import result._
val (typeDecl, modifier) =
getType(result.typeExpr) match {
case Some(tp) if !constantValue || !constantTypes(tp) =>
(s": $tp", "")
case _ if constantValue =>
("", "final ")
case _ =>
("", "")
}
List(
s" /** The value is ${quote(value)}. */",
s" ${modifier}val $identifier$typeDecl = ${quote(value)}"
)
}

def toStringLines(results: Seq[BuildInfoResult]): String = {
val idents = results.map(_.identifier)
val fmt = idents.map("%s: %%s" format _).mkString(", ")
val vars = idents.mkString(", ")
s""" override val toString: String = {
| "$fmt".format(
| $vars
| )
| }""".stripMargin
}
}
29 changes: 29 additions & 0 deletions src/main/scala/sbtbuildinfo/Scala3Renderer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package sbtbuildinfo

abstract class Scala3Renderer extends ScalaRenderer {

override protected def toJsonLines: Seq[String] =
if (options contains BuildInfoOption.ToJson)
List(
"""| private def quote(x: scala.Any): String = "\"" + x + "\""
| private def toJsonValue[T <: Matchable](value: T): String = {
| value match {
| case elem: scala.collection.Seq[? <: Matchable] => elem.map(toJsonValue).mkString("[", ",", "]")
| case elem: scala.Option[? <: Matchable] => elem.map(toJsonValue).getOrElse("null")
| case elem: scala.collection.Map[?, ? <: Matchable] => elem.map {
| case (k, v) => toJsonValue(k.toString) + ":" + toJsonValue(v)
| }.mkString("{", ", ", "}")
| case d: scala.Double => d.toString
| case f: scala.Float => f.toString
| case l: scala.Long => l.toString
| case i: scala.Int => i.toString
| case s: scala.Short => s.toString
| case bool: scala.Boolean => bool.toString
| case str: String => quote(str)
| case other => quote(other.toString)
| }
| }
|
| val toJson: String = toJsonValue(toMap)""".stripMargin)
else Nil
}