Skip to content

Commit 14d4d84

Browse files
authored
Merge pull request #10659 from dotty-staging/scala3doc/cli-skip-setting
Scala3doc: make skipping documenting a def a CLI setting
2 parents 90a6d36 + 397bc5a commit 14d4d84

File tree

4 files changed

+104
-27
lines changed

4 files changed

+104
-27
lines changed

project/Build.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,15 @@ object Build {
16051605
IO.write(dest / "CNAME", "dotty.epfl.ch")
16061606
}.dependsOn(generateDocumentation(
16071607
roots, "Scala 3", dest.getAbsolutePath, "master",
1608+
// contains special definitions which are "transplanted" elsewhere
1609+
// and which therefore confuse Scala3doc when accessed from this pkg
1610+
"-skip-by-id:scala.runtime.stdLibPatches " +
1611+
// MatchCase is a special type that represents match type cases,
1612+
// Reflect doesn't expect to see it as a standalone definition
1613+
// and therefore it's easier just not to document it
1614+
"-skip-by-id:scala.runtime.MatchCase " +
1615+
"-skip-by-regex:.+\\.internal($|\\..+) " +
1616+
"-skip-by-regex:.+\\.impl($|\\..+) " +
16081617
"-comment-syntax wiki -siteroot scala3doc/scala3-docs -project-logo scala3doc/scala3-docs/logo.svg " +
16091618
"-external-mappings " + raw".*java.*" + "::" +
16101619
"javadoc" + "::" +

scala3doc/src/dotty/dokka/Scala3doc.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ object Scala3doc:
6565
defaultSyntax: CommentSyntax = CommentSyntax.Markdown,
6666
sourceLinks: List[String] = Nil,
6767
revision: Option[String] = None,
68-
externalMappings: List[List[String]] = List.empty
68+
externalMappings: List[List[String]] = List.empty,
69+
identifiersToSkip: List[String] = Nil,
70+
regexesToSkip: List[String] = Nil,
6971
)
7072

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

scala3doc/src/dotty/dokka/Scala3docArgs.scala

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ class Scala3docArgs extends SettingGroup with CommonScalaSettings:
3737
val externalDocumentationMappings: Setting[String] =
3838
StringSetting("-external-mappings", "external-mappings", "Mapping between regex matching class file and external documentation", "")
3939

40-
def scala3docSpecificSettings: Set[Setting[_]] = Set(sourceLinks, syntax, revision, externalDocumentationMappings)
40+
val skipById: Setting[List[String]] =
41+
MultiStringSetting("-skip-by-id", "package or class identifier", "Identifiers of packages or top-level classes to skip when generating documentation")
42+
43+
val skipByRegex: Setting[List[String]] =
44+
MultiStringSetting("-skip-by-regex", "regex", "Regexes that match fully qualified names of packages or top-level classes to skip when generating documentation")
45+
46+
def scala3docSpecificSettings: Set[Setting[_]] = Set(sourceLinks, syntax, revision, externalDocumentationMappings, skipById, skipByRegex)
4147

4248
object Scala3docArgs:
4349
def extract(args: List[String], rootCtx: CompilerContext):(Scala3doc.Args, CompilerContext) =
@@ -76,7 +82,7 @@ object Scala3docArgs:
7682
val (existing, nonExisting) = inFiles.partition(_.exists)
7783

7884
if nonExisting.nonEmpty then report.warning(
79-
s"Scala3doc will ignore following nonexisiten paths: ${nonExisting.mkString(", ")}"
85+
s"Scala3doc will ignore following non-existent paths: ${nonExisting.mkString(", ")}"
8086
)
8187

8288
val (dirs, files) = existing.partition(_.isDirectory)
@@ -120,6 +126,8 @@ object Scala3docArgs:
120126
parseSyntax,
121127
sourceLinks.nonDefault.fold(Nil)(_.split(",").toList),
122128
revision.nonDefault,
123-
externalMappings
129+
externalMappings,
130+
skipById.get,
131+
skipByRegex.get,
124132
)
125-
(docArgs, newContext)
133+
(docArgs, newContext)

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

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ import org.jetbrains.dokka.model.properties.PropertyContainer
1414
import org.jetbrains.dokka.model.properties.PropertyContainerKt._
1515
import org.jetbrains.dokka.model.properties.{WithExtraProperties}
1616

17-
import quoted.Quotes
17+
import java.util.regex.Pattern
18+
19+
import scala.util.{Try, Success, Failure}
1820
import scala.tasty.inspector.DocTastyInspector
19-
import dotty.dokka.model.api.withNewMembers
21+
import scala.quoted.Quotes
22+
23+
import dotty.tools.dotc
24+
2025
import dotty.dokka.tasty.comments.MemberLookup
2126
import dotty.dokka.tasty.comments.QueryParser
22-
import scala.util.Try
2327
import dotty.dokka.model.api._
2428

