diff --git a/project/Build.scala b/project/Build.scala index 8d56d1e80ae4..fcfb4e90a107 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1528,14 +1528,9 @@ object Build { def generateDocumentation(targets: String, name: String, outDir: String, ref: String, params: String = "") = Def.taskDyn { val projectVersion = version.value IO.createDirectory(file(outDir)) - val managedSources = - (`stdlib-bootstrapped`/Compile/sourceManaged).value / "scala-library-src" - val projectRoot = (ThisBuild/baseDirectory).value.toPath - val stdLibRoot = projectRoot.relativize(managedSources.toPath.normalize()) - val scalaSourceLink = - s"$stdLibRoot=github://scala/scala/v${stdlibVersion(Bootstrapped)}#src/library" - val sourcesAndRevision = s"-source-links $scalaSourceLink,github://lampepfl/dotty -revision $ref -project-version $projectVersion" - val cmd = s""" -d $outDir -project "$name" $sourcesAndRevision $params $targets""" + val sourceLinks = "-source-links:github://lampepfl/dotty " + val revision = s"-revision $ref -project-version $projectVersion" + val cmd = s""" -d $outDir -project "$name" $sourceLinks $revision $params $targets""" run.in(Compile).toTask(cmd) } @@ -1575,12 +1570,9 @@ object Build { classDirectory.in(Compile).value.getAbsolutePath, "scala3doc", "scala3doc/output/self", VersionUtil.gitHash, "-siteroot scala3doc/documentation -project-logo scala3doc/documentation/logo.svg " + - "-external-mappings " + raw".*scala.*" + "::" + - "scala3doc" + "::" + - "http://dotty.epfl.ch/api/" + ":::" + - raw".*java.*" + "::" + - "javadoc" + "::" + - "https://docs.oracle.com/javase/8/docs/api/" + "-external-mappings:" + + ".*scala.*::scala3doc::http://dotty.epfl.ch/api/," + + ".*java.*::javadoc::https://docs.oracle.com/javase/8/docs/api/" ) }.value, @@ -1597,6 +1589,12 @@ object Build { val roots = joinProducts(dottyJars) + val managedSources = + (`stdlib-bootstrapped`/Compile/sourceManaged).value / "scala-library-src" + val projectRoot = (ThisBuild/baseDirectory).value.toPath + val stdLibRoot = projectRoot.relativize(managedSources.toPath.normalize()) + val docRootFile = stdLibRoot.resolve("rootdoc.txt") + if (dottyJars.isEmpty) Def.task { streams.value.log.error("Dotty lib wasn't found") } else Def.task{ IO.write(dest / "versions" / "latest-nightly-base", majorVersion) @@ -1615,9 +1613,9 @@ object Build { "-skip-by-regex:.+\\.internal($|\\..+) " + "-skip-by-regex:.+\\.impl($|\\..+) " + "-comment-syntax wiki -siteroot scala3doc/scala3-docs -project-logo scala3doc/scala3-docs/logo.svg " + - "-external-mappings " + raw".*java.*" + "::" + - "javadoc" + "::" + - "https://docs.oracle.com/javase/8/docs/api/" + "-external-mappings:.*java.*::javadoc::https://docs.oracle.com/javase/8/docs/api/ " + + s"-source-links:$stdLibRoot=github://scala/scala/v${stdlibVersion(Bootstrapped)}#src/library " + + s"-doc-root-content $docRootFile" )) }.evaluated, diff --git a/scala3doc/src/dotty/dokka/DocContext.scala b/scala3doc/src/dotty/dokka/DocContext.scala index 7eb58039d1bd..2031a0f6b987 100644 --- a/scala3doc/src/dotty/dokka/DocContext.scala +++ b/scala3doc/src/dotty/dokka/DocContext.scala @@ -69,6 +69,9 @@ extension (r: report.type) def warn(m: String, f: File)(using CompilerContext): Unit = r.warning(createMessage(m, f, null), sourcePostionFor(f)) + def warn(m: String, e: Throwable)(using CompilerContext): Unit = + r.warning(s"$m: ${throwableToString(e)}") + case class NavigationNode(name: String, dri: DRI, nested: Seq[NavigationNode]) case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) @@ -99,51 +102,7 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) sourceLinks )(using compilerContext)) - def parseDocTool(docTool: String) = docTool match { - case "scaladoc" => Some(DocumentationKind.Scaladoc) - case "scala3doc" => Some(DocumentationKind.Scala3doc) - case "javadoc" => Some(DocumentationKind.Javadoc) - case other => None - } - val externalDocumentationLinks: List[Scala3docExternalDocumentationLink] = args.externalMappings.filter(_.size >= 3).flatMap { mapping => - val regexStr = mapping(0) - val docTool = mapping(1) - val urlStr = mapping(2) - val packageListUrlStr = if mapping.size > 3 then Some(mapping(3)) else None - val regex = Try(regexStr.r).toOption - val url = Try(URL(urlStr)).toOption - val packageListUrl = Try(packageListUrlStr.map(URL(_))) - .fold( - e => { - logger.warn(s"Wrong packageListUrl parameter in external mapping. Found '$packageListUrlStr'. " + - s"Package list url will be omitted") - None}, - res => res - ) - - val parsedDocTool = parseDocTool(docTool) - val res = if regexStr.isEmpty then - logger.warn(s"Wrong regex parameter in external mapping. Found '$regexStr'. Mapping will be omitted") - None - else if url.isEmpty then - logger.warn(s"Wrong url parameter in external mapping. Found '$urlStr'. Mapping will be omitted") - None - else if parsedDocTool.isEmpty then - logger.warn(s"Wrong doc-tool parameter in external mapping. " + - s"Expected one of: 'scaladoc', 'scala3doc', 'javadoc'. Found:'$docTool'. Mapping will be omitted " - ) - None - else - Some( - Scala3docExternalDocumentationLink( - List(regexStr.r), - URL(urlStr), - parsedDocTool.get, - packageListUrlStr.map(URL(_)) - ) - ) - res - } + val externalDocumentationLinks = args.externalMappings override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = JNil diff --git a/scala3doc/src/dotty/dokka/ExternalDocLink.scala b/scala3doc/src/dotty/dokka/ExternalDocLink.scala new file mode 100644 index 000000000000..a67d93ddebd1 --- /dev/null +++ b/scala3doc/src/dotty/dokka/ExternalDocLink.scala @@ -0,0 +1,57 @@ +package dotty.dokka + +import java.net.URL +import scala.util.matching._ +import scala.util.Try + +case class ExternalDocLink( + originRegexes: List[Regex], + documentationUrl: URL, + kind: DocumentationKind, + packageListUrl: Option[URL] = None +): + def withPackageList(url: URL): ExternalDocLink = copy(packageListUrl = Some(url)) + +enum DocumentationKind: + case Javadoc extends DocumentationKind + case Scaladoc extends DocumentationKind + case Scala3doc extends DocumentationKind + +object ExternalDocLink: + def parse(mapping: String)(using CompilerContext): Option[ExternalDocLink] = + def fail(msg: String) = + report.warning(s"Unable to parocess external mapping $mapping. $msg") + None + + def tryParse[T](descr: String)(op: => T): Option[T] = try Some(op) catch + case e: RuntimeException => + report.warn(s"Unable to parse $descr", e) + None + + def parsePackageList(elements: List[String]) = elements match + case List(urlStr) => tryParse("packageList")(Option(URL(urlStr))) + case Nil => Some(None) + case other => fail(s"Provided multiple package lists: $other") + + def doctoolByName(name: String) = name match + case "javadoc" => Some(DocumentationKind.Javadoc) + case "scaladoc" => Some(DocumentationKind.Scaladoc) + case "scala3doc" => Some(DocumentationKind.Scala3doc) + case other => fail(s"Unknown doctool: $other") + + + mapping.split("::").toList match + case regexStr :: docToolStr :: urlStr :: rest => + for { + regex <- tryParse("regex")(regexStr.r) + url <- tryParse("url")(URL(urlStr)) + doctool <- doctoolByName(docToolStr) + packageList <- parsePackageList(rest) + } yield ExternalDocLink( + List(regex), + url, + doctool, + packageList + ) + case _ => + fail("Accepted format: `regexStr::docToolStr::urlStr[::rest]`") \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/IO.java b/scala3doc/src/dotty/dokka/IO.java index 3ab8849a4ee5..c8e985ac3ff0 100644 --- a/scala3doc/src/dotty/dokka/IO.java +++ b/scala3doc/src/dotty/dokka/IO.java @@ -43,4 +43,8 @@ public FileVisitResult visitFile( public static String read(Path path) throws IOException { return new String(Files.readAllBytes(path), Charset.defaultCharset()); } + + public static String read(String path) throws IOException { + return read(Paths.get(path)); + } } diff --git a/scala3doc/src/dotty/dokka/Scala3doc.scala b/scala3doc/src/dotty/dokka/Scala3doc.scala index f0f7700c6e75..55d548d38ef6 100644 --- a/scala3doc/src/dotty/dokka/Scala3doc.scala +++ b/scala3doc/src/dotty/dokka/Scala3doc.scala @@ -65,9 +65,10 @@ object Scala3doc: defaultSyntax: CommentSyntax = CommentSyntax.Markdown, sourceLinks: List[String] = Nil, revision: Option[String] = None, - externalMappings: List[List[String]] = List.empty, + externalMappings: List[ExternalDocLink] = Nil, identifiersToSkip: List[String] = Nil, regexesToSkip: List[String] = Nil, + rootDocPath: Option[String] = None ) def run(args: Array[String], rootContext: CompilerContext): Reporter = diff --git a/scala3doc/src/dotty/dokka/Scala3docArgs.scala b/scala3doc/src/dotty/dokka/Scala3docArgs.scala index 7e09aa9280bf..b075e45373b8 100644 --- a/scala3doc/src/dotty/dokka/Scala3docArgs.scala +++ b/scala3doc/src/dotty/dokka/Scala3docArgs.scala @@ -25,8 +25,8 @@ class Scala3docArgs extends SettingGroup with CommonScalaSettings: sourcepath, sourceroot ) - val sourceLinks: Setting[String] = - StringSetting("-source-links", "sources", SourceLinks.usage, "") + val sourceLinks: Setting[List[String]] = + MultiStringSetting("-source-links", "sources", SourceLinks.usage) val syntax: Setting[String] = StringSetting("-comment-syntax", "syntax", "Syntax of the comment used", "") @@ -34,8 +34,13 @@ class Scala3docArgs extends SettingGroup with CommonScalaSettings: val revision: Setting[String] = StringSetting("-revision", "revision", "Revision (branch or ref) used to build project project", "") - val externalDocumentationMappings: Setting[String] = - StringSetting("-external-mappings", "external-mappings", "Mapping between regex matching class file and external documentation", "") + val externalDocumentationMappings: Setting[List[String]] = + MultiStringSetting("-external-mappings", "external-mappings", + "Mapping between regexes matching classpath entries and external documentation. " + + "'regex::[scaladoc|scala3doc|javadoc]::path' syntax is used") + + val deprecatedSkipPackages: Setting[List[String]] = + MultiStringSetting("-skip-packages", "packages", "Deprecated, please use `-skip-by-id` or `-skip-by-regex`") val skipById: Setting[List[String]] = MultiStringSetting("-skip-by-id", "package or class identifier", "Identifiers of packages or top-level classes to skip when generating documentation") @@ -43,7 +48,11 @@ class Scala3docArgs extends SettingGroup with CommonScalaSettings: val skipByRegex: Setting[List[String]] = MultiStringSetting("-skip-by-regex", "regex", "Regexes that match fully qualified names of packages or top-level classes to skip when generating documentation") - def scala3docSpecificSettings: Set[Setting[_]] = Set(sourceLinks, syntax, revision, externalDocumentationMappings, skipById, skipByRegex) + val docRootContent: Setting[String] = + StringSetting("-doc-root-content", "path", "The file from which the root package documentation should be imported.", "") + + def scala3docSpecificSettings: Set[Setting[_]] = + Set(sourceLinks, syntax, revision, externalDocumentationMappings, skipById, skipByRegex, deprecatedSkipPackages, docRootContent) object Scala3docArgs: def extract(args: List[String], rootCtx: CompilerContext):(Scala3doc.Args, CompilerContext) = @@ -104,7 +113,8 @@ object Scala3docArgs: CommentSyntax.default } } - val externalMappings = externalDocumentationMappings.get.split(":::").map(_.split("::").toList).toList + val externalMappings = + externalDocumentationMappings.get.flatMap(ExternalDocLink.parse) unsupportedSettings.filter(s => s.get != s.default).foreach { s => report.warning(s"Setting ${s.name} is currently not supported.") @@ -114,6 +124,8 @@ object Scala3docArgs: report.inform( s"Generating documenation $printableProjectName in $destFile") + if deprecatedSkipPackages.get.nonEmpty then report.warning(deprecatedSkipPackages.description) + val docArgs = Args( projectName.withDefault("root"), dirs, @@ -124,10 +136,11 @@ object Scala3docArgs: projectVersion.nonDefault, projectLogo.nonDefault, parseSyntax, - sourceLinks.nonDefault.fold(Nil)(_.split(",").toList), + sourceLinks.get, revision.nonDefault, externalMappings, - skipById.get, + skipById.get ++ deprecatedSkipPackages.get, skipByRegex.get, + docRootContent.nonDefault ) (docArgs, newContext) diff --git a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala index 161f932a0976..f7911db4c9d2 100644 --- a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala +++ b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala @@ -16,7 +16,7 @@ import kotlin.coroutines.Continuation class ScalaModuleProvider(using ctx: DocContext) extends SourceToDocumentableTranslator: override def invoke(sourceSet: DokkaSourceSet, cxt: DokkaContext, unused: Continuation[? >: DModule]) = - val result = DokkaTastyInspector(new MarkdownParser(_ => null)).result() + val (result, rootDoc) = DokkaTastyInspector(new MarkdownParser(_ => null)).result() val (rootPck, rest) = result.partition(_.name == "") val packageMembers = (rest ++ rootPck.flatMap(_.allMembers)).sortBy(_.name) @@ -32,7 +32,7 @@ class ScalaModuleProvider(using ctx: DocContext) extends SourceToDocumentableTra null, JSet(ctx.sourceSet), PropertyContainer.Companion.empty() - ).withNewMembers(packageMembers).withKind(Kind.RootPackage).asInstanceOf[DPackage] + ).withNewMembers(packageMembers).withKind(Kind.RootPackage).withDocs(rootDoc).asInstanceOf[DPackage] new DModule( sourceSet.getDisplayName, diff --git a/scala3doc/src/dotty/dokka/externalDocumentationLinks.scala b/scala3doc/src/dotty/dokka/externalDocumentationLinks.scala deleted file mode 100644 index 9d8ddfbe7d5a..000000000000 --- a/scala3doc/src/dotty/dokka/externalDocumentationLinks.scala +++ /dev/null @@ -1,18 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka._ -import java.net.URL -import scala.util.matching._ - -case class Scala3docExternalDocumentationLink( - originRegexes: List[Regex], - documentationUrl: URL, - kind: DocumentationKind, - packageListUrl: Option[URL] = None -): - def withPackageList(url: URL): Scala3docExternalDocumentationLink = copy(packageListUrl = Some(url)) - -enum DocumentationKind: - case Javadoc extends DocumentationKind - case Scaladoc extends DocumentationKind - case Scala3doc extends DocumentationKind \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala index e28538100cf9..de11e0cd2799 100644 --- a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala +++ b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala @@ -79,6 +79,10 @@ extension (member: Member) val ext = MemberExtension.getFrom(member).getOrElse(MemberExtension.empty).copy(kind = kind) putInMember(ext) + def withDocs(docs: Option[Comment]): Member = + val ext = MemberExtension.getFrom(member).getOrElse(MemberExtension.empty).copy(rawDoc = docs) + putInMember(ext) + def withMembers(newMembers: Seq[Member]): Member = val original = member.compositeMemberExt.getOrElse(CompositeMemberExtension()) val newExt = original.copy(members = newMembers) diff --git a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala index d2713bb7e7fb..4aabb5f57ba0 100644 --- a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala @@ -29,16 +29,10 @@ trait BasicSupport: Annotation(dri, params) extension (sym: Symbol) - def documentation = sym.docstring match - case Some(docstring) => - Map(ctx.sourceSet -> parseComment(docstring, sym.tree)) - case None => - Map.empty - - def documentation2 = sym.docstring.map(preparseComment(_, sym.tree)) + def documentation = sym.docstring.map(parseComment(_, sym.tree)) def source(using Quotes) = - val path = sym.pos.filter(isValidPos(_)).map(_.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath).map(_.toString) + val path = sym.pos.map(_.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath).map(_.toString) path.map(TastyDocumentableSource(_, sym.pos.get.startLine)) def getAnnotations(): List[Annotation] = diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index f164bfebd55c..1c99f5714de4 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -465,7 +465,7 @@ trait ClassLikeSupport: placeholderModifier, ctx.sourceSet.toSet, /*isExpectActual =*/ false, - PropertyContainer.Companion.empty().plus(member.copy(rawDoc = symbol.documentation2)).plus(compositeExt) + PropertyContainer.Companion.empty().plus(member.copy(rawDoc = symbol.documentation)).plus(compositeExt) ) private def isUsingModifier(parameters: Seq[ValDef]): Boolean = diff --git a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala index 8bf914ff4040..29b953bb3452 100644 --- a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala @@ -12,24 +12,8 @@ import dotty.dokka.tasty.comments.Comment trait ScaladocSupport { self: TastyParser => import qctx.reflect._ - def preparseComment( - docstring: String, - tree: Tree - ): Comment = - val commentString: String = - if tree.symbol.isClassDef || tree.symbol.owner.isClassDef then - import dotty.tools.dotc - given ctx: 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.expanded.get - else - docstring - - val preparsed = - comments.Preparser.preparse(comments.Cleaner.clean(commentString)) + def parseCommentString(comment: String, sym: Symbol, pos: Option[Position]): Comment = + val preparsed = comments.Preparser.preparse(comments.Cleaner.clean(comment)) val commentSyntax = preparsed.syntax.headOption match { @@ -37,7 +21,8 @@ trait ScaladocSupport { self: TastyParser => CommentSyntax.parse(commentSetting).getOrElse { val msg = s"not a valid comment syntax: $commentSetting, defaulting to Markdown syntax." // we should update pos with span from documentation - report.warning(msg, tree.pos) + pos.fold(report.warning(msg))(report.warning(msg, _)) + CommentSyntax.default } case None => ctx.args.defaultSyntax @@ -45,16 +30,13 @@ trait ScaladocSupport { self: TastyParser => val parser = commentSyntax match { case CommentSyntax.Wiki => - comments.WikiCommentParser(comments.Repr(qctx)(tree.symbol)) + comments.WikiCommentParser(comments.Repr(qctx)(sym)) case CommentSyntax.Markdown => - comments.MarkdownCommentParser(comments.Repr(qctx)(tree.symbol)) + comments.MarkdownCommentParser(comments.Repr(qctx)(sym)) } parser.parse(preparsed) - def parseComment( - docstring: String, - tree: Tree - ): dkkd.DocumentationNode = { + def parseComment(docstring: String, tree: Tree): Comment = val commentString: String = if tree.symbol.isClassDef || tree.symbol.owner.isClassDef then import dotty.tools.dotc @@ -67,75 +49,6 @@ trait ScaladocSupport { self: TastyParser => else docstring - val preparsed = - comments.Preparser.preparse(comments.Cleaner.clean(commentString)) - - val commentSyntax = - preparsed.syntax.headOption match { - case Some(commentSetting) => - CommentSyntax.parse(commentSetting).getOrElse { - val msg = s"not a valid comment syntax: $commentSetting, defaulting to Markdown syntax." - // we should update pos with span from documentation - report.warning(msg, tree.pos) - CommentSyntax.default - } - case None => ctx.args.defaultSyntax - } - - val parser = commentSyntax match { - case CommentSyntax.Wiki => - comments.WikiCommentParser(comments.Repr(qctx)(tree.symbol)) - case CommentSyntax.Markdown => - comments.MarkdownCommentParser(comments.Repr(qctx)(tree.symbol)) - } - val parsed = parser.parse(preparsed) - - import kotlin.collections.builders.{ListBuilder => KtListBuilder} - val bld = new KtListBuilder[dkkd.TagWrapper] - parsed.short match { - case Some(tag) => bld.add(dkkd.Description(tag)) - case None => bld.add(dkkd.Description(dkk.text(""))) - } - bld.add(dkkd.Description(parsed.body)) - - inline def addOpt(opt: Option[dkkd.DocTag])(wrap: dkkd.DocTag => dkkd.TagWrapper) = - opt.foreach { t => bld.add(wrap(t)) } - - inline def addSeq[T](seq: Iterable[T])(wrap: T => dkkd.TagWrapper) = - seq.foreach { t => bld.add(wrap(t)) } - - // this is a total kludge, this should be done in a deeper layer but we'd - // need to refactor code there first - def correctParagraphTags(tag: dkkd.DocTag): dkkd.DocTag = - tag match { - case tag: dkkd.P => - // NOTE we recurse once, since both the top-level element and its children can be P - // (there is no special root DocTag) - dkkd.Span(tag.getChildren.iterator.asScala.map(correctParagraphTags).toSeq.asJava, tag.getParams) - case tag => tag - } - - addSeq(parsed.authors)(dkkd.Author(_)) - addOpt(parsed.version)(dkkd.Version(_)) - addOpt(parsed.since)(dkkd.Since(_)) - addOpt(parsed.deprecated)(dkkd.Deprecated(_)) - addSeq(parsed.todo)(ScalaTagWrapper.Todo.apply) - addSeq(parsed.see)(ScalaTagWrapper.See.apply) - addSeq(parsed.note)(ScalaTagWrapper.Note.apply) - addSeq(parsed.example)(ScalaTagWrapper.Example.apply) - - addOpt(parsed.constructor)(dkkd.Constructor(_)) - addSeq(parsed.valueParams){ case (name, tag) => - ScalaTagWrapper.NestedNamedTag("Param", name, dkk.text(name), correctParagraphTags(tag)) - } - addSeq(parsed.typeParams){ case (name, tag) => - ScalaTagWrapper.NestedNamedTag("Type param", name, dkk.text(name), correctParagraphTags(tag)) - } - addSeq(parsed.throws){ case (key, (exc, desc)) => - ScalaTagWrapper.NestedNamedTag("Throws", key, exc, correctParagraphTags(desc)) - } - addOpt(parsed.result)(dkkd.Return(_)) + parseCommentString(commentString, tree.symbol, Some(tree.pos)) - new dkkd.DocumentationNode(bld.build()) - } } diff --git a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala index ad0a676455f4..f78bfaed6d64 100644 --- a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala +++ b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala @@ -24,8 +24,12 @@ import dotty.tools.dotc import dotty.dokka.tasty.comments.MemberLookup import dotty.dokka.tasty.comments.QueryParser +import dotty.dokka.tasty.comments.Comment import dotty.dokka.model.api._ +import java.nio.file.Paths +import java.nio.file.Files + /** Responsible for collectively inspecting all the Tasty files we're interested in. * * Delegates most of the work to [[TastyParser]] [[dotty.dokka.tasty.TastyParser]]. @@ -33,6 +37,7 @@ import dotty.dokka.model.api._ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends DocTastyInspector: private val topLevels = Seq.newBuilder[(String, Member)] + private var rootDoc: Option[Comment] = None def processCompilationUnit(using quotes: Quotes)(root: quotes.reflect.Tree): Unit = () @@ -84,24 +89,49 @@ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends Do isSkippedById(sym) || isSkippedByRx(sym) - hackForeachTree { root => - if !isSkipped(root.symbol) then - val parser = new TastyParser(q, this)(isSkipped) + val parser = new TastyParser(q, this)(isSkipped) + def driFor(link: String): Option[DRI] = + val symOps = new SymOps[q.type](q) + import symOps._ + Try(QueryParser(link).readQuery()).toOption.flatMap(q => + MemberLookup.lookupOpt(q, None).map{ case (sym, _) => sym.dri} + ) + ctx.staticSiteContext.foreach(_.memberLinkResolver = driFor) + + var alreadyProcessed = false + def processRootDocIfNeeded(tree: parser.qctx.reflect.Tree) = + def readFile(pathStr: String)(using CompilerContext): Option[String] = + try + val path = Paths.get(pathStr) + if Files.exists(path) then Some(IO.read(path)) + else + report.inform("Rootdoc at $pathStr does not exisits") + None + catch + case e: RuntimeException => + report.warning(s"Unable to read root package doc from $pathStr: ${throwableToString(e)}") + None - def driFor(link: String): Option[DRI] = - val symOps = new SymOps[q.type](q) - import symOps._ - Try(QueryParser(link).readQuery()).toOption.flatMap(q => - MemberLookup.lookupOpt(q, None).map{ case (sym, _) => sym.dri} - ) + if !alreadyProcessed then + alreadyProcessed = true + ctx.args.rootDocPath.flatMap(readFile).map { content => + import parser.qctx.reflect._ + def root(s: Symbol): Symbol = if s.owner.isNoSymbol then s else root(s.owner) + val topLevelPck = root(tree.symbol) + rootDoc = Some(parser.parseCommentString(content, topLevelPck, None)) + } - ctx.staticSiteContext.foreach(_.memberLinkResolver = driFor) - topLevels ++= parser.parseRootTree(root.asInstanceOf[parser.qctx.reflect.Tree]) + hackForeachTree { root => + if !isSkipped(root.symbol) then + val treeRoot = root.asInstanceOf[parser.qctx.reflect.Tree] + processRootDocIfNeeded(treeRoot) + topLevels ++= parser.parseRootTree(treeRoot) } - def result(): List[Member] = + def result(): (List[Member], Option[Comment]) = topLevels.clear() + rootDoc = None val filePaths = ctx.args.tastyFiles.map(_.getAbsolutePath).toList val classpath = ctx.args.classpath.split(java.io.File.pathSeparator).toList @@ -114,7 +144,7 @@ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends Do p1.withNewMembers(p2.allMembers) // TODO add doc ) basePck.withMembers((basePck.allMembers ++ rest).sortBy(_.name)) - }.toList + }.toList -> rootDoc /** Parses a single Tasty compilation unit. */ case class TastyParser(