aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/base/src/main/kotlin')
-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
7 files changed, 425 insertions, 226 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 = "",