2529
/** Responsible for collectively inspecting all the Tasty files we're interested in.
@@ -30,23 +34,71 @@ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends Do
3034

3135
private val topLevels = Seq.newBuilder[(String, Member)]
3236

33-
def processCompilationUnit(using q: Quotes)(root: q.reflect.Tree): Unit =
34-
// NOTE we avoid documenting definitions in the magical stdLibPatches directory;
35-
// the symbols there are "patched" through dark Dotty magic onto other stdlib
36-
// definitions, so if we documented their origin, we'd get defs with duplicate DRIs
37-
if !root.symbol.fullName.startsWith("scala.runtime.stdLibPatches") then
38-
val parser = new TastyParser(q, this)
39-
40-
def driFor(link: String): Option[DRI] =
41-
val symOps = new SymOps[q.type](q)
42-
import symOps._
43-
Try(QueryParser(link).readQuery()).toOption.flatMap(q =>
44-
MemberLookup.lookupOpt(q, None).map{ case (sym, _) => sym.dri}
45-
)
37+
def processCompilationUnit(using quotes: Quotes)(root: quotes.reflect.Tree): Unit = ()
38+
39+
override def postProcess(using q: Quotes): Unit =
40+
// hack into the compiler to get a list of all top-level trees
41+
// in principle, to do this, one would collect trees in processCompilationUnit
42+
// however, path-dependent types disallow doing so w/o using casts
43+
inline def hackForeachTree(thunk: q.reflect.Tree => Unit): Unit =
44+
given dctx: dotc.core.Contexts.Context = q.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
45+
dctx.run.units.foreach { compilationUnit =>
46+
// mirrors code from TastyInspector
47+
thunk(compilationUnit.tpdTree.asInstanceOf[q.reflect.Tree])
48+
}
49+
50+
val symbolsToSkip: Set[q.reflect.Symbol] =
51+
ctx.args.identifiersToSkip.flatMap { ref =>
52+
val qrSymbol = q.reflect.Symbol
53+
Try(qrSymbol.requiredPackage(ref)).orElse(Try(qrSymbol.requiredClass(ref))) match {
54+
case Success(sym) => Some(sym)
55+
case Failure(err) =>
56+
report.warning(
57+
s"Failed to resolve identifier to skip - $ref - because: ${throwableToString(err)}",
58+
dotc.util.NoSourcePosition,
59+
)
60+
None
61+
}
62+
}.toSet
63+
64+
val patternsToSkip: List[Pattern] =
65+
ctx.args.regexesToSkip.flatMap { regexString =>
66+
Try(Pattern.compile(regexString)) match
67+
case Success(pat) => Some(pat)
68+
case Failure(err) =>
69+
report.warning(
70+
s"Failed to compile regex to skip - $regexString - because: ${throwableToString(err)}",
71+
dotc.util.NoSourcePosition,
72+
)
73+
None
74+
}
75+
76+
def isSkipped(sym: q.reflect.Symbol): Boolean =
77+
def isSkippedById(sym: q.reflect.Symbol): Boolean =
78+
if !sym.exists then false else
79+
symbolsToSkip.contains(sym) || isSkipped(sym.owner)
80+
81+
def isSkippedByRx(sym: q.reflect.Symbol): Boolean =
82+
val symStr = sym.fullName
83+
patternsToSkip.exists(p => p.matcher(symStr).matches())
84+
85+
isSkippedById(sym) || isSkippedByRx(sym)
86+
87+
hackForeachTree { root =>
88+
if !isSkipped(root.symbol) then
89+
val parser = new TastyParser(q, this)(isSkipped)
90+
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+
)
97+
98+
ctx.staticSiteContext.foreach(_.memberLinkResolver = driFor)
99+
topLevels ++= parser.parseRootTree(root.asInstanceOf[parser.qctx.reflect.Tree])
100+
}
46101

47-
ctx.staticSiteContext.foreach(_.memberLinkResolver = driFor)
48-
topLevels ++= parser.parseRootTree(root.asInstanceOf[parser.qctx.reflect.Tree])
49-
end processCompilationUnit
50102

51103
def result(): List[Member] =
52104
topLevels.clear()
@@ -65,8 +117,14 @@ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends Do
65117
}.toList
66118

67119
/** Parses a single Tasty compilation unit. */
68-
case class TastyParser(qctx: Quotes, inspector: DokkaTastyInspector)(using val ctx: DocContext)
69-
extends ScaladocSupport with BasicSupport with TypesSupport with ClassLikeSupport with SyntheticsSupport with PackageSupport with NameNormalizer:
120+
case class TastyParser(
121+
qctx: Quotes,
122+
inspector: DokkaTastyInspector,
123+
)(
124+
isSkipped: qctx.reflect.Symbol => Boolean
125+
)(
126+
using val ctx: DocContext
127+
) extends ScaladocSupport with BasicSupport with TypesSupport with ClassLikeSupport with SyntheticsSupport with PackageSupport with NameNormalizer:
70128
import qctx.reflect._
71129

72130
def processTree[T](tree: Tree)(op: => T): Option[T] = try Option(op) catch
@@ -92,7 +150,7 @@ case class TastyParser(qctx: Quotes, inspector: DokkaTastyInspector)(using val c
92150

93151
override def traverseTree(tree: Tree)(owner: Symbol): Unit =
94152
seen = tree :: seen
95-
tree match {
153+
if !isSkipped(tree.symbol) then tree match {
96154
case pck: PackageClause =>
97155
docs += parsePackage(pck)
98156
super.traverseTree(tree)(owner)

0 commit comments

Comments
 (0)