diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index fe17973ecda8..09980ea96f60 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -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( @@ -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 } diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala index 763821d0de59..46869b7114b3 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetChecker.scala @@ -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._ @@ -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( @@ -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 } diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala index f4fe2f8223fb..4df5922c1690 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilationResult.scala @@ -2,14 +2,11 @@ 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, @@ -17,7 +14,16 @@ case class SnippetCompilationResult( 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") diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala index 00f89b94c626..1e741f421d1b 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala @@ -6,7 +6,7 @@ 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 } @@ -14,8 +14,9 @@ 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 } @@ -49,7 +50,7 @@ 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 @@ -57,9 +58,16 @@ class SnippetCompiler( 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 @@ -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 @@ -88,7 +96,8 @@ class SnippetCompiler( def compile( wrappedSnippet: WrappedSnippet, - arg: SnippetCompilerArg + arg: SnippetCompilerArg, + sourceFile: SourceFile ): SnippetCompilationResult = { val context = SnippetDriver.currentCtx.fresh .setSetting( @@ -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) diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala index 475d38fef895..c1629f8d35cc 100644 --- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala +++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerDataCollector.scala @@ -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 { diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index bcd7dfcb438d..1481f13f15f5 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -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 } } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala index dd87527625d5..634e76e6ef3f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala @@ -125,7 +125,7 @@ object SnippetRenderer: else val content = messages .map { msg => - s"""${msg.getSummary}""" + s"""${msg.message}""" } .mkString("
") s"""
$content""" diff --git a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala index 5bfac840091b..dc0bcefbc1cc 100644 --- a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetCompilerTest.scala @@ -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)) @@ -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) ) } diff --git a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala index 46cb90152ba9..cf0c52ee2a50 100644 --- a/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/snippets/SnippetsE2eTest.scala @@ -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 => @@ -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")) } }