Skip to content

Scala3doc: make skipping documenting a def a CLI setting #10659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,15 @@ object Build {
IO.write(dest / "CNAME", "dotty.epfl.ch")
}.dependsOn(generateDocumentation(
roots, "Scala 3", dest.getAbsolutePath, "master",
// contains special definitions which are "transplanted" elsewhere
// and which therefore confuse Scala3doc when accessed from this pkg
"-skip-by-id:scala.runtime.stdLibPatches " +
// MatchCase is a special type that represents match type cases,
// Reflect doesn't expect to see it as a standalone definition
// and therefore it's easier just not to document it
"-skip-by-id:scala.runtime.MatchCase " +
"-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" + "::" +
Expand Down
4 changes: 3 additions & 1 deletion scala3doc/src/dotty/dokka/Scala3doc.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ object Scala3doc:
defaultSyntax: CommentSyntax = CommentSyntax.Markdown,
sourceLinks: List[String] = Nil,
revision: Option[String] = None,
externalMappings: List[List[String]] = List.empty
externalMappings: List[List[String]] = List.empty,
identifiersToSkip: List[String] = Nil,
regexesToSkip: List[String] = Nil,
)

def run(args: Array[String], rootContext: CompilerContext): Reporter =
Expand Down
16 changes: 12 additions & 4 deletions scala3doc/src/dotty/dokka/Scala3docArgs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ class Scala3docArgs extends SettingGroup with CommonScalaSettings:
val externalDocumentationMappings: Setting[String] =
StringSetting("-external-mappings", "external-mappings", "Mapping between regex matching class file and external documentation", "")

def scala3docSpecificSettings: Set[Setting[_]] = Set(sourceLinks, syntax, revision, externalDocumentationMappings)
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")

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)

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

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

val (dirs, files) = existing.partition(_.isDirectory)
Expand Down Expand Up @@ -120,6 +126,8 @@ object Scala3docArgs:
parseSyntax,
sourceLinks.nonDefault.fold(Nil)(_.split(",").toList),
revision.nonDefault,
externalMappings
externalMappings,
skipById.get,
skipByRegex.get,
)
(docArgs, newContext)
(docArgs, newContext)
102 changes: 80 additions & 22 deletions scala3doc/src/dotty/dokka/tasty/TastyParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.model.properties.PropertyContainerKt._
import org.jetbrains.dokka.model.properties.{WithExtraProperties}

import quoted.Quotes
import java.util.regex.Pattern

import scala.util.{Try, Success, Failure}
import scala.tasty.inspector.DocTastyInspector
import dotty.dokka.model.api.withNewMembers
import scala.quoted.Quotes

import dotty.tools.dotc

import dotty.dokka.tasty.comments.MemberLookup
import dotty.dokka.tasty.comments.QueryParser
import scala.util.Try
import dotty.dokka.model.api._

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

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

def processCompilationUnit(using q: Quotes)(root: q.reflect.Tree): Unit =
// NOTE we avoid documenting definitions in the magical stdLibPatches directory;
// the symbols there are "patched" through dark Dotty magic onto other stdlib
// definitions, so if we documented their origin, we'd get defs with duplicate DRIs
if !root.symbol.fullName.startsWith("scala.runtime.stdLibPatches") then
val parser = new TastyParser(q, this)

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}
)
def processCompilationUnit(using quotes: Quotes)(root: quotes.reflect.Tree): Unit = ()

override def postProcess(using q: Quotes): Unit =
// hack into the compiler to get a list of all top-level trees
// in principle, to do this, one would collect trees in processCompilationUnit
// however, path-dependent types disallow doing so w/o using casts
inline def hackForeachTree(thunk: q.reflect.Tree => Unit): Unit =
given dctx: dotc.core.Contexts.Context = q.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
dctx.run.units.foreach { compilationUnit =>
// mirrors code from TastyInspector
thunk(compilationUnit.tpdTree.asInstanceOf[q.reflect.Tree])
}

val symbolsToSkip: Set[q.reflect.Symbol] =
ctx.args.identifiersToSkip.flatMap { ref =>
val qrSymbol = q.reflect.Symbol
Try(qrSymbol.requiredPackage(ref)).orElse(Try(qrSymbol.requiredClass(ref))) match {
case Success(sym) => Some(sym)
case Failure(err) =>
report.warning(
s"Failed to resolve identifier to skip - $ref - because: ${throwableToString(err)}",
dotc.util.NoSourcePosition,
)
None
}
}.toSet

val patternsToSkip: List[Pattern] =
ctx.args.regexesToSkip.flatMap { regexString =>
Try(Pattern.compile(regexString)) match
case Success(pat) => Some(pat)
case Failure(err) =>
report.warning(
s"Failed to compile regex to skip - $regexString - because: ${throwableToString(err)}",
dotc.util.NoSourcePosition,
)
None
}

def isSkipped(sym: q.reflect.Symbol): Boolean =
def isSkippedById(sym: q.reflect.Symbol): Boolean =
if !sym.exists then false else
symbolsToSkip.contains(sym) || isSkipped(sym.owner)

def isSkippedByRx(sym: q.reflect.Symbol): Boolean =
val symStr = sym.fullName
patternsToSkip.exists(p => p.matcher(symStr).matches())

isSkippedById(sym) || isSkippedByRx(sym)

hackForeachTree { root =>
if !isSkipped(root.symbol) then
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)
topLevels ++= parser.parseRootTree(root.asInstanceOf[parser.qctx.reflect.Tree])
}

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

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

/** Parses a single Tasty compilation unit. */
case class TastyParser(qctx: Quotes, inspector: DokkaTastyInspector)(using val ctx: DocContext)
extends ScaladocSupport with BasicSupport with TypesSupport with ClassLikeSupport with SyntheticsSupport with PackageSupport with NameNormalizer:
case class TastyParser(
qctx: Quotes,
inspector: DokkaTastyInspector,
)(
isSkipped: qctx.reflect.Symbol => Boolean
)(
using val ctx: DocContext
) extends ScaladocSupport with BasicSupport with TypesSupport with ClassLikeSupport with SyntheticsSupport with PackageSupport with NameNormalizer:
import qctx.reflect._

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

override def traverseTree(tree: Tree)(owner: Symbol): Unit =
seen = tree :: seen
tree match {
if !isSkipped(tree.symbol) then tree match {
case pck: PackageClause =>
docs += parsePackage(pck)
super.traverseTree(tree)(owner)
Expand Down