diff options
4 files changed, 164 insertions, 53 deletions
diff --git a/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt b/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt index 371ab12d..6e288c01 100644 --- a/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt +++ b/plugins/base/src/main/kotlin/transformers/documentables/DefaultDocumentableMerger.kt @@ -3,6 +3,8 @@ package org.jetbrains.dokka.base.transformers.documentables import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.DokkaSourceSetID import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.properties.ExtraProperty +import org.jetbrains.dokka.model.properties.MergeStrategy import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.model.properties.mergeExtras import org.jetbrains.dokka.plugability.DokkaContext @@ -36,14 +38,14 @@ internal class DefaultDocumentableMerger(val context: DokkaContext) : Documentab allModules.groupBy { it.sourceSets.single().sourceSetID } //this returns representation of graph where directed edges are leading from module to modules that depend on it - val graph: Map<ModuleOfDifferentTranslators?, List<ModuleOfDifferentTranslators>> = modulesMap.flatMap { (_, module) -> - module.first().sourceSets.single().dependentSourceSets.map { sourceSet -> - modulesMap[sourceSet] to module + val graph: Map<ModuleOfDifferentTranslators, List<ModuleOfDifferentTranslators>> = + modulesMap.flatMap { (_, module) -> + module.first().sourceSets.single().dependentSourceSets.map { sourceSet -> + modulesMap[sourceSet]!! to module + } + }.groupingBy { it.first }.fold({ _, value -> listOf(value.second) }) { _, accumulator, value -> + accumulator + listOf(value.second) } - }.groupBy { it.first }.entries - .map { it.key to it.value.map { it.second } } - .toMap() - val visited = modulesMap.map { it.value to false }.toMap().toMutableMap() val topologicalSortedList: MutableList<ModuleOfDifferentTranslators> = mutableListOf() @@ -82,37 +84,64 @@ internal class DefaultDocumentableMerger(val context: DokkaContext) : Documentab fun T.isExpectActual(): Boolean = this.safeAs<WithExtraProperties<T>>().let { it != null && it.extra[IsExpectActual] != null } - fun Set<DokkaConfiguration.DokkaSourceSet>.parentSourceSet(): String = singleOrNull { - it.dependentSourceSets.all { it !in this.map { it.sourceSetID } } - }?.displayName - ?: "unresolved".also { context.logger.error("Ill-defined dependency between sourceSets") } - - fun mergeClashingElements(elements: List<T>): List<T> = elements.groupBy { it.name }.values.flatMap { - if(it.size > 1) it.map { - when(it) { - is DClass -> it.copy(name = "${it.name}(${it.sourceSets.parentSourceSet()})") - is DObject -> it.copy(name = "${it.name}(${it.sourceSets.parentSourceSet()})") - is DAnnotation -> it.copy(name = "${it.name}(${it.sourceSets.parentSourceSet()})") - is DInterface -> it.copy(name = "${it.name}(${it.sourceSets.parentSourceSet()})") - is DEnum -> it.copy(name = "${it.name}(${it.sourceSets.parentSourceSet()})") - is DFunction -> it.copy(name = "${it.name}(${it.sourceSets.parentSourceSet()})") - is DProperty -> it.copy(name = "${it.name}(${it.sourceSets.parentSourceSet()})") - else -> it - } - } as List<T> else it - } + fun mergeClashingElements(elements: List<Pair<T, Set<DokkaConfiguration.DokkaSourceSet>>>): List<T> = + elements.groupBy { it.first.name }.values.flatMap { listOfDocumentableToSSIds -> + listOfDocumentableToSSIds.map { (documentable, sourceSets) -> + when (documentable) { + is DClass -> documentable.copy( + extra = documentable.extra + ClashingDriIdentifier( + sourceSets + (documentable.extra[ClashingDriIdentifier]?.value ?: emptySet()) + ) + ) + is DObject -> documentable.copy( + extra = documentable.extra + ClashingDriIdentifier( + sourceSets + (documentable.extra[ClashingDriIdentifier]?.value ?: emptySet()) + ) + ) + is DAnnotation -> documentable.copy( + extra = documentable.extra + ClashingDriIdentifier( + sourceSets + (documentable.extra[ClashingDriIdentifier]?.value ?: emptySet()) + ) + ) + is DInterface -> documentable.copy( + extra = documentable.extra + ClashingDriIdentifier( + sourceSets + (documentable.extra[ClashingDriIdentifier]?.value ?: emptySet()) + ) + ) + is DEnum -> documentable.copy( + extra = documentable.extra + ClashingDriIdentifier( + sourceSets + (documentable.extra[ClashingDriIdentifier]?.value ?: emptySet()) + ) + ) + is DFunction -> documentable.copy( + extra = documentable.extra + ClashingDriIdentifier( + sourceSets + (documentable.extra[ClashingDriIdentifier]?.value ?: emptySet()) + ) + ) + is DProperty -> documentable.copy( + extra = documentable.extra + ClashingDriIdentifier( + sourceSets + (documentable.extra[ClashingDriIdentifier]?.value ?: emptySet()) + ) + ) + else -> documentable + } + } as List<T> + } + fun analyzeExpectActual(sameDriElements: List<T>): List<T> { val (expects, actuals) = sameDriElements.partition { it.expectPresentInSet != null } - val groupedByOwnExpect = expects.map { expect -> - listOf(expect) + actuals.filter { actual -> + val groupedByOwnExpectWithActualSourceSetIds = expects.map { expect -> + val actualsForGivenExpect = actuals.filter { actual -> dependencyInfo[actual.sourceSets.single()] ?.contains(expect.expectPresentInSet!!) ?: throw IllegalStateException("Cannot resolve expect/actual relation for ${actual.name}") } + (listOf(expect) + actualsForGivenExpect) to actualsForGivenExpect.flatMap { it.sourceSets }.toSet() } - val reducedToOneDocumentable = groupedByOwnExpect.map { it.reduce(reducer) } - val uniqueNamedDocumentables = reducedToOneDocumentable.let(::mergeClashingElements) + val reducedToOneDocumentableWithActualSourceSetIds = + groupedByOwnExpectWithActualSourceSetIds.map { it.first.reduce(reducer) to it.second } + val uniqueNamedDocumentables = reducedToOneDocumentableWithActualSourceSetIds.let(::mergeClashingElements) return uniqueNamedDocumentables } @@ -120,8 +149,9 @@ internal class DefaultDocumentableMerger(val context: DokkaContext) : Documentab return elements.partition { it.isExpectActual() }.let { (expectActuals, notExpectActuals) -> - notExpectActuals.groupBy { it.dri }.values.flatMap(::mergeClashingElements) + - expectActuals.groupBy { it.dri }.values.flatMap(::analyzeExpectActual) + notExpectActuals.map { it to it.sourceSets } + .groupBy { it.first.dri }.values.flatMap(::mergeClashingElements) + + expectActuals.groupBy { it.dri }.values.flatMap(::analyzeExpectActual) } } @@ -277,3 +307,15 @@ internal class DefaultDocumentableMerger(val context: DokkaContext) : Documentab } private typealias ModuleOfDifferentTranslators = List<DModule> + +data class ClashingDriIdentifier(val value: Set<DokkaConfiguration.DokkaSourceSet>) : ExtraProperty<Documentable> { + companion object : ExtraProperty.Key<Documentable, ClashingDriIdentifier> { + override fun mergeStrategyFor( + left: ClashingDriIdentifier, + right: ClashingDriIdentifier + ): MergeStrategy<Documentable> = + MergeStrategy.Replace(ClashingDriIdentifier(left.value + right.value)) + } + + override val key: ExtraProperty.Key<Documentable, *> = ClashingDriIdentifier +} diff --git a/plugins/base/src/main/kotlin/transformers/pages/merger/FallbackPageMergerStrategy.kt b/plugins/base/src/main/kotlin/transformers/pages/merger/FallbackPageMergerStrategy.kt index df0c27ee..8698e84b 100644 --- a/plugins/base/src/main/kotlin/transformers/pages/merger/FallbackPageMergerStrategy.kt +++ b/plugins/base/src/main/kotlin/transformers/pages/merger/FallbackPageMergerStrategy.kt @@ -1,10 +1,14 @@ package org.jetbrains.dokka.base.transformers.pages.merger +import org.jetbrains.dokka.pages.ContentPage import org.jetbrains.dokka.pages.PageNode import org.jetbrains.dokka.utilities.DokkaLogger class FallbackPageMergerStrategy(private val logger: DokkaLogger) : PageMergerStrategy { override fun tryMerge(pages: List<PageNode>, path: List<String>): List<PageNode> { + pages.map { + (it as? ContentPage) + } val renderedPath = path.joinToString(separator = "/") if (pages.size != 1) logger.warn("For $renderedPath: expected 1 page, but got ${pages.size}") return listOf(pages.first()) diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index 615412f4..55e59b77 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.base.transformers.documentables.ClashingDriIdentifier private typealias GroupedTags = Map<KClass<out TagWrapper>, List<Pair<DokkaSourceSet?, TagWrapper>>> @@ -34,14 +35,14 @@ open class DefaultPageCreator( open fun pageForPackage(p: DPackage): PackagePageNode = PackagePageNode( p.name, contentForPackage(p), setOf(p.dri), p, - p.classlikes.map(::pageForClasslike) + + p.classlikes.renameClashingClasslikes().map(::pageForClasslike) + p.functions.map(::pageForFunction) ) open fun pageForEnumEntry(e: DEnumEntry): ClasslikePageNode = ClasslikePageNode( e.name, contentForEnumEntry(e), setOf(e.dri), e, - e.classlikes.map(::pageForClasslike) + + e.classlikes.renameClashingClasslikes().map(::pageForClasslike) + e.filteredFunctions.map(::pageForFunction) ) @@ -51,12 +52,25 @@ open class DefaultPageCreator( return ClasslikePageNode( c.name.orEmpty(), contentForClasslike(c), setOf(c.dri), c, constructors.map(::pageForFunction) + - c.classlikes.map(::pageForClasslike) + + c.classlikes.renameClashingClasslikes().map(::pageForClasslike) + c.filteredFunctions.map(::pageForFunction) + if (c is DEnum) c.entries.map(::pageForEnumEntry) else emptyList() ) } + private fun List<DClasslike>.renameClashingClasslikes(): List<DClasslike> = groupBy { it.dri }.values.flatMap { classlikes -> + if (classlikes.size == 1) classlikes else classlikes.map { classlike -> + fun ClashingDriIdentifier?.toName() = this?.value?.joinToString(", ", "(", ")") { it.displayName } ?: "" + when(classlike) { + is DClass -> classlike.copy(name = classlike.name + classlike.extra[ClashingDriIdentifier]?.toName()) + is DObject -> classlike.copy(name = classlike.name.orEmpty() + classlike.extra[ClashingDriIdentifier]?.toName()) + is DAnnotation -> classlike.copy(name = classlike.name + classlike.extra[ClashingDriIdentifier]?.toName()) + is DInterface -> classlike.copy(name = classlike.name + classlike.extra[ClashingDriIdentifier]?.toName()) + is DEnum -> classlike.copy(name = classlike.name + classlike.extra[ClashingDriIdentifier]?.toName()) + } + } + } + open fun pageForFunction(f: DFunction) = MemberPageNode(f.name, contentForFunction(f), setOf(f.dri), f) open fun pageForTypeAlias(t: DTypeAlias) = MemberPageNode(t.name, contentForTypeAlias(t), setOf(t.dri), t) diff --git a/plugins/base/src/test/kotlin/expectActuals/ExpectActualsTest.kt b/plugins/base/src/test/kotlin/expectActuals/ExpectActualsTest.kt index 090196b3..624db485 100644 --- a/plugins/base/src/test/kotlin/expectActuals/ExpectActualsTest.kt +++ b/plugins/base/src/test/kotlin/expectActuals/ExpectActualsTest.kt @@ -12,7 +12,7 @@ class ExpectActualsTest : AbstractCoreTest() { fun PageNode.childrenRec(): List<PageNode> = listOf(this) + children.flatMap { it.childrenRec() } @Test - fun `two same named expect actual classes`() { + fun `three same named expect actual classes`() { val configuration = dokkaConfiguration { sourceSets { @@ -31,12 +31,20 @@ class ExpectActualsTest : AbstractCoreTest() { sourceRoots = listOf("src/commonJMain/kotlin/pageMerger/Test.kt") dependentSourceSets = setOf(common.sourceSetID) } - val commonN = sourceSet { + val commonN1 = sourceSet { moduleName = "example" - name = "commonN" - displayName = "commonN" + name = "commonN1" + displayName = "commonN1" analysisPlatform = "common" - sourceRoots = listOf("src/commonNMain/kotlin/pageMerger/Test.kt") + sourceRoots = listOf("src/commonN1Main/kotlin/pageMerger/Test.kt") + dependentSourceSets = setOf(common.sourceSetID) + } + val commonN2 = sourceSet { + moduleName = "example" + name = "commonN2" + displayName = "commonN2" + analysisPlatform = "common" + sourceRoots = listOf("src/commonN2Main/kotlin/pageMerger/Test.kt") dependentSourceSets = setOf(common.sourceSetID) } val js = sourceSet { @@ -60,7 +68,7 @@ class ExpectActualsTest : AbstractCoreTest() { name = "linuxX64" displayName = "linuxX64" analysisPlatform = "native" - dependentSourceSets = setOf(commonN.sourceSetID) + dependentSourceSets = setOf(commonN1.sourceSetID) sourceRoots = listOf("src/linuxX64Main/kotlin/pageMerger/Test.kt") } val mingwX64 = sourceSet { @@ -68,9 +76,25 @@ class ExpectActualsTest : AbstractCoreTest() { name = "mingwX64" displayName = "mingwX64" analysisPlatform = "native" - dependentSourceSets = setOf(commonN.sourceSetID) + dependentSourceSets = setOf(commonN1.sourceSetID) sourceRoots = listOf("src/mingwX64Main/kotlin/pageMerger/Test.kt") } + val iosArm64 = sourceSet { + moduleName = "example" + name = "iosArm64" + displayName = "iosArm64" + analysisPlatform = "native" + dependentSourceSets = setOf(commonN2.sourceSetID) + sourceRoots = listOf("src/iosArm64Main/kotlin/pageMerger/Test.kt") + } + val iosX64 = sourceSet { + moduleName = "example" + name = "iosX64" + displayName = "iosX64" + analysisPlatform = "native" + dependentSourceSets = setOf(commonN2.sourceSetID) + sourceRoots = listOf("src/iosX64Main/kotlin/pageMerger/Test.kt") + } } } @@ -84,7 +108,12 @@ class ExpectActualsTest : AbstractCoreTest() { | |expect class A | - |/src/commonNMain/kotlin/pageMerger/Test.kt + |/src/commonN1Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |expect class A + | + |/src/commonN2Main/kotlin/pageMerger/Test.kt |package pageMerger | |expect class A @@ -99,7 +128,7 @@ class ExpectActualsTest : AbstractCoreTest() { | |actual class A | - |/src/linuxX64/kotlin/pageMerger/Test.kt + |/src/linuxX64Main/kotlin/pageMerger/Test.kt |package pageMerger | |actual class A @@ -109,22 +138,44 @@ class ExpectActualsTest : AbstractCoreTest() { | |actual class A | + |/src/iosArm64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual class A + | + |/src/iosX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual class A + | """.trimMargin(), configuration ) { pagesTransformationStage = { - println(it) val allChildren = it.childrenRec().filterIsInstance<ClasslikePageNode>() - val jvmClass = allChildren.filter { it.name == "DoNotMerge(jvm)" } - val jsClass = allChildren.filter { it.name == "DoNotMerge(js)" } - val noClass = allChildren.filter { it.name == "DoNotMerge" } - assertTrue(jvmClass.size == 1) { "There can be only one DoNotMerge(jvm) page" } - assertTrue(jvmClass.first().documentable?.sourceSets?.single()?.analysisPlatform?.key == "jvm") { "DoNotMerge(jvm) should have only jvm sources" } + val commonJ = allChildren.filter { it.name == "A(js, jvm)" } + val commonN1 = allChildren.filter { it.name == "A(linuxX64, mingwX64)" } + val commonN2 = allChildren.filter { it.name == "A(iosArm64, iosX64)" } + val noClass = allChildren.filter { it.name == "A" } + assertTrue(commonJ.size == 1) { "There can be only one A(js, jvm) page" } + assertTrue( + commonJ.first().documentable?.sourceSets?.map { it.displayName } + ?.containsAll(listOf("commonJ", "js", "jvm")) ?: false + ) { "A(js, jvm)should have commonJ, js, jvm sources" } + + assertTrue(commonN1.size == 1) { "There can be only one A(linuxX64, mingwX64) page" } + assertTrue( + commonN1.first().documentable?.sourceSets?.map { it.displayName } + ?.containsAll(listOf("commonN1", "linuxX64", "mingwX64")) ?: false + ) { "A(linuxX64, mingwX64) should have commonN1, linuxX64, mingwX64 sources" } - assertTrue(jsClass.size == 1) { "There can be only one DoNotMerge(js) page" } - assertTrue(jsClass.first().documentable?.sourceSets?.single()?.analysisPlatform?.key == "js") { "DoNotMerge(js) should have only js sources" } + assertTrue(commonN2.size == 1) { "There can be only one A(iosArm64, iosX64) page" } + assertTrue( + commonN2.first().documentable?.sourceSets?.map { it.displayName } + ?.containsAll(listOf("commonN2", "iosArm64", "iosX64")) ?: false + ) { "A(iosArm64, iosX64) should have commonN2, iosArm64, iosX64 sources" } - assertTrue(noClass.isEmpty()) { "There can't be any DoNotMerge page" } + assertTrue(noClass.isEmpty()) { "There can't be any A page" } } } } |