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

mdoc.internal.markdown.Renderer.scala Maven / Gradle / Ivy

The newest version!
package mdoc.internal.markdown

import mdoc.Reporter
import mdoc.Variable
import mdoc.document.CompileResult
import mdoc.document.CrashResult
import mdoc.document.CrashResult.Crashed
import mdoc.document.RangePosition
import mdoc.internal.cli.Context
import mdoc.internal.cli.InputFile
import mdoc.internal.cli.Settings
import mdoc.internal.document.FailSection
import mdoc.internal.document.MdocExceptions
import mdoc.internal.document.Printing
import mdoc.internal.pos.PositionSyntax._
import mdoc.internal.pos.TokenEditDistance

import java.io.ByteArrayOutputStream
import java.io.PrintStream
import scala.meta._
import scala.meta.inputs.Position

object Renderer {

  def render(
      file: InputFile,
      sections: List[Input],
      compiler: MarkdownCompiler,
      settings: Settings,
      reporter: Reporter,
      filename: String,
      printer: Variable => String,
      context: Context
  ): String = {
    val inputs =
      sections.map(s => SectionInput(s, Modifier.Default(), context))
    val instrumented = Instrumenter.instrument(file, inputs, settings, reporter)
    val doc =
      MarkdownBuilder.buildDocument(
        compiler,
        reporter,
        inputs,
        instrumented,
        filename
      )
    doc.sections
      .map(s => s"""```scala
                   |${Renderer.renderEvaluatedSection(doc, s, reporter, printer, compiler)}
                   |```""".stripMargin)
      .mkString("\n")
  }

  def renderCrashSection(
      section: EvaluatedSection,
      reporter: Reporter,
      edit: TokenEditDistance
  ): String = {
    require(section.mod.isCrash, section.mod)
    val out = new ByteArrayOutputStream()
    val ps = new PrintStream(out)
    ps.println("```scala")
    ps.println(section.source.pos.text)
    val crashes = for {
      statement <- section.section.statements
      binder <- statement.binders
      if binder.value.isInstanceOf[Crashed]
    } yield binder.value.asInstanceOf[Crashed]
    crashes.headOption match {
      case Some(CrashResult.Crashed(e, _)) =>
        MdocExceptions.trimStacktrace(e)
        val stacktrace = new ByteArrayOutputStream()
        e.printStackTrace(new PrintStream(stacktrace))
        appendFreshMultiline(ps, stacktrace.toString())
        ps.append('\n')
      case None =>
        val mpos = section.source.pos.toUnslicedPosition
        reporter.error(mpos, "Expected runtime exception but program completed successfully")
    }
    ps.println("```")
    out.toString()
  }

  @deprecated("this method will be removed", "2020-06-01")
  def appendMultiline(sb: PrintStream, string: String, N: Int): Unit = {
    sb.appendMultiline(string, N)
  }

  def appendFreshMultiline(sb: PrintStream, string: String): Unit = {
    val N = string.length - (if (string.endsWith("\n")) 1 else 0)
    sb.append("// ")
    sb.appendMultiline(string, N)
  }

  // Beneath each binding statement, we insert the evaluated variable, e.g., `x: Int = 1`
  def renderEvaluatedSection(
      doc: EvaluatedDocument,
      section: EvaluatedSection,
      reporter: Reporter,
      printer: Variable => String,
      compiler: MarkdownCompiler
  ): String = {
    val baos = new ByteArrayOutputStream()
    val sb = new PrintStream(baos)
    val stats = section.source.stats.lift
    val input = section.source.pos.input
    val totalStats = section.source.stats.length
    if (section.mod.isFailOrWarn) {
      sb.print(section.input.text)
    }
    section.section.statements.zip(section.source.stats).zipWithIndex.foreach {
      case ((statement, tree), statementIndex) =>
        val pos = tree.pos
        // for each statement, we need to manage:
        //   1. the leading trivia: whitespace, newlines
        //   2. a footer: empty until filled on the last statement in the section
        //   3. and, internally, the trailing single-line comments of the previous statement
        val (leading, footer) = stats(statementIndex - 1) match {
          case None =>
            (Position.Range(input, 0, pos.start).text, "")
          case Some(previousStatement) =>
            val Array(prevTrailingSingleLineComment, leadingTrivia) =
              section.source.pos.text.substring(previousStatement.pos.end, pos.start).split("\n", 2)
            val foot =
              if (statementIndex != (totalStats - 1)) ""
              else
                section.source.pos.text
                  .substring(pos.end)
                  .split("\n")
                  .drop(1)
                  .mkString("\n", "\n", "")
            ("\n" + leadingTrivia, foot)
        }
        if (!section.mod.isFailOrWarn) {
          sb.append(leading)
        }
        val endOfLinePosition =
          Position.Range(pos.input, pos.startLine, pos.startColumn, pos.endLine, Int.MaxValue)
        if (!section.mod.isFailOrWarn) {
          sb.append(endOfLinePosition.text)
        }
        if (statement.out.nonEmpty) {
          sb.append("\n")
          appendFreshMultiline(sb, statement.out)
        }
        val N = statement.binders.length
        statement.binders.zipWithIndex.foreach { case (binder, i) =>
          section.mod match {
            case Modifier.Fail() | Modifier.Warn() =>
              sb.append('\n')
              binder.value match {
                case FailSection(instrumented, startLine, startColumn, endLine, endColumn) =>
                  val input = Input.String(instrumented)
                  val edit =
                    TokenEditDistance.fromTrees(Seq(section.source.source), input)
                  val compiled = compiler.fail(edit, input, section.source.pos)
                  val tpos = new RangePosition(startLine, startColumn, endLine, endColumn)
                  val pos = tpos.toMeta(section)
                  if (section.mod.isWarn && compiler.hasErrors) {
                    reporter.error(
                      pos,
                      s"Expected compile warnings but program failed to compile"
                    )
                  } else if (section.mod.isWarn && !compiler.hasWarnings) {
                    reporter.error(
                      pos,
                      s"Expected compile warnings but program compiled successfully without warnings"
                    )
                  } else if (section.mod.isFail && !compiler.hasErrors) {
                    reporter.error(
                      pos,
                      s"Expected compile errors but program compiled successfully without errors"
                    )
                  }
                  appendFreshMultiline(sb, compiled)
                case _ =>
                  val obtained = binder.stringValue
                  throw new IllegalArgumentException(
                    s"Expected FailSection. Obtained $obtained"
                  )
              }
            case _ =>
              val pos = binder.pos.toMeta(section)
              val variable = new mdoc.Variable(
                binder.name,
                Printing.typeString(binder.tpe),
                binder.value,
                pos,
                i,
                N,
                statementIndex,
                totalStats,
                section.mod
              )
              sb.append(printer(variable))
          }
        }
        sb.append(footer)
    }
    baos.toString.trim
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy