diff --git a/scaladoc-testcases/src/tests/abstractmembersignatures.scala b/scaladoc-testcases/src/tests/abstractmembersignatures.scala new file mode 100644 index 000000000000..a32336d8f76d --- /dev/null +++ b/scaladoc-testcases/src/tests/abstractmembersignatures.scala @@ -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 diff --git a/scaladoc-testcases/src/tests/listindocstring.scala b/scaladoc-testcases/src/tests/listindocstring.scala index 58574d9ab2e2..261817ddb61c 100644 --- a/scaladoc-testcases/src/tests/listindocstring.scala +++ b/scaladoc-testcases/src/tests/listindocstring.scala @@ -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 diff --git a/scaladoc-testcases/src/tests/secondaryconstructor.scala b/scaladoc-testcases/src/tests/secondaryconstructor.scala new file mode 100644 index 000000000000..41a32469c7bf --- /dev/null +++ b/scaladoc-testcases/src/tests/secondaryconstructor.scala @@ -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) + diff --git a/scaladoc/src/dotty/tools/scaladoc/SourceLinks.scala b/scaladoc/src/dotty/tools/scaladoc/SourceLinks.scala index 825f1dba4060..7f0cdc830fb7 100644 --- a/scaladoc/src/dotty/tools/scaladoc/SourceLinks.scala +++ b/scaladoc/src/dotty/tools/scaladoc/SourceLinks.scala @@ -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: diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index 51f923323826..18715b511cd2 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -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) @@ -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, @@ -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) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala index 560f50b259b7..cdc23bf54359 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala @@ -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 => @@ -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" @@ -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( @@ -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") @@ -363,7 +375,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext ), div(cls := "signature monospace")( annotations(m), - memberSingnature(m) + memberSignature(m) ) ) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala index 1bef73d54dbe..43d28a80606f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala @@ -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 diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index 40b8b157bdd2..178fd9656d5e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -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, diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MarkdownParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MarkdownParser.scala index 8ed84930c4e0..11f8c49eebe8 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MarkdownParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MarkdownParser.scala @@ -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/") diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/AbstractMemberSignaturesTest.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/AbstractMemberSignaturesTest.scala new file mode 100644 index 000000000000..0d47c9ee6560 --- /dev/null +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/AbstractMemberSignaturesTest.scala @@ -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) diff --git a/scaladoc/test/dotty/tools/scaladoc/SignatureTest.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/SignatureTest.scala similarity index 99% rename from scaladoc/test/dotty/tools/scaladoc/SignatureTest.scala rename to scaladoc/test/dotty/tools/scaladoc/signatures/SignatureTest.scala index 21a19f477439..05fa7164508e 100644 --- a/scaladoc/test/dotty/tools/scaladoc/SignatureTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/SignatureTest.scala @@ -1,4 +1,5 @@ package dotty.tools.scaladoc +package signatures import scala.io.Source import scala.jdk.CollectionConverters._ diff --git a/scaladoc/test/dotty/tools/scaladoc/SignatureTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala similarity index 98% rename from scaladoc/test/dotty/tools/scaladoc/SignatureTestCases.scala rename to scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index 4a85b191dc92..92c48dcf5b32 100644 --- a/scaladoc/test/dotty/tools/scaladoc/SignatureTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -1,4 +1,4 @@ -package dotty.tools.scaladoc +package dotty.tools.scaladoc.signatures class GenericSignaftures extends SignatureTest("genericSignatures", Seq("class"))