From ab7a5383e0804eeeb3d7d2400515d19a7592b22b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Warcho=C5=82?= Date: Fri, 26 Jun 2026 14:23:28 +0200 Subject: [PATCH] feat: support Scala 3 nightly versions in sbt --- .../scala/org/scastie/api/ScalaTarget.scala | 7 ++++- .../scala/org/scastie/api/ScalaVersions.scala | 2 ++ .../client/components/VersionSelector.scala | 27 +++++++++++++++++-- .../client/scalacli/ScalaVersionUtil.scala | 4 +++ .../scastie/instrumentation/Instrument.scala | 2 ++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/api/src/main/scala/org/scastie/api/ScalaTarget.scala b/api/src/main/scala/org/scastie/api/ScalaTarget.scala index b3e9c52c1..5d760f735 100644 --- a/api/src/main/scala/org/scastie/api/ScalaTarget.scala +++ b/api/src/main/scala/org/scastie/api/ScalaTarget.scala @@ -255,7 +255,12 @@ case class Scala3(scalaVersion: String) extends SbtScalaTarget { else renderSbtDouble(lib) } - def sbtConfig: String = sbtConfigScalaVersion + def sbtConfig: String = + if (ScalaVersions.isNightly(scalaVersion)) + // TODO: change this to "resolvers += Resolver.scalaNightlyRepository" after upgrading to sbt 1.11.5 + s"""|$sbtConfigScalaVersion + |resolvers += "scala-nightly" at "https://repo.scala-lang.org/artifactory/maven-nightlies"""".stripMargin + else sbtConfigScalaVersion def sbtPluginsConfig: String = "" diff --git a/api/src/main/scala/org/scastie/api/ScalaVersions.scala b/api/src/main/scala/org/scastie/api/ScalaVersions.scala index a0381f810..f57115aad 100644 --- a/api/src/main/scala/org/scastie/api/ScalaVersions.scala +++ b/api/src/main/scala/org/scastie/api/ScalaVersions.scala @@ -129,4 +129,6 @@ object ScalaVersions { def find(tpe: ScalaTargetType, sv: String): String = allVersions(tpe).find(_.startsWith(sv)).getOrElse(sv) + + def isNightly(version: String): Boolean = version.endsWith("-NIGHTLY") } diff --git a/client/src/main/scala/org/scastie/client/components/VersionSelector.scala b/client/src/main/scala/org/scastie/client/components/VersionSelector.scala index 18becbce4..1aedd307d 100644 --- a/client/src/main/scala/org/scastie/client/components/VersionSelector.scala +++ b/client/src/main/scala/org/scastie/client/components/VersionSelector.scala @@ -6,6 +6,8 @@ import japgolly.scalajs.react._ import vdom.all._ import org.scastie.buildinfo.BuildInfo +import org.scastie.client.scalacli.ScalaVersionUtil +import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue case class VersionSelector(scalaTarget: SbtScalaTarget, onChange: ScalaTarget ~=> Callback) { @inline def render: VdomElement = VersionSelector.versionSelectorHook(this) @@ -16,7 +18,11 @@ object VersionSelector { val versionSelectorHook = ScalaFnComponent .withHooks[VersionSelector] - .render(props => { + .useState(Option.empty[String]) + .useEffectOnMountBy((_, nightly) => + Callback.future(ScalaVersionUtil.resolveScala3Nightly.map(nightly.setState)) + ) + .render((props, nightly) => { def versionSelectors(scalaVersion: String) = props.scalaTarget match { case d: Scala2 => Scala2.apply(scalaVersion) @@ -49,12 +55,29 @@ object VersionSelector { ) } .toTagMod, + TagMod.when(props.scalaTarget.targetType == ScalaTargetType.Scala3)( + nightly.value match { + case Some(nightlyVersion) => + li( + input( + `type` := "radio", + id := "scala-nightly", + name := "scalaV", + onChange --> props.onChange(Scala3(nightlyVersion)), + checked := ScalaVersions.isNightly(props.scalaTarget.scalaVersion) + ), + label(`for` := "scala-nightly", className := "radio", role := "button", "Nightly") + ) + case None => EmptyVdom + } + ), li( label( div(cls := "select-wrapper"){ val isRecommended = ScalaVersions .suggestedScalaVersions(props.scalaTarget.targetType) - .contains(props.scalaTarget.scalaVersion) + .contains(props.scalaTarget.scalaVersion) || + ScalaVersions.isNightly(props.scalaTarget.scalaVersion) select( name := "scalaVersion", diff --git a/client/src/main/scala/org/scastie/client/scalacli/ScalaVersionUtil.scala b/client/src/main/scala/org/scastie/client/scalacli/ScalaVersionUtil.scala index 058b486fa..38a85ca14 100644 --- a/client/src/main/scala/org/scastie/client/scalacli/ScalaVersionUtil.scala +++ b/client/src/main/scala/org/scastie/client/scalacli/ScalaVersionUtil.scala @@ -6,6 +6,7 @@ import org.scalajs.dom import scala.util.matching.Regex import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue import org.scastie.buildinfo.BuildInfo +import org.scastie.api.ScalaVersions object ScalaVersionUtil { val location = dom.window.location @@ -102,4 +103,7 @@ object ScalaVersionUtil { Future.successful(version) } } + + def resolveScala3Nightly: Future[Option[String]] = + resolveVersion(scala3Nightly).map(v => Option.when(ScalaVersions.isNightly(v))(v)) } diff --git a/instrumentation/src/main/scala/org/scastie/instrumentation/Instrument.scala b/instrumentation/src/main/scala/org/scastie/instrumentation/Instrument.scala index 830d3a045..79e234304 100644 --- a/instrumentation/src/main/scala/org/scastie/instrumentation/Instrument.scala +++ b/instrumentation/src/main/scala/org/scastie/instrumentation/Instrument.scala @@ -61,6 +61,8 @@ object Instrument { def instrumentOne(term: Term, tpeTree: Option[Type], offset: Int, isScalaJs: Boolean): Patch = { + // TODO: rename `$t` to a `$`-free identifier to silence the warning: + // "The identifier `$t` should not contain `$`, which is reserved for internal compiler use." val treeQuote = tpeTree match { case None => s"val $$t = $term"