All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.cli.commands.shared.ScalacOptions.scala Maven / Gradle / Ivy

There is a newer version: 1.5.0
Show newest version
package scala.cli.commands.shared

import caseapp.*
import caseapp.core.Scala3Helpers.*
import caseapp.core.parser.{Argument, ConsParser, NilParser, StandardArgument}
import caseapp.core.util.Formatter
import caseapp.core.{Arg, Error}
import com.github.plokhotnyuk.jsoniter_scala.core.*
import com.github.plokhotnyuk.jsoniter_scala.macros.*

import scala.cli.commands.tags

// format: off
final case class ScalacOptions(
  @Recurse
  argsFiles: List[ArgFileOption] = Nil,
  @Group(HelpGroup.Scala.toString)
  @HelpMessage("Add a scalac option")
  @ValueDescription("option")
  @Name("O")
  @Name("scala-opt")
  @Name("scala-option")
  @Tag(tags.must)
    scalacOption: List[String] = Nil,
)
// format: on

object ScalacOptions {

  private val scalacOptionsArg = Arg("scalacOption").copy(
    extraNames = Seq(Name("scala-opt"), Name("O"), Name("scala-option")),
    valueDescription = Some(ValueDescription("option")),
    helpMessage = Some(HelpMessage(
      "Add a `scalac` option. Note that options starting with `-g`, `-language`, `-opt`, `-P`, `-target`, `-V`, `-W`, `-X`, and `-Y` are assumed to be Scala compiler options and don't require to be passed after `-O` or `--scalac-option`."
    )),
    group = Some(Group("Scala")),
    origin = Some("ScalacOptions")
  )
  // .withIsFlag(true) // The scalac options we handle accept no value after the -… argument
  val YScriptRunnerOption = "-Yscriptrunner"
  private val scalacOptionsPurePrefixes =
    Set("-V", "-W", "-X", "-Y")
  private val scalacOptionsPrefixes =
    Set("-g", "-language", "-opt", "-P", "-target", "-source") ++ scalacOptionsPurePrefixes
  private val scalacAliasedOptions = // these options don't require being passed after -O and accept an arg
    Set("-encoding", "-release", "-color", YScriptRunnerOption)
  private val scalacNoArgAliasedOptions = // these options don't require being passed after -O and don't accept an arg
    Set(
      "-unchecked",
      "-nowarn",
      "-feature",
      "-deprecation",
      "-rewrite",
      "-old-syntax",
      "-new-syntax",
      "-indent",
      "-no-indent"
    )

  /** This includes all the scalac options which disregard inputs and print a help and/or context
    * message instead.
    */
  val ScalacPrintOptions: Set[String] =
    scalacOptionsPurePrefixes ++ Set(
      "-help",
      "-opt:help",
      "-Xshow-phases",
      "-Xsource:help",
      "-Xplugin-list",
      "-Xmixin-force-forwarders:help",
      "-Xlint:help",
      "-Vphases"
    )

  /** This includes all the scalac options which are redirected to native Scala CLI options. */
  val ScalaCliRedirectedOptions = Set(
    "-classpath",
    "-cp", // redirected to --extra-jars
    "-d"   // redirected to --compilation-output
  )
  val ScalacDeprecatedOptions: Set[String] = Set(
    YScriptRunnerOption // old 'scala' runner specific, no longer supported
  )

  private val scalacOptionsArgument: Argument[List[String]] =
    new Argument[List[String]] {

      val underlying: StandardArgument[List[String]] = StandardArgument(scalacOptionsArg)

      val arg: Arg = scalacOptionsArg

      def withDefaultOrigin(origin: String): Argument[List[String]] = this
      def init: Option[List[String]]                                = Some(Nil)
      def step(
        args: List[String],
        index: Int,
        acc: Option[List[String]],
        formatter: Formatter[Name]
      ): Either[(Error, List[String]), Option[(Option[List[String]], List[String])]] =
        args match {
          case h :: t
              if scalacOptionsPrefixes.exists(h.startsWith) &&
              !ScalacDeprecatedOptions.contains(h) =>
            Right(Some((Some(h :: acc.getOrElse(Nil)), t)))
          case h :: t if scalacNoArgAliasedOptions.contains(h) =>
            Right(Some((Some(h :: acc.getOrElse(Nil)), t)))
          case h :: t if scalacAliasedOptions.contains(h) =>
            // check if the next scalac arg is a different option or a param to the current option
            val maybeOptionArg = t.headOption.filter(!_.startsWith("-"))
            // if it's a param, it'll be treated as such and considered already parsed
            val newTail = maybeOptionArg.map(_ => t.drop(1)).getOrElse(t)
            val newHead = List(h) ++ maybeOptionArg
            Right(Some((Some(newHead ++ acc.getOrElse(Nil)), newTail)))
          case _ => underlying.step(args, index, acc, formatter)
        }
      def get(acc: Option[List[String]], formatter: Formatter[Name]): Either[Error, List[String]] =
        Right(acc.getOrElse(Nil))
    }

  implicit lazy val parser: Parser[ScalacOptions] = {
    val baseParser = scalacOptionsArgument :: NilParser
    implicit val p = ArgFileOption.parser
    baseParser.addAll[List[ArgFileOption]].to[ScalacOptions]
  }

  implicit lazy val help: Help[ScalacOptions]                = Help.derive
  implicit lazy val jsonCodec: JsonValueCodec[ScalacOptions] = JsonCodecMaker.make
}

case class ArgFileOption(file: String) extends AnyVal

object ArgFileOption {
  val arg = Arg(
    name = Name("args-file"),
    valueDescription = Some(ValueDescription("@arguments-file")),
    helpMessage = Some(HelpMessage("File with scalac options.")),
    group = Some(Group("Scala")),
    origin = Some("ScalacOptions")
  )
  implicit lazy val parser: Parser[List[ArgFileOption]] = new Parser[List[ArgFileOption]] {
    type D = List[ArgFileOption] *: EmptyTuple

    override def withDefaultOrigin(origin: String): Parser[List[ArgFileOption]] = this

    override def init: D = Nil *: EmptyTuple

    override def step(args: List[String], index: Int, d: D, nameFormatter: Formatter[Name])
      : Either[(core.Error, Arg, List[String]), Option[(D, Arg, List[String])]] =
      args match
        case head :: rest if head.startsWith("@") =>
          val newD = (ArgFileOption(head.stripPrefix("@")) :: d._1) *: EmptyTuple
          Right(Some(newD, arg, rest))
        case _ => Right(None)

    override def get(
      d: D,
      nameFormatter: Formatter[Name]
    ): Either[core.Error, List[ArgFileOption]] = Right(d.head)

    override def args: Seq[Arg] = Seq(arg)

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy