From 448ee45725373699f0bc3021629707030e884041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 14 Mar 2026 21:22:48 +0100 Subject: [PATCH] feat(renderer): Move fake constructors into their target type's page --- .../src/commonMain/kotlin/MyInterface.kt | 2 +- .../kotlin/MethodGroupingTransformer.kt | 113 +++++++++++++++++- 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt b/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt index 6428a68..0942f19 100644 --- a/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt +++ b/docs/example/example-core/src/commonMain/kotlin/MyInterface.kt @@ -3,7 +3,7 @@ package opensavvy.dokka.material.mkdocs.example /** * Simple interface. */ -interface MyInterface { +sealed interface MyInterface { /** * This is a method from the interface [MyInterface]. diff --git a/renderer/src/jvmMain/kotlin/MethodGroupingTransformer.kt b/renderer/src/jvmMain/kotlin/MethodGroupingTransformer.kt index 4f675f4..621da73 100644 --- a/renderer/src/jvmMain/kotlin/MethodGroupingTransformer.kt +++ b/renderer/src/jvmMain/kotlin/MethodGroupingTransformer.kt @@ -1,6 +1,9 @@ package opensavvy.dokka.material.mkdocs import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.Bound +import org.jetbrains.dokka.model.DFunction +import org.jetbrains.dokka.model.TypeConstructor import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.transformers.pages.PageTransformer @@ -9,22 +12,57 @@ class MethodGroupingTransformer : PageTransformer { val driToPage = mutableMapOf() val allMergedPages = mutableSetOf() - // 1. Collect all member pages + // 1. Collect all member pages and all potential target pages (types) + val typeDRIs = mutableSetOf() input.transformContentPagesTree { page -> if (page is MemberPageNode) { page.documentables.forEach { doc -> driToPage[doc.dri] = page } } + if (page is ClasslikePageNode) { + page.dri.forEach { typeDRIs.add(it) } + } page } + val fakeConstructorsByClass = mutableMapOf>() + driToPage.forEach { (dri, page) -> + // A fake constructor is a top-level function (classNames == null) + // with the same name as a type in the same package, + // AND it returns that type. + if (dri.classNames == null && dri.callable != null) { + val targetTypeDRI = typeDRIs.firstOrNull { + it.packageName == dri.packageName && it.classNames == dri.callable!!.name + } + if (targetTypeDRI != null) { + val returnsTargetType = page.documentables.any { doc -> + doc.dri == dri && doc is DFunction && doc.type.extractDRI() == targetTypeDRI + } + if (returnsTargetType) { + fakeConstructorsByClass.getOrPut(targetTypeDRI) { mutableListOf() }.add(page) + } + } + } + } + return input.transformContentPagesTree { page -> if (page is ClasslikePageNode) { val classDRI = page.dri.firstOrNull() ?: return@transformContentPagesTree page + val fakeConstructors = page.dri.flatMap { fakeConstructorsByClass[it] ?: emptyList() }.distinct() + + // Important: mark them as merged so they are removed from the package page + allMergedPages.addAll(fakeConstructors) + + val pageContent = if (fakeConstructors.isNotEmpty()) { + injectFakeConstructors(page.content as ContentGroup, fakeConstructors) + } else { + page.content + } + val alreadyMerged = mutableSetOf() // 2. Replace summary rows with full member content - val newContent = page.content.recursiveMapTransform { table -> + val newContent = pageContent.recursiveMapTransform { table -> if (table.dci.kind in listOf(ContentKind.Functions, ContentKind.Properties, ContentKind.Constructors)) { table.copy( children = table.children.map { row -> @@ -88,6 +126,72 @@ class MethodGroupingTransformer : PageTransformer { } } + private fun injectFakeConstructors(content: ContentGroup, fakeConstructors: List): ContentGroup { + // Try to find the Main group where tables usually live + val mainGroupIndex = content.children.indexOfFirst { it is ContentGroup && it.dci.kind == ContentKind.Main } + if (mainGroupIndex != -1) { + val mainGroup = content.children[mainGroupIndex] as ContentGroup + val newMainGroup = injectFakeConstructors(mainGroup, fakeConstructors) + val newChildren = content.children.toMutableList() + newChildren[mainGroupIndex] = newMainGroup + return content.copy(children = newChildren) + } + + val existingTableIndex = content.children.indexOfFirst { it is ContentTable && it.dci.kind == ContentKind.Constructors } + if (existingTableIndex != -1) { + val table = content.children[existingTableIndex] as ContentTable + val newRows = fakeConstructors.map { createRowFor(it) } + val newChildren = content.children.toMutableList() + newChildren[existingTableIndex] = table.copy(children = table.children + newRows) + return content.copy(children = newChildren) + } else { + val newRows = fakeConstructors.map { createRowFor(it) } + val dci = DCI(content.dci.dri, ContentKind.Constructors) + val header = ContentHeader( + children = listOf(ContentText("Constructors", dci, content.sourceSets)), + level = 2, + dci = dci, + sourceSets = content.sourceSets, + style = emptySet() + ) + val table = ContentTable( + header = emptyList(), + children = newRows, + dci = dci, + sourceSets = content.sourceSets, + style = emptySet() + ) + + // Insert before first existing table or level 2 header, or at the end + val insertIndex = content.children.indexOfFirst { + it is ContentTable || (it is ContentHeader && it.level == 2) + }.let { if (it == -1) content.children.size else it } + + val newChildren = content.children.toMutableList() + newChildren.add(insertIndex, header) + newChildren.add(insertIndex + 1, table) + return content.copy(children = newChildren) + } + } + + private fun createRowFor(memberPage: MemberPageNode): ContentGroup { + val documentable = memberPage.documentables.first() + val dri = documentable.dri + val sourceSets = memberPage.content.sourceSets + val dci = DCI(setOf(dri), ContentKind.Main) + return ContentGroup( + children = listOf(ContentDRILink( + children = listOf(ContentText(memberPage.name, dci, sourceSets)), + address = dri, + dci = dci, + sourceSets = sourceSets + )), + dci = dci, + sourceSets = sourceSets, + style = emptySet() + ) + } + private fun ContentNode.findDRI(): DRI? { if (this is ContentDRILink) return dci.dri.firstOrNull() if (this is ContentComposite) { @@ -104,4 +208,9 @@ class MethodGroupingTransformer : PageTransformer { header.copy(level = header.level + by) } } + + private fun Bound.extractDRI(): DRI? = when (this) { + is TypeConstructor -> this.dri + else -> null + } } -- 2.51.2