aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main
diff options
context:
space:
mode:
authorAndrey Tyrin <andrei.tyrin@jetbrains.com>2023-01-09 13:28:05 +0100
committerGitHub <noreply@github.com>2023-01-09 13:28:05 +0100
commitc3aa879c3c77233c401cf314d736bf20891991ba (patch)
tree8f5ee6cff051069f72f88b6d86c9339ce02d599f /plugins/base/src/main
parent5ad955f28bde39d2f9e972bd3ec2c341579d9474 (diff)
downloaddokka-c3aa879c3c77233c401cf314d736bf20891991ba.tar.gz
dokka-c3aa879c3c77233c401cf314d736bf20891991ba.tar.bz2
dokka-c3aa879c3c77233c401cf314d736bf20891991ba.zip
Move inheritors, params, see also and samples tabs to description for classlikes (#2749)
Diffstat (limited to 'plugins/base/src/main')
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt385
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt346
-rw-r--r--plugins/base/src/main/resources/dokka/styles/style.css7
3 files changed, 394 insertions, 344 deletions
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
index 9d45f7a3..266d6e94 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
@@ -6,28 +6,19 @@ 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.links.PointingToDeclaration
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.doc.*
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.model.properties.WithExtraProperties
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.utilities.DokkaLogger
-import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import kotlin.reflect.KClass
-import kotlin.reflect.full.isSubclassOf
-internal const val KDOC_TAG_HEADER_LEVEL = 4
-
-private typealias GroupedTags = Map<KClass<out TagWrapper>, List<Pair<DokkaSourceSet?, TagWrapper>>>
-
-private val specialTags: Set<KClass<out TagWrapper>> =
- setOf(Property::class, Description::class, Constructor::class, Param::class, See::class)
+internal typealias GroupedTags = Map<KClass<out TagWrapper>, List<Pair<DokkaSourceSet?, TagWrapper>>>
open class DefaultPageCreator(
configuration: DokkaBaseConfiguration?,
@@ -203,7 +194,6 @@ open class DefaultPageCreator(
}
}
}
- +contentForComments(m)
block(
"Packages",
@@ -244,7 +234,6 @@ open class DefaultPageCreator(
}
}
group(styles = setOf(ContentStyle.TabbedContent)) {
- +contentForComments(p)
+contentForScope(p, p.dri, p.sourceSets)
}
}
@@ -254,25 +243,13 @@ open class DefaultPageCreator(
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
+ scopes.flatMap { it.properties }
)
}
@@ -285,12 +262,7 @@ open class DefaultPageCreator(
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)
+ return contentForScope(setOf(dri), sourceSets, types, s.functions, s.properties)
}
protected open fun contentForScope(
@@ -298,8 +270,7 @@ open class DefaultPageCreator(
sourceSets: Set<DokkaSourceSet>,
types: List<Documentable>,
functions: List<DFunction>,
- properties: List<DProperty>,
- inheritors: SourceSetDependent<List<DRI>>
+ properties: List<DProperty>
) = contentBuilder.contentFor(dri, sourceSets) {
divergentBlock("Types", types, ContentKind.Classlikes, extra = mainExtra + SimpleAttr.header("Types"))
if (separateInheritedMembers) {
@@ -313,31 +284,6 @@ open class DefaultPageCreator(
functionsBlock("Functions", functions)
propertiesBlock("Properties", properties, sourceSets)
}
- 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() =
@@ -367,9 +313,7 @@ open class DefaultPageCreator(
}
}
}
-
group(styles = setOf(ContentStyle.TabbedContent), sourceSets = mainSourcesetData + extensions.sourceSets) {
- +contentForComments(documentables)
val csWithConstructor = classlikes.filterIsInstance<WithConstructors>()
if (csWithConstructor.isNotEmpty() && documentables.shouldRenderConstructors()) {
val constructorsToDocumented = csWithConstructor.flatMap { it.constructors }
@@ -442,272 +386,28 @@ open class DefaultPageCreator(
// and instantiated directly under normal circumstances, so constructors should not be rendered.
private fun List<Documentable>.shouldRenderConstructors() = !this.any { it is DAnnotation }
- @Suppress("UNCHECKED_CAST")
- private inline fun <reified T : TagWrapper> GroupedTags.withTypeUnnamed(): SourceSetDependent<T> =
- (this[T::class] as List<Pair<DokkaSourceSet, T>>?)?.toMap().orEmpty()
-
- @Suppress("UNCHECKED_CAST")
- private inline fun <reified T : NamedTagWrapper> GroupedTags.withTypeNamed(): Map<String, SourceSetDependent<T>> =
- (this[T::class] as List<Pair<DokkaSourceSet, T>>?)
- ?.groupByTo(linkedMapOf()) { it.second.name }
- ?.mapValues { (_, v) -> v.toMap() }
- .orEmpty()
-
- private inline fun <reified T : TagWrapper> GroupedTags.isNotEmptyForTag(): Boolean =
- this[T::class]?.isNotEmpty() ?: false
-
protected open fun contentForDescription(
d: Documentable
): List<ContentNode> {
- val tags: GroupedTags = d.groupedTags
- val platforms = d.sourceSets.toSet()
-
- return contentBuilder.contentFor(d, styles = setOf(TextStyle.Block)) {
- deprecatedSectionContent(d, platforms)
-
- val descriptions = d.descriptions
- if (descriptions.any { it.value.root.children.isNotEmpty() }) {
- platforms.forEach { platform ->
- descriptions[platform]?.also {
- group(sourceSets = setOf(platform), styles = emptySet()) {
- comment(it.root)
- }
- }
- }
- }
-
- val customTags = d.customTags
- if (customTags.isNotEmpty()) {
- platforms.forEach { platform ->
- customTags.forEach { (_, sourceSetTag) ->
- sourceSetTag[platform]?.let { tag ->
- customTagContentProviders.filter { it.isApplicable(tag) }.forEach { provider ->
- group(sourceSets = setOf(platform), styles = setOf(ContentStyle.KDocTag)) {
- with(provider) {
- contentForDescription(platform, tag)
- }
- }
- }
- }
- }
- }
- }
-
- val unnamedTags = tags.filterNot { (k, _) -> k.isSubclassOf(NamedTagWrapper::class) || k in specialTags }
- .values.flatten().groupBy { it.first }.mapValues { it.value.map { it.second } }
- if (unnamedTags.isNotEmpty()) {
- platforms.forEach { platform ->
- unnamedTags[platform]?.let { tags ->
- if (tags.isNotEmpty()) {
- tags.groupBy { it::class }.forEach { (_, sameCategoryTags) ->
- group(sourceSets = setOf(platform), styles = setOf(ContentStyle.KDocTag)) {
- header(
- level = KDOC_TAG_HEADER_LEVEL,
- text = sameCategoryTags.first().toHeaderString(),
- styles = setOf()
- )
- sameCategoryTags.forEach { comment(it.root, styles = setOf()) }
- }
- }
- }
- }
- }
- }
- }.children
- }
-
- 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] }
-
- 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> {
-
- fun DocumentableContentBuilder.buildContent(
- platforms: Set<DokkaSourceSet>,
- contentBuilder: DocumentableContentBuilder.() -> Unit
- ) = if (isPlatformHintedContent)
- sourceSetDependentHint(
- sourceSets = platforms,
- kind = ContentKind.SourceSetDependentHint,
- block = contentBuilder
- )
- else
- contentBuilder()
-
- fun DocumentableContentBuilder.contentForParams() {
- if (tags.isNotEmptyForTag<Param>()) {
- val params = tags.withTypeNamed<Param>()
- val availablePlatforms = params.values.flatMap { it.keys }.toSet()
-
- header(KDOC_TAG_HEADER_LEVEL, "Parameters", kind = ContentKind.Parameters, sourceSets = availablePlatforms)
- group(
- extra = mainExtra + SimpleAttr.header("Parameters"),
- styles = setOf(ContentStyle.WithExtraAttributes),
- sourceSets = availablePlatforms
- ) {
- buildContent(availablePlatforms) {
- table(kind = ContentKind.Parameters, sourceSets = availablePlatforms) {
- availablePlatforms.forEach { platform ->
- val possibleFallbacks = sourceSets.getPossibleFallbackSourcesets(platform)
- params.mapNotNull { (_, param) ->
- (param[platform] ?: param.fallback(possibleFallbacks))?.let {
- row(sourceSets = setOf(platform), kind = ContentKind.Parameters) {
- text(
- it.name,
- kind = ContentKind.Parameters,
- styles = mainStyles + setOf(ContentStyle.RowTitle, TextStyle.Underlined)
- )
- if (it.isNotEmpty()) {
- comment(it.root)
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
+ val sourceSets = d.sourceSets.toSet()
+ val tags = d.groupedTags
- fun DocumentableContentBuilder.contentForSeeAlso() {
- if (tags.isNotEmptyForTag<See>()) {
- val seeAlsoTags = tags.withTypeNamed<See>()
- val availablePlatforms = seeAlsoTags.values.flatMap { it.keys }.toSet()
+ return contentBuilder.contentFor(d) {
+ deprecatedSectionContent(d, sourceSets)
- header(KDOC_TAG_HEADER_LEVEL, "See also", kind = ContentKind.Comment, sourceSets = availablePlatforms)
- group(
- extra = mainExtra + SimpleAttr.header("See also"),
- styles = setOf(ContentStyle.WithExtraAttributes),
- sourceSets = availablePlatforms
- ) {
- buildContent(availablePlatforms) {
- table(kind = ContentKind.Sample) {
- availablePlatforms.forEach { platform ->
- val possibleFallbacks = sourceSets.getPossibleFallbackSourcesets(platform)
- seeAlsoTags.forEach { (_, see) ->
- (see[platform] ?: see.fallback(possibleFallbacks))?.let { seeTag ->
- row(
- sourceSets = setOf(platform),
- kind = ContentKind.Comment,
- styles = this@group.mainStyles,
- ) {
- seeTag.address?.let { dri ->
- link(
- text = seeTag.name.removePrefix("${dri.packageName}."),
- address = dri,
- kind = ContentKind.Comment,
- styles = mainStyles + ContentStyle.RowTitle
- )
- } ?: text(
- text = seeTag.name,
- kind = ContentKind.Comment,
- styles = mainStyles + ContentStyle.RowTitle
- )
- if (seeTag.isNotEmpty()) {
- comment(seeTag.root)
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
+ descriptionSectionContent(d, sourceSets)
+ customTagSectionContent(d, sourceSets, customTagContentProviders)
+ unnamedTagSectionContent(d, sourceSets) { toHeaderString() }
- fun DocumentableContentBuilder.contentForThrows() {
- val throws = tags.withTypeNamed<Throws>()
- if (throws.isNotEmpty()) {
- val availablePlatforms = throws.values.flatMap { it.keys }.toSet()
-
- header(KDOC_TAG_HEADER_LEVEL, "Throws", sourceSets = availablePlatforms)
- buildContent(availablePlatforms) {
- availablePlatforms.forEach { sourceset ->
- table(
- kind = ContentKind.Main,
- sourceSets = setOf(sourceset),
- extra = mainExtra + SimpleAttr.header("Throws")
- ) {
- throws.entries.forEach { entry ->
- entry.value[sourceset]?.let { throws ->
- row(sourceSets = setOf(sourceset)) {
- group(styles = mainStyles + ContentStyle.RowTitle) {
- throws.exceptionAddress?.let {
- val className = it.takeIf { it.target is PointingToDeclaration }?.classNames
- link(text = className ?: entry.key, address = it)
- } ?: text(entry.key)
- }
- if (throws.isNotEmpty()) {
- comment(throws.root)
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- fun DocumentableContentBuilder.contentForSamples() {
- val samples = tags.withTypeNamed<Sample>()
- if (samples.isNotEmpty()) {
- val availablePlatforms = samples.values.flatMap { it.keys }.toSet()
- header(KDOC_TAG_HEADER_LEVEL, "Samples", kind = ContentKind.Sample, sourceSets = availablePlatforms)
- group(
- extra = mainExtra + SimpleAttr.header("Samples"),
- styles = emptySet(),
- sourceSets = availablePlatforms
- ) {
- buildContent(availablePlatforms) {
- availablePlatforms.map { platformData ->
- val content = samples.filter { it.value.isEmpty() || platformData in it.value }
- group(
- sourceSets = setOf(platformData),
- kind = ContentKind.Sample,
- styles = setOf(TextStyle.Monospace, ContentStyle.RunnableSample)
- ) {
- content.forEach {
- text(it.key)
- }
- }
- }
- }
- }
- }
- }
+ paramsSectionContent(tags)
+ seeAlsoSectionContent(tags)
+ throwsSectionContent(tags)
+ samplesSectionContent(tags)
- return contentBuilder.contentFor(dri, sourceSets) {
- if (tags.isNotEmpty()) {
- contentForSamples()
- contentForSeeAlso()
- contentForParams()
- contentForThrows()
- }
+ inheritorsSectionContent(d, logger)
}.children
}
- private fun TagWrapper.isNotEmpty() = this.children.isNotEmpty()
-
protected open fun DocumentableContentBuilder.contentForBrief(documentable: Documentable) {
documentable.sourceSets.forEach { sourceSet ->
documentable.documentation[sourceSet]?.let {
@@ -761,7 +461,6 @@ open class DefaultPageCreator(
}
after {
+contentForDescription(d)
- +contentForComments(d, isPlatformHintedContent = false)
}
}
}
@@ -890,35 +589,39 @@ open class DefaultPageCreator(
}
protected open fun TagWrapper.toHeaderString() = this.javaClass.toGenericString().split('.').last()
+}
- private val List<Documentable>.sourceSets: Set<DokkaSourceSet>
- get() = flatMap { it.sourceSets }.toSet()
+internal val List<Documentable>.sourceSets: Set<DokkaSourceSet>
+ get() = flatMap { it.sourceSets }.toSet()
- private val List<Documentable>.dri: Set<DRI>
- get() = map { it.dri }.toSet()
+internal val List<Documentable>.dri: Set<DRI>
+ get() = map { it.dri }.toSet()
- private val Documentable.groupedTags: GroupedTags
- get() = documentation.flatMap { (pd, doc) ->
- doc.children.map { pd to it }.toList()
- }.groupBy { it.second::class }
+internal val Documentable.groupedTags: GroupedTags
+ get() = documentation.flatMap { (pd, doc) ->
+ 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 }
+internal val Documentable.descriptions: SourceSetDependent<Description>
+ get() = groupedTags.withTypeUnnamed()
- private val Documentable.descriptions: SourceSetDependent<Description>
- get() = groupedTags.withTypeUnnamed()
+internal val Documentable.customTags: Map<String, SourceSetDependent<CustomTagWrapper>>
+ get() = groupedTags.withTypeNamed()
- private val Documentable.customTags: Map<String, SourceSetDependent<CustomTagWrapper>>
- get() = groupedTags.withTypeNamed()
+private val Documentable.hasSeparatePage: Boolean
+ get() = this !is DTypeAlias
- 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()
- @Suppress("UNCHECKED_CAST")
- private fun <T : Documentable> T.nameAfterClash(): String =
- ((this as? WithExtraProperties<out Documentable>)?.extra?.get(DriClashAwareName)?.value ?: name).orEmpty()
-}
+@Suppress("UNCHECKED_CAST")
+internal inline fun <reified T : TagWrapper> GroupedTags.withTypeUnnamed(): SourceSetDependent<T> =
+ (this[T::class] as List<Pair<DokkaSourceSet, T>>?)?.toMap().orEmpty()
+
+@Suppress("UNCHECKED_CAST")
+internal inline fun <reified T : NamedTagWrapper> GroupedTags.withTypeNamed(): Map<String, SourceSetDependent<T>> =
+ (this[T::class] as List<Pair<DokkaSourceSet, T>>?)
+ ?.groupByTo(linkedMapOf()) { it.second.name }
+ ?.mapValues { (_, v) -> v.toMap() }
+ .orEmpty() \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt b/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt
new file mode 100644
index 00000000..1720b1d0
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt
@@ -0,0 +1,346 @@
+package org.jetbrains.dokka.base.translators.documentables
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo
+import org.jetbrains.dokka.base.transformers.pages.tags.CustomTagContentProvider
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.PointingToDeclaration
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.SourceSetDependent
+import org.jetbrains.dokka.model.WithScope
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.orEmpty
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+import org.jetbrains.dokka.pages.ContentKind
+import org.jetbrains.dokka.pages.ContentStyle
+import org.jetbrains.dokka.pages.SimpleAttr
+import org.jetbrains.dokka.pages.TextStyle
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import kotlin.reflect.KClass
+import kotlin.reflect.full.isSubclassOf
+
+internal const val KDOC_TAG_HEADER_LEVEL = 4
+
+private val unnamedTagsExceptions: Set<KClass<out TagWrapper>> =
+ setOf(Property::class, Description::class, Constructor::class, Param::class, See::class)
+
+internal fun PageContentBuilder.DocumentableContentBuilder.descriptionSectionContent(
+ documentable: Documentable,
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet>,
+) {
+ val descriptions = documentable.descriptions
+ if (descriptions.any { it.value.root.children.isNotEmpty() }) {
+ sourceSets.forEach { sourceSet ->
+ descriptions[sourceSet]?.also {
+ group(sourceSets = setOf(sourceSet), styles = emptySet()) {
+ comment(it.root)
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Custom tags are tags which are not part of the [KDoc specification](https://kotlinlang.org/docs/kotlin-doc.html). For instance, a user-defined tag
+ * which is specific to the user's code base would be considered a custom tag.
+ *
+ * For details, see [CustomTagContentProvider]
+ */
+internal fun PageContentBuilder.DocumentableContentBuilder.customTagSectionContent(
+ documentable: Documentable,
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet>,
+ customTagContentProviders: List<CustomTagContentProvider>,
+) {
+ val customTags = documentable.customTags
+ if (customTags.isEmpty()) return
+
+ sourceSets.forEach { sourceSet ->
+ customTags.forEach { (_, sourceSetTag) ->
+ sourceSetTag[sourceSet]?.let { tag ->
+ customTagContentProviders.filter { it.isApplicable(tag) }.forEach { provider ->
+ group(sourceSets = setOf(sourceSet), styles = setOf(ContentStyle.KDocTag)) {
+ with(provider) {
+ contentForDescription(sourceSet, tag)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Tags in KDoc are used in form of "@tag name value".
+ * This function handles tags that have only value parameter without name.
+ * List of such tags: `@return`, `@author`, `@since`, `@receiver`
+ */
+internal fun PageContentBuilder.DocumentableContentBuilder.unnamedTagSectionContent(
+ documentable: Documentable,
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet>,
+ toHeaderString: TagWrapper.() -> String,
+) {
+ val unnamedTags = documentable.groupedTags
+ .filterNot { (k, _) -> k.isSubclassOf(NamedTagWrapper::class) || k in unnamedTagsExceptions }
+ .values.flatten().groupBy { it.first }
+ .mapValues { it.value.map { it.second } }
+ .takeIf { it.isNotEmpty() } ?: return
+
+ sourceSets.forEach { sourceSet ->
+ unnamedTags[sourceSet]?.let { tags ->
+ if (tags.isNotEmpty()) {
+ tags.groupBy { it::class }.forEach { (_, sameCategoryTags) ->
+ group(sourceSets = setOf(sourceSet), styles = setOf(ContentStyle.KDocTag)) {
+ header(
+ level = KDOC_TAG_HEADER_LEVEL,
+ text = sameCategoryTags.first().toHeaderString(),
+ styles = setOf()
+ )
+ sameCategoryTags.forEach { comment(it.root, styles = setOf()) }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+internal fun PageContentBuilder.DocumentableContentBuilder.paramsSectionContent(tags: GroupedTags) {
+ val params = tags.withTypeNamed<Param>()
+ if (params.isEmpty()) return
+
+ val availableSourceSets = params.availableSourceSets()
+ tableSectionContentBlock(
+ blockName = "Parameters",
+ kind = ContentKind.Parameters,
+ sourceSets = availableSourceSets
+ ) {
+ availableSourceSets.forEach { sourceSet ->
+ val possibleFallbacks = availableSourceSets.getPossibleFallback(sourceSet)
+ params.mapNotNull { (_, param) ->
+ (param[sourceSet] ?: param.fallback(possibleFallbacks))?.let {
+ row(sourceSets = setOf(sourceSet), kind = ContentKind.Parameters) {
+ text(
+ it.name,
+ kind = ContentKind.Parameters,
+ styles = mainStyles + setOf(ContentStyle.RowTitle, TextStyle.Underlined)
+ )
+ if (it.isNotEmpty()) {
+ comment(it.root)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+internal fun PageContentBuilder.DocumentableContentBuilder.seeAlsoSectionContent(tags: GroupedTags) {
+ val seeAlsoTags = tags.withTypeNamed<See>()
+ if (seeAlsoTags.isEmpty()) return
+
+ val availableSourceSets = seeAlsoTags.availableSourceSets()
+ tableSectionContentBlock(
+ blockName = "See also",
+ kind = ContentKind.Comment,
+ sourceSets = availableSourceSets
+ ) {
+ availableSourceSets.forEach { sourceSet ->
+ val possibleFallbacks = availableSourceSets.getPossibleFallback(sourceSet)
+ seeAlsoTags.forEach { (_, see) ->
+ (see[sourceSet] ?: see.fallback(possibleFallbacks))?.let { seeTag ->
+ row(
+ sourceSets = setOf(sourceSet),
+ kind = ContentKind.Comment
+ ) {
+ seeTag.address?.let { dri ->
+ link(
+ text = seeTag.name.removePrefix("${dri.packageName}."),
+ address = dri,
+ kind = ContentKind.Comment,
+ styles = mainStyles + ContentStyle.RowTitle
+ )
+ } ?: text(
+ text = seeTag.name,
+ kind = ContentKind.Comment,
+ styles = mainStyles + ContentStyle.RowTitle
+ )
+ if (seeTag.isNotEmpty()) {
+ comment(seeTag.root)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Used for multi-value tags (e.g. params) when values are missed on some platforms.
+ * It this case description is inherited from parent platform.
+ * E.g. if param hasn't description in JVM, the description is taken from common.
+ */
+private fun Set<DokkaConfiguration.DokkaSourceSet>.getPossibleFallback(sourceSet: DokkaConfiguration.DokkaSourceSet) =
+ this.filter { it.sourceSetID in sourceSet.dependentSourceSets }
+
+private fun <V> Map<DokkaConfiguration.DokkaSourceSet, V>.fallback(sourceSets: List<DokkaConfiguration.DokkaSourceSet>): V? =
+ sourceSets.firstOrNull { it in this.keys }.let { this[it] }
+
+internal fun PageContentBuilder.DocumentableContentBuilder.throwsSectionContent(tags: GroupedTags) {
+ val throwsTags = tags.withTypeNamed<Throws>()
+ if (throwsTags.isEmpty()) return
+
+ val availableSourceSets = throwsTags.availableSourceSets()
+ tableSectionContentBlock(
+ blockName = "Throws",
+ kind = ContentKind.Main,
+ sourceSets = availableSourceSets
+ ) {
+ throwsTags.forEach { (throwsName, throwsPerSourceSet) ->
+ throwsPerSourceSet.forEach { (sourceSet, throws) ->
+ row(sourceSets = setOf(sourceSet)) {
+ group(styles = mainStyles + ContentStyle.RowTitle) {
+ throws.exceptionAddress?.let {
+ val className = it.takeIf { it.target is PointingToDeclaration }?.classNames
+ link(text = className ?: throwsName, address = it)
+ } ?: text(throwsName)
+ }
+ if (throws.isNotEmpty()) {
+ comment(throws.root)
+ }
+ }
+ }
+ }
+ }
+}
+
+private fun TagWrapper.isNotEmpty() = this.children.isNotEmpty()
+
+internal fun PageContentBuilder.DocumentableContentBuilder.samplesSectionContent(tags: GroupedTags) {
+ val samples = tags.withTypeNamed<Sample>()
+ if (samples.isEmpty()) return
+
+ val availableSourceSets = samples.availableSourceSets()
+
+ header(KDOC_TAG_HEADER_LEVEL, "Samples", kind = ContentKind.Sample, sourceSets = availableSourceSets)
+ availableSourceSets.forEach { sourceSet ->
+ group(
+ sourceSets = setOf(sourceSet),
+ kind = ContentKind.Sample,
+ styles = setOf(TextStyle.Monospace, ContentStyle.RunnableSample),
+ extra = mainExtra + SimpleAttr.header("Samples")
+ ) {
+ samples.filter { it.value.isEmpty() || sourceSet in it.value }
+ .forEach { text(text = it.key, sourceSets = setOf(sourceSet)) }
+ }
+ }
+}
+
+internal fun PageContentBuilder.DocumentableContentBuilder.inheritorsSectionContent(
+ documentable: Documentable,
+ logger: DokkaLogger,
+) {
+ val inheritors = if (documentable is WithScope) documentable.inheritors() else return
+ if (inheritors.values.none()) return
+
+ // split content section for the case:
+ // parent is in the shared source set (without expect-actual) and inheritor is in the platform code
+ if (documentable.isDefinedInSharedSourceSetOnly(inheritors.keys.toSet()))
+ sharedSourceSetOnlyInheritorsSectionContent(inheritors, logger)
+ else
+ multiplatformInheritorsSectionContent(documentable, inheritors, logger)
+}
+
+private fun WithScope.inheritors() = safeAs<WithExtraProperties<Documentable>>()
+ ?.let { it.extra[InheritorsInfo] }
+ ?.let { inheritors -> inheritors.value.filter { it.value.isNotEmpty() } }
+ .orEmpty()
+
+/**
+ * Detect that documentable is located only in the shared code without expect-actuals
+ * Value of `analysisPlatform` will be [Platform.common] in cases if a source set shared between 2 different platforms.
+ * But if it shared between 2 same platforms (e.g. jvm("awt") and jvm("android"))
+ * then the source set will be still marked as jvm platform.
+ *
+ * So, we also try to check if any of inheritors source sets depends on current documentable source set.
+ * that will mean that the source set is shared.
+ */
+private fun Documentable.isDefinedInSharedSourceSetOnly(inheritorsSourceSets: Set<DokkaConfiguration.DokkaSourceSet>) =
+ sourceSets.size == 1 &&
+ (sourceSets.first().analysisPlatform == Platform.common
+ || sourceSets.first().hasDependentSourceSet(inheritorsSourceSets))
+
+private fun DokkaConfiguration.DokkaSourceSet.hasDependentSourceSet(
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet>,
+) =
+ sourceSets.any { sourceSet -> sourceSet.dependentSourceSets.any { it == this.sourceSetID } }
+
+private fun PageContentBuilder.DocumentableContentBuilder.multiplatformInheritorsSectionContent(
+ documentable: Documentable,
+ inheritors: Map<DokkaConfiguration.DokkaSourceSet, List<DRI>>,
+ logger: DokkaLogger,
+) {
+ // intersect is used for removing duplication in case of merged classlikes from different platforms
+ val availableSourceSets = inheritors.keys.toSet().intersect(documentable.sourceSets)
+
+ tableSectionContentBlock(
+ blockName = "Inheritors",
+ kind = ContentKind.Inheritors,
+ sourceSets = availableSourceSets
+ ) {
+ availableSourceSets.forEach { sourceSet ->
+ inheritors[sourceSet]?.forEach { classlike: DRI ->
+ inheritorRow(classlike, logger, sourceSet)
+ }
+ }
+ }
+}
+
+private fun PageContentBuilder.DocumentableContentBuilder.sharedSourceSetOnlyInheritorsSectionContent(
+ inheritors: Map<DokkaConfiguration.DokkaSourceSet, List<DRI>>,
+ logger: DokkaLogger,
+) {
+ val uniqueInheritors = inheritors.values.flatten().toSet()
+ tableSectionContentBlock(
+ blockName = "Inheritors",
+ kind = ContentKind.Inheritors,
+ ) {
+ uniqueInheritors.forEach { classlike ->
+ inheritorRow(classlike, logger)
+ }
+ }
+}
+
+private fun PageContentBuilder.TableBuilder.inheritorRow(
+ classlike: DRI, logger: DokkaLogger, sourceSet: DokkaConfiguration.DokkaSourceSet? = null,
+) = row {
+ link(
+ text = classlike.friendlyClassName()
+ ?: classlike.toString().also { logger.warn("No class name found for DRI $classlike") },
+ address = classlike,
+ sourceSets = sourceSet?.let { setOf(it) } ?: mainSourcesetData
+ )
+}
+
+private fun PageContentBuilder.DocumentableContentBuilder.tableSectionContentBlock(
+ blockName: String,
+ kind: ContentKind,
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet> = mainSourcesetData,
+ body: PageContentBuilder.TableBuilder.() -> Unit,
+) {
+ header(KDOC_TAG_HEADER_LEVEL, text = blockName, kind = kind, sourceSets = sourceSets)
+ table(
+ kind = kind,
+ sourceSets = sourceSets,
+ extra = mainExtra + SimpleAttr.header(blockName)
+ ) {
+ body()
+ }
+}
+
+private fun DRI.friendlyClassName() = classNames?.substringAfterLast(".")
+
+private fun <T> Map<String, SourceSetDependent<T>>.availableSourceSets() = values.flatMap { it.keys }.toSet()
+
+
diff --git a/plugins/base/src/main/resources/dokka/styles/style.css b/plugins/base/src/main/resources/dokka/styles/style.css
index 431db3b8..5242a76d 100644
--- a/plugins/base/src/main/resources/dokka/styles/style.css
+++ b/plugins/base/src/main/resources/dokka/styles/style.css
@@ -946,9 +946,10 @@ td.content {
display: none;
}
-/*Work around an issue: https://github.com/JetBrains/kotlin-playground/issues/91*/
-.platform-hinted[data-togglable="Samples"] > .content:not([data-active]),
-.tabs-section-body > *[data-togglable="Samples"]:not([data-active]) {
+/* Work around an issue: https://github.com/JetBrains/kotlin-playground/issues/91
+Applies for main description blocks with platform tabs.
+Just in case of possible performance degradation it excluding tabs with briefs on classlike page */
+#content > div:not(.tabbedcontent) .sourceset-dependent-content:not([data-active]) {
display: block !important;
visibility: hidden;
height: 0;