Skip to content

Commit 2c3fb45

Browse files
committed
Add support for -doc-root-content
1 parent 7b44916 commit 2c3fb45

File tree

9 files changed

+82
-43
lines changed

9 files changed

+82
-43
lines changed

project/Build.scala

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,13 +1528,7 @@ object Build {
15281528
def generateDocumentation(targets: String, name: String, outDir: String, ref: String, params: String = "") = Def.taskDyn {
15291529
val projectVersion = version.value
15301530
IO.createDirectory(file(outDir))
1531-
val managedSources =
1532-
(`stdlib-bootstrapped`/Compile/sourceManaged).value / "scala-library-src"
1533-
val projectRoot = (ThisBuild/baseDirectory).value.toPath
1534-
val stdLibRoot = projectRoot.relativize(managedSources.toPath.normalize())
1535-
val scalaSourceLink =
1536-
s"$stdLibRoot=github://scala/scala/v${stdlibVersion(Bootstrapped)}#src/library"
1537-
val sourceLinks = s"-source-links:$scalaSourceLink,github://lampepfl/dotty "
1531+
val sourceLinks = "-source-links:github://lampepfl/dotty "
15381532
val revision = s"-revision $ref -project-version $projectVersion"
15391533
val cmd = s""" -d $outDir -project "$name" $sourceLinks $revision $params $targets"""
15401534
run.in(Compile).toTask(cmd)
@@ -1595,6 +1589,12 @@ object Build {
15951589

15961590
val roots = joinProducts(dottyJars)
15971591

1592+
val managedSources =
1593+
(`stdlib-bootstrapped`/Compile/sourceManaged).value / "scala-library-src"
1594+
val projectRoot = (ThisBuild/baseDirectory).value.toPath
1595+
val stdLibRoot = projectRoot.relativize(managedSources.toPath.normalize())
1596+
val docRootFile = stdLibRoot.resolve("rootdoc.txt")
1597+
15981598
if (dottyJars.isEmpty) Def.task { streams.value.log.error("Dotty lib wasn't found") }
15991599
else Def.task{
16001600
IO.write(dest / "versions" / "latest-nightly-base", majorVersion)
@@ -1613,7 +1613,9 @@ object Build {
16131613
"-skip-by-regex:.+\\.internal($|\\..+) " +
16141614
"-skip-by-regex:.+\\.impl($|\\..+) " +
16151615
"-comment-syntax wiki -siteroot scala3doc/scala3-docs -project-logo scala3doc/scala3-docs/logo.svg " +
1616-
"-external-mappings:.*java.*::javadoc::https://docs.oracle.com/javase/8/docs/api/"
1616+
"-external-mappings:.*java.*::javadoc::https://docs.oracle.com/javase/8/docs/api/ " +
1617+
s"-source-links:$stdLibRoot=github://scala/scala/v${stdlibVersion(Bootstrapped)}#src/library " +
1618+
s"-doc-root-content $docRootFile"
16171619
))
16181620
}.evaluated,
16191621

scala3doc/src/dotty/dokka/ExternalDocLink.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ object ExternalDocLink:
2525

2626
def tryParse[T](descr: String)(op: => T): Option[T] = try Some(op) catch
2727
case e: RuntimeException =>
28-
report.warn("Unable to parse $descr", e)
28+
report.warn(s"Unable to parse $descr", e)
2929
None
3030

3131
def parsePackageList(elements: List[String]) = elements match

scala3doc/src/dotty/dokka/IO.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,8 @@ public FileVisitResult visitFile(
4343
public static String read(Path path) throws IOException {
4444
return new String(Files.readAllBytes(path), Charset.defaultCharset());
4545
}
46+
47+
public static String read(String path) throws IOException {
48+
return read(Paths.get(path));
49+
}
4650
}

scala3doc/src/dotty/dokka/Scala3doc.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ object Scala3doc:
6868
externalMappings: List[ExternalDocLink] = Nil,
6969
identifiersToSkip: List[String] = Nil,
7070
regexesToSkip: List[String] = Nil,
71+
rootDocPath: Option[String] = None
7172
)
7273

7374
def run(args: Array[String], rootContext: CompilerContext): Reporter =

scala3doc/src/dotty/dokka/Scala3docArgs.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ class Scala3docArgs extends SettingGroup with CommonScalaSettings:
4848
val skipByRegex: Setting[List[String]] =
4949
MultiStringSetting("-skip-by-regex", "regex", "Regexes that match fully qualified names of packages or top-level classes to skip when generating documentation")
5050

51+
val docRootContent: Setting[String] =
52+
StringSetting("-doc-root-content", "path", "The file from which the root package documentation should be imported.", "")
5153

5254
def scala3docSpecificSettings: Set[Setting[_]] =
53-
Set(sourceLinks, syntax, revision, externalDocumentationMappings, skipById, skipByRegex, deprecatedSkipPackages)
55+
Set(sourceLinks, syntax, revision, externalDocumentationMappings, skipById, skipByRegex, deprecatedSkipPackages, docRootContent)
5456

5557
object Scala3docArgs:
5658
def extract(args: List[String], rootCtx: CompilerContext):(Scala3doc.Args, CompilerContext) =
@@ -139,5 +141,6 @@ object Scala3docArgs:
139141
externalMappings,
140142
skipById.get ++ deprecatedSkipPackages.get,
141143
skipByRegex.get,
144+
docRootContent.nonDefault
142145
)
143146
(docArgs, newContext)

