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

scala.build.preprocessing.ScalaPreprocessor.scala Maven / Gradle / Ivy

package scala.build.preprocessing

import dependency.AnyDependency
import dependency.parser.DependencyParser

import java.nio.charset.StandardCharsets

import scala.build.EitherCps.{either, value}
import scala.build.Ops.*
import scala.build.directives.{
  HasBuildOptions,
  HasBuildOptionsWithRequirements,
  HasBuildRequirements
}
import scala.build.errors.*
import scala.build.input.{Inputs, ScalaCliInvokeData, ScalaFile, SingleElement, VirtualScalaFile}
import scala.build.internal.Util
import scala.build.options.*
import scala.build.preprocessing.directives.{
  DirectiveHandler,
  DirectiveUtil,
  PreprocessedDirectives,
  ScopedDirective
}
import scala.build.preprocessing.{DeprecatedDirectives, directives}
import scala.build.{Logger, Position, Positioned}

case object ScalaPreprocessor extends Preprocessor {
  case class ProcessingOutput(
    globalReqs: BuildRequirements,
    scopedReqs: Seq[Scoped[BuildRequirements]],
    opts: BuildOptions,
    optsWithReqs: List[WithBuildRequirements[BuildOptions]],
    updatedContent: Option[String],
    directivesPositions: Option[Position.File]
  )

  object ProcessingOutput {
    def empty: ProcessingOutput = ProcessingOutput(
      BuildRequirements(),
      Nil,
      BuildOptions(),
      List.empty,
      None,
      None
    )
  }

  def preprocess(
    input: SingleElement,
    logger: Logger,
    maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e),
    allowRestrictedFeatures: Boolean,
    suppressWarningOptions: SuppressWarningOptions
  )(using ScalaCliInvokeData): Option[Either[BuildException, Seq[PreprocessedSource]]] =
    input match {
      case f: ScalaFile =>
        val res = either {
          val content   = value(PreprocessingUtil.maybeRead(f.path))
          val scopePath = ScopePath.fromPath(f.path)
          val source =
            value(
              process(
                content,
                Right(f.path),
                scopePath / os.up,
                logger,
                maybeRecoverOnError,
                allowRestrictedFeatures,
                suppressWarningOptions
              )
            ) match {
              case None =>
                PreprocessedSource.OnDisk(f.path, None, List.empty, None, Nil, None, None)
              case Some(ProcessingOutput(
                    requirements,
                    scopedRequirements,
                    options,
                    optionsWithReqs,
                    Some(updatedCode),
                    directivesPositions
                  )) =>
                PreprocessedSource.InMemory(
                  originalPath = Right((f.subPath, f.path)),
                  relPath = f.subPath,
                  content = updatedCode.getBytes(StandardCharsets.UTF_8),
                  wrapperParamsOpt = None,
                  options = Some(options),
                  optionsWithTargetRequirements = optionsWithReqs,
                  requirements = Some(requirements),
                  scopedRequirements = scopedRequirements,
                  mainClassOpt = None,
                  scopePath = scopePath,
                  directivesPositions = directivesPositions
                )
              case Some(ProcessingOutput(
                    requirements,
                    scopedRequirements,
                    options,
                    optionsWithReqs,
                    None,
                    directivesPositions
                  )) =>
                PreprocessedSource.OnDisk(
                  path = f.path,
                  options = Some(options),
                  optionsWithTargetRequirements = optionsWithReqs,
                  requirements = Some(requirements),
                  scopedRequirements = scopedRequirements,
                  mainClassOpt = None,
                  directivesPositions = directivesPositions
                )
            }
          Seq(source)
        }
        Some(res)

      case v: VirtualScalaFile =>
        val res = either {
          val relPath = v match {
            case v if !v.isStdin && !v.isSnippet => v.subPath
            case v                               => os.sub / v.generatedSourceFileName
          }
          val content = new String(v.content, StandardCharsets.UTF_8)
          val (
            requirements: BuildRequirements,
            scopedRequirements: Seq[Scoped[BuildRequirements]],
            options: BuildOptions,
            optionsWithTargetRequirements: List[WithBuildRequirements[BuildOptions]],
            updatedContentOpt: Option[String],
            directivesPositions: Option[Position.File]
          ) =
            value(
              process(
                content,
                Left(v.source),
                v.scopePath / os.up,
                logger,
                maybeRecoverOnError,
                allowRestrictedFeatures,
                suppressWarningOptions
              )
            ).map {
              case ProcessingOutput(
                    reqs,
                    scopedReqs,
                    opts,
                    optsWithReqs,
                    updatedContent,
                    dirsPositions
                  ) =>
                (reqs, scopedReqs, opts, optsWithReqs, updatedContent, dirsPositions)
            }.getOrElse((
              BuildRequirements(),
              Nil,
              BuildOptions(),
              List(WithBuildRequirements(BuildRequirements(), BuildOptions())),
              None,
              None
            ))
          val s = PreprocessedSource.InMemory(
            originalPath = Left(v.source),
            relPath = relPath,
            updatedContentOpt.map(_.getBytes(StandardCharsets.UTF_8)).getOrElse(v.content),
            wrapperParamsOpt = None,
            options = Some(options),
            optionsWithTargetRequirements = optionsWithTargetRequirements,
            requirements = Some(requirements),
            scopedRequirements,
            mainClassOpt = None,
            scopePath = v.scopePath,
            directivesPositions = directivesPositions
          )
          Seq(s)
        }
        Some(res)

      case _ =>
        None
    }

  def process(
    content: String,
    path: Either[String, os.Path],
    scopeRoot: ScopePath,
    logger: Logger,
    maybeRecoverOnError: BuildException => Option[BuildException],
    allowRestrictedFeatures: Boolean,
    suppressWarningOptions: SuppressWarningOptions
  )(using ScalaCliInvokeData): Either[BuildException, Option[ProcessingOutput]] = either {
    val (contentWithNoShebang, _) = SheBang.ignoreSheBangLines(content)
    val extractedDirectives: ExtractedDirectives = value(ExtractedDirectives.from(
      contentWithNoShebang.toCharArray,
      path,
      logger,
      maybeRecoverOnError
    ))
    value {
      processSources(
        content,
        extractedDirectives,
        path,
        scopeRoot,
        logger,
        allowRestrictedFeatures,
        suppressWarningOptions,
        maybeRecoverOnError
      )
    }
  }

  def processSources(
    content: String,
    extractedDirectives: ExtractedDirectives,
    path: Either[String, os.Path],
    scopeRoot: ScopePath,
    logger: Logger,
    allowRestrictedFeatures: Boolean,
    suppressWarningOptions: SuppressWarningOptions,
    maybeRecoverOnError: BuildException => Option[BuildException]
  )(using ScalaCliInvokeData): Either[BuildException, Option[ProcessingOutput]] = either {
    DeprecatedDirectives.issueWarnings(path, extractedDirectives.directives, logger)

    val (content0, isSheBang) = SheBang.ignoreSheBangLines(content)
    val preprocessedDirectives: PreprocessedDirectives =
      value(DirectivesPreprocessor(
        path,
        scopeRoot,
        logger,
        allowRestrictedFeatures,
        suppressWarningOptions,
        maybeRecoverOnError
      ).preprocess(
        extractedDirectives
      ))

    if (preprocessedDirectives.isEmpty) None
    else {
      val allRequirements    = Seq(preprocessedDirectives.globalReqs)
      val summedRequirements = allRequirements.foldLeft(BuildRequirements())(_ orElse _)
      val allOptions         = Seq(preprocessedDirectives.globalUsings)
      val summedOptions      = allOptions.foldLeft(BuildOptions())(_ orElse _)
      val lastContentOpt = preprocessedDirectives.strippedContent
        .orElse(if (isSheBang) Some(content0) else None)
      val directivesPositions = preprocessedDirectives.directivesPositions.map { pos =>
        if (isSheBang) pos.copy(endPos = pos.endPos._1 + 1 -> pos.endPos._2) else pos
      }

      val scopedRequirements = preprocessedDirectives.scopedReqs
      Some(ProcessingOutput(
        summedRequirements,
        scopedRequirements,
        summedOptions,
        preprocessedDirectives.usingsWithReqs,
        lastContentOpt,
        directivesPositions
      ))
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy