From df0749a0123f3b9ddf973d7352304a6069202ec3 Mon Sep 17 00:00:00 2001 From: Oleg Yukhnevich Date: Tue, 21 Nov 2023 16:27:55 +0200 Subject: [PATCH] Render member extensions correctly --- .../dokka/model/documentableUtils.kt | 1 + .../plugin-base/api/plugin-base.api | 5 +- .../documentables/DefaultPageCreator.kt | 353 +++++++++++------- 3 files changed, 218 insertions(+), 141 deletions(-) diff --git a/dokka-subprojects/core/src/main/kotlin/org/jetbrains/dokka/model/documentableUtils.kt b/dokka-subprojects/core/src/main/kotlin/org/jetbrains/dokka/model/documentableUtils.kt index c9d75bf4197..63baa85daec 100644 --- a/dokka-subprojects/core/src/main/kotlin/org/jetbrains/dokka/model/documentableUtils.kt +++ b/dokka-subprojects/core/src/main/kotlin/org/jetbrains/dokka/model/documentableUtils.kt @@ -24,4 +24,5 @@ public fun DTypeParameter.filter(filteredSet: Set): DTypeParamet ) } +// TODO: do we really need it in core here, or we could deprecate it? public fun Documentable.isExtension(): Boolean = this is Callable && receiver != null diff --git a/dokka-subprojects/plugin-base/api/plugin-base.api b/dokka-subprojects/plugin-base/api/plugin-base.api index 8d768b42229..d0ff8ffe5ec 100644 --- a/dokka-subprojects/plugin-base/api/plugin-base.api +++ b/dokka-subprojects/plugin-base/api/plugin-base.api @@ -1371,12 +1371,9 @@ public class org/jetbrains/dokka/base/translators/documentables/DefaultPageCreat protected fun contentForModule (Lorg/jetbrains/dokka/model/DModule;)Lorg/jetbrains/dokka/pages/ContentGroup; protected fun contentForPackage (Lorg/jetbrains/dokka/model/DPackage;)Lorg/jetbrains/dokka/pages/ContentGroup; protected fun contentForProperty (Lorg/jetbrains/dokka/model/DProperty;)Lorg/jetbrains/dokka/pages/ContentGroup; - protected fun contentForScope (Lorg/jetbrains/dokka/model/WithScope;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Ljava/util/List;)Lorg/jetbrains/dokka/pages/ContentGroup; - public static synthetic fun contentForScope$default (Lorg/jetbrains/dokka/base/translators/documentables/DefaultPageCreator;Lorg/jetbrains/dokka/model/WithScope;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/dokka/pages/ContentGroup; + protected fun contentForScope (Lorg/jetbrains/dokka/model/WithScope;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;)Lorg/jetbrains/dokka/pages/ContentGroup; protected fun contentForScopes (Ljava/util/List;Ljava/util/Set;Ljava/util/List;)Lorg/jetbrains/dokka/pages/ContentGroup; public static synthetic fun contentForScopes$default (Lorg/jetbrains/dokka/base/translators/documentables/DefaultPageCreator;Ljava/util/List;Ljava/util/Set;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/dokka/pages/ContentGroup; - protected fun divergentBlock (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Ljava/lang/String;Ljava/util/Collection;Lorg/jetbrains/dokka/pages/ContentKind;Lorg/jetbrains/dokka/model/properties/PropertyContainer;)V - public static synthetic fun divergentBlock$default (Lorg/jetbrains/dokka/base/translators/documentables/DefaultPageCreator;Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Ljava/lang/String;Ljava/util/Collection;Lorg/jetbrains/dokka/pages/ContentKind;Lorg/jetbrains/dokka/model/properties/PropertyContainer;ILjava/lang/Object;)V protected fun getContentBuilder ()Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder; public final fun getCustomTagContentProviders ()Ljava/util/List; public final fun getDocumentableAnalyzer ()Lorg/jetbrains/dokka/analysis/kotlin/internal/DocumentableSourceLanguageParser; diff --git a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/translators/documentables/DefaultPageCreator.kt b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/translators/documentables/DefaultPageCreator.kt index dac5144d8cb..f0d43c0253c 100644 --- a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/translators/documentables/DefaultPageCreator.kt +++ b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/translators/documentables/DefaultPageCreator.kt @@ -71,7 +71,8 @@ public open class DefaultPageCreator( * @see ActualTypealias */ private fun List.filterOutActualTypeAlias(): List { - fun List.hasExpectClass(dri: DRI) = find { it is DClasslike && it.dri == dri && it.expectPresentInSet != null } != null + fun List.hasExpectClass(dri: DRI) = + find { it is DClasslike && it.dri == dri && it.expectPresentInSet != null } != null return this.filterNot { it is DTypeAlias && this.hasExpectClass(it.dri) } } @@ -382,28 +383,42 @@ public open class DefaultPageCreator( extensions: List = emptyList() ): ContentGroup { val types = scopes.flatMap { it.classlikes } + scopes.filterIsInstance().flatMap { it.typealiases } + val (extensionProperties, extensionFunctions) = extensions.splitPropsAndFuns() return contentForScope( - @Suppress("UNCHECKED_CAST") + dri = @Suppress("UNCHECKED_CAST") (scopes as List).dri, - sourceSets, - types, - scopes.flatMap { it.functions }, - scopes.flatMap { it.properties }, - extensions + sourceSets = sourceSets, + types = types, + functions = scopes.flatMap { it.functions }, + properties = scopes.flatMap { it.properties }, + extensionFunctions = extensionFunctions, + extensionProperties = extensionProperties ) } protected open fun contentForScope( s: WithScope, dri: DRI, - sourceSets: Set, - extensions: List = emptyList() + sourceSets: Set ): ContentGroup { val types = listOf( s.classlikes, (s as? DPackage)?.typealiases ?: emptyList() ).flatten() - return contentForScope(setOf(dri), sourceSets, types, s.functions, s.properties, extensions) + + // can be used only for packages + val (extensionFunctions, functions) = s.functions.partition { it.receiver != null } + val (extensionProperties, properties) = s.properties.partition { it.receiver != null } + + return contentForScope( + dri = setOf(dri), + sourceSets = sourceSets, + types = types, + functions = functions, + properties = properties, + extensionFunctions = extensionFunctions, + extensionProperties = extensionProperties + ) } private fun contentForScope( @@ -412,41 +427,49 @@ public open class DefaultPageCreator( types: List, functions: List, properties: List, - extensions: List + extensionFunctions: List, + extensionProperties: List, ) = contentBuilder.contentFor(dri, sourceSets) { - divergentBlock( - "Types", - types, - ContentKind.Classlikes - ) - val (extensionProps, extensionFuns) = extensions.splitPropsAndFuns() + typesBlock(types) if (separateInheritedMembers) { val (inheritedFunctions, memberFunctions) = functions.splitInherited() val (inheritedProperties, memberProperties) = properties.splitInherited() - val (inheritedExtensionFunctions, extensionFunctions) = extensionFuns.splitInheritedExtension(dri) - val (inheritedExtensionProperties, extensionProperties) = extensionProps.splitInheritedExtension(dri) - propertiesBlock( - "Properties", memberProperties + extensionProperties + val (inheritedExtensionFunctions, directExtensionFunctions) = extensionFunctions.splitInheritedExtension(dri) + val (inheritedExtensionProperties, directExtensionProperties) = extensionProperties.splitInheritedExtension( + dri ) - propertiesBlock( - "Inherited properties", inheritedProperties + inheritedExtensionProperties + + declarationsBlock( + name = "Properties", + isFunctions = false, + declarations = memberProperties, + extensions = directExtensionProperties ) - functionsBlock("Functions", memberFunctions + extensionFunctions) - functionsBlock( - "Inherited functions", inheritedFunctions + inheritedExtensionFunctions + declarationsBlock( + name = "Inherited properties", + isFunctions = false, + declarations = inheritedProperties, + extensions = inheritedExtensionProperties ) - } else { - propertiesBlock( - "Properties", properties + extensionProps + declarationsBlock( + name = "Functions", + isFunctions = true, + declarations = memberFunctions, + extensions = directExtensionFunctions ) - functionsBlock("Functions", functions + extensionFuns) + declarationsBlock( + name = "Inherited functions", + isFunctions = true, + declarations = inheritedFunctions, + extensions = inheritedExtensionFunctions + ) + } else { + declarationsBlock("Properties", isFunctions = false, properties, extensionProperties) + declarationsBlock("Functions", isFunctions = true, functions, extensionFunctions) } } - private fun Iterable.sorted() = - sortedWith(compareBy({ it.name }, { it.parameters.size }, { it.dri.toString() })) - /** * @param documentables a list of [DClasslike] and [DEnumEntry] and [DTypeAlias] with the same dri in different sourceSets */ @@ -491,6 +514,7 @@ public open class DefaultPageCreator( +contentForScopes(scopes, documentables.sourceSets, extensions) } } + protected open fun contentForConstructors( constructorsToDocumented: List, dri: Set, @@ -560,7 +584,6 @@ public open class DefaultPageCreator( } - protected open fun contentForDescription( d: Documentable ): List { @@ -611,7 +634,7 @@ public open class DefaultPageCreator( tag: TagWrapper ) { val language = documentableAnalyzer.getLanguage(documentable, sourceSet) - when(language) { + when (language) { DocumentableLanguage.JAVA -> firstSentenceComment(tag.root) DocumentableLanguage.KOTLIN -> firstParagraphComment(tag.root) else -> firstParagraphComment(tag.root) @@ -643,128 +666,184 @@ public open class DefaultPageCreator( } } - private fun DocumentableContentBuilder.functionsBlock( - name: String, - list: Collection - ) { + private fun DocumentableContentBuilder.typesBlock(types: List) { + if (types.isEmpty()) return + + val grouped = types + // This groupBy should probably use LocationProvider + .groupBy(Documentable::name) + .mapValues { (_, elements) -> + // This hacks displaying actual typealias signatures along classlike ones + if (elements.any { it is DClasslike }) elements.filter { it !is DTypeAlias } else elements + } + + val groups = grouped.entries + .sortedWith(compareBy(nullsFirst(canonicalAlphabeticalOrder)) { it.key }) + .map { (name, elements) -> + DivergentElementGroup( + name = name, + kind = ContentKind.Classlikes, + contentTypeOverride = null, + elements = elements + ) + } + divergentBlock( - name, - list.sorted(), - ContentKind.Functions, - extra = mainExtra + name = "Types", + kind = ContentKind.Classlikes, + extra = mainExtra, + contentType = BasicTabbedContentType.TYPE, + groups = groups ) } - private fun DocumentableContentBuilder.propertiesBlock( + private fun DocumentableContentBuilder.declarationsBlock( name: String, - list: Collection + isFunctions: Boolean, + declarations: List, + extensions: List ) { + if (declarations.isEmpty() && extensions.isEmpty()) return + + val contentKind = when { + isFunctions -> ContentKind.Functions + else -> ContentKind.Properties + } + + val declarationContentType = when { + isFunctions -> BasicTabbedContentType.FUNCTION + else -> BasicTabbedContentType.PROPERTY + } + + val extensionContentType = when { + isFunctions -> BasicTabbedContentType.EXTENSION_FUNCTION + else -> BasicTabbedContentType.EXTENSION_PROPERTY + } + + // This groupBy should probably use LocationProvider + val grouped = declarations.groupBy { + NameAndIsExtension(it.name, isExtension = false) + } + extensions.groupBy { + NameAndIsExtension(it.name, isExtension = true) + } + + val groups = grouped.entries + .sortedWith(compareBy(NameAndIsExtension.comparator) { it.key }) + .map { (nameAndIsExtension, elements) -> + DivergentElementGroup( + name = nameAndIsExtension.name, + kind = when { + nameAndIsExtension.isExtension -> ContentKind.Extensions + else -> contentKind + }, + contentTypeOverride = when { + nameAndIsExtension.isExtension -> extensionContentType + else -> null + }, + elements = elements + ) + } + divergentBlock( - name, - list, - ContentKind.Properties, - extra = mainExtra + name = name, + kind = contentKind, + extra = mainExtra, + contentType = when { + // if only extensions + declarations.isEmpty() -> extensionContentType + else -> declarationContentType + }, + groups = groups ) - } - private data class NameAndIsExtension(val name:String?, val isExtension: Boolean) - - private fun groupAndSortDivergentCollection(collection: Collection): List>> { - val groupKeyComparator: Comparator>> = - compareBy>, String?>( - nullsFirst(canonicalAlphabeticalOrder) - ) { it.key.name } - .thenBy { it.key.isExtension } - - return collection - .groupBy { - NameAndIsExtension( - it.name, - it.isExtension() - ) - } // This groupBy should probably use LocationProvider - // This hacks displaying actual typealias signatures along classlike ones - .mapValues { if (it.value.any { it is DClasslike }) it.value.filter { it !is DTypeAlias } else it.value } - .entries.sortedWith(groupKeyComparator) + + private data class NameAndIsExtension(val name: String?, val isExtension: Boolean) { + companion object { + val comparator = compareBy( + comparator = nullsFirst(canonicalAlphabeticalOrder), + selector = NameAndIsExtension::name + ).thenBy(NameAndIsExtension::isExtension) + } } - protected open fun DocumentableContentBuilder.divergentBlock( + private class DivergentElementGroup( + val name: String?, + val kind: ContentKind, + val contentTypeOverride: BasicTabbedContentType?, + val elements: List + ) + + private fun DocumentableContentBuilder.divergentBlock( name: String, - collection: Collection, kind: ContentKind, - extra: PropertyContainer = mainExtra + extra: PropertyContainer, + contentType: BasicTabbedContentType, + groups: List, ) { - if (collection.any()) { - val onlyExtensions = collection.all { it.isExtension() } - val groupExtra = when(kind) { - ContentKind.Functions -> extra + TabbedContentTypeExtra(if (onlyExtensions) BasicTabbedContentType.EXTENSION_FUNCTION else BasicTabbedContentType.FUNCTION) - ContentKind.Properties -> extra + TabbedContentTypeExtra(if (onlyExtensions) BasicTabbedContentType.EXTENSION_PROPERTY else BasicTabbedContentType.PROPERTY) - ContentKind.Classlikes -> extra + TabbedContentTypeExtra(BasicTabbedContentType.TYPE) - else -> extra - } - - group(extra = groupExtra) { - // be careful: groupExtra will be applied for children by default - header(2, name, kind = kind, extra = extra) { } - val isFunctions = collection.any { it is DFunction } - table(kind, extra = extra, styles = emptySet()) { - header { - group { text("Name") } - group { text("Summary") } - } - groupAndSortDivergentCollection(collection) - .forEach { (elementNameAndIsExtension, elements) -> // This groupBy should probably use LocationProvider - val elementName = elementNameAndIsExtension.name - val isExtension = elementNameAndIsExtension.isExtension - val rowExtra = - if (isExtension) extra + TabbedContentTypeExtra(if(isFunctions) BasicTabbedContentType.EXTENSION_FUNCTION else BasicTabbedContentType.EXTENSION_PROPERTY) else extra - val rowKind = if (isExtension) ContentKind.Extensions else kind - val sortedElements = sortDivergentElementsDeterministically(elements) - row( - dri = sortedElements.map { it.dri }.toSet(), - sourceSets = sortedElements.flatMap { it.sourceSets }.toSet(), - kind = rowKind, - styles = emptySet(), - extra = elementName?.let { name -> rowExtra + SymbolAnchorHint(name, kind) } ?: rowExtra - ) { - link( - text = elementName.orEmpty(), - address = sortedElements.first().dri, - kind = rowKind, - styles = setOf(ContentStyle.RowTitle), - sourceSets = sortedElements.sourceSets.toSet(), - extra = extra - ) - divergentGroup( - ContentDivergentGroup.GroupID(name), - sortedElements.map { it.dri }.toSet(), - kind = rowKind, - extra = extra + if (groups.isEmpty()) return + group(extra = extra + TabbedContentTypeExtra(contentType)) { + // be careful: groupExtra will be applied for children by default + header(2, name, kind = kind, extra = extra) { } + table(kind, extra = extra, styles = emptySet()) { + header { + group { text("Name") } + group { text("Summary") } + } + groups.forEach { group -> + val elementName = group.name + val rowKind = group.kind + val sortedElements = sortDivergentElementsDeterministically(group.elements) + + row( + dri = sortedElements.map { it.dri }.toSet(), + sourceSets = sortedElements.flatMap { it.sourceSets }.toSet(), + kind = rowKind, + styles = emptySet(), + extra = extra.addAll( + listOfNotNull( + group.contentTypeOverride?.let(::TabbedContentTypeExtra), + elementName?.let { name -> SymbolAnchorHint(name, kind) } + ) + ) + ) { + link( + text = elementName.orEmpty(), + address = sortedElements.first().dri, + kind = rowKind, + styles = setOf(ContentStyle.RowTitle), + sourceSets = sortedElements.sourceSets.toSet(), + extra = extra + ) + divergentGroup( + ContentDivergentGroup.GroupID(name), + sortedElements.map { it.dri }.toSet(), + kind = rowKind, + extra = extra + ) { + sortedElements.map { element -> + instance( + setOf(element.dri), + element.sourceSets.toSet(), + // TODO: looks like this is not needed + extra = PropertyContainer.withAll( + SymbolAnchorHint(element.name ?: "", rowKind) + ) ) { - sortedElements.map { element -> - instance( - setOf(element.dri), - element.sourceSets.toSet(), - extra = PropertyContainer.withAll( - SymbolAnchorHint(element.name ?: "", rowKind) - ) - ) { - divergent(extra = PropertyContainer.empty()) { - group { - +buildSignature(element) - } - } - after( - extra = PropertyContainer.empty() - ) { - contentForBrief(element) - contentForCustomTagsBrief(element) - } + divergent(extra = PropertyContainer.empty()) { + group { + +buildSignature(element) } } + after( + extra = PropertyContainer.empty() + ) { + contentForBrief(element) + contentForCustomTagsBrief(element) + } } } } + } } } }