Skip to content

UX improvements for scaladoc #11434

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 2 commits into from
Feb 19, 2021
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
22 changes: 22 additions & 0 deletions scaladoc-testcases/src/tests/abstractmembersignatures.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package tests
package abstractmembersignatures


trait TestTrait:
def shouldBeAbstract: Int
def shouldBeConcrete: Int = 1

class TestClass:
def shouldBeConcrete: Int = 1

abstract class AbstractTestClass:
def shouldBeAbstract: Int
def shouldBeConcrete: Int = 1

object TestObject:
abstract class AbstractInnerClass:
def shouldBeAbstract: Int
def shouldBeConcrete: Int = 1

class InnerClass:
def shouldBeConcrete: Int = 1
5 changes: 4 additions & 1 deletion scaladoc-testcases/src/tests/listindocstring.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package tests
package listindocstring

/**
* These are useful methods that exist for both $some and $none.
* * [[isDefined]] — True if not empty
* - [[isDefined]] — True if not empty
* - [[isEmpty]] — True if empty
* - [[nonEmpty]] — True if not empty
* - [[orElse]] — Evaluate and return alternate optional value if empty
Expand Down
7 changes: 7 additions & 0 deletions scaladoc-testcases/src/tests/secondaryconstructor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package tests
package secondaryconstructors

class Person(val firstName: String, val lastName: String, val age: Int):
def this(firstName: String) = this(firstName, "", 0)
def this(firstName: String, lastName: String) = this(firstName, lastName, 0)

2 changes: 1 addition & 1 deletion scaladoc/src/dotty/tools/scaladoc/SourceLinks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ case class SourceLinks(links: Seq[SourceLink], projectRoot: Path):
else resolveRelativePath(rawPath)

def pathTo(member: Member): Option[String] =
member.sources.flatMap(s => pathTo(Paths.get(s.path), member.name, Option(s.lineNumber).map(_ + 1)))
member.sources.flatMap(s => pathTo(s.path, member.name, Option(s.lineNumber).map(_ + 1)))

object SourceLinks:

Expand Down
5 changes: 3 additions & 2 deletions scaladoc/src/dotty/tools/scaladoc/api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum VisibilityScope:

enum Modifier(val name: String, val prefix: Boolean):
case Abstract extends Modifier("abstract", true)
case Deferred extends Modifier("", true)
case Final extends Modifier("final", true)
case Empty extends Modifier("", true)
case Sealed extends Modifier("sealed", true)
Expand Down Expand Up @@ -146,7 +147,7 @@ case class Member(
modifiers: Seq[Modifier] = Nil,
annotations: List[Annotation] = Nil,
signature: Signature = Signature(),
sources: Option[TastyDocumentableSource] = None,
sources: Option[TastyMemberSource] = None,
origin: Origin = Origin.RegularlyDefined,
inheritedFrom: Option[InheritedFrom] = None,
graph: HierarchyGraph = HierarchyGraph.empty,
Expand Down Expand Up @@ -231,4 +232,4 @@ extension (s: Signature)
case l: Link => l.name
}.mkString

case class TastyDocumentableSource(val path: String, val lineNumber: Int)
case class TastyMemberSource(val path: java.nio.file.Path, val lineNumber: Int)
34 changes: 23 additions & 11 deletions scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext

def source(m: Member): Seq[AppliedTag] =
summon[DocContext].sourceLinks.pathTo(m).fold(Nil){ link =>
tableRow("Source", a(href := link)("(source)"))
tableRow("Source", a(href := link)(m.sources.fold("(source)")(_.path.getFileName().toString())))
}

def deprecation(m: Member): Seq[AppliedTag] = m.deprecated.fold(Nil){ a =>
Expand Down Expand Up @@ -123,7 +123,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext
case _ => Nil
}

def memberSingnature(member: Member) =
def memberSignature(member: Member) =
val depStyle = if member.deprecated.isEmpty then "" else "deprecated"
val nameClasses = cls := s"documentableName $depStyle"

Expand Down Expand Up @@ -164,7 +164,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext
div(topLevelAttr:_*)(
a(href := (if member.needsOwnPage then link(member.dri).getOrElse("#") else s"#${member.dri.anchor}"), cls := "documentableAnchor"),
div(annotations(member)),
div(cls := "header monospace")(memberSingnature(member)),
div(cls := "header monospace")(memberSignature(member)),
div(cls := "docs")(
span(cls := "modifiers"), // just to have padding on left
div(
Expand Down Expand Up @@ -202,22 +202,34 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext
case m: Member => m.inheritedFrom.nonEmpty
case g: MGroup => g.members.exists(isInherited)

private def isAbstract(m: Member | MGroup): Boolean = m match
case m: Member => m.modifiers.exists(Set(Modifier.Abstract, Modifier.Deferred).contains)
case g: MGroup => g.members.exists(isAbstract)

private type SubGroup = (String, Seq[Member | MGroup])
private def buildGroup(name: String, subgroups: Seq[SubGroup]): Tab =
val all = subgroups.map { case (name, members) =>
val (allInherited, allDefined) = members.partition(isInherited)
val (depDefined, defined) = allDefined.partition(isDeprecated)
val (depInherited, inherited) = allInherited.partition(isDeprecated)
(
actualGroup(name, defined),
actualGroup(s"Deprecated ${name.toLowerCase}", depDefined),
actualGroup(s"Inherited ${name.toLowerCase}", inherited),
actualGroup(s"Deprecated and Inherited ${name.toLowerCase}", depInherited)
val normalizedName = name.toLowerCase
val definedWithGroup = if Set("methods", "fields").contains(normalizedName) then
val (abstr, concr) = defined.partition(isAbstract)
Seq(
actualGroup(s"Abstract ${normalizedName}", abstr),
actualGroup(s"Concrete ${normalizedName}", concr)
)
else
Seq(actualGroup(name, defined))

definedWithGroup ++ List(
actualGroup(s"Deprecated ${normalizedName}", depDefined),
actualGroup(s"Inherited ${normalizedName}", inherited),
actualGroup(s"Deprecated and Inherited ${normalizedName}", depInherited)
)
}

val children =
all.flatMap(_._1) ++ all.flatMap(_._2) ++ all.flatMap(_._3) ++ all.flatMap(_._4)
val children = all.flatten.flatten
if children.isEmpty then emptyTab
else Tab(name, name, h2(tabAttr(name))(name) +: children, "selected")

Expand Down Expand Up @@ -363,7 +375,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext
),
div(cls := "signature monospace")(
annotations(m),
memberSingnature(m)
memberSignature(m)
)
)

Expand Down
4 changes: 2 additions & 2 deletions scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ trait BasicSupport:
def documentation = sym.docstring.map(parseComment(_, sym.tree))

def source(using Quotes) =
val path = sym.pos.map(_.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath).map(_.toString)
path.map(TastyDocumentableSource(_, sym.pos.get.startLine))
val path = sym.pos.map(_.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath)
path.map(TastyMemberSource(_, sym.pos.get.startLine))

def getAnnotations(): List[Annotation] =
sym.annotations.filterNot(_.symbol.packageName.startsWith("scala.annotation.internal")).map(parseAnnotation).reverse
Expand Down
1 change: 1 addition & 0 deletions scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class SymOps[Q <: Quotes](val q: Q):
Flags.Sealed -> Modifier.Sealed,
Flags.Erased -> Modifier.Erased,
Flags.Abstract -> Modifier.Abstract,
Flags.Deferred -> Modifier.Deferred,
Flags.Implicit -> Modifier.Implicit,
Flags.Inline -> Modifier.Inline,
Flags.Lazy -> Modifier.Lazy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object MarkdownParser {
) ++ additionalExtensions

new MutableDataSet()
.setFrom(ParserEmulationProfile.KRAMDOWN.getOptions)
.setFrom(ParserEmulationProfile.COMMONMARK.getOptions)
.set(Parser.EXTENSIONS, Arrays.asList(extArray:_*))
.set(EmojiExtension.ROOT_IMAGE_PATH,
"https://github.global.ssl.fastly.net/images/icons/emoji/")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dotty.tools.scaladoc
package signatures

import scala.io.Source
import scala.jdk.CollectionConverters._
import scala.util.matching.Regex
import dotty.tools.scaladoc.test.BuildInfo
import java.nio.file.Path;
import org.jsoup.Jsoup
import util.IO
import org.junit.Assert.assertTrue

class AbstractMembers extends ScaladocTest("abstractmembersignatures"):

def runTest = {
afterRendering {
val actualSignatures = signaturesFromDocumentation()

actualSignatures.foreach { (k, v) => k match
case "Abstract methods" => assertTrue(v.forall(_._2 == "shouldBeAbstract"))
case "Concrete methods" => assertTrue(v.forall(_._2 == "shouldBeConcrete"))
case "Classlikes" => assertTrue(v.forall((m, n) => m.contains("abstract") == n.contains("Abstract")))
case _ =>
}
}
}

private def signaturesFromDocumentation()(using DocContext): Map[String, List[(String, String)]] =
val output = summon[DocContext].args.output.toPath.resolve("api")
val signatures = List.newBuilder[(String, (String, String))]
def processFile(path: Path): Unit =
val document = Jsoup.parse(IO.read(path))
val content = document.select(".documentableList").forEach { elem =>
val group = elem.select(".groupHeader").eachText.asScala.mkString("")
elem.select(".documentableElement").forEach { elem =>
val modifiers = elem.select(".header .other-modifiers").eachText.asScala.mkString("")
val name = elem.select(".header .documentableName").eachText.asScala.mkString("")
signatures += group -> (modifiers, name)
}
}
IO.foreachFileIn(output, processFile)
signatures.result.groupMap(_._1)(_._2)
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package dotty.tools.scaladoc
package signatures

import scala.io.Source
import scala.jdk.CollectionConverters._
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dotty.tools.scaladoc
package dotty.tools.scaladoc.signatures

class GenericSignaftures extends SignatureTest("genericSignatures", Seq("class"))

Expand Down