aboutsummaryrefslogblamecommitdiff
path: root/src/dotty/tools/dotc/reporting/MessageRendering.scala
blob: 6d9e45a6e9122e77785b761054e9dd3f80f2be97 (plain) (tree)














































































































                                                                                                                  
package dotty.tools
package dotc
package reporting

import core.Contexts.Context
import core.Decorators._
import printing.Highlighting.{Blue, Red}
import diagnostic.{Message, MessageContainer, NoExplanation}
import diagnostic.messages._
import util.SourcePosition

import scala.collection.mutable

trait MessageRendering {
  def stripColor(str: String): String =
    str.replaceAll("\u001B\\[[;\\d]*m", "")

  def sourceLines(pos: SourcePosition)(implicit ctx: Context): (List[String], List[String], Int) = {
    var maxLen = Int.MinValue
    def render(xs: List[Int]) =
      xs.map(pos.source.offsetToLine(_))
        .map { lineNbr =>
          val prefix = s"${lineNbr + 1} |"
          maxLen = math.max(maxLen, prefix.length)
          (prefix, pos.lineContent(lineNbr).stripLineEnd)
        }
        .map { case (prefix, line) =>
          val lnum = Red(" " * math.max(0, maxLen - prefix.length) + prefix)
          hl"$lnum$line"
        }

    val (before, after) = pos.beforeAndAfterPoint
    (render(before), render(after), maxLen)
  }

  def columnMarker(pos: SourcePosition, offset: Int)(implicit ctx: Context): String = {
    val prefix = " " * (offset - 1)
    val whitespace = " " * pos.startColumn
    val carets = Red {
      if (pos.startLine == pos.endLine)
        "^" * math.max(1, pos.endColumn - pos.startColumn)
      else "^"
    }

    s"$prefix|$whitespace${carets.show}"
  }

  def errorMsg(pos: SourcePosition, msg: String, offset: Int)(implicit ctx: Context): String = {
    val leastWhitespace = msg.lines.foldLeft(Int.MaxValue) { (minPad, line) =>
      val lineLength = stripColor(line).length
      val padding =
        math.min(math.max(0, ctx.settings.pageWidth.value - offset - lineLength), offset + pos.startColumn)

      if (padding < minPad) padding
      else minPad
    }

    msg.lines
      .map { line => " " * (offset - 1) + "|" + (" " * (leastWhitespace - offset)) + line }
      .mkString(sys.props("line.separator"))
  }

  def posStr(pos: SourcePosition, diagnosticLevel: String, message: Message)(implicit ctx: Context): String =
    if (pos.exists) Blue({
      val file = pos.source.file.toString
      val errId =
        if (message.errorId != NoExplanation.ID)
          s"[E${"0" * (3 - message.errorId.toString.length) + message.errorId}] "
        else ""
      val kind =
        if (message.kind == "") diagnosticLevel
        else s"${message.kind} $diagnosticLevel"
      val prefix = s"-- ${errId}${kind}: $file "

      prefix +
        ("-" * math.max(ctx.settings.pageWidth.value - stripColor(prefix).length, 0))
    }).show else ""

  def explanation(m: Message)(implicit ctx: Context): String = {
    val sb = new StringBuilder(hl"""|
                                    |${Blue("Explanation")}
                                    |${Blue("===========")}""".stripMargin)
    sb.append('\n').append(m.explanation)
    if (m.explanation.lastOption != Some('\n')) sb.append('\n')
    sb.toString
  }

  def messageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(implicit ctx: Context): String = {
    val sb = mutable.StringBuilder.newBuilder
    sb.append(posStr(pos, diagnosticLevel, msg)).append('\n')
    if (pos.exists) {
      val (srcBefore, srcAfter, offset) = sourceLines(pos)
      val marker = columnMarker(pos, offset)
      val err = errorMsg(pos, msg.msg, offset)
      sb.append((srcBefore ::: marker :: err :: srcAfter).mkString("\n"))
    } else sb.append(msg.msg)
    sb.toString
  }

  def diagnosticLevel(cont: MessageContainer): String = {
    cont match {
      case m: Error => "Error"
      case m: FeatureWarning => "Feature Warning"
      case m: DeprecationWarning => "Deprecation Warning"
      case m: UncheckedWarning => "Unchecked Warning"
      case m: MigrationWarning => "Migration Warning"
      case m: Warning => "Warning"
      case m: Info => "Info"
    }
  }
}