aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/base/src')
-rw-r--r--plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt4
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt19
-rw-r--r--plugins/base/src/main/kotlin/transformers/pages/merger/SameMethodNamePageMergerStrategy.kt8
-rw-r--r--plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt14
-rw-r--r--plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt48
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt519
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt39
-rw-r--r--plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt10
-rw-r--r--plugins/base/src/test/kotlin/content/functions/ContentForConstructors.kt2
-rw-r--r--plugins/base/src/test/kotlin/locationProvider/DokkaLocationProviderTest.kt5
-rw-r--r--plugins/base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt352
11 files changed, 784 insertions, 236 deletions
diff --git a/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt b/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt
index 21757d70..8ea8818d 100644
--- a/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt
+++ b/plugins/base/src/main/kotlin/DokkaBaseConfiguration.kt
@@ -8,12 +8,14 @@ data class DokkaBaseConfiguration(
var customStyleSheets: List<File> = defaultCustomStyleSheets,
var customAssets: List<File> = defaultCustomAssets,
var separateInheritedMembers: Boolean = separateInheritedMembersDefault,
- var footerMessage: String = defaultFooterMessage
+ var footerMessage: String = defaultFooterMessage,
+ var mergeImplicitExpectActualDeclarations: Boolean = mergeImplicitExpectActualDeclarationsDefault
) : ConfigurableBlock {
companion object {
val defaultFooterMessage = "© ${Year.now().value} Copyright"
val defaultCustomStyleSheets: List<File> = emptyList()
val defaultCustomAssets: List<File> = emptyList()
const val separateInheritedMembersDefault: Boolean = false
+ const val mergeImplicitExpectActualDeclarationsDefault: Boolean = false
}
} \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
index 43526dc3..3297d09f 100644
--- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
+++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
@@ -1,14 +1,14 @@
package org.jetbrains.dokka.base.renderers.html
-import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.DokkaBaseConfiguration
import org.jetbrains.dokka.base.renderers.sourceSets
-import org.jetbrains.dokka.base.templating.AddToSearch
import org.jetbrains.dokka.base.templating.AddToSourcesetDependencies
import org.jetbrains.dokka.base.templating.toJsonString
-import org.jetbrains.dokka.links.DRI
-import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.DEnum
+import org.jetbrains.dokka.model.DEnumEntry
+import org.jetbrains.dokka.model.DFunction
+import org.jetbrains.dokka.model.withDescendants
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.configuration
@@ -30,19 +30,18 @@ abstract class NavigationDataProvider {
when {
this !is ClasslikePageNode ->
children.filterIsInstance<ContentPage>().map { visit(it) }
- documentable is DEnum ->
- children.filter { it is ContentPage && it.documentable is DEnumEntry }.map { visit(it as ContentPage) }
+ documentables.any { it is DEnum } ->
+ children.filter { it is WithDocumentables && it.documentables.any { it is DEnumEntry } }
+ .map { visit(it as ContentPage) }
else -> emptyList()
}.sortedBy { it.name.toLowerCase() }
/**
- * Parenthesis is applied in 2 cases:
+ * Parenthesis is applied in 1 case:
* - page only contains functions (therefore documentable from this page is [DFunction])
- * - page merges only functions (documentable is null because [SameMethodNamePageMergerStrategy][org.jetbrains.dokka.base.transformers.pages.merger.SameMethodNamePageMergerStrategy]
- * removes it from page)
*/
private val ContentPage.displayableName: String
- get() = if (documentable is DFunction || (documentable == null && dri.all { it.callable != null })) {
+ get() = if (this is WithDocumentables && documentables.all { it is DFunction }) {
"$name()"
} else {
name
diff --git a/plugins/base/src/main/kotlin/transformers/pages/merger/SameMethodNamePageMergerStrategy.kt b/plugins/base/src/main/kotlin/transformers/pages/merger/SameMethodNamePageMergerStrategy.kt
index 2fb70fc8..6c12c719 100644
--- a/plugins/base/src/main/kotlin/transformers/pages/merger/SameMethodNamePageMergerStrategy.kt
+++ b/plugins/base/src/main/kotlin/transformers/pages/merger/SameMethodNamePageMergerStrategy.kt
@@ -23,7 +23,7 @@ class SameMethodNamePageMergerStrategy(val logger: DokkaLogger) : PageMergerStra
children = members.flatMap { it.children }.distinct(),
content = squashDivergentInstances(members).withSourceSets(members.allSourceSets()),
embeddedResources = members.flatMap { it.embeddedResources }.distinct(),
- documentable = null
+ documentables = members.flatMap { it.documentables }
)
return (pages - members) + listOf(merged)
@@ -37,9 +37,9 @@ class SameMethodNamePageMergerStrategy(val logger: DokkaLogger) : PageMergerStra
.reduce { acc, node ->
acc.mapTransform<ContentDivergentGroup, ContentNode> { g ->
g.copy(children = (g.children +
- (node.dfs { it is ContentDivergentGroup && it.groupID == g.groupID } as? ContentDivergentGroup)
- ?.children?.single()
- ).filterNotNull()
+ ((node.dfs { it is ContentDivergentGroup && it.groupID == g.groupID } as? ContentDivergentGroup)
+ ?.children ?: emptyList())
+ )
)
}
}
diff --git a/plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt
index 3ff7a77d..db133bb8 100644
--- a/plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt
+++ b/plugins/base/src/main/kotlin/transformers/pages/samples/SamplesTransformer.kt
@@ -35,14 +35,18 @@ abstract class SamplesTransformer(val context: DokkaContext) : PageTransformer {
"<script src=\"https://unpkg.com/kotlin-playground@1\"></script>"
return input.transformContentPagesTree { page ->
- page.documentable?.documentation?.entries?.fold(page) { acc, entry ->
- entry.value.children.filterIsInstance<Sample>().fold(acc) { acc, sample ->
+ val samples = (page as? WithDocumentables)?.documentables?.flatMap {
+ it.documentation.entries.flatMap { entry ->
+ entry.value.children.filterIsInstance<Sample>().map { entry.key to it }
+ }
+ }
+
+ samples?.fold(page as ContentPage) { acc, (sampleSourceSet, sample) ->
acc.modified(
- content = acc.content.addSample(page, entry.key, sample.name, analysis),
+ content = acc.content.addSample(page, sampleSourceSet, sample.name, analysis),
embeddedResources = acc.embeddedResources + kotlinPlaygroundScript
)
- }
- } ?: page
+ } ?: page
}
}
diff --git a/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt
index f66ff222..93305055 100644
--- a/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt
+++ b/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt
@@ -1,15 +1,15 @@
package org.jetbrains.dokka.base.transformers.pages.sourcelinks
-import com.intellij.psi.PsiElement
import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiElement
import org.jetbrains.dokka.DokkaConfiguration
-import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder
-import org.jetbrains.dokka.model.DocumentableSource
import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
import org.jetbrains.dokka.analysis.DescriptorDocumentableSource
import org.jetbrains.dokka.analysis.PsiDocumentableSource
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint
+import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder
+import org.jetbrains.dokka.model.DocumentableSource
import org.jetbrains.dokka.model.WithSources
import org.jetbrains.dokka.model.toDisplaySourceSets
import org.jetbrains.dokka.pages.*
@@ -32,8 +32,9 @@ class SourceLinksTransformer(val context: DokkaContext) : PageTransformer {
override fun invoke(input: RootPageNode) =
input.transformContentPagesTree { node ->
- when (val documentable = node.documentable) {
- is WithSources -> resolveSources(documentable)
+ when (node) {
+ is WithDocumentables ->
+ node.documentables.filterIsInstance<WithSources>().flatMap { resolveSources(it) }
.takeIf { it.isNotEmpty() }
?.let { node.addSourcesContent(it) }
?: node
@@ -65,23 +66,26 @@ class SourceLinksTransformer(val context: DokkaContext) : PageTransformer {
private fun PageContentBuilder.buildSourcesContent(
node: ContentPage,
sources: List<Pair<DokkaSourceSet, String>>
- ) = contentFor(
- node.dri.first(),
- node.documentable!!.sourceSets.toSet()
- ) {
- header(2, "Sources", kind = ContentKind.Source)
- +ContentTable(
- header = emptyList(),
- children = sources.map {
- buildGroup(node.dri, setOf(it.first), kind = ContentKind.Source, extra = mainExtra + SymbolAnchorHint(it.second, ContentKind.Source)) {
- link("${it.first.displayName} source", it.second)
- }
- },
- dci = DCI(node.dri, ContentKind.Source),
- sourceSets = node.documentable!!.sourceSets.toDisplaySourceSets(),
- style = emptySet(),
- extra = mainExtra + SimpleAttr.header("Sources")
- )
+ ): ContentGroup {
+ val documentables = (node as? WithDocumentables)?.documentables.orEmpty()
+ return contentFor(
+ node.dri,
+ documentables.flatMap { it.sourceSets }.toSet()
+ ) {
+ header(2, "Sources", kind = ContentKind.Source)
+ +ContentTable(
+ header = emptyList(),
+ children = sources.map {
+ buildGroup(node.dri, setOf(it.first), kind = ContentKind.Source, extra = mainExtra + SymbolAnchorHint(it.second, ContentKind.Source)) {
+ link("${it.first.displayName} source", it.second)
+ }
+ },
+ dci = DCI(node.dri, ContentKind.Source),
+ sourceSets = documentables.flatMap { it.sourceSets }.toDisplaySourceSets(),
+ style = emptySet(),
+ extra = mainExtra + SimpleAttr.header("Sources")
+ )
+ }
}
private fun DocumentableSource.toLink(sourceLink: SourceLink): String {
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
index e2aca6f9..c5136c27 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
@@ -1,9 +1,14 @@
package org.jetbrains.dokka.base.translators.documentables
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.base.DokkaBaseConfiguration
+import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint
import org.jetbrains.dokka.base.signatures.SignatureProvider
import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions
+import org.jetbrains.dokka.base.transformers.documentables.ClashingDriIdentifier
import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo
import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.base.transformers.pages.tags.CustomTagContentProvider
import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.*
@@ -15,11 +20,6 @@ import org.jetbrains.dokka.utilities.DokkaLogger
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.DokkaBaseConfiguration
-import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint
-import org.jetbrains.dokka.base.transformers.documentables.ClashingDriIdentifier
-import org.jetbrains.dokka.base.transformers.pages.tags.CustomTagContentProvider
private typealias GroupedTags = Map<KClass<out TagWrapper>, List<Pair<DokkaSourceSet?, TagWrapper>>>
@@ -35,66 +35,139 @@ open class DefaultPageCreator(
) {
protected open val contentBuilder = PageContentBuilder(commentsToContentConverter, signatureProvider, logger)
+ protected val mergeImplicitExpectActualDeclarations =
+ configuration?.mergeImplicitExpectActualDeclarations
+ ?: DokkaBaseConfiguration.mergeImplicitExpectActualDeclarationsDefault
+
protected val separateInheritedMembers =
configuration?.separateInheritedMembers ?: DokkaBaseConfiguration.separateInheritedMembersDefault
- open fun pageForModule(m: DModule) =
- ModulePageNode(m.name.ifEmpty { "<root>" }, contentForModule(m), m, m.packages.map(::pageForPackage))
+ open fun pageForModule(m: DModule): ModulePageNode =
+ ModulePageNode(m.name.ifEmpty { "<root>" }, contentForModule(m), listOf(m), m.packages.map(::pageForPackage))
open fun pageForPackage(p: DPackage): PackagePageNode = PackagePageNode(
- p.name, contentForPackage(p), setOf(p.dri), p,
- p.classlikes.renameClashingDocumentable().map(::pageForClasslike) +
- p.functions.renameClashingDocumentable()
- .map(::pageForFunction) + p.properties.mapNotNull(::pageForProperty)
+ p.name, contentForPackage(p), setOf(p.dri), listOf(p),
+ if (mergeImplicitExpectActualDeclarations)
+ p.classlikes.mergeClashingDocumentable().map(::pageForClasslikes) +
+ p.functions.mergeClashingDocumentable().map(::pageForFunctions) +
+ p.properties.mergeClashingDocumentable().map(::pageForProperties)
+ else
+ p.classlikes.renameClashingDocumentable().map(::pageForClasslike) +
+ p.functions.renameClashingDocumentable().map(::pageForFunction) +
+ p.properties.mapNotNull(::pageForProperty)
)
- open fun pageForEnumEntry(e: DEnumEntry): ClasslikePageNode =
- ClasslikePageNode(
- e.nameAfterClash(), contentForEnumEntry(e), setOf(e.dri), e,
- e.classlikes.renameClashingDocumentable().map(::pageForClasslike) +
- e.filteredFunctions.renameClashingDocumentable().map(::pageForFunction) +
- e.filteredProperties.renameClashingDocumentable().mapNotNull(::pageForProperty)
- )
+ open fun pageForEnumEntry(e: DEnumEntry): ClasslikePageNode = pageForEnumEntries(listOf(e))
- open fun pageForClasslike(c: DClasslike): ClasslikePageNode {
- val constructors = if (c is WithConstructors) c.constructors else emptyList()
+ open fun pageForClasslike(c: DClasslike): ClasslikePageNode = pageForClasslikes(listOf(c))
+
+ open fun pageForEnumEntries(documentables: List<DEnumEntry>): ClasslikePageNode {
+ val dri = documentables.dri.also {
+ if (it.size != 1) {
+ logger.error("Documentable dri should have the same one ${it.first()} inside the one page!")
+ }
+ }
+
+ val classlikes = documentables.flatMap { it.classlikes }
+ val functions = documentables.flatMap { it.filteredFunctions }
+ val props = documentables.flatMap { it.filteredProperties }
+
+ val childrenPages = if (mergeImplicitExpectActualDeclarations)
+ functions.mergeClashingDocumentable().map(::pageForFunctions) +
+ props.mergeClashingDocumentable().map(::pageForProperties)
+ else
+ classlikes.renameClashingDocumentable().map(::pageForClasslike) +
+ functions.renameClashingDocumentable().map(::pageForFunction) +
+ props.renameClashingDocumentable().mapNotNull(::pageForProperty)
return ClasslikePageNode(
- c.nameAfterClash(), contentForClasslike(c), setOf(c.dri), c,
- constructors.map(::pageForFunction) +
- c.classlikes.renameClashingDocumentable().map(::pageForClasslike) +
- c.filteredFunctions.renameClashingDocumentable().map(::pageForFunction) +
- c.filteredProperties.renameClashingDocumentable().mapNotNull(::pageForProperty) +
- if (c is DEnum) c.entries.map(::pageForEnumEntry) else emptyList()
+ documentables.first().nameAfterClash(), contentForClasslikesAndEntries(documentables), dri, documentables,
+ childrenPages
+ )
+ }
+
+ open fun pageForClasslikes(documentables: List<DClasslike>): ClasslikePageNode {
+ val dri = documentables.dri.also {
+ if (it.size != 1) {
+ logger.error("Documentable dri should have the same one ${it.first()} inside the one page!")
+ }
+ }
+
+ val constructors = documentables.flatMap { if (it is WithConstructors) it.constructors else emptyList() }
+
+ val classlikes = documentables.flatMap { it.classlikes }
+ val functions = documentables.flatMap { it.filteredFunctions }
+ val props = documentables.flatMap { it.filteredProperties }
+ val entries = documentables.flatMap { if (it is DEnum) it.entries else emptyList() }
+
+ val childrenPages = constructors.map(::pageForFunction) +
+ if (mergeImplicitExpectActualDeclarations)
+ classlikes.mergeClashingDocumentable().map(::pageForClasslikes) +
+ functions.mergeClashingDocumentable().map(::pageForFunctions) +
+ props.mergeClashingDocumentable().map(::pageForProperties) +
+ entries.mergeClashingDocumentable().map(::pageForEnumEntries)
+ else
+ classlikes.renameClashingDocumentable().map(::pageForClasslike) +
+ functions.renameClashingDocumentable().map(::pageForFunction) +
+ props.renameClashingDocumentable().mapNotNull(::pageForProperty) +
+ entries.renameClashingDocumentable().map(::pageForEnumEntry)
+
+ return ClasslikePageNode(
+ documentables.first().nameAfterClash(), contentForClasslikesAndEntries(documentables), dri, documentables,
+ childrenPages
)
}
private fun <T> T.toClashedName() where T : Documentable, T : WithExtraProperties<T> =
(extra[ClashingDriIdentifier]?.value?.joinToString(", ", "[", "]") { it.displayName } ?: "") + name.orEmpty()
- @Suppress("UNCHECKED_CAST")
private fun <T : Documentable> List<T>.renameClashingDocumentable(): List<T> =
groupBy { it.dri }.values.flatMap { elements ->
if (elements.size == 1) elements else elements.mapNotNull { element ->
- when (element) {
- is DClass -> element.copy(extra = element.extra + DriClashAwareName(element.toClashedName()))
- is DObject -> element.copy(extra = element.extra + DriClashAwareName(element.toClashedName()))
- is DAnnotation -> element.copy(extra = element.extra + DriClashAwareName(element.toClashedName()))
- is DInterface -> element.copy(extra = element.extra + DriClashAwareName(element.toClashedName()))
- is DEnum -> element.copy(extra = element.extra + DriClashAwareName(element.toClashedName()))
- is DFunction -> element.copy(extra = element.extra + DriClashAwareName(element.toClashedName()))
- is DProperty -> element.copy(extra = element.extra + DriClashAwareName(element.toClashedName()))
- is DTypeAlias -> element.copy(extra = element.extra + DriClashAwareName(element.toClashedName()))
- else -> null
- } as? T?
+ element.renameClashingDocumentable()
}
}
- open fun pageForFunction(f: DFunction) = MemberPageNode(f.nameAfterClash(), contentForFunction(f), setOf(f.dri), f)
+ @Suppress("UNCHECKED_CAST")
+ private fun <T : Documentable> T.renameClashingDocumentable(): T? = when (this) {
+ is DClass -> copy(extra = this.extra + DriClashAwareName(this.toClashedName()))
+ is DObject -> copy(extra = this.extra + DriClashAwareName(this.toClashedName()))
+ is DAnnotation -> copy(extra = this.extra + DriClashAwareName(this.toClashedName()))
+ is DInterface -> copy(extra = this.extra + DriClashAwareName(this.toClashedName()))
+ is DEnum -> copy(extra = this.extra + DriClashAwareName(this.toClashedName()))
+ is DFunction -> copy(extra = this.extra + DriClashAwareName(this.toClashedName()))
+ is DProperty -> copy(extra = this.extra + DriClashAwareName(this.toClashedName()))
+ is DTypeAlias -> copy(extra = this.extra + DriClashAwareName(this.toClashedName()))
+ else -> null
+ } as? T?
+
+ private fun <T : Documentable> List<T>.mergeClashingDocumentable(): List<List<T>> =
+ groupBy { it.dri }.values.toList()
+
+ open fun pageForFunction(f: DFunction) =
+ MemberPageNode(f.nameAfterClash(), contentForFunction(f), setOf(f.dri), listOf(f))
+
+ open fun pageForFunctions(fs: List<DFunction>): MemberPageNode {
+ val dri = fs.dri.also {
+ if (it.size != 1) {
+ logger.error("Function dri should have the same one ${it.first()} inside the one page!")
+ }
+ }
+ return MemberPageNode(fs.first().nameAfterClash(), contentForMembers(fs), dri, fs)
+ }
open fun pageForProperty(p: DProperty): MemberPageNode? =
- MemberPageNode(p.nameAfterClash(), contentForProperty(p), setOf(p.dri), p)
+ MemberPageNode(p.nameAfterClash(), contentForProperty(p), setOf(p.dri), listOf(p))
+
+ open fun pageForProperties(ps: List<DProperty>): MemberPageNode {
+ val dri = ps.dri.also {
+ if (it.size != 1) {
+ logger.error("Property dri should have the same one ${it.first()} inside the one page!")
+ }
+ }
+ return MemberPageNode(ps.first().nameAfterClash(), contentForMembers(ps), dri, ps)
+ }
private fun <T> T.isInherited(): Boolean where T : Documentable, T : WithExtraProperties<T> =
sourceSets.all { sourceSet -> extra[InheritedMember]?.isInherited(sourceSet) == true }
@@ -168,148 +241,193 @@ open class DefaultPageCreator(
}
}
+ protected open fun contentForScopes(
+ scopes: List<WithScope>,
+ sourceSets: Set<DokkaSourceSet>
+ ): ContentGroup {
+ val types = scopes.flatMap { it.classlikes } + scopes.filterIsInstance<DPackage>().flatMap { it.typealiases }
+ val inheritors = scopes.fold(mutableMapOf<DokkaSourceSet, List<DRI>>()) { acc, scope ->
+ val inheritorsForScope =
+ scope.safeAs<WithExtraProperties<Documentable>>()?.let { it.extra[InheritorsInfo] }?.let { inheritors ->
+ inheritors.value.filter { it.value.isNotEmpty() }
+ }.orEmpty()
+ inheritorsForScope.forEach { (k, v) ->
+ acc.compute(k) { _, value -> value?.plus(v) ?: v }
+ }
+ acc
+ }
+
+ return contentForScope(
+ @Suppress("UNCHECKED_CAST")
+ (scopes as List<Documentable>).dri,
+ sourceSets,
+ types,
+ scopes.flatMap { it.functions },
+ scopes.flatMap { it.properties },
+ inheritors
+ )
+ }
+
protected open fun contentForScope(
s: WithScope,
dri: DRI,
sourceSets: Set<DokkaSourceSet>
- ) = contentBuilder.contentFor(s as Documentable) {
+ ): ContentGroup {
val types = listOf(
s.classlikes,
(s as? DPackage)?.typealiases ?: emptyList()
).flatten()
+ val inheritors =
+ s.safeAs<WithExtraProperties<Documentable>>()?.let { it.extra[InheritorsInfo] }?.let { inheritors ->
+ inheritors.value.filter { it.value.isNotEmpty() }
+ }.orEmpty()
+
+ return contentForScope(setOf(dri), sourceSets, types, s.functions, s.properties, inheritors)
+ }
+
+ protected open fun contentForScope(
+ dri: Set<DRI>,
+ sourceSets: Set<DokkaSourceSet>,
+ types: List<Documentable>,
+ functions: List<DFunction>,
+ properties: List<DProperty>,
+ inheritors: SourceSetDependent<List<DRI>>
+ ) = contentBuilder.contentFor(dri, sourceSets) {
divergentBlock("Types", types, ContentKind.Classlikes, extra = mainExtra + SimpleAttr.header("Types"))
if (separateInheritedMembers) {
- val (inheritedFunctions, memberFunctions) = s.functions.splitInherited()
- val (inheritedProperties, memberProperties) = s.properties.splitInherited()
+ val (inheritedFunctions, memberFunctions) = functions.splitInherited()
+ val (inheritedProperties, memberProperties) = properties.splitInherited()
propertiesBlock("Properties", memberProperties, sourceSets)
propertiesBlock("Inherited properties", inheritedProperties, sourceSets)
functionsBlock("Functions", memberFunctions)
functionsBlock("Inherited functions", inheritedFunctions)
} else {
- functionsBlock("Functions", s.functions)
- propertiesBlock("Properties", s.properties, sourceSets)
+ functionsBlock("Functions", functions)
+ propertiesBlock("Properties", properties, sourceSets)
}
- s.safeAs<WithExtraProperties<Documentable>>()?.let { it.extra[InheritorsInfo] }?.let { inheritors ->
- val map = inheritors.value.filter { it.value.isNotEmpty() }
- if (map.values.any()) {
- header(2, "Inheritors") { }
- +ContentTable(
- header = listOf(contentBuilder.contentFor(mainDRI, mainSourcesetData) {
- text("Name")
- }),
- children = map.entries.flatMap { entry -> entry.value.map { Pair(entry.key, it) } }
- .groupBy({ it.second }, { it.first }).map { (classlike, platforms) ->
- val label = classlike.classNames?.substringAfterLast(".") ?: classlike.toString()
- .also { logger.warn("No class name found for DRI $classlike") }
- buildGroup(
- setOf(classlike),
- platforms.toSet(),
- ContentKind.Inheritors,
- extra = mainExtra + SymbolAnchorHint(label, ContentKind.Inheritors)
- ) {
- link(label, classlike)
- }
- },
- dci = DCI(setOf(dri), ContentKind.Inheritors),
- sourceSets = sourceSets.toDisplaySourceSets(),
- style = emptySet(),
- extra = mainExtra + SimpleAttr.header("Inheritors")
- )
- }
+ if (inheritors.values.any()) {
+ header(2, "Inheritors") { }
+ +ContentTable(
+ header = listOf(contentBuilder.contentFor(mainDRI, mainSourcesetData) {
+ text("Name")
+ }),
+ children = inheritors.entries.flatMap { entry -> entry.value.map { Pair(entry.key, it) } }
+ .groupBy({ it.second }, { it.first }).map { (classlike, platforms) ->
+ val label = classlike.classNames?.substringAfterLast(".") ?: classlike.toString()
+ .also { logger.warn("No class name found for DRI $classlike") }
+ buildGroup(
+ setOf(classlike),
+ platforms.toSet(),
+ ContentKind.Inheritors,
+ extra = mainExtra + SymbolAnchorHint(label, ContentKind.Inheritors)
+ ) {
+ link(label, classlike)
+ }
+ },
+ dci = DCI(dri, ContentKind.Inheritors),
+ sourceSets = sourceSets.toDisplaySourceSets(),
+ style = emptySet(),
+ extra = mainExtra + SimpleAttr.header("Inheritors")
+ )
}
}
private fun Iterable<DFunction>.sorted() =
sortedWith(compareBy({ it.name }, { it.parameters.size }, { it.dri.toString() }))
- protected open fun contentForEnumEntry(e: DEnumEntry) = contentBuilder.contentFor(e) {
- group(kind = ContentKind.Cover) {
- cover(e.name)
- sourceSetDependentHint(e.dri, e.sourceSets.toSet()) {
- +buildSignature(e)
- +contentForDescription(e)
- }
- }
- group(styles = setOf(ContentStyle.TabbedContent)) {
- +contentForComments(e)
- +contentForScope(e, e.dri, e.sourceSets)
- }
- }
-
- protected open fun contentForClasslike(c: DClasslike) = contentBuilder.contentFor(c) {
- @Suppress("UNCHECKED_CAST")
- val extensions = (c as WithExtraProperties<DClasslike>)
- .extra[CallableExtensions]?.extensions
- ?.filterIsInstance<Documentable>().orEmpty()
- // Extensions are added to sourceSets since they can be placed outside the sourceSets from classlike
- // Example would be an Interface in common and extension function in jvm
- group(kind = ContentKind.Cover, sourceSets = mainSourcesetData + extensions.sourceSets) {
- cover(c.name.orEmpty())
- sourceSetDependentHint(c.dri, c.sourceSets) {
- +buildSignature(c)
- +contentForDescription(c)
+ /**
+ * @param documentables a list of [DClasslike] and [DEnumEntry] with the same dri in different sourceSets
+ */
+ protected open fun contentForClasslikesAndEntries(documentables: List<Documentable>): ContentGroup =
+ contentBuilder.contentFor(documentables.dri, documentables.sourceSets) {
+ val classlikes = documentables.filterIsInstance<DClasslike>()
+
+ @Suppress("UNCHECKED_CAST")
+ val extensions = (classlikes as List<WithExtraProperties<DClasslike>>).flatMap {
+ it.extra[CallableExtensions]?.extensions
+ ?.filterIsInstance<Documentable>().orEmpty()
}
- }
- group(styles = setOf(ContentStyle.TabbedContent), sourceSets = mainSourcesetData + extensions.sourceSets) {
- +contentForComments(c)
- if (c is WithConstructors) {
- block(
- "Constructors",
- 2,
- ContentKind.Constructors,
- c.constructors,
- c.sourceSets,
- needsAnchors = true,
- extra = PropertyContainer.empty<ContentNode>() + SimpleAttr.header("Constructors")
- ) {
- link(it.name, it.dri, kind = ContentKind.Main, styles = setOf(ContentStyle.RowTitle))
- sourceSetDependentHint(
- it.dri,
- it.sourceSets.toSet(),
- kind = ContentKind.SourceSetDependentHint,
- styles = emptySet(),
- extra = PropertyContainer.empty()
- ) {
+ // Extensions are added to sourceSets since they can be placed outside the sourceSets from classlike
+ // Example would be an Interface in common and extension function in jvm
+ group(kind = ContentKind.Cover, sourceSets = mainSourcesetData + extensions.sourceSets) {
+ cover(documentables.first().name.orEmpty())
+ sourceSetDependentHint(documentables.dri, documentables.sourceSets) {
+ documentables.forEach {
+buildSignature(it)
- contentForBrief(it)
+ +contentForDescription(it)
}
}
}
- if (c is DEnum) {
- block(
- "Entries",
- 2,
- ContentKind.Classlikes,
- c.entries,
- c.sourceSets.toSet(),
- needsSorting = false,
- needsAnchors = true,
- extra = mainExtra + SimpleAttr.header("Entries"),
- styles = emptySet()
- ) {
- link(it.name, it.dri)
- sourceSetDependentHint(
- it.dri,
- it.sourceSets.toSet(),
- kind = ContentKind.SourceSetDependentHint,
- extra = PropertyContainer.empty()
- ) {
- +buildSignature(it)
- contentForBrief(it)
+
+ group(styles = setOf(ContentStyle.TabbedContent), sourceSets = mainSourcesetData + extensions.sourceSets) {
+ +contentForComments(documentables)
+ val csWithConstructor = classlikes.filterIsInstance<WithConstructors>()
+ if (csWithConstructor.isNotEmpty()) {
+ val constructorsToDocumented = csWithConstructor.flatMap { it.constructors }
+ multiBlock(
+ "Constructors",
+ 2,
+ ContentKind.Constructors,
+ constructorsToDocumented.groupBy { it.parameters.map { it.dri } }
+ .map { (_, v) -> v.first().name to v },
+ @Suppress("UNCHECKED_CAST")
+ (csWithConstructor as List<Documentable>).sourceSets,
+ needsAnchors = true,
+ extra = PropertyContainer.empty<ContentNode>() + SimpleAttr.header("Constructors")
+ ) { key, ds ->
+ link(key, ds.first().dri, kind = ContentKind.Main, styles = setOf(ContentStyle.RowTitle))
+ sourceSetDependentHint(
+ ds.dri,
+ ds.sourceSets,
+ kind = ContentKind.SourceSetDependentHint,
+ styles = emptySet(),
+ extra = PropertyContainer.empty<ContentNode>()
+ ) {
+ ds.forEach {
+ +buildSignature(it)
+ contentForBrief(it)
+ }
+ }
}
}
- }
- +contentForScope(c, c.dri, c.sourceSets)
+ val csEnum = classlikes.filterIsInstance<DEnum>()
+ if (csEnum.isNotEmpty()) {
+ multiBlock(
+ "Entries",
+ 2,
+ ContentKind.Classlikes,
+ csEnum.flatMap { it.entries }.groupBy { it.name }.toList(),
+ csEnum.sourceSets,
+ needsSorting = false,
+ needsAnchors = true,
+ extra = mainExtra + SimpleAttr.header("Entries"),
+ styles = emptySet()
+ ) { key, ds ->
+ link(key, ds.first().dri)
+ sourceSetDependentHint(
+ ds.dri,
+ ds.sourceSets,
+ kind = ContentKind.SourceSetDependentHint,
+ extra = PropertyContainer.empty<ContentNode>()
+ ) {
+ ds.forEach {
+ +buildSignature(it)
+ contentForBrief(it)
+ }
+ }
+ }
+ }
+ +contentForScopes(documentables.filterIsInstance<WithScope>(), documentables.sourceSets)
- divergentBlock(
- "Extensions",
- extensions,
- ContentKind.Extensions,
- extra = mainExtra + SimpleAttr.header("Extensions")
- )
+ divergentBlock(
+ "Extensions",
+ extensions,
+ ContentKind.Extensions,
+ extra = mainExtra + SimpleAttr.header("Extensions")
+ )
+ }
}
- }
@Suppress("UNCHECKED_CAST")
private inline fun <reified T : TagWrapper> GroupedTags.withTypeUnnamed(): SourceSetDependent<T> =
@@ -347,7 +465,7 @@ open class DefaultPageCreator(
if (customTags.isNotEmpty()) {
group(styles = setOf(TextStyle.Block)) {
platforms.forEach { platform ->
- customTags.forEach { (tagName, sourceSetTag) ->
+ customTags.forEach { (_, sourceSetTag) ->
sourceSetTag[platform]?.let { tag ->
customTagContentProviders.filter { it.isApplicable(tag) }.forEach { provider ->
with(provider) {
@@ -379,8 +497,8 @@ open class DefaultPageCreator(
}.children
}
- private fun Documentable.getPossibleFallbackSourcesets(sourceSet: DokkaSourceSet) =
- this.sourceSets.filter { it.sourceSetID in sourceSet.dependentSourceSets }
+ private fun Set<DokkaSourceSet>.getPossibleFallbackSourcesets(sourceSet: DokkaSourceSet) =
+ this.filter { it.sourceSetID in sourceSet.dependentSourceSets }
private fun <V> Map<DokkaSourceSet, V>.fallback(sourceSets: List<DokkaSourceSet>): V? =
sourceSets.firstOrNull { it in this.keys }.let { this[it] }
@@ -388,8 +506,19 @@ open class DefaultPageCreator(
protected open fun contentForComments(
d: Documentable,
isPlatformHintedContent: Boolean = true
+ ) = contentForComments(d.dri, d.sourceSets, d.groupedTags, isPlatformHintedContent)
+
+ protected open fun contentForComments(
+ d: List<Documentable>,
+ isPlatformHintedContent: Boolean = true
+ ) = contentForComments(d.first().dri, d.sourceSets, d.groupedTags, isPlatformHintedContent)
+
+ protected open fun contentForComments(
+ dri: DRI,
+ sourceSets: Set<DokkaSourceSet>,
+ tags: GroupedTags,
+ isPlatformHintedContent: Boolean = true
): List<ContentNode> {
- val tags = d.groupedTags
fun DocumentableContentBuilder.buildContent(
platforms: Set<DokkaSourceSet>,
@@ -417,7 +546,7 @@ open class DefaultPageCreator(
buildContent(availablePlatforms) {
table(kind = ContentKind.Parameters, sourceSets = availablePlatforms) {
availablePlatforms.forEach { platform ->
- val possibleFallbacks = d.getPossibleFallbackSourcesets(platform)
+ val possibleFallbacks = sourceSets.getPossibleFallbackSourcesets(platform)
params.mapNotNull { (_, param) ->
(param[platform] ?: param.fallback(possibleFallbacks))?.let {
row(sourceSets = setOf(platform), kind = ContentKind.Parameters) {
@@ -451,7 +580,7 @@ open class DefaultPageCreator(
buildContent(availablePlatforms) {
table(kind = ContentKind.Sample) {
availablePlatforms.forEach { platform ->
- val possibleFallbacks = d.getPossibleFallbackSourcesets(platform)
+ val possibleFallbacks = sourceSets.getPossibleFallbackSourcesets(platform)
seeAlsoTags.forEach { (_, see) ->
(see[platform] ?: see.fallback(possibleFallbacks))?.let {
row(
@@ -537,12 +666,11 @@ open class DefaultPageCreator(
}
}
- return contentBuilder.contentFor(d) {
+ return contentBuilder.contentFor(dri, sourceSets) {
if (tags.isNotEmpty()) {
contentForSamples()
contentForSeeAlso()
- if (d !is DProperty)
- contentForParams()
+ contentForParams()
contentForThrows()
}
}.children
@@ -558,7 +686,8 @@ open class DefaultPageCreator(
We purposefully ignore all other tags as they should not be visible in brief
*/
- it.firstMemberOfTypeOrNull<Description>() ?: it.firstMemberOfTypeOrNull<Property>().takeIf { documentable is DProperty }
+ it.firstMemberOfTypeOrNull<Description>() ?: it.firstMemberOfTypeOrNull<Property>()
+ .takeIf { documentable is DProperty }
}?.let {
group(sourceSets = setOf(sourceSet), kind = ContentKind.BriefComment) {
if (documentable.hasSeparatePage) createBriefComment(documentable, sourceSet, it)
@@ -568,9 +697,13 @@ open class DefaultPageCreator(
}
}
- private fun DocumentableContentBuilder.createBriefComment(documentable: Documentable, sourceSet: DokkaSourceSet, tag: TagWrapper){
+ private fun DocumentableContentBuilder.createBriefComment(
+ documentable: Documentable,
+ sourceSet: DokkaSourceSet,
+ tag: TagWrapper
+ ) {
(documentable as? WithSources)?.documentableLanguage(sourceSet)?.let {
- when(it){
+ when (it) {
DocumentableLanguage.KOTLIN -> firstParagraphComment(tag.root)
DocumentableLanguage.JAVA -> firstSentenceComment(tag.root)
}
@@ -581,22 +714,27 @@ open class DefaultPageCreator(
protected open fun contentForProperty(p: DProperty) = contentForMember(p)
- protected open fun contentForMember(d: Documentable) = contentBuilder.contentFor(d) {
- group(kind = ContentKind.Cover) {
- cover(d.name.orEmpty())
- }
- divergentGroup(ContentDivergentGroup.GroupID("member")) {
- instance(setOf(d.dri), d.sourceSets.toSet()) {
- divergent {
- +buildSignature(d)
- }
- after {
- +contentForDescription(d)
- +contentForComments(d, isPlatformHintedContent = false)
+ protected open fun contentForMember(d: Documentable) = contentForMembers(listOf(d))
+
+ protected open fun contentForMembers(doumentables: List<Documentable>) =
+ contentBuilder.contentFor(doumentables.dri, doumentables.sourceSets) {
+ group(kind = ContentKind.Cover) {
+ cover(doumentables.first().name.orEmpty())
+ }
+ divergentGroup(ContentDivergentGroup.GroupID("member")) {
+ doumentables.forEach { d ->
+ instance(setOf(d.dri), d.sourceSets) {
+ divergent {
+ +buildSignature(d)
+ }
+ after {
+ +contentForDescription(d)
+ +contentForComments(d, isPlatformHintedContent = false)
+ }
+ }
}
}
}
- }
private fun DocumentableContentBuilder.functionsBlock(name: String, list: Collection<DFunction>) = divergentBlock(
name,
@@ -610,22 +748,24 @@ open class DefaultPageCreator(
list: Collection<DProperty>,
sourceSets: Set<DokkaSourceSet>
) {
- block(
+ multiBlock(
name,
2,
ContentKind.Properties,
- list,
+ list.groupBy { it.name }.toList(),
sourceSets,
needsAnchors = true,
extra = mainExtra + SimpleAttr.header(name),
headers = listOf(
headers("Name", "Summary")
)
- ) {
- link(it.name, it.dri, kind = ContentKind.Main)
- sourceSetDependentHint(it.dri, it.sourceSets.toSet(), kind = ContentKind.SourceSetDependentHint) {
- +buildSignature(it)
- contentForBrief(it)
+ ) { key, props ->
+ link(key, props.first().dri, kind = ContentKind.Main)
+ sourceSetDependentHint(props.dri, props.sourceSets, kind = ContentKind.SourceSetDependentHint) {
+ props.forEach {
+ +buildSignature(it)
+ contentForBrief(it)
+ }
}
}
}
@@ -699,7 +839,7 @@ open class DefaultPageCreator(
if (customTags.isEmpty()) return
documentable.sourceSets.forEach { sourceSet ->
- customTags.forEach { (tagName, sourceSetTag) ->
+ customTags.forEach { (_, sourceSetTag) ->
sourceSetTag[sourceSet]?.let { tag ->
customTagContentProviders.filter { it.isApplicable(tag) }.forEach { provider ->
with(provider) {
@@ -716,9 +856,19 @@ open class DefaultPageCreator(
private val List<Documentable>.sourceSets: Set<DokkaSourceSet>
get() = flatMap { it.sourceSets }.toSet()
+ private val List<Documentable>.dri: Set<DRI>
+ get() = map { it.dri }.toSet()
+
private val Documentable.groupedTags: GroupedTags
get() = documentation.flatMap { (pd, doc) ->
- doc.children.asSequence().map { pd to it }.toList()
+ doc.children.map { pd to it }.toList()
+ }.groupBy { it.second::class }
+
+ private val List<Documentable>.groupedTags: GroupedTags
+ get() = this.flatMap {
+ it.documentation.flatMap { (pd, doc) ->
+ doc.children.map { pd to it }.toList()
+ }
}.groupBy { it.second::class }
private val Documentable.descriptions: SourceSetDependent<Description>
@@ -730,6 +880,7 @@ open class DefaultPageCreator(
private val Documentable.hasSeparatePage: Boolean
get() = this !is DTypeAlias
+ @Suppress("UNCHECKED_CAST")
private fun <T : Documentable> T.nameAfterClash(): String =
((this as? WithExtraProperties<out Documentable>)?.extra?.get(DriClashAwareName)?.value ?: name).orEmpty()
}
diff --git a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
index 8c3fcd84..7b6fbc7a 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
@@ -227,6 +227,45 @@ open class PageContentBuilder(
}
}
+ fun <T : Pair<String, List<Documentable>>> multiBlock(
+ name: String,
+ level: Int,
+ kind: Kind = ContentKind.Main,
+ groupedElements: Iterable<T>,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ renderWhenEmpty: Boolean = false,
+ needsSorting: Boolean = true,
+ headers: List<ContentGroup> = emptyList(),
+ needsAnchors: Boolean = false,
+ operation: DocumentableContentBuilder.(String, List<Documentable>) -> Unit
+ ) {
+ if (renderWhenEmpty || groupedElements.any()) {
+ header(level, name, kind = kind) { }
+ contents += ContentTable(
+ header = headers,
+ children = groupedElements
+ .let {
+ if (needsSorting)
+ it.sortedWith(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)) { it.first })
+ else it
+ }
+ .map {
+ val newExtra = if (needsAnchors) extra + SymbolAnchorHint(it.first, kind) else extra
+ val documentables = it.second
+ buildGroup(documentables.map { it.dri }.toSet(), documentables.flatMap { it.sourceSets }.toSet(), kind, styles, newExtra) {
+ operation(it.first, documentables)
+ }
+ },
+ dci = DCI(mainDRI, kind),
+ sourceSets = sourceSets.toDisplaySourceSets(),
+ style = styles,
+ extra = extra
+ )
+ }
+ }
+
fun <T> list(
elements: List<T>,
prefix: String = "",
diff --git a/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt b/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt
index 7d8a169b..50f9e357 100644
--- a/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt
+++ b/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt
@@ -1,11 +1,11 @@
package content.functions
-import org.junit.Assert.assertEquals
import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
import org.jetbrains.dokka.links.TypeConstructor
import org.jetbrains.dokka.model.DClass
import org.jetbrains.dokka.model.dfs
import org.jetbrains.dokka.pages.*
+import org.junit.Assert.assertEquals
import org.junit.jupiter.api.Test
import kotlin.test.assertNull
@@ -60,7 +60,7 @@ class ContentForBriefTest : BaseAbstractTest() {
testInline(codeWithSecondaryAndPrimaryConstructorsDocumented, testConfiguration) {
pagesTransformationStage = { module ->
val classPage =
- module.dfs { it.name == "Example" && (it as ContentPage).documentable is DClass } as ContentPage
+ module.dfs { it.name == "Example" && (it as WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage
val constructorsTable =
classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable
@@ -84,7 +84,7 @@ class ContentForBriefTest : BaseAbstractTest() {
testInline(codeWithSecondaryAndPrimaryConstructorsDocumented, testConfiguration) {
pagesTransformationStage = { module ->
val classPage =
- module.dfs { it.name == "Example" && (it as ContentPage).documentable is DClass } as ContentPage
+ module.dfs { it.name == "Example" && (it as WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage
val constructorsTable =
classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable
@@ -108,7 +108,7 @@ class ContentForBriefTest : BaseAbstractTest() {
testInline(codeWithDocumentedParameter, testConfiguration) {
pagesTransformationStage = { module ->
val classPage =
- module.dfs { it.name == "Example" && (it as ContentPage).documentable is DClass } as ContentPage
+ module.dfs { it.name == "Example" && (it as WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage
val constructorsTable =
classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable
@@ -319,7 +319,7 @@ class ContentForBriefTest : BaseAbstractTest() {
}
private fun RootPageNode.singleFunctionDescription(className: String): ContentGroup {
- val classPage = dfs { it.name == className && (it as ContentPage).documentable is DClass } as ContentPage
+ val classPage = dfs { it.name == className && (it as WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage
val functionsTable =
classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Functions } as ContentTable
diff --git a/plugins/base/src/test/kotlin/content/functions/ContentForConstructors.kt b/plugins/base/src/test/kotlin/content/functions/ContentForConstructors.kt
index 832fa6f6..8da3be54 100644
--- a/plugins/base/src/test/kotlin/content/functions/ContentForConstructors.kt
+++ b/plugins/base/src/test/kotlin/content/functions/ContentForConstructors.kt
@@ -32,7 +32,7 @@ class ContentForConstructors : BaseAbstractTest() {
""".trimIndent(), testConfiguration) {
pagesTransformationStage = { module ->
val classPage =
- module.dfs { it.name == "Example" && (it as ContentPage).documentable is DClass } as ContentPage
+ module.dfs { it.name == "Example" && (it as WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage
val constructorsTable =
classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable
diff --git a/plugins/base/src/test/kotlin/locationProvider/DokkaLocationProviderTest.kt b/plugins/base/src/test/kotlin/locationProvider/DokkaLocationProviderTest.kt
index 8c96eab3..59406e1e 100644
--- a/plugins/base/src/test/kotlin/locationProvider/DokkaLocationProviderTest.kt
+++ b/plugins/base/src/test/kotlin/locationProvider/DokkaLocationProviderTest.kt
@@ -53,8 +53,7 @@ class DokkaLocationProviderTest : BaseAbstractTest() {
ModulePageNode(
name = name,
children = packages.pages,
- content = stubContentNode,
- documentable = null
+ content = stubContentNode
)
)
}
@@ -69,7 +68,6 @@ class DokkaLocationProviderTest : BaseAbstractTest() {
name = name,
children = packages.pages,
content = stubContentNode,
- documentable = null,
dri = emptySet()
)
)
@@ -84,7 +82,6 @@ class DokkaLocationProviderTest : BaseAbstractTest() {
name = name,
children = emptyList(),
content = stubContentNode,
- documentable = null,
dri = emptySet()
)
)
diff --git a/plugins/base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt b/plugins/base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt
new file mode 100644
index 00000000..ad3b5d38
--- /dev/null
+++ b/plugins/base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt
@@ -0,0 +1,352 @@
+package transformers
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.PluginConfigurationImpl
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.childrenOfType
+import org.jetbrains.dokka.model.dfs
+import org.jetbrains.dokka.model.firstChildOfType
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.kotlin.utils.addIfNotNull
+import org.junit.jupiter.api.Test
+import utils.assertNotNull
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() {
+
+ @Suppress("UNUSED_VARIABLE")
+ private fun configuration(switchOn: Boolean) = dokkaConfiguration {
+ sourceSets {
+ val common = sourceSet {
+ name = "common"
+ displayName = "common"
+ analysisPlatform = "common"
+ sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt")
+ }
+ val js = sourceSet {
+ name = "js"
+ displayName = "js"
+ analysisPlatform = "js"
+ dependentSourceSets = setOf(common.value.sourceSetID)
+ sourceRoots = listOf("src/jsMain/kotlin/pageMerger/Test.kt")
+ }
+ val jvm = sourceSet {
+ name = "jvm"
+ displayName = "jvm"
+ analysisPlatform = "jvm"
+ sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt")
+ }
+ }
+ pluginsConfigurations.addIfNotNull(
+ PluginConfigurationImpl(
+ DokkaBase::class.qualifiedName!!,
+ DokkaConfiguration.SerializationFormat.JSON,
+ """{ "mergeImplicitExpectActualDeclarations": $switchOn }""",
+ )
+ )
+ }
+
+ private fun ClasslikePageNode.findSectionWithName(name: String) : ContentNode? {
+ var sectionHeader: ContentHeader? = null
+ return content.dfs { node ->
+ node.children.filterIsInstance<ContentHeader>().any { header ->
+ header.children.firstOrNull { it is ContentText && it.text == name }?.also { sectionHeader = header } != null
+ }
+ }?.children?.dropWhile { child -> child != sectionHeader }?.drop(1)?.firstOrNull()
+ }
+
+ @Test
+ fun `should merge fun`() {
+ testInline(
+ """
+
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | fun method1(): String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | fun method1(): Int
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val functions = classPage.findSectionWithName("Functions").assertNotNull("Functions")
+ val method1 = functions.children.singleOrNull().assertNotNull("method1")
+
+ assertEquals(
+ 2,
+ method1.firstChildOfType<ContentDivergentGroup>().childrenOfType<ContentDivergentInstance>().size,
+ "Incorrect number of divergent instances found"
+ )
+
+ val methodPage = root.dfs { it.name == "method1" } as? MemberPageNode
+ assertNotNull(methodPage, "Tested method not found!")
+
+ val divergentGroup = methodPage.content.dfs { it is ContentDivergentGroup } as? ContentDivergentGroup
+
+ assertEquals(
+ 2,
+ divergentGroup?.childrenOfType<ContentDivergentInstance>()?.size,
+ "Incorrect number of divergent instances found in method page"
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `should merge method and prop`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | fun method1(): String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | val prop1: Int
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val props = classPage.findSectionWithName("Properties").assertNotNull("Properties")
+ val prop1 = props.children.singleOrNull().assertNotNull("prop1")
+
+ val functions = classPage.findSectionWithName("Functions").assertNotNull("Functions")
+ val method1 = functions.children.singleOrNull().assertNotNull("method1")
+ }
+ }
+ }
+
+ @Test
+ fun `should merge prop`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | val prop1: String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | val prop1: Int
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val props = classPage.findSectionWithName("Properties").assertNotNull("Properties")
+ val prop1 = props.children.singleOrNull().assertNotNull("prop1")
+
+ assertEquals(
+ 2,
+ prop1.firstChildOfType<PlatformHintedContent>().inner.children.size,
+ "Incorrect number of divergent instances found"
+ )
+
+ val propPage = root.dfs { it.name == "prop1" } as? MemberPageNode
+ assertNotNull(propPage, "Tested method not found!")
+
+ val divergentGroup = propPage.content.dfs { it is ContentDivergentGroup } as? ContentDivergentGroup
+
+ assertEquals(
+ 2,
+ divergentGroup?.childrenOfType<ContentDivergentInstance>()?.size,
+ "Incorrect number of divergent instances found in method page"
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `should merge enum and class`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | val prop1: String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |enum class classA {
+ | ENTRY
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val entries = classPage.findSectionWithName("Entries").assertNotNull("Entries")
+ val entry = entries.children.singleOrNull().assertNotNull("ENTRY")
+
+ val props = classPage.findSectionWithName("Properties").assertNotNull("Properties")
+ assertEquals(
+ 3,
+ props.children.size,
+ "Incorrect number of properties found in method page"
+ )
+ }
+ }
+ }
+
+ fun PageNode.childrenRec(): List<PageNode> = listOf(this) + children.flatMap { it.childrenRec() }
+
+ @Test
+ fun `should merge enum entries`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |enum class classA {
+ | SMTH;
+ | fun method1(): Int
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |enum class classA {
+ | SMTH;
+ | fun method1(): Int
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "SMTH" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val functions = classPage.findSectionWithName("Functions").assertNotNull("Functions")
+ val method1 = functions.children.singleOrNull().assertNotNull("method1")
+
+ assertEquals(
+ 2,
+ method1.firstChildOfType<ContentDivergentGroup>().childrenOfType<ContentDivergentInstance>().size,
+ "Incorrect number of divergent instances found"
+ )
+ }
+ }
+ }
+
+ /**
+ * There is a case when a property and fun from different source sets
+ * have the same name so pages have the same urls respectively.
+ */
+ @Test
+ fun `should no merge prop and method with the same name`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | fun merged():String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | val merged:String
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val allChildren = root.childrenRec().filterIsInstance<MemberPageNode>()
+
+ assertEquals(
+ 1,
+ allChildren.filter { it.name == "merged" }.size,
+ "Incorrect number of fun pages"
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `should always merge constructor`() {
+ testInline(
+ """
+ |/src/commonMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |expect class classA(a: Int)
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |actual class classA(a: Int)
+ """.trimMargin(),
+ configuration(false),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val constructors = classPage.findSectionWithName("Constructors").assertNotNull("Constructors")
+
+ assertEquals(
+ 1,
+ constructors.children.size,
+ "Incorrect number of constructors"
+ )
+
+ val platformHinted = constructors.dfs { it is PlatformHintedContent } as? PlatformHintedContent
+
+ assertEquals(
+ 2,
+ platformHinted?.sourceSets?.size,
+ "Incorrect number of source sets"
+ )
+ }
+ }
+ }
+} \ No newline at end of file