diff --git a/scala3doc-testcases/src/tests/hierarchy.scala b/scala3doc-testcases/src/tests/hierarchy.scala new file mode 100644 index 000000000000..f7d7119e354c --- /dev/null +++ b/scala3doc-testcases/src/tests/hierarchy.scala @@ -0,0 +1,21 @@ +package tests + +package hierarchy + +trait A1 +trait A2[T] +trait A3[A, B] + + +trait B1 extends A1 +trait B2 extends A1 with A2[Int] +trait B3 extends A2[Int] with A3[Int, String] + +class C1[A, B, C] extends B1 with B2 with B3 + +trait D1 +trait D2[T, R] +trait D3 + +class E1 extends C1[Int, String, Boolean] with D1 +class E2 extends C1[Int, Boolean, Any] with D2[Int, Boolean] with D3 diff --git a/scala3doc/src/dotty/dokka/model/api/membersUtils.scala b/scala3doc/src/dotty/dokka/model/api/membersUtils.scala new file mode 100644 index 000000000000..a780876b106f --- /dev/null +++ b/scala3doc/src/dotty/dokka/model/api/membersUtils.scala @@ -0,0 +1,27 @@ +package dotty.dokka.model.api + +import org.jetbrains.dokka.model.DModule +import scala.jdk.CollectionConverters.{ListHasAsScala, SeqHasAsJava} +import dotty.dokka.model.api._ + +extension (m: DModule) + def visitMembers(callback: Member => Unit): Unit = + def visitClasslike(c: Member): Unit = + callback(c) + c.allMembers.foreach(visitClasslike(_)) + m.getPackages.asScala.foreach(_.allMembers.foreach(visitClasslike(_))) + +extension (s: Signature) + def getName: String = + s.map { + case s: String => s + case l: Link => l.name + }.mkString + +extension (m: Member): + def getDirectParentsAsStrings: Seq[String] = + m.directParents.map(_.getName).sorted + def getParentsAsStrings: Seq[String] = + m.parents.map(_.signature.getName).sorted + def getKnownChildrenAsStrings: Seq[String] = + m.knownChildren.map(_.signature.getName).sorted diff --git a/scala3doc/test/dotty/dokka/diagram/HierarchyTest.scala b/scala3doc/test/dotty/dokka/diagram/HierarchyTest.scala new file mode 100644 index 000000000000..e2b166ddda19 --- /dev/null +++ b/scala3doc/test/dotty/dokka/diagram/HierarchyTest.scala @@ -0,0 +1,98 @@ +package dotty.dokka.diagram + +import dotty.dokka.ScaladocTest +import dotty.dokka.Assertion.AfterDocumentablesTransformation +import dotty.dokka.kUnit +import dotty.dokka.model.api._ +import scala.jdk.CollectionConverters.{ListHasAsScala, SeqHasAsJava} +import org.junit.Assert.{assertSame, assertTrue, assertEquals} + +class HierarchyTest extends ScaladocTest("hierarchy"): + override def assertions = Seq( + AfterDocumentablesTransformation { m => + m.visitMembers { x => + if (x.getName == "C1") { + assertEquals(List("A1", "A2[Int]", "A3[Int, String]", "Any", "B1", "B2", "B3", "Object"), x.getParentsAsStrings) + assertEquals(List("B1", "B2", "B3"), x.getDirectParentsAsStrings) + assertEquals(List("E1", "E2"), x.getKnownChildrenAsStrings) + val graph = MemberExtension.getFrom(x).map(_.graph) + assertTrue("Graph is empty!", graph.isDefined) + assertEquals( + Set( + "Object" -> "Any", + "A1" -> "Object", + "A2[Int]" -> "Object", + "A3[Int, String]" -> "Object", + "B1" -> "Object", + "B1" -> "A1", + "B2" -> "Object", + "B2" -> "A1", + "B2" -> "A2[Int]", + "B3" -> "Object", + "B3" -> "A2[Int]", + "B3" -> "A3[Int, String]", + "C1[A, B, C]" -> "Object", + "C1[A, B, C]" -> "B1", + "C1[A, B, C]" -> "B2", + "C1[A, B, C]" -> "B3", + "E1" -> "C1[A, B, C]", + "E2" -> "C1[A, B, C]" + ), + graph.get.edges.map((a, b) => (a.signature.getName, b.signature.getName)).toSet + ) + } + if (x.getName == "E2") { + assertEquals(List("A1", "A2[Int]", "A3[Int, String]", "Any", "B1", "B2", "B3", "C1[Int, Boolean, Any]", "D2[Int, Boolean]", "D3", "Object"), x.getParentsAsStrings) + assertEquals(List("C1[Int, Boolean, Any]", "D2[Int, Boolean]", "D3"), x.getDirectParentsAsStrings) + assertEquals(List.empty, x.getKnownChildrenAsStrings) + val graph = MemberExtension.getFrom(x).map(_.graph) + assertTrue("Graph is empty!", graph.isDefined) + assertEquals( + Set( + "Object" -> "Any", + // "A1" -> "Object", // These are not applicable beacuase of bug and its workaround + // "A2[Int]" -> "Object", // More info at ClassLikeSupport.scala:37 + // "A3[Int, String]" -> "Object", + // "B1" -> "Object", + // "B1" -> "A1", + // "B2" -> "Object", + // "B2" -> "A1", + // "B2" -> "A2[Int]", + // "B3" -> "Object", + // "B3" -> "A2[Int]", + // "B3" -> "A3[Int, String]", + // "C1[Int, Boolean, Any]" -> "Object", + // "C1[Int, Boolean, Any]" -> "B1", + // "C1[Int, Boolean, Any]" -> "B2", + // "C1[Int, Boolean, Any]" -> "B3", + "E2" -> "D2[Int, Boolean]", + "E2" -> "D3", + "D2[Int, Boolean]" -> "Object", + "D3" -> "Object", + "E2" -> "C1[Int, Boolean, Any]" + ), + graph.get.edges.map((a, b) => (a.signature.getName, b.signature.getName)).toSet + ) + } + if (x.getName == "A2") { + assertEquals(List("Any", "Object"), x.getParentsAsStrings) + assertEquals(List.empty, x.getDirectParentsAsStrings) + assertEquals(List("B2", "B3", "C1[A, B, C]", "E1", "E2"), x.getKnownChildrenAsStrings) + val graph = MemberExtension.getFrom(x).map(_.graph) + assertTrue("Graph is empty!", graph.isDefined) + assertEquals( + Set( + "Object" -> "Any", + "A2[T]" -> "Object", + "B2" -> "A2[T]", // These are not actually true, becuase we lose information about hierarchy in subtypes and their possible mapping to supertypes other that that type itself, e. g. linking to `Object` + "B3" -> "A2[T]", + "C1[A, B, C]" -> "A2[T]", + "E1" -> "A2[T]", + "E2" -> "A2[T]" + ), + graph.get.edges.map((a, b) => (a.signature.getName, b.signature.getName)).toSet + ) + } + } + } + )