diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 0199e2474354..902540d2394d 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -44,7 +44,13 @@ object Comments { * @param expanded If this comment has been expanded, it's expansion, otherwise `None`. * @param usecases The usecases for this comment. */ - final case class Comment(span: Span, raw: String, expanded: Option[String], usecases: List[UseCase]) { + final case class Comment( + span: Span, + raw: String, + expanded: Option[String], + usecases: List[UseCase], + variables: Map[String, String], + ) { /** Has this comment been cooked or expanded? */ def isExpanded: Boolean = expanded.isDefined @@ -65,7 +71,7 @@ object Comments { def expand(f: String => String)(using Context): Comment = { val expandedComment = f(raw) val useCases = Comment.parseUsecases(expandedComment, span) - Comment(span, raw, Some(expandedComment), useCases) + Comment(span, raw, Some(expandedComment), useCases, Map.empty) } } @@ -74,7 +80,7 @@ object Comments { def isDocComment(comment: String): Boolean = comment.startsWith("/**") def apply(span: Span, raw: String): Comment = - Comment(span, raw, None, Nil) + Comment(span, raw, None, Nil, Map.empty) private def parseUsecases(expandedComment: String, span: Span)(using Context): List[UseCase] = if (!isDocComment(expandedComment)) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 4e769c037ba1..97878c199af4 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -190,11 +190,7 @@ object Scanners { private def addComment(comment: Comment): Unit = { val lookahead = lookaheadReader() def nextPos: Int = (lookahead.getc(): @switch) match { - case ' ' | '\t' => nextPos - case CR | LF | FF => - // if we encounter line delimiting whitespace we don't count it, since - // it seems not to affect positions in source - nextPos - 1 + case ' ' | '\t' | CR | LF | FF => nextPos case _ => lookahead.charOffset - 1 } docstringMap = docstringMap + (nextPos -> comment) @@ -854,6 +850,9 @@ object Scanners { if (comment.isDocComment) addComment(comment) + else + // "forward" doc comments over normal ones + getDocComment(start).foreach(addComment) } true diff --git a/compiler/test/dotty/tools/dotc/parsing/DocstringTests.scala b/compiler/test/dotty/tools/dotc/parsing/DocstringTests.scala index 4dfbba836ad9..fcd02954d9e0 100644 --- a/compiler/test/dotty/tools/dotc/parsing/DocstringTests.scala +++ b/compiler/test/dotty/tools/dotc/parsing/DocstringTests.scala @@ -474,6 +474,37 @@ class DocstringTests extends DocstringTest { } } + + @Test def overNL = { + val source = + """ + |/** Class1 */ + | + |class Class1 + """.stripMargin + + import dotty.tools.dotc.ast.untpd._ + checkFrontend(source) { + case p @ PackageDef(_, Seq(c: TypeDef)) => + checkDocString(c.rawComment.map(_.raw), "/** Class1 */") + } + } + + @Test def overComment = { + val source = + """ + |/** Class1 */ + |// foo + |class Class1 + """.stripMargin + + import dotty.tools.dotc.ast.untpd._ + checkFrontend(source) { + case p @ PackageDef(_, Seq(c: TypeDef)) => + checkDocString(c.rawComment.map(_.raw), "/** Class1 */") + } + } + @Test def withAnnotation = { val source = """ @@ -489,6 +520,22 @@ class DocstringTests extends DocstringTest { } } + @Test def withAnnotationOverComment = { + val source = + """ + |/** Class1 */ + |// foo + |@SerialVersionUID(1) + |class Class1 + """.stripMargin + + import dotty.tools.dotc.ast.untpd._ + checkFrontend(source) { + case p @ PackageDef(_, Seq(c: TypeDef)) => + checkDocString(c.rawComment.map(_.raw), "/** Class1 */") + } + } + @Test def nestedComment = { val source = """ diff --git a/scala3doc-testcases/src/tests/tests.scala b/scala3doc-testcases/src/tests/tests.scala index b5bf87fbe15a..4f5721cdbf6f 100644 --- a/scala3doc-testcases/src/tests/tests.scala +++ b/scala3doc-testcases/src/tests/tests.scala @@ -10,6 +10,8 @@ package tests * This is an *important* _test_ class. * Important enough to get multiple sentences in its summary. * + * Here is foo: $foo + * * And `this` is inline code. * * And this is the **strong** __emphasis__ test. @@ -56,10 +58,12 @@ package tests * @version 1.0.0 * @result A class doesn't actually have a result. * @constructor A class has a constructor, and this one is important. + * + * @define foo Foo expanded. */ class A { - /** This is a method. + /** This is my method. * * This is a link: [[AA]]. * @@ -67,6 +71,9 @@ class A { * * And yet another: [[B]]. */ + final def myMethod(s: String): String = s + + /** This is foo: $foo */ def method(s: String): String = s class AA @@ -85,6 +92,8 @@ object A * * This is an ''important'' '''test''' __class__. And `this` is inline code. * + * Here is foo: $foo + * * While * {{{ * this.is("a code block") @@ -104,9 +113,12 @@ object A * * And this is his companion: [[tests.A$]]. * @syntax wiki + * @define foo Bar, actually. */ class B extends A { - /** This is a method. */ + /** @inheritdoc */ override def method(s: String): String = s + + /** This is my foo: $foo */ def otherMethod(s: String): String = s class BB @@ -127,7 +139,8 @@ object B { val Z: Int = 0 } -class C { +/** This is foo: $foo */ +class C extends A { object CC class CC } @@ -192,3 +205,10 @@ class Methods: def primitives(a: Int, b: Double, c: Short): Byte = 0 def strings(a: String): String = "" def arrays(a: Array[String], b: Array[Int]): Array[Double] = ??? + +/** @define foo O's foo. + */ +object O: + + /** This is foo: $foo */ + def method(s: String) = s diff --git a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala index e3327830d39b..773ee6e61e9c 100644 --- a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala @@ -12,11 +12,24 @@ trait ScaladocSupport { self: TastyParser => import qctx.reflect._ def parseComment( - commentNode: Documentation, + commentPre: Documentation, tree: Tree ): dkkd.DocumentationNode = { + val commentNode = + if tree.symbol.isClassDef || tree.symbol.owner.isClassDef then + import dotty.tools.dotc + given ctx as dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + + val sym = tree.symbol.asInstanceOf[dotc.core.Symbols.Symbol] + + comments.CommentExpander.cookComment(sym)(using ctx) + .get.asInstanceOf[Documentation] + else + commentPre + + val commentString = commentNode.expanded getOrElse commentNode.raw val preparsed = - comments.Preparser.preparse(comments.Cleaner.clean(commentNode.raw)) + comments.Preparser.preparse(comments.Cleaner.clean(commentString)) val commentSyntax = preparsed.syntax.headOption match { diff --git a/scala3doc/src/dotty/dokka/tasty/comments/CommentExpander.scala b/scala3doc/src/dotty/dokka/tasty/comments/CommentExpander.scala new file mode 100644 index 000000000000..72721741276d --- /dev/null +++ b/scala3doc/src/dotty/dokka/tasty/comments/CommentExpander.scala @@ -0,0 +1,391 @@ +package dotty.dokka.tasty.comments + +import dotty.tools._ +import dotc._ +import core._ + +import ast.{ untpd, tpd } +import Decorators._, Symbols._, Contexts._, Comments.{_, given} +import util.{SourceFile, ReadOnlyMap} +import util.Spans._ +import util.CommentParsing._ +import util.Property.Key +import parsing.Parsers.Parser +import reporting.ProperDefinitionNotFound + +/** Port of DocComment.scala from nsc + * @author Martin Odersky + * @author Felix Mulder + * @author Aleksander Boruch-Gruszecki + */ +class CommentExpander { + import scala.collection.mutable + + def expand(sym: Symbol, site: Symbol)(using Context): String = { + val parent = if (site != NoSymbol) site else sym + defineVariables(parent) + expandedDocComment(sym, parent) + } + + /** The cooked doc comment of symbol `sym` after variable expansion, or "" if missing. + * + * @param sym The symbol for which doc comment is returned + * @param site The class for which doc comments are generated + * @throws ExpansionLimitExceeded when more than 10 successive expansions + * of the same string are done, which is + * interpreted as a recursive variable definition. + */ + def expandedDocComment(sym: Symbol, site: Symbol, docStr: String = "")(using Context): String = { + // when parsing a top level class or module, use the (module-)class itself to look up variable definitions + val parent = if ((sym.is(Flags.Module) || sym.isClass) && site.is(Flags.Package)) sym + else site + expandVariables(cookedDocComment(sym, docStr), sym, parent) + } + + private def template(raw: String): String = + removeSections(raw, "@define") + + private def defines(raw: String): List[String] = { + val sections = tagIndex(raw) + val defines = sections filter { startsWithTag(raw, _, "@define") } + val usecases = sections filter { startsWithTag(raw, _, "@usecase") } + val end = startTag(raw, (defines ::: usecases).sortBy(_._1)) + + defines map { case (start, end) => raw.substring(start, end) } + } + + private def replaceInheritDocToInheritdoc(docStr: String): String = + docStr.replaceAll("""\{@inheritDoc\p{Zs}*\}""", "@inheritdoc") + + /** The cooked doc comment of an overridden symbol */ + protected def superComment(sym: Symbol)(using Context): Option[String] = + allInheritedOverriddenSymbols(sym).iterator map (x => cookedDocComment(x)) find (_ != "") + + private val cookedDocComments = MutableSymbolMap[String]() + + /** The raw doc comment of symbol `sym`, minus usecase and define sections, augmented by + * missing sections of an inherited doc comment. + * If a symbol does not have a doc comment but some overridden version of it does, + * the doc comment of the overridden version is copied instead. + */ + def cookedDocComment(sym: Symbol, docStr: String = "")(using Context): String = cookedDocComments.getOrElseUpdate(sym, { + var ownComment = + if (docStr.length == 0) ctx.docCtx.flatMap(_.docstring(sym).map(c => template(c.raw))).getOrElse("") + else template(docStr) + ownComment = replaceInheritDocToInheritdoc(ownComment) + + superComment(sym) match { + case None => + // SI-8210 - The warning would be false negative when this symbol is a setter + if (ownComment.indexOf("@inheritdoc") != -1 && ! sym.isSetter) + println(s"${sym.span}: the comment for ${sym} contains @inheritdoc, but no parent comment is available to inherit from.") + ownComment.replace("@inheritdoc", "") + case Some(sc) => + if (ownComment == "") sc + else expandInheritdoc(sc, merge(sc, ownComment, sym), sym) + } + }) + + private def isMovable(str: String, sec: (Int, Int)): Boolean = + startsWithTag(str, sec, "@param") || + startsWithTag(str, sec, "@tparam") || + startsWithTag(str, sec, "@return") + + def merge(src: String, dst: String, sym: Symbol, copyFirstPara: Boolean = false): String = { + val srcSections = tagIndex(src) + val dstSections = tagIndex(dst) + val srcParams = paramDocs(src, "@param", srcSections) + val dstParams = paramDocs(dst, "@param", dstSections) + val srcTParams = paramDocs(src, "@tparam", srcSections) + val dstTParams = paramDocs(dst, "@tparam", dstSections) + val out = new StringBuilder + var copied = 0 + var tocopy = startTag(dst, dstSections dropWhile (!isMovable(dst, _))) + + if (copyFirstPara) { + val eop = // end of comment body (first para), which is delimited by blank line, or tag, or end of comment + (findNext(src, 0)(src.charAt(_) == '\n')) min startTag(src, srcSections) + out append src.substring(0, eop).trim + copied = 3 + tocopy = 3 + } + + def mergeSection(srcSec: Option[(Int, Int)], dstSec: Option[(Int, Int)]) = dstSec match { + case Some((start, end)) => + if (end > tocopy) tocopy = end + case None => + srcSec match { + case Some((start1, end1)) => + out append dst.substring(copied, tocopy).trim + out append "\n" + copied = tocopy + out append src.substring(start1, end1).trim + case None => + } + } + + //TODO: enable this once you know how to get `sym.paramss` + /* + for (params <- sym.paramss; param <- params) + mergeSection(srcParams get param.name.toString, dstParams get param.name.toString) + for (tparam <- sym.typeParams) + mergeSection(srcTParams get tparam.name.toString, dstTParams get tparam.name.toString) + + mergeSection(returnDoc(src, srcSections), returnDoc(dst, dstSections)) + mergeSection(groupDoc(src, srcSections), groupDoc(dst, dstSections)) + */ + + if (out.length == 0) dst + else { + out append dst.substring(copied) + out.toString + } + } + + /** + * Expand inheritdoc tags + * - for the main comment we transform the inheritdoc into the super variable, + * and the variable expansion can expand it further + * - for the param, tparam and throws sections we must replace comments on the spot + * + * This is done separately, for two reasons: + * 1. It takes longer to run compared to merge + * 2. The inheritdoc annotation should not be used very often, as building the comment from pieces severely + * impacts performance + * + * @param parent The source (or parent) comment + * @param child The child (overriding member or usecase) comment + * @param sym The child symbol + * @return The child comment with the inheritdoc sections expanded + */ + def expandInheritdoc(parent: String, child: String, sym: Symbol): String = + if (child.indexOf("@inheritdoc") == -1) + child + else { + val parentSections = tagIndex(parent) + val childSections = tagIndex(child) + val parentTagMap = sectionTagMap(parent, parentSections) + val parentNamedParams = Map() + + ("@param" -> paramDocs(parent, "@param", parentSections)) + + ("@tparam" -> paramDocs(parent, "@tparam", parentSections)) + + ("@throws" -> paramDocs(parent, "@throws", parentSections)) + + val out = new StringBuilder + + def replaceInheritdoc(childSection: String, parentSection: => String) = + if (childSection.indexOf("@inheritdoc") == -1) + childSection + else + childSection.replace("@inheritdoc", parentSection) + + def getParentSection(section: (Int, Int)): String = { + + def getSectionHeader = extractSectionTag(child, section) match { + case param@("@param"|"@tparam"|"@throws") => param + " " + extractSectionParam(child, section) + case other => other + } + + def sectionString(param: String, paramMap: Map[String, (Int, Int)]): String = + paramMap.get(param) match { + case Some(section) => + // Cleanup the section tag and parameter + val sectionTextBounds = extractSectionText(parent, section) + cleanupSectionText(parent.substring(sectionTextBounds._1, sectionTextBounds._2)) + case None => + println(s"""${sym.span}: the """" + getSectionHeader + "\" annotation of the " + sym + + " comment contains @inheritdoc, but the corresponding section in the parent is not defined.") + "" + } + + child.substring(section._1, section._1 + 7) match { + case param@("@param "|"@tparam"|"@throws") => + sectionString(extractSectionParam(child, section), parentNamedParams(param.trim)) + case _ => + sectionString(extractSectionTag(child, section), parentTagMap) + } + } + + def mainComment(str: String, sections: List[(Int, Int)]): String = + if (str.trim.length > 3) + str.trim.substring(3, startTag(str, sections)) + else + "" + + // Append main comment + out.append("/**") + out.append(replaceInheritdoc(mainComment(child, childSections), mainComment(parent, parentSections))) + + // Append sections + for (section <- childSections) + out.append(replaceInheritdoc(child.substring(section._1, section._2), getParentSection(section))) + + out.append("*/") + out.toString + } + + protected def expandVariables(initialStr: String, sym: Symbol, site: Symbol)(using Context): String = { + val expandLimit = 10 + + def expandInternal(str: String, depth: Int): String = { + if (depth >= expandLimit) + throw new ExpansionLimitExceeded(str) + + val out = new StringBuilder + var copied, idx = 0 + // excluding variables written as \$foo so we can use them when + // necessary to document things like Symbol#decode + def isEscaped = idx > 0 && str.charAt(idx - 1) == '\\' + while (idx < str.length) + if ((str charAt idx) != '$' || isEscaped) + idx += 1 + else { + val vstart = idx + idx = skipVariable(str, idx + 1) + def replaceWith(repl: String) = { + out append str.substring(copied, vstart) + out append repl + copied = idx + } + variableName(str.substring(vstart + 1, idx)) match { + case "super" => + superComment(sym) foreach { sc => + val superSections = tagIndex(sc) + replaceWith(sc.substring(3, startTag(sc, superSections))) + for (sec @ (start, end) <- superSections) + if (!isMovable(sc, sec)) out append sc.substring(start, end) + } + case "" => idx += 1 + case vname => + lookupVariable(vname, site) match { + case Some(replacement) => replaceWith(replacement) + case None => ; + println(s"Variable $vname undefined in comment for $sym in $site") + } + } + } + if (out.length == 0) str + else { + out append str.substring(copied) + expandInternal(out.toString, depth + 1) + } + } + + // We suppressed expanding \$ throughout the recursion, and now we + // need to replace \$ with $ so it looks as intended. + expandInternal(initialStr, 0).replace("""\$""", "$") + } + + def defineVariables(sym: Symbol)(using Context): Unit = { + val Trim = "(?s)^[\\s&&[^\n\r]]*(.*?)\\s*$".r + + val raw = ctx.docCtx.flatMap(_.docstring(sym).map(_.raw)).getOrElse("") + defs(sym) ++= defines(raw).map { + str => { + val start = skipWhitespace(str, "@define".length) + val (key, value) = str.splitAt(skipVariable(str, start)) + key.drop(start) -> value + } + } map { + case (key, Trim(value)) => + variableName(key) -> value.replaceAll("\\s+\\*+$", "") + } + } + + /** Maps symbols to the variable -> replacement maps that are defined + * in their doc comments + */ + val defs = mutable.HashMap[Symbol, Map[String, String]]() withDefaultValue Map() + + def lookupDefs(sym: Symbol)(using Context): Map[String, String] = + ctx.docCtx.map(_.docstring(sym) match { + case Some(cmt) if cmt.isExpanded => cmt.variables + case _ => defs(sym) + }) getOrElse Map() + + /** Lookup definition of variable. + * + * @param vble The variable for which a definition is searched + * @param site The class for which doc comments are generated + */ + def lookupVariable(vble: String, site: Symbol)(using Context): Option[String] = site match { + case NoSymbol => None + case _ => + val searchList = + if (site.flags.is(Flags.Module)) site :: site.info.baseClasses + else site.info.baseClasses + + searchList collectFirst { case x if lookupDefs(x) contains vble => lookupDefs(x)(vble) } match { + case Some(str) if str startsWith "$" => lookupVariable(str.tail, site) + case res => res orElse lookupVariable(vble, site.owner) + } + } + + /** The position of the raw doc comment of symbol `sym`, or NoPosition if missing + * If a symbol does not have a doc comment but some overridden version of it does, + * the position of the doc comment of the overridden version is returned instead. + */ + def docCommentPos(sym: Symbol)(using Context): Span = + ctx.docCtx.flatMap(_.docstring(sym).map(_.span)).getOrElse(NoSpan) + + /** A version which doesn't consider self types, as a temporary measure: + * an infinite loop has broken out between superComment and cookedDocComment + * since r23926. + */ + private def allInheritedOverriddenSymbols(sym: Symbol)(using Context): List[Symbol] = + if (!sym.owner.isClass) Nil + else sym.allOverriddenSymbols.toList.filter(_ != NoSymbol) //TODO: could also be `sym.owner.allOverrid..` + //else sym.owner.ancestors map (sym overriddenSymbol _) filter (_ != NoSymbol) + + class ExpansionLimitExceeded(str: String) extends Exception +} + +object CommentExpander { + + // TODO: handle package / non-class top-level definitions (possibly just return their comments?) + def cookComment(sym: Symbol)(using Context): Option[Comment] = + val owner = if sym.isClass then sym else sym.owner + cookComment(sym, owner) + + def cookComment(sym: Symbol, owner: Symbol)(using Context): Option[Comment] = + ctx.docCtx.flatMap { docCtx => + expand(sym, owner)(using ctx)(using docCtx) + } + + private def expand(sym: Symbol, owner: Symbol)(using Context)(using docCtx: ContextDocstrings): Option[Comment] = + docCtx.docstring(sym).flatMap { + case cmt if cmt.isExpanded => + Some(cmt) + case _ => + expandComment(sym).map { expanded => + val commentWithUsecases = expanded + docCtx.addDocstring(sym, Some(commentWithUsecases)) + commentWithUsecases + } + } + + private def expandComment(sym: Symbol, owner: Symbol, comment: Comment)(using Context)(using docCtx: ContextDocstrings): Comment = { + val tplExp = new CommentExpander + tplExp.defineVariables(sym) + val newComment = { + val expandedComment = tplExp.expandedDocComment(sym, owner, comment.raw) + Comment(comment.span, comment.raw, Some(expandedComment), Nil, tplExp.defs(sym)) + } + docCtx.addDocstring(sym, Some(newComment)) + newComment + } + + /** Expands comment of `sym`, but only after expanding all comments necessary to perform that. */ + private def expandComment(sym: Symbol)(using Context)(using docCtx: ContextDocstrings): Option[Comment] = + if (sym eq NoSymbol) None + else docCtx.docstring(sym) match + case Some(cmt) if !cmt.isExpanded => + expandComment(sym.owner) + if sym.isClass then + for ptype <- sym.info.asInstanceOf[Types.ClassInfo].classParents do + expandComment(ptype.classSymbol) + Some(expandComment(sym, sym.owner, cmt)) + case _ => + None + end if +} + diff --git a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala index 7f91e123a2dd..2bf589764fa8 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala @@ -115,12 +115,12 @@ class Converter(val repr: Repr) extends BaseConverter { case Superscript(i) => def name = inl.getClass.getSimpleName - println(s"WARN: Wiki syntax tag not yet fully supported: $name") + // println(s"WARN: Wiki syntax tag not yet fully supported: $name") emitInline(i) case Subscript(i) => def name = inl.getClass.getSimpleName - println(s"WARN: Wiki syntax tag not yet fully supported: $name") + // println(s"WARN: Wiki syntax tag not yet fully supported: $name") emitInline(i) case HtmlTag(content) => @@ -128,7 +128,7 @@ class Converter(val repr: Repr) extends BaseConverter { case _: RepresentationLink => val name = inl.getClass.getSimpleName - println(s"WARN: Wiki syntax tag not yet supported: $name") + // println(s"WARN: Wiki syntax tag not yet supported: $name") emit(dkk.text(name)) } diff --git a/scala3doc/test/dotty/dokka/tasty/comments/CommentExpanderTests.scala b/scala3doc/test/dotty/dokka/tasty/comments/CommentExpanderTests.scala new file mode 100644 index 000000000000..1dcaa6431523 --- /dev/null +++ b/scala3doc/test/dotty/dokka/tasty/comments/CommentExpanderTests.scala @@ -0,0 +1,52 @@ +package dotty.dokka.tasty.comments + +import scala.quoted._ + +import org.junit.{Test, Rule} +import org.junit.Assert.{assertSame, assertTrue, assertEquals} +import dotty.dokka.tasty.util._ +import dotty.dokka.tasty.TastyParser + +class CommentExpanderTests { + def check(using quoted.Quotes)(): Unit = + assertCommentEquals( + qr.Symbol.requiredClass("tests.B").method("otherMethod").head, + "/** This is my foo: Bar, actually. */", + ) + assertCommentEquals( + qr.Symbol.requiredClass("tests.C"), + "/** This is foo: Foo expanded. */", + ) + assertCommentEquals( + qr.Symbol.requiredModule("tests.O").method("method").head, + "/** This is foo: O's foo. */", + ) + + + def assertCommentEquals( + using quoted.Quotes + )( + rsym: quotes.reflect.Symbol, + str: String + ): Unit = + import dotty.tools.dotc + given ctx as dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + val sym = rsym.asInstanceOf[dotc.core.Symbols.Symbol] + val comment = CommentExpander.cookComment(sym).get + assertEquals(comment.expanded.get, str) + + @Test + def test(): Unit = { + import scala.tasty.inspector.TastyInspector + class Inspector extends TastyInspector: + + def processCompilationUnit(using quoted.Quotes)(root: quotes.reflect.Tree): Unit = () + + override def postProcess(using quoted.Quotes): Unit = + check() + + Inspector().inspectTastyFiles(TestUtils.listOurClasses()) + } + + private def qr(using quoted.Quotes): quotes.reflect.type = quotes.reflect +} diff --git a/scala3doc/test/dotty/dokka/tasty/comments/MemberLookupTests.scala b/scala3doc/test/dotty/dokka/tasty/comments/MemberLookupTests.scala index d7dbbd1d0dcd..34c3f1538af1 100644 --- a/scala3doc/test/dotty/dokka/tasty/comments/MemberLookupTests.scala +++ b/scala3doc/test/dotty/dokka/tasty/comments/MemberLookupTests.scala @@ -4,7 +4,7 @@ import scala.quoted.Quotes import org.junit.{Test, Rule} import org.junit.Assert.{assertSame, assertTrue} -import dotty.dokka.BuildInfo +import dotty.dokka.tasty.util._ class LookupTestCases[Q <: Quotes](val q: Quotes) { @@ -118,27 +118,6 @@ class MemberLookupTests { cases.testAll() } - Inspector().inspectTastyFiles(listOurClasses()) - } - - def listOurClasses(): List[String] = { - import java.io.File - import scala.collection.mutable.ListBuffer - - val classRoot = new File(BuildInfo.test_testcasesOutputDir) - - def go(bld: ListBuffer[String])(file: File): Unit = - file.listFiles.foreach { f => - if f.isFile() then - if f.toString.endsWith(".tasty") then bld.append(f.toString) - else go(bld)(f) - } - - if classRoot.isDirectory then - val bld = new ListBuffer[String] - go(bld)(classRoot) - bld.result - else - sys.error(s"Class root could not be found: $classRoot") + Inspector().inspectTastyFiles(TestUtils.listOurClasses()) } } diff --git a/scala3doc/test/dotty/dokka/tasty/util/TestUtils.scala b/scala3doc/test/dotty/dokka/tasty/util/TestUtils.scala new file mode 100644 index 000000000000..055cfe6f5003 --- /dev/null +++ b/scala3doc/test/dotty/dokka/tasty/util/TestUtils.scala @@ -0,0 +1,26 @@ +package dotty.dokka.tasty.util + +import dotty.dokka.BuildInfo + +object TestUtils { + def listOurClasses(): List[String] = { + import java.io.File + import scala.collection.mutable.ListBuffer + + val classRoot = new File(BuildInfo.test_testcasesOutputDir) + + def go(bld: ListBuffer[String])(file: File): Unit = + file.listFiles.foreach { f => + if f.isFile() then + if f.toString.endsWith(".tasty") then bld.append(f.toString) + else go(bld)(f) + } + + if classRoot.isDirectory then + val bld = new ListBuffer[String] + go(bld)(classRoot) + bld.result + else + sys.error(s"Class root could not be found: $classRoot") + } +}