Skip to content

Commit 72f1fcf

Browse files
authored
Merge pull request #11434 from BarkingBad/scala3doc/ux-fixes-part-2
UX improvements for scaladoc
2 parents 9254ef6 + 69e91d4 commit 72f1fcf

File tree

12 files changed

+108
-19
lines changed

12 files changed

+108
-19
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package tests
2+
package abstractmembersignatures
3+
4+
5+
trait TestTrait:
6+
def shouldBeAbstract: Int
7+
def shouldBeConcrete: Int = 1
8+
9+
class TestClass:
10+
def shouldBeConcrete: Int = 1
11+
12+
abstract class AbstractTestClass:
13+
def shouldBeAbstract: Int
14+
def shouldBeConcrete: Int = 1
15+
16+
object TestObject:
17+
abstract class AbstractInnerClass:
18+
def shouldBeAbstract: Int
19+
def shouldBeConcrete: Int = 1
20+
21+
class InnerClass:
22+
def shouldBeConcrete: Int = 1

scaladoc-testcases/src/tests/listindocstring.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
package tests
2+
package listindocstring
3+
14
/**
25
* These are useful methods that exist for both $some and $none.
3-
* * [[isDefined]] — True if not empty
6+
* - [[isDefined]] — True if not empty
47
* - [[isEmpty]] — True if empty
58
* - [[nonEmpty]] — True if not empty
69
* - [[orElse]] — Evaluate and return alternate optional value if empty
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package tests
2+
package secondaryconstructors
3+
4+
class Person(val firstName: String, val lastName: String, val age: Int):
5+
def this(firstName: String) = this(firstName, "", 0)
6+
def this(firstName: String, lastName: String) = this(firstName, lastName, 0)
7+

scaladoc/src/dotty/tools/scaladoc/SourceLinks.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ case class SourceLinks(links: Seq[SourceLink], projectRoot: Path):
122122
else resolveRelativePath(rawPath)
123123

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

127127
object SourceLinks:
128128

scaladoc/src/dotty/tools/scaladoc/api.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ enum VisibilityScope:
2828

2929
enum Modifier(val name: String, val prefix: Boolean):
3030
case Abstract extends Modifier("abstract", true)
31+
case Deferred extends Modifier("", true)
3132
case Final extends Modifier("final", true)
3233
case Empty extends Modifier("", true)
3334
case Sealed extends Modifier("sealed", true)
@@ -146,7 +147,7 @@ case class Member(
146147
modifiers: Seq[Modifier] = Nil,
147148
annotations: List[Annotation] = Nil,
148149
signature: Signature = Signature(),
149-
sources: Option[TastyDocumentableSource] = None,
150+
sources: Option[TastyMemberSource] = None,
150151
origin: Origin = Origin.RegularlyDefined,
151152
inheritedFrom: Option[InheritedFrom] = None,
152153
graph: HierarchyGraph = HierarchyGraph.empty,
@@ -231,4 +232,4 @@ extension (s: Signature)
231232
case l: Link => l.name
232233
}.mkString
233234

234-
case class TastyDocumentableSource(val path: String, val lineNumber: Int)
235+
case class TastyMemberSource(val path: java.nio.file.Path, val lineNumber: Int)

scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext
6868

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

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

126-
def memberSingnature(member: Member) =
126+
def memberSignature(member: Member) =
127127
val depStyle = if member.deprecated.isEmpty then "" else "deprecated"
128128
val nameClasses = cls := s"documentableName $depStyle"
129129

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

205+
private def isAbstract(m: Member | MGroup): Boolean = m match
206+
case m: Member => m.modifiers.exists(Set(Modifier.Abstract, Modifier.Deferred).contains)
207+
case g: MGroup => g.members.exists(isAbstract)
208+
205209
private type SubGroup = (String, Seq[Member | MGroup])
206210
private def buildGroup(name: String, subgroups: Seq[SubGroup]): Tab =
207211
val all = subgroups.map { case (name, members) =>
208212
val (allInherited, allDefined) = members.partition(isInherited)
209213
val (depDefined, defined) = allDefined.partition(isDeprecated)
210214
val (depInherited, inherited) = allInherited.partition(isDeprecated)
211-
(
212-
actualGroup(name, defined),
213-
actualGroup(s"Deprecated ${name.toLowerCase}", depDefined),
214-
actualGroup(s"Inherited ${name.toLowerCase}", inherited),
215-
actualGroup(s"Deprecated and Inherited ${name.toLowerCase}", depInherited)
215+
val normalizedName = name.toLowerCase
216+
val definedWithGroup = if Set("methods", "fields").contains(normalizedName) then
217+
val (abstr, concr) = defined.partition(isAbstract)
218+
Seq(
219+
actualGroup(s"Abstract ${normalizedName}", abstr),
220+
actualGroup(s"Concrete ${normalizedName}", concr)
221+
)
222+
else
223+
Seq(actualGroup(name, defined))
224+
225+
definedWithGroup ++ List(
226+
actualGroup(s"Deprecated ${normalizedName}", depDefined),
227+
actualGroup(s"Inherited ${normalizedName}", inherited),
228+
actualGroup(s"Deprecated and Inherited ${normalizedName}", depInherited)
216229
)
217230
}
218231

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

@@ -363,7 +375,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext
363375
),
364376
div(cls := "signature monospace")(
365377
annotations(m),
366-
memberSingnature(m)
378+
memberSignature(m)
367379
)
368380
)
369381

scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ trait BasicSupport:
3636
def documentation = sym.docstring.map(parseComment(_, sym.tree))
3737

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

4242
def getAnnotations(): List[Annotation] =
4343
sym.annotations.filterNot(_.symbol.packageName.startsWith("scala.annotation.internal")).map(parseAnnotation).reverse

scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class SymOps[Q <: Quotes](val q: Q):
6565
Flags.Sealed -> Modifier.Sealed,
6666
Flags.Erased -> Modifier.Erased,
6767
Flags.Abstract -> Modifier.Abstract,
68+
Flags.Deferred -> Modifier.Deferred,
6869
Flags.Implicit -> Modifier.Implicit,
6970
Flags.Inline -> Modifier.Inline,
7071
Flags.Lazy -> Modifier.Lazy,

scaladoc/src/dotty/tools/scaladoc/tasty/comments/MarkdownParser.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ object MarkdownParser {
3636
) ++ additionalExtensions
3737

3838
new MutableDataSet()
39-
.setFrom(ParserEmulationProfile.KRAMDOWN.getOptions)
39+
.setFrom(ParserEmulationProfile.COMMONMARK.getOptions)
4040
.set(Parser.EXTENSIONS, Arrays.asList(extArray:_*))
4141
.set(EmojiExtension.ROOT_IMAGE_PATH,
4242
"https://github.global.ssl.fastly.net/images/icons/emoji/")
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dotty.tools.scaladoc
2+
package signatures
3+
4+
import scala.io.Source
5+
import scala.jdk.CollectionConverters._
6+
import scala.util.matching.Regex
7+
import dotty.tools.scaladoc.test.BuildInfo
8+
import java.nio.file.Path;
9+
import org.jsoup.Jsoup
10+
import util.IO
11+
import org.junit.Assert.assertTrue
12+
13+
class AbstractMembers extends ScaladocTest("abstractmembersignatures"):
14+
15+
def runTest = {
16+
afterRendering {
17+
val actualSignatures = signaturesFromDocumentation()
18+
19+
actualSignatures.foreach { (k, v) => k match
20+
case "Abstract methods" => assertTrue(v.forall(_._2 == "shouldBeAbstract"))
21+
case "Concrete methods" => assertTrue(v.forall(_._2 == "shouldBeConcrete"))
22+
case "Classlikes" => assertTrue(v.forall((m, n) => m.contains("abstract") == n.contains("Abstract")))
23+
case _ =>
24+
}
25+
}
26+
}
27+
28+
private def signaturesFromDocumentation()(using DocContext): Map[String, List[(String, String)]] =
29+
val output = summon[DocContext].args.output.toPath.resolve("api")
30+
val signatures = List.newBuilder[(String, (String, String))]
31+
def processFile(path: Path): Unit =
32+
val document = Jsoup.parse(IO.read(path))
33+
val content = document.select(".documentableList").forEach { elem =>
34+
val group = elem.select(".groupHeader").eachText.asScala.mkString("")
35+
elem.select(".documentableElement").forEach { elem =>
36+
val modifiers = elem.select(".header .other-modifiers").eachText.asScala.mkString("")
37+
val name = elem.select(".header .documentableName").eachText.asScala.mkString("")
38+
signatures += group -> (modifiers, name)
39+
}
40+
}
41+
IO.foreachFileIn(output, processFile)
42+
signatures.result.groupMap(_._1)(_._2)

scaladoc/test/dotty/tools/scaladoc/SignatureTest.scala renamed to scaladoc/test/dotty/tools/scaladoc/signatures/SignatureTest.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package dotty.tools.scaladoc
2+
package signatures
23

34
import scala.io.Source
45
import scala.jdk.CollectionConverters._

scaladoc/test/dotty/tools/scaladoc/SignatureTestCases.scala renamed to scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dotty.tools.scaladoc
1+
package dotty.tools.scaladoc.signatures
22

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

0 commit comments

Comments
 (0)