scala3doc/src/dotty/dokka/ScalaModuleCreator.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import kotlin.coroutines.Continuation
1616

1717
class ScalaModuleProvider(using ctx: DocContext) extends SourceToDocumentableTranslator:
1818
override def invoke(sourceSet: DokkaSourceSet, cxt: DokkaContext, unused: Continuation[? >: DModule]) =
19-
val result = DokkaTastyInspector(new MarkdownParser(_ => null)).result()
19+
val (result, rootDoc) = DokkaTastyInspector(new MarkdownParser(_ => null)).result()
2020
val (rootPck, rest) = result.partition(_.name == "<empty>")
2121
val packageMembers = (rest ++ rootPck.flatMap(_.allMembers)).sortBy(_.name)
2222

@@ -32,7 +32,7 @@ class ScalaModuleProvider(using ctx: DocContext) extends SourceToDocumentableTra
3232
null,
3333
JSet(ctx.sourceSet),
3434
PropertyContainer.Companion.empty()
35-
).withNewMembers(packageMembers).withKind(Kind.RootPackage).asInstanceOf[DPackage]
35+
).withNewMembers(packageMembers).withKind(Kind.RootPackage).withDocs(rootDoc).asInstanceOf[DPackage]
3636

3737
new DModule(
3838
sourceSet.getDisplayName,

scala3doc/src/dotty/dokka/model/api/internalExtensions.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ extension (member: Member)
7979
val ext = MemberExtension.getFrom(member).getOrElse(MemberExtension.empty).copy(kind = kind)
8080
putInMember(ext)
8181

82+
def withDocs(docs: Option[Comment]): Member =
83+
val ext = MemberExtension.getFrom(member).getOrElse(MemberExtension.empty).copy(rawDoc = docs)
84+
putInMember(ext)
85+
8286
def withMembers(newMembers: Seq[Member]): Member =
8387
val original = member.compositeMemberExt.getOrElse(CompositeMemberExtension())
8488
val newExt = original.copy(members = newMembers)

scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,43 @@ import dotty.dokka.tasty.comments.Comment
1212
trait ScaladocSupport { self: TastyParser =>
1313
import qctx.reflect._
1414

15-
def parseComment(docstring: String, tree: Tree): Comment =
16-
val commentString: String =
17-
if tree.symbol.isClassDef || tree.symbol.owner.isClassDef then
18-
import dotty.tools.dotc
19-
given ctx: dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
20-
21-
val sym = tree.symbol.asInstanceOf[dotc.core.Symbols.Symbol]
22-
23-
comments.CommentExpander.cookComment(sym)(using ctx)
24-
.get.expanded.get
25-
else
26-
docstring
27-
28-
val preparsed =
29-
comments.Preparser.preparse(comments.Cleaner.clean(commentString))
15+
def parseCommentString(comment: String, sym: Symbol, pos: Option[Position]): Comment =
16+
val preparsed = comments.Preparser.preparse(comments.Cleaner.clean(comment))
3017

3118
val commentSyntax =
3219
preparsed.syntax.headOption match {
3320
case Some(commentSetting) =>
3421
CommentSyntax.parse(commentSetting).getOrElse {
3522
val msg = s"not a valid comment syntax: $commentSetting, defaulting to Markdown syntax."
3623
// we should update pos with span from documentation
37-
report.warning(msg, tree.pos)
24+
pos.fold(report.warning(msg))(report.warning(msg, _))
25+
3826
CommentSyntax.default
3927
}
4028
case None => ctx.args.defaultSyntax
4129
}
4230

4331
val parser = commentSyntax match {
4432
case CommentSyntax.Wiki =>
45-
comments.WikiCommentParser(comments.Repr(qctx)(tree.symbol))
33+
comments.WikiCommentParser(comments.Repr(qctx)(sym))
4634
case CommentSyntax.Markdown =>
47-
comments.MarkdownCommentParser(comments.Repr(qctx)(tree.symbol))
35+
comments.MarkdownCommentParser(comments.Repr(qctx)(sym))
4836
}
4937
parser.parse(preparsed)
38+
39+
def parseComment(docstring: String, tree: Tree): Comment =
40+
val commentString: String =
41+
if tree.symbol.isClassDef || tree.symbol.owner.isClassDef then
42+
import dotty.tools.dotc
43+
given ctx: dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
44+
45+
val sym = tree.symbol.asInstanceOf[dotc.core.Symbols.Symbol]
46+
47+
comments.CommentExpander.cookComment(sym)(using ctx)
48+
.get.expanded.get
49+
else
50+
docstring
51+
52+
parseCommentString(commentString, tree.symbol, Some(tree.pos))
53+
5054
}

scala3doc/src/dotty/dokka/tasty/TastyParser.scala

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import dotty.tools.dotc
2424

2525
import dotty.dokka.tasty.comments.MemberLookup
2626
import dotty.dokka.tasty.comments.QueryParser
27+
import dotty.dokka.tasty.comments.Comment
2728
import dotty.dokka.model.api._
2829

2930
/** Responsible for collectively inspecting all the Tasty files we're interested in.
@@ -33,6 +34,7 @@ import dotty.dokka.model.api._
3334
case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends DocTastyInspector:
3435

3536
private val topLevels = Seq.newBuilder[(String, Member)]
37+
private var rootDoc: Option[Comment] = None
3638

3739
def processCompilationUnit(using quotes: Quotes)(root: quotes.reflect.Tree): Unit = ()
3840

@@ -84,24 +86,43 @@ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends Do
8486

8587
isSkippedById(sym) || isSkippedByRx(sym)
8688

87-
hackForeachTree { root =>
88-
if !isSkipped(root.symbol) then
89-
val parser = new TastyParser(q, this)(isSkipped)
89+
val parser = new TastyParser(q, this)(isSkipped)
90+
def driFor(link: String): Option[DRI] =
91+
val symOps = new SymOps[q.type](q)
92+
import symOps._
93+
Try(QueryParser(link).readQuery()).toOption.flatMap(q =>
94+
MemberLookup.lookupOpt(q, None).map{ case (sym, _) => sym.dri}
95+
)
96+
ctx.staticSiteContext.foreach(_.memberLinkResolver = driFor)
97+
98+
var alreadyProcessed = false
99+
def processRootDocIfNeeded(tree: parser.qctx.reflect.Tree) =
100+
def readFile(path: String)(using CompilerContext): Option[String] =
101+
try Some(IO.read(path)) catch
102+
case e: RuntimeException =>
103+
report.warning(s"Unable to read root package doc from $path: ${throwableToString(e)}")
104+
None
90105

91-
def driFor(link: String): Option[DRI] =
92-
val symOps = new SymOps[q.type](q)
93-
import symOps._
94-
Try(QueryParser(link).readQuery()).toOption.flatMap(q =>
95-
MemberLookup.lookupOpt(q, None).map{ case (sym, _) => sym.dri}
96-
)
106+
if !alreadyProcessed then
107+
alreadyProcessed = true
108+
ctx.args.rootDocPath.flatMap(readFile).map { content =>
109+
import parser.qctx.reflect._
110+
def root(s: Symbol): Symbol = if s.owner.isNoSymbol then s else root(s.owner)
111+
val topLevelPck = root(tree.symbol)
112+
rootDoc = Some(parser.parseCommentString(content, topLevelPck, None))
113+
}
97114

98-
ctx.staticSiteContext.foreach(_.memberLinkResolver = driFor)
99-
topLevels ++= parser.parseRootTree(root.asInstanceOf[parser.qctx.reflect.Tree])
115+
hackForeachTree { root =>
116+
if !isSkipped(root.symbol) then
117+
val treeRoot = root.asInstanceOf[parser.qctx.reflect.Tree]
118+
processRootDocIfNeeded(treeRoot)
119+
topLevels ++= parser.parseRootTree(treeRoot)
100120
}
101121

102122

103-
def result(): List[Member] =
123+
def result(): (List[Member], Option[Comment]) =
104124
topLevels.clear()
125+
rootDoc = None
105126
val filePaths = ctx.args.tastyFiles.map(_.getAbsolutePath).toList
106127
val classpath = ctx.args.classpath.split(java.io.File.pathSeparator).toList
107128

@@ -114,7 +135,7 @@ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends Do
114135
p1.withNewMembers(p2.allMembers) // TODO add doc
115136
)
116137
basePck.withMembers((basePck.allMembers ++ rest).sortBy(_.name))
117-
}.toList
138+
}.toList -> rootDoc
118139

119140
/** Parses a single Tasty compilation unit. */
120141
case class TastyParser(

0 commit comments

Comments
 (0)