Skip to content

Scaladoc: Refactor snippet compiler reporting #13351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions scaladoc/src/dotty/tools/scaladoc/site/templates.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ case class TemplateFile(
lazy val snippetCheckingFunc: SnippetChecker.SnippetCheckingFunc =
val path = Some(Paths.get(file.getAbsolutePath))
val pathBasedArg = ssctx.snippetCompilerArgs.get(path)
val sourceFile = dotty.tools.dotc.util.SourceFile(dotty.tools.io.AbstractFile.getFile(path.get), scala.io.Codec.UTF8)
(str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => {
val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_))
val compilerData = SnippetCompilerData(
Expand All @@ -76,10 +77,9 @@ case class TemplateFile(
Nil,
SnippetCompilerData.Position(configOffset - 1, 0)
)
ssctx.snippetChecker.checkSnippet(str, Some(compilerData), arg, lineOffset).collect {
ssctx.snippetChecker.checkSnippet(str, Some(compilerData), arg, lineOffset, sourceFile).collect {
case r: SnippetCompilationResult if !r.isSuccessful =>
val msg = s"In static site (${file.getAbsolutePath}):\n${r.getSummary}"
report.error(msg)(using ssctx.outerCtx)
r.reportMessages()(using ssctx.outerCtx)
r
case r => r
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dotty.tools.scaladoc.DocContext
import java.nio.file.Paths
import java.io.File

import dotty.tools.dotc.util.SourceFile
import dotty.tools.io.AbstractFile
import dotty.tools.dotc.fromtasty.TastyFileUtil
import dotty.tools.dotc.config.Settings._
Expand Down Expand Up @@ -39,7 +40,8 @@ class SnippetChecker(val args: Scaladoc.Args)(using cctx: CompilerContext):
snippet: String,
data: Option[SnippetCompilerData],
arg: SnippetCompilerArg,
lineOffset: SnippetChecker.LineOffset
lineOffset: SnippetChecker.LineOffset,
sourceFile: SourceFile
): Option[SnippetCompilationResult] = {
if arg.flag != SCFlags.NoCompile then
val wrapped = WrappedSnippet(
Expand All @@ -50,7 +52,7 @@ class SnippetChecker(val args: Scaladoc.Args)(using cctx: CompilerContext):
lineOffset + data.fold(0)(_.position.line) + constantLineOffset,
data.fold(0)(_.position.column) + constantColumnOffset
)
val res = compiler.compile(wrapped, arg)
val res = compiler.compile(wrapped, arg, sourceFile)
Some(res)
else None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@ package dotty.tools.scaladoc
package snippets

import dotty.tools.io.{ AbstractFile }
import dotty.tools.dotc.util.{ SourcePosition, SrcPos }

case class Position(line: Int, column: Int, sourceLine: String, relativeLine: Int)
case class Position(srcPos: SourcePosition, relativeLine: Int)

case class SnippetCompilerMessage(position: Option[Position], message: String, level: MessageLevel):
def getSummary: String =
position.fold(s"${level.text}: ${message}") { pos =>
s"At ${pos.line}:${pos.column}:\n${pos.sourceLine}${level.text}: ${message}"
}
case class SnippetCompilerMessage(position: Option[Position], message: String, level: MessageLevel)

case class SnippetCompilationResult(
wrappedSnippet: WrappedSnippet,
isSuccessful: Boolean,
result: Option[AbstractFile],
messages: Seq[SnippetCompilerMessage]
):
def getSummary: String = messages.map(_.getSummary).mkString("\n")
def reportMessages()(using CompilerContext) = messages.foreach {
case SnippetCompilerMessage(posOpt, msg, level) =>
val pos: SrcPos = posOpt.fold(dotty.tools.dotc.util.NoSourcePosition)(_.srcPos)
level match {
case MessageLevel.Info => report.log(msg, pos)
case MessageLevel.Warning => report.warning(msg, pos)
case MessageLevel.Error => report.error(msg, pos)
case MessageLevel.Debug => report.log(msg, pos)
}
}

enum MessageLevel(val text: String):
case Info extends MessageLevel("Info")
Expand Down
27 changes: 18 additions & 9 deletions scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ import dotty.tools.dotc.Driver
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Mode
import dotty.tools.dotc.config.Settings.Setting._
import dotty.tools.dotc.interfaces.SourcePosition
import dotty.tools.dotc.interfaces.{ SourcePosition => ISourcePosition }
import dotty.tools.dotc.ast.Trees.Tree
import dotty.tools.dotc.interfaces.{SourceFile => ISourceFile}
import dotty.tools.dotc.reporting.{ Diagnostic, StoreReporter }
import dotty.tools.dotc.parsing.Parsers.Parser
import dotty.tools.dotc.{ Compiler, Run }
import dotty.tools.io.{AbstractFile, VirtualDirectory}
import dotty.tools.repl.AbstractFileClassLoader
import dotty.tools.dotc.util.SourceFile
import dotty.tools.dotc.util.Spans._
import dotty.tools.dotc.interfaces.Diagnostic._
import dotty.tools.dotc.util.{ SourcePosition, NoSourcePosition, SourceFile, NoSource }

import scala.util.{ Try, Success, Failure }

Expand Down Expand Up @@ -49,17 +50,24 @@ class SnippetCompiler(
private def nullableMessage(msgOrNull: String): String =
if (msgOrNull == null) "" else msgOrNull

private def createReportMessage(wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg, diagnostics: Seq[Diagnostic]): Seq[SnippetCompilerMessage] = {
private def createReportMessage(wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg, diagnostics: Seq[Diagnostic], sourceFile: SourceFile): Seq[SnippetCompilerMessage] = {
val line = wrappedSnippet.outerLineOffset
val column = wrappedSnippet.outerColumnOffset
val innerLineOffset = wrappedSnippet.innerLineOffset
val innerColumnOffset = wrappedSnippet.innerColumnOffset
val infos = diagnostics.toSeq.sortBy(_.pos.source.path)
val errorMessages = infos.map {
case diagnostic if diagnostic.position.isPresent =>
val diagPos = diagnostic.position.get
val diagPos = diagnostic.position.get match
case s: SourcePosition => s
case _ => NoSourcePosition
val offsetFromLine = sourceFile match
case NoSource => 0
case sf: SourceFile => sf.lineToOffset(diagPos.line + line - innerLineOffset - 1)
val offsetFromColumn = diagPos.column + column - innerColumnOffset
val span = Span(offsetFromLine + offsetFromColumn, offsetFromLine + offsetFromColumn)
val pos = Some(
Position(diagPos.line + line - innerLineOffset, diagPos.column + column - innerColumnOffset, diagPos.lineContent, diagPos.line)
Position(dotty.tools.dotc.util.SourcePosition(sourceFile, span), diagPos.line)
)
val dmsg = Try(diagnostic.message) match {
case Success(msg) => msg
Expand All @@ -75,7 +83,7 @@ class SnippetCompiler(
errorMessages
}

private def additionalMessages(wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg, context: Context): Seq[SnippetCompilerMessage] = {
private def additionalMessages(wrappedSnippet: WrappedSnippet, arg: SnippetCompilerArg, sourceFile: SourceFile, context: Context): Seq[SnippetCompilerMessage] = {
Option.when(arg.flag == SCFlags.Fail && !context.reporter.hasErrors)(
SnippetCompilerMessage(None, "Snippet should not compile but compiled succesfully", MessageLevel.Error)
).toList
Expand All @@ -88,7 +96,8 @@ class SnippetCompiler(

def compile(
wrappedSnippet: WrappedSnippet,
arg: SnippetCompilerArg
arg: SnippetCompilerArg,
sourceFile: SourceFile
): SnippetCompilationResult = {
val context = SnippetDriver.currentCtx.fresh
.setSetting(
Expand All @@ -100,8 +109,8 @@ class SnippetCompiler(
run.compileFromStrings(List(wrappedSnippet.snippet))

val messages =
createReportMessage(wrappedSnippet, arg, context.reporter.pendingMessages(using context)) ++
additionalMessages(wrappedSnippet, arg, context)
createReportMessage(wrappedSnippet, arg, context.reporter.pendingMessages(using context), sourceFile) ++
additionalMessages(wrappedSnippet, arg, sourceFile, context)

val t = Option.when(!context.reporter.hasErrors)(target)
SnippetCompilationResult(wrappedSnippet, isSuccessful(arg, context), t, messages)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@ package snippets
import scala.quoted._
import dotty.tools.scaladoc.tasty.SymOps._
import dotty.tools.dotc.core._
import dotty.tools.dotc.util.{ SourceFile => CSourceFile, NoSource }

class SnippetCompilerDataCollector[Q <: Quotes](val qctx: Q):
import qctx.reflect._
given qctx.type = qctx

def getSourceFile(sym: Symbol): CSourceFile =
given ctx: Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
sym match
case csym: Symbols.Symbol => csym.source(using ctx)
case _ =>
report.warning(s"Can't cast symbol $sym to compiler symbol. This is a bug of snippet compiler, please create an issue on dotty repository.")
NoSource

def getSnippetCompilerData(sym: Symbol, originalSym: Symbol): SnippetCompilerData =
val packageName = sym.packageName
if !sym.isPackageDef then sym.tree match {
Expand Down
15 changes: 6 additions & 9 deletions scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,14 @@ abstract class MarkupConversion[T](val repr: Repr)(using dctx: DocContext) {
(s: qctx.reflect.Symbol) => {
val path = s.source.map(_.path)
val pathBasedArg = dctx.snippetCompilerArgs.get(path)
val data = SnippetCompilerDataCollector[qctx.type](qctx).getSnippetCompilerData(s, s)
val scDataCollector = SnippetCompilerDataCollector[qctx.type](qctx)
val data = scDataCollector.getSnippetCompilerData(s, s)
val sourceFile = scDataCollector.getSourceFile(s)
(str: String, lineOffset: SnippetChecker.LineOffset, argOverride: Option[SCFlags]) => {
val arg = argOverride.fold(pathBasedArg)(pathBasedArg.overrideFlag(_))

snippetChecker.checkSnippet(str, Some(data), arg, lineOffset).collect {
case r: SnippetCompilationResult if !r.isSuccessful =>
val msg = s"In member ${s.name} (${s.dri.location}):\n${r.getSummary}"
report.error(msg)(using dctx.compilerContext)
r
case r => r
}
val res = snippetChecker.checkSnippet(str, Some(data), arg, lineOffset, sourceFile)
res.filter(r => !r.isSuccessful).foreach(_.reportMessages()(using compilerContext))
res
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ object SnippetRenderer:
else
val content = messages
.map { msg =>
s"""<span class="${compileMessageCSSClass(msg)}">${msg.getSummary}</span>"""
s"""<span class="${compileMessageCSSClass(msg)}">${msg.message}</span>"""
}
.mkString("<br>")
s"""<hr>$content"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ class SnippetCompilerTest {
0
)

def runTest(str: String) = compiler.compile(wrapFn(str), SnippetCompilerArg(SCFlags.Compile))
def runTest(str: String) = compiler.compile(wrapFn(str), SnippetCompilerArg(SCFlags.Compile), dotty.tools.dotc.util.SourceFile.virtual("test", str))

private def assertSuccessfulCompilation(res: SnippetCompilationResult): Unit = res match {
case r @ SnippetCompilationResult(_, isSuccessful, _, messages) => assert(isSuccessful, r.getSummary)
case r @ SnippetCompilationResult(_, isSuccessful, _, messages) => assert(isSuccessful, r.messages.map(_.message).mkString("\n"))
}

private def assertFailedCompilation(res: SnippetCompilationResult): Unit = res match {
case r @ SnippetCompilationResult(_, isSuccessful, _, messages) => assert(!isSuccessful, r.getSummary)
case r @ SnippetCompilationResult(_, isSuccessful, _, messages) => assert(!isSuccessful, r.messages.map(_.message).mkString("\n"))
}

def assertSuccessfulCompilation(str: String): Unit = assertSuccessfulCompilation(runTest(str))
Expand All @@ -36,7 +36,7 @@ class SnippetCompilerTest {

def assertMessageLevelPresent(res: SnippetCompilationResult, level: MessageLevel): Unit = res match {
case r @ SnippetCompilationResult(_, isSuccessful, _, messages) => assertTrue(
s"Expected message with level: ${level.text}. Got result ${r.getSummary}",
s"Expected message with level: ${level.text}. Got result ${r.messages.map(_.message).mkString("\n")}",
messages.exists(_.level == level)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,19 @@ abstract class SnippetsE2eTest(testName: String, flag: SCFlags) extends Scaladoc
case m @ SnippetCompilerMessage(Some(_), _, _) => m
}.toList
def isSamePosition(msg: Message, cmsg: SnippetCompilerMessage): Boolean =
cmsg.level == msg.level && cmsg.position.get.line == msg.offset.line && cmsg.position.get.column == msg.offset.column
cmsg.level == msg.level && (cmsg.position.get.srcPos.line + 1) == msg.offset.line && cmsg.position.get.srcPos.column == msg.offset.column

def checkRelativeLines(msg: Message, cmsg: SnippetCompilerMessage): Seq[String] =
val pos = cmsg.position.get
if !(pos.relativeLine == pos.line - ws.outerLineOffset + ws.innerLineOffset) then Seq(
s"Expected ${msg.level.text} message at relative line: ${pos.line - ws.outerLineOffset + ws.innerLineOffset} " +
if !(pos.relativeLine == pos.srcPos.line + ws.innerLineOffset - ws.outerLineOffset + 1) then Seq(
s"Expected ${msg.level.text} message at relative line: ${pos.srcPos.line + ws.innerLineOffset - ws.outerLineOffset + 1} " +
s"but found at ${pos.relativeLine}"
) else Nil

val mResult = compilationMessagesWithPos.flatMap { cmsg =>
messages
.find(msg => isSamePosition(msg, cmsg))
.fold(Seq(s"Unexpected compilation message: ${cmsg.message} at relative line: ${cmsg.position.fold(-1)(_.line)}"))(_ => Seq())
.fold(Seq(s"Unexpected compilation message: ${cmsg.message} at relative line: ${cmsg.position.fold(-1)(_.relativeLine)}"))(_ => Seq())
}

val result = mResult ++ messages.flatMap { msg =>
Expand All @@ -103,7 +103,7 @@ abstract class SnippetsE2eTest(testName: String, flag: SCFlags) extends Scaladoc

if !result.isEmpty then {
val errors = result.mkString("\n")
val foundMessages = compilationMessages.map(m => s"${m.level} at ${m.position.get.line}:${m.position.get.column}").mkString("\n")
val foundMessages = compilationMessages.map(m => s"${m.level} at ${m.position.get.srcPos.line}:${m.position.get.srcPos.column}").mkString("\n")
throw AssertionError(Seq("Errors:", errors,"Found:", foundMessages).mkString("\n", "\n", "\n"))
}
}
Expand Down