From 6fec129cc61bd2a54f9d1111057c650430426013 Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Tue, 31 Mar 2020 08:39:01 +0200 Subject: Make PageContentBuilder to create proper content for all tags --- core/src/main/kotlin/model/Documentable.kt | 29 ++++- .../documentables/DefaultPageCreator.kt | 124 ++++++++++++++++----- .../documentables/PageContentBuilder.kt | 16 +++ .../kotlin/content/params/ContentForParamsTest.kt | 47 ++++---- plugins/base/src/test/kotlin/utils/contentUtils.kt | 7 ++ 5 files changed, 169 insertions(+), 54 deletions(-) diff --git a/core/src/main/kotlin/model/Documentable.kt b/core/src/main/kotlin/model/Documentable.kt index 00a26d90..fec94537 100644 --- a/core/src/main/kotlin/model/Documentable.kt +++ b/core/src/main/kotlin/model/Documentable.kt @@ -37,9 +37,27 @@ data class PlatformDependent( yieldAll(map.values) } + val allEntries: Sequence> = sequence { + expect?.also { yield(null to it) } + map.forEach { (k, v) -> yield(k to v) } + } + + fun getOrExpect(platform: PlatformData): T? = map[platform] ?: expect + companion object { fun empty(): PlatformDependent = PlatformDependent(emptyMap()) + fun from(platformData: PlatformData, element: T) = PlatformDependent(mapOf(platformData to element)) + + @Suppress("UNCHECKED_CAST") + fun from(pairs: Iterable>) = + PlatformDependent( + pairs.filter { it.first != null }.toMap() as Map, + pairs.firstOrNull { it.first == null }?.second + ) + + fun from(vararg pairs: Pair) = from(pairs.asIterable()) + fun expectFrom(element: T) = PlatformDependent(map = emptyMap(), expect = element) } } @@ -330,12 +348,18 @@ sealed class Projection sealed class Bound : Projection() data class OtherParameter(val name: String) : Bound() object Star : Projection() -data class TypeConstructor(val dri: DRI, val projections: List, val modifier: FunctionModifiers = FunctionModifiers.NONE) : Bound() +data class TypeConstructor( + val dri: DRI, + val projections: List, + val modifier: FunctionModifiers = FunctionModifiers.NONE +) : Bound() + data class Nullable(val inner: Bound) : Bound() data class Variance(val kind: Kind, val inner: Bound) : Projection() { enum class Kind { In, Out } } -data class PrimitiveJavaType(val name: String): Bound() + +data class PrimitiveJavaType(val name: String) : Bound() object Void : Bound() object JavaObject : Bound() @@ -376,6 +400,7 @@ sealed class JavaVisibility(name: String) : Visibility(name) { } fun PlatformDependent?.orEmpty(): PlatformDependent = this ?: PlatformDependent.empty() + sealed class DocumentableSource(val path: String) class DescriptorDocumentableSource(val descriptor: DeclarationDescriptor) : diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index 41d4b917..177d5021 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -3,15 +3,21 @@ package org.jetbrains.dokka.base.translators.documentables import org.jetbrains.dokka.base.signatures.SignatureProvider import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter +import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* -import org.jetbrains.dokka.model.DFunction -import org.jetbrains.dokka.model.doc.Property -import org.jetbrains.dokka.model.doc.TagWrapper +import org.jetbrains.dokka.model.doc.* import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.kotlin.backend.common.phaser.defaultDumper +import kotlin.reflect.KClass +import kotlin.reflect.full.isSubclassOf + +private typealias GroupedTags = Map, List>> + +private val specialTags: Set> = + setOf(Property::class, Description::class, Constructor::class, Receiver::class, Param::class) + open class DefaultPageCreator( commentsToContentConverter: CommentsToContentConverter, @@ -51,12 +57,12 @@ open class DefaultPageCreator( open fun pageForFunction(f: DFunction) = MemberPageNode(f.name, contentForFunction(f), setOf(f.dri), f) private val WithScope.filteredFunctions - get() = functions.filter { it.extra[InheritedFunction]?.isInherited != true } + get() = functions.filter { it.extra[InheritedFunction]?.isInherited != true } protected open fun contentForModule(m: DModule) = contentBuilder.contentFor(m) { header(1) { text(m.name) } block("Packages", 2, ContentKind.Packages, m.packages, m.platformData.toSet()) { - link(it.name, it.dri) + link(it.name, it.dri) } // text("Index\n") TODO // text("Link to allpage here") @@ -123,7 +129,7 @@ open class DefaultPageCreator( header(1) { text(e.name.orEmpty()) } +buildSignature(e) - +contentForComments(e) { it !is Property } + +contentForComments(e) +contentForScope(e, e.dri, e.platformData) } @@ -135,7 +141,8 @@ open class DefaultPageCreator( +buildSignature(c) } } - +contentForComments(c) { it !is Property } + breakLine() + +contentForComments(c) if (c is WithConstructors) { block( @@ -173,33 +180,98 @@ open class DefaultPageCreator( +contentForScope(c, c.dri, c.platformData) } + @Suppress("UNCHECKED_CAST") + private inline fun GroupedTags.withTypeUnnamed(): PlatformDependent = + (this[T::class] as List>?) + ?.let { PlatformDependent.from(it) }.orEmpty() + + @Suppress("UNCHECKED_CAST") + private inline fun GroupedTags.withTypeNamed(): Map> = + (this[T::class] as List>?) + ?.groupBy { it.second.name } + ?.mapValues { (_, v) -> PlatformDependent.from(v) } + .orEmpty() + protected open fun contentForComments( - d: Documentable, - filtering: (TagWrapper) -> Boolean = { true } - ) = contentBuilder.contentFor(d) { - d.documentation.map{(k,v) -> (k to v.children.filter(filtering).map{p -> (k to p)})}.flatMap { it.second } - .groupBy { it.second.toHeaderString() }.mapValues {(k,v) -> v.groupBy { it.first }} - .forEach{ groupedByHeader -> - header(3) { text(groupedByHeader.key) } - d.documentation.expect?.also{ - it.children.filter(filtering).filter{it.toHeaderString() == groupedByHeader.key} - .forEach { + d: Documentable + ): List { + val tags: GroupedTags = d.documentation.allEntries.flatMap { (pd, doc) -> + doc.children.asSequence().map { pd to it } + }.groupBy { it.second::class } + + val platforms = d.platformData + + fun DocumentableContentBuilder.contentForDescription() { + val description = tags.withTypeUnnamed() + if (description.any { it.value.root.children.isNotEmpty() }) { + platforms.forEach { platform -> + description.getOrExpect(platform)?.also { + group(platformData = setOf(platform)) { comment(it.root) breakLine() } + } } - platformDependentHint(d.dri,groupedByHeader.value.keys){ - groupedByHeader.value.forEach{ - group(d.dri, setOf(it.key)){ - it.value.forEach { - comment(it.second.root) + } + } + + fun DocumentableContentBuilder.contentForParams() { + val receiver = tags.withTypeUnnamed() + val params = tags.withTypeNamed() + + if (params.isNotEmpty()) { + header(4, kind = ContentKind.Parameters) { text("Parameters") } + table(kind = ContentKind.Parameters) { + platforms.flatMap { platform -> + val receiverRow = receiver.getOrExpect(platform)?.let { + buildGroup(platformData = setOf(platform)) { + text("") + comment(it.root) } - breakLine() } + + val paramRows = params.mapNotNull { (_, param) -> + param.getOrExpect(platform)?.let { + buildGroup(platformData = setOf(platform)) { + text(it.name) + comment(it.root) + } + } + } + + listOfNotNull(receiverRow) + paramRows } } } - }.children + } + + fun DocumentableContentBuilder.contentForUnnamedTags() { + val unnamedTags: List> = + tags.filterNot { (k, _) -> k.isSubclassOf(NamedTagWrapper::class) || k in specialTags } + .map { (_, v) -> PlatformDependent.from(v) } + platforms.forEach { platform -> + unnamedTags.forEach { pdTag -> + pdTag.getOrExpect(platform)?.also { tag -> + group(platformData = setOf(platform)) { + header(4) { text(tag.toHeaderString()) } + comment(tag.root) + } + } + } + } + } + + return contentBuilder.contentFor(d) { + if (tags.isNotEmpty()) { + header(3) { text("Description") } + platformDependentHint(platformData = platforms.toSet()) { + contentForDescription() + contentForParams() + contentForUnnamedTags() + } + } + }.children + } protected open fun contentForFunction(f: DFunction) = contentBuilder.contentFor(f) { group(f.dri, f.platformData.toSet(), ContentKind.Functions) { @@ -221,4 +293,4 @@ open class DefaultPageCreator( // ?.firstOrNull() // ?.root // ?.docTagSummary() ?: "" -} \ No newline at end of file +} diff --git a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt index 516e5524..f77b4592 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt @@ -114,6 +114,22 @@ open class PageContentBuilder( ) } + fun table( + dri: DRI = mainDRI, + kind: Kind = ContentKind.Main, + platformData: Set = mainPlatformData, + styles: Set