From 605ce9ee6ba096dc206784e3c2ab1ea3680e2980 Mon Sep 17 00:00:00 2001 From: Vadim Mishenev Date: Fri, 24 Feb 2023 17:44:24 +0200 Subject: [PATCH] Reorganize tabs for Classlike (#2764) (cherry picked from commit 1040288ca76e070445f1400df2fcc5a56310be28) --- core/api/core.api | 33 +- .../api/content-matcher-test-utils.api | 2 + .../matchers/content/ContentMatchersDsl.kt | 18 + core/src/main/kotlin/model/Documentable.kt | 2 +- .../main/kotlin/model/documentableUtils.kt | 2 + core/src/main/kotlin/pages/ContentNodes.kt | 9 +- .../kotlin/pages/contentNodeProperties.kt | 20 +- plugins/base/api/base.api | 15 +- plugins/base/src/main/kotlin/DokkaBase.kt | 5 +- .../renderers/DefaultTabSortingStrategy.kt | 30 -- .../kotlin/renderers/html/HtmlRenderer.kt | 160 ++++++- .../documentables/DefaultPageCreator.kt | 405 +++++++++++------- .../documentables/DescriptionSections.kt | 2 - .../documentables/PageContentBuilder.kt | 52 ++- .../dokka/scripts/platform-content-handler.js | 52 ++- .../src/main/resources/dokka/styles/style.css | 10 +- .../signatures/ConstructorsSignaturesTest.kt | 171 ++++---- .../src/test/kotlin/enums/KotlinEnumsTest.kt | 2 +- .../kotlin/pageMerger/PageNodeMergerTest.kt | 12 +- .../kotlin/renderers/html/CoverPageTest.kt | 2 +- .../html/HtmlRenderingOnlyTestBase.kt | 2 - .../renderers/html/TabbedContentTest.kt | 111 +++++ .../signatures/ObviousTypeSkippingTest.kt | 19 +- .../test/kotlin/signatures/SignatureTest.kt | 4 +- .../PageTransformerBuilderTest.kt | 17 +- ...rgeImplicitExpectActualDeclarationsTest.kt | 10 +- .../src/test/kotlin/utils/contentUtils.kt | 95 ++-- .../src/test/kotlin/KotlinAsJavaPluginTest.kt | 64 +-- 28 files changed, 873 insertions(+), 453 deletions(-) delete mode 100644 plugins/base/src/main/kotlin/renderers/DefaultTabSortingStrategy.kt create mode 100644 plugins/base/src/test/kotlin/renderers/html/TabbedContentTest.kt diff --git a/core/api/core.api b/core/api/core.api index a6f6fd4a4c..8068094842 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -1453,6 +1453,7 @@ public final class org/jetbrains/dokka/model/DocumentableUtilsKt { public static final fun filter (Lorg/jetbrains/dokka/model/DTypeParameter;Ljava/util/Set;)Lorg/jetbrains/dokka/model/DTypeParameter; public static final fun filtered (Ljava/util/Map;Ljava/util/Set;)Ljava/util/Map; public static final fun filtered (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Ljava/util/Set;)Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet; + public static final fun isExtension (Lorg/jetbrains/dokka/model/Documentable;)Z } public final class org/jetbrains/dokka/model/DoubleConstant : org/jetbrains/dokka/model/Expression { @@ -3462,6 +3463,18 @@ public abstract interface class org/jetbrains/dokka/model/properties/WithExtraPr public abstract fun withNewExtras (Lorg/jetbrains/dokka/model/properties/PropertyContainer;)Ljava/lang/Object; } +public final class org/jetbrains/dokka/pages/BasicTabbedContentType : java/lang/Enum, org/jetbrains/dokka/pages/TabbedContentType { + public static final field CONSTRUCTOR Lorg/jetbrains/dokka/pages/BasicTabbedContentType; + public static final field ENTRY Lorg/jetbrains/dokka/pages/BasicTabbedContentType; + public static final field EXTENSION_FUNCTION Lorg/jetbrains/dokka/pages/BasicTabbedContentType; + public static final field EXTENSION_PROPERTY Lorg/jetbrains/dokka/pages/BasicTabbedContentType; + public static final field FUNCTION Lorg/jetbrains/dokka/pages/BasicTabbedContentType; + public static final field PROPERTY Lorg/jetbrains/dokka/pages/BasicTabbedContentType; + public static final field TYPE Lorg/jetbrains/dokka/pages/BasicTabbedContentType; + public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/dokka/pages/BasicTabbedContentType; + public static fun values ()[Lorg/jetbrains/dokka/pages/BasicTabbedContentType; +} + public abstract interface class org/jetbrains/dokka/pages/ClasslikePage : org/jetbrains/dokka/pages/ContentPage, org/jetbrains/dokka/pages/WithDocumentables { } @@ -4241,17 +4254,12 @@ public abstract class org/jetbrains/dokka/pages/RootPageNode : org/jetbrains/dok } public final class org/jetbrains/dokka/pages/SimpleAttr : org/jetbrains/dokka/model/properties/ExtraProperty { - public static final field Companion Lorg/jetbrains/dokka/pages/SimpleAttr$Companion; public fun (Ljava/lang/String;Ljava/lang/String;)V public final fun getExtraKey ()Ljava/lang/String; public final fun getExtraValue ()Ljava/lang/String; public fun getKey ()Lorg/jetbrains/dokka/model/properties/ExtraProperty$Key; } -public final class org/jetbrains/dokka/pages/SimpleAttr$Companion { - public final fun header (Ljava/lang/String;)Lorg/jetbrains/dokka/pages/SimpleAttr; -} - public final class org/jetbrains/dokka/pages/SimpleAttr$SimpleAttrKey : org/jetbrains/dokka/model/properties/ExtraProperty$Key { public fun (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; @@ -4275,6 +4283,21 @@ public final class org/jetbrains/dokka/pages/SymbolContentKind : java/lang/Enum, public static fun values ()[Lorg/jetbrains/dokka/pages/SymbolContentKind; } +public abstract interface class org/jetbrains/dokka/pages/TabbedContentType { +} + +public final class org/jetbrains/dokka/pages/TabbedContentTypeExtra : org/jetbrains/dokka/model/properties/ExtraProperty { + public static final field Companion Lorg/jetbrains/dokka/pages/TabbedContentTypeExtra$Companion; + public fun (Lorg/jetbrains/dokka/pages/TabbedContentType;)V + public fun getKey ()Lorg/jetbrains/dokka/model/properties/ExtraProperty$Key; + public final fun getValue ()Lorg/jetbrains/dokka/pages/TabbedContentType; +} + +public final class org/jetbrains/dokka/pages/TabbedContentTypeExtra$Companion : org/jetbrains/dokka/model/properties/ExtraProperty$Key { + public synthetic fun mergeStrategyFor (Ljava/lang/Object;Ljava/lang/Object;)Lorg/jetbrains/dokka/model/properties/MergeStrategy; + public fun mergeStrategyFor (Lorg/jetbrains/dokka/pages/TabbedContentTypeExtra;Lorg/jetbrains/dokka/pages/TabbedContentTypeExtra;)Lorg/jetbrains/dokka/model/properties/MergeStrategy; +} + public final class org/jetbrains/dokka/pages/TextStyle : java/lang/Enum, org/jetbrains/dokka/pages/Style { public static final field Block Lorg/jetbrains/dokka/pages/TextStyle; public static final field Bold Lorg/jetbrains/dokka/pages/TextStyle; diff --git a/core/content-matcher-test-utils/api/content-matcher-test-utils.api b/core/content-matcher-test-utils/api/content-matcher-test-utils.api index 0a7f153b3f..58881a15b3 100644 --- a/core/content-matcher-test-utils/api/content-matcher-test-utils.api +++ b/core/content-matcher-test-utils/api/content-matcher-test-utils.api @@ -30,6 +30,8 @@ public final class matchers/content/ContentMatchersDslKt { public static final fun platformHinted (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V public static final fun skipAllNotMatching (Lmatchers/content/ContentMatcherBuilder;)V public static final fun somewhere (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V + public static final fun tab (Lmatchers/content/ContentMatcherBuilder;Lorg/jetbrains/dokka/pages/TabbedContentType;Lkotlin/jvm/functions/Function1;)V + public static final fun tabbedGroup (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V public static final fun table (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V } diff --git a/core/content-matcher-test-utils/src/main/kotlin/matchers/content/ContentMatchersDsl.kt b/core/content-matcher-test-utils/src/main/kotlin/matchers/content/ContentMatchersDsl.kt index 08442536d8..1378071875 100644 --- a/core/content-matcher-test-utils/src/main/kotlin/matchers/content/ContentMatchersDsl.kt +++ b/core/content-matcher-test-utils/src/main/kotlin/matchers/content/ContentMatchersDsl.kt @@ -3,6 +3,7 @@ package matchers.content import assertk.assertThat import assertk.assertions.contains import assertk.assertions.isEqualTo +import assertk.assertions.isNotNull import org.jetbrains.dokka.model.withDescendants import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.test.tools.matchers.content.* @@ -70,6 +71,23 @@ fun ContentMatcherBuilder<*>.skipAllNotMatching() { // Convenience functions: fun ContentMatcherBuilder<*>.group(block: ContentMatcherBuilder.() -> Unit) = composite(block) +fun ContentMatcherBuilder<*>.tabbedGroup( + block: ContentMatcherBuilder.() -> Unit +) = composite { + block() + check { assertThat(this::style).transform { style -> style.contains(ContentStyle.TabbedContent) }.isEqualTo(true) } +} + +fun ContentMatcherBuilder<*>.tab( + tabbedContentType: TabbedContentType, block: ContentMatcherBuilder.() -> Unit +) = composite { + block() + check { + assertThat(this::extra).transform { extra -> extra[TabbedContentTypeExtra]?.value } + .isEqualTo(tabbedContentType) + } +} + fun ContentMatcherBuilder<*>.header(expectedLevel: Int? = null, block: ContentMatcherBuilder.() -> Unit) = composite { block() diff --git a/core/src/main/kotlin/model/Documentable.kt b/core/src/main/kotlin/model/Documentable.kt index 73ab35d41e..adc51cfc73 100644 --- a/core/src/main/kotlin/model/Documentable.kt +++ b/core/src/main/kotlin/model/Documentable.kt @@ -519,4 +519,4 @@ interface DocumentableSource { val path: String } -data class TypeConstructorWithKind(val typeConstructor: TypeConstructor, val kind: ClassKind) +data class TypeConstructorWithKind(val typeConstructor: TypeConstructor, val kind: ClassKind) \ No newline at end of file diff --git a/core/src/main/kotlin/model/documentableUtils.kt b/core/src/main/kotlin/model/documentableUtils.kt index e32605ca17..076ca23ac5 100644 --- a/core/src/main/kotlin/model/documentableUtils.kt +++ b/core/src/main/kotlin/model/documentableUtils.kt @@ -19,3 +19,5 @@ fun DTypeParameter.filter(filteredSet: Set) = extra ) } + +fun Documentable.isExtension() = this is Callable && receiver != null \ No newline at end of file diff --git a/core/src/main/kotlin/pages/ContentNodes.kt b/core/src/main/kotlin/pages/ContentNodes.kt index 3c6fa5ac08..7876004419 100644 --- a/core/src/main/kotlin/pages/ContentNodes.kt +++ b/core/src/main/kotlin/pages/ContentNodes.kt @@ -392,7 +392,14 @@ enum class TextStyle : Style { } enum class ContentStyle : Style { - RowTitle, TabbedContent, WithExtraAttributes, RunnableSample, InDocumentationAnchor, Caption, + RowTitle, + /** + * The style is used only for HTML. It is applied only for [ContentGroup]. + * Creating and rendering tabs is a part of a renderer. + */ + TabbedContent, + + WithExtraAttributes, RunnableSample, InDocumentationAnchor, Caption, Wrapped, Indented, KDocTag, Footnote } diff --git a/core/src/main/kotlin/pages/contentNodeProperties.kt b/core/src/main/kotlin/pages/contentNodeProperties.kt index cfd7f76934..3c9bd42208 100644 --- a/core/src/main/kotlin/pages/contentNodeProperties.kt +++ b/core/src/main/kotlin/pages/contentNodeProperties.kt @@ -6,9 +6,23 @@ class SimpleAttr(val extraKey: String, val extraValue: String) : ExtraProperty override val key: ExtraProperty.Key = SimpleAttrKey(extraKey) - companion object { - fun header(value: String) = SimpleAttr("data-togglable", value) - } +} + +enum class BasicTabbedContentType : TabbedContentType { + TYPE, CONSTRUCTOR, FUNCTION, PROPERTY, ENTRY, EXTENSION_PROPERTY, EXTENSION_FUNCTION +} + +/** + * It is used only to mark content for tabs in HTML format + */ +interface TabbedContentType + +/** + * @see TabbedContentType + */ +class TabbedContentTypeExtra(val value: TabbedContentType) : ExtraProperty { + companion object : ExtraProperty.Key + override val key: ExtraProperty.Key = TabbedContentTypeExtra } object HtmlContent : ExtraProperty, ExtraProperty.Key { diff --git a/plugins/base/api/base.api b/plugins/base/api/base.api index f529b0ee87..5e36f375bf 100644 --- a/plugins/base/api/base.api +++ b/plugins/base/api/base.api @@ -10,7 +10,6 @@ public final class org/jetbrains/dokka/base/DokkaBase : org/jetbrains/dokka/plug public final fun getDefaultExternalDocumentablesProvider ()Lorg/jetbrains/dokka/plugability/Extension; public final fun getDefaultKotlinAnalysis ()Lorg/jetbrains/dokka/plugability/Extension; public final fun getDefaultSamplesTransformer ()Lorg/jetbrains/dokka/plugability/Extension; - public final fun getDefaultTabSortingStrategy ()Lorg/jetbrains/dokka/plugability/Extension; public final fun getDeprecatedDocumentableFilter ()Lorg/jetbrains/dokka/plugability/Extension; public final fun getDescriptorToDocumentableTranslator ()Lorg/jetbrains/dokka/plugability/Extension; public final fun getDocTagToContentConverter ()Lorg/jetbrains/dokka/plugability/Extension; @@ -272,11 +271,6 @@ public final class org/jetbrains/dokka/base/renderers/DefaultRendererKt { public static final fun sourceSets (Lorg/jetbrains/dokka/pages/ContentPage;)Ljava/util/Set; } -public final class org/jetbrains/dokka/base/renderers/DefaultTabSortingStrategy : org/jetbrains/dokka/base/renderers/TabSortingStrategy { - public fun ()V - public fun sort (Ljava/util/Collection;)Ljava/util/List; -} - public final class org/jetbrains/dokka/base/renderers/FileWriter : org/jetbrains/dokka/base/renderers/OutputWriter { public fun (Lorg/jetbrains/dokka/plugability/DokkaContext;)V public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext; @@ -1450,16 +1444,19 @@ public class org/jetbrains/dokka/base/translators/documentables/DefaultPageCreat public synthetic fun (Lorg/jetbrains/dokka/base/DokkaBaseConfiguration;Lorg/jetbrains/dokka/base/transformers/pages/comments/CommentsToContentConverter;Lorg/jetbrains/dokka/base/signatures/SignatureProvider;Lorg/jetbrains/dokka/utilities/DokkaLogger;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V protected fun contentForBrief (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/Documentable;)V protected fun contentForClasslikesAndEntries (Ljava/util/List;)Lorg/jetbrains/dokka/pages/ContentGroup; + protected fun contentForConstructors (Ljava/util/List;Ljava/util/Set;Ljava/util/Set;)Lorg/jetbrains/dokka/pages/ContentGroup; protected fun contentForDescription (Lorg/jetbrains/dokka/model/Documentable;)Ljava/util/List; + protected fun contentForEntries (Ljava/util/List;Ljava/util/Set;Ljava/util/Set;)Lorg/jetbrains/dokka/pages/ContentGroup; protected fun contentForFunction (Lorg/jetbrains/dokka/model/DFunction;)Lorg/jetbrains/dokka/pages/ContentGroup; protected fun contentForMember (Lorg/jetbrains/dokka/model/Documentable;)Lorg/jetbrains/dokka/pages/ContentGroup; protected fun contentForMembers (Ljava/util/List;)Lorg/jetbrains/dokka/pages/ContentGroup; 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 (Ljava/util/Set;Ljava/util/Set;Ljava/util/List;Ljava/util/List;Ljava/util/List;)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;)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 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; diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index 456a587bf8..f76eef6366 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -54,6 +54,7 @@ class DokkaBase : DokkaPlugin() { val outputWriter by extensionPoint() val htmlPreprocessors by extensionPoint() val kotlinAnalysis by extensionPoint() + @Deprecated("It is not used anymore") val tabSortingStrategy by extensionPoint() val immediateHtmlCommandConsumer by extensionPoint() val externalDocumentablesProvider by extensionPoint() @@ -182,10 +183,6 @@ class DokkaBase : DokkaPlugin() { } } - val defaultTabSortingStrategy by extending { - tabSortingStrategy with DefaultTabSortingStrategy() - } - val htmlRenderer by extending { CoreExtensions.renderer providing ::HtmlRenderer } diff --git a/plugins/base/src/main/kotlin/renderers/DefaultTabSortingStrategy.kt b/plugins/base/src/main/kotlin/renderers/DefaultTabSortingStrategy.kt deleted file mode 100644 index 056e0f93cc..0000000000 --- a/plugins/base/src/main/kotlin/renderers/DefaultTabSortingStrategy.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.jetbrains.dokka.base.renderers - -import org.jetbrains.dokka.pages.ContentKind -import org.jetbrains.dokka.pages.ContentNode -import org.jetbrains.dokka.pages.Kind - -private val kindsOrder = listOf( - ContentKind.Classlikes, - ContentKind.Constructors, - ContentKind.Functions, - ContentKind.Properties, - ContentKind.Extensions, - ContentKind.Parameters, - ContentKind.Inheritors, - ContentKind.Source, - ContentKind.Sample, - ContentKind.Comment -) - -class DefaultTabSortingStrategy : TabSortingStrategy { - override fun sort(tabs: Collection): List { - val tabMap: Map> = kindsOrder.asSequence().map { it to mutableListOf() }.toMap() - val unrecognized: MutableList = mutableListOf() - tabs.forEach { - tabMap[it.dci.kind]?.add(it) ?: unrecognized.add(it) - } - return tabMap.values.flatten() + unrecognized - } - -} diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index b4d98b5896..dfefbad86d 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -14,9 +14,11 @@ import org.jetbrains.dokka.base.renderers.html.innerTemplating.HtmlTemplater import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint import org.jetbrains.dokka.base.resolvers.local.DokkaBaseLocationProvider import org.jetbrains.dokka.base.templating.* +import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.properties.PropertyContainer +import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.pages.HtmlContent import org.jetbrains.dokka.plugability.* @@ -24,6 +26,7 @@ import org.jetbrains.dokka.utilities.htmlEscape import org.jetbrains.kotlin.utils.addIfNotNull internal const val TEMPLATE_REPLACEMENT: String = "###" +internal const val TOGGLEABLE_CONTENT_TYPE_ATTR = "data-togglable" open class HtmlRenderer( context: DokkaContext @@ -45,19 +48,102 @@ open class HtmlRenderer( override val preprocessors = context.plugin().query { htmlPreprocessors } - private val tabSortingStrategy = context.plugin().querySingle { tabSortingStrategy } + /** + * Tabs themselves are created in HTML plugin since, currently, only HTML format supports them. + * [TabbedContentType] is used to mark content that should be inside tab content. + * A tab can display multiple [TabbedContentType]. + * The content style [ContentStyle.TabbedContent] is used to determine where tabs will be generated. + * + * @see TabbedContentType + * @see ContentStyle.TabbedContent + */ + private fun createTabs(pageContext: ContentPage): List { + return when(pageContext) { + is ClasslikePage -> createTabsForClasslikes(pageContext) + is PackagePage -> createTabsForPackage(pageContext) + else -> throw IllegalArgumentException("Page ${pageContext.name} cannot have tabs") + } + } + + private fun createTabsForClasslikes(page: ClasslikePage): List { + val documentables = page.documentables + fun List.shouldDocumentConstructors() = !this.any { it is DAnnotation } + val csEnum = documentables.filterIsInstance() + val csWithConstructor = documentables.filterIsInstance() + val scopes = documentables.filterIsInstance() + val constructorsToDocumented = csWithConstructor.flatMap { it.constructors } + + val containsRenderableConstructors = constructorsToDocumented.isNotEmpty() && documentables.shouldDocumentConstructors() + val containsRenderableMembers = + containsRenderableConstructors || scopes.any { it.classlikes.isNotEmpty() || it.functions.isNotEmpty() || it.properties.isNotEmpty() } + + @Suppress("UNCHECKED_CAST") + val extensions = (documentables as List>).flatMap { + it.extra[CallableExtensions]?.extensions + ?.filterIsInstance().orEmpty() + } + .distinctBy { it.sourceSets to it.dri } // [Documentable] has expensive equals/hashCode at the moment, see #2620 + return listOfNotNull( + if(!containsRenderableMembers) null else + ContentTab( + "Members", + listOf( + BasicTabbedContentType.CONSTRUCTOR, + BasicTabbedContentType.TYPE, + BasicTabbedContentType.FUNCTION, + BasicTabbedContentType.PROPERTY + ) + ), + if (extensions.isEmpty()) null else ContentTab( + "Members & Extensions", + listOf( + BasicTabbedContentType.CONSTRUCTOR, + BasicTabbedContentType.TYPE, + BasicTabbedContentType.FUNCTION, + BasicTabbedContentType.PROPERTY, + BasicTabbedContentType.EXTENSION_PROPERTY, + BasicTabbedContentType.EXTENSION_FUNCTION + ) + ), + if(csEnum.isEmpty()) null else ContentTab( + "Entries", + listOf( + BasicTabbedContentType.ENTRY + ) + ) + ) + } + + private fun createTabsForPackage(page: PackagePage): List { + val p = page.documentables.single() as DPackage + return listOfNotNull( + ContentTab( + "Types", + listOf( + BasicTabbedContentType.TYPE, + ) + ), + if (p.functions.isEmpty()) null else ContentTab( + "Functions", + listOf( + BasicTabbedContentType.FUNCTION, + BasicTabbedContentType.EXTENSION_FUNCTION, + ) + ), + if (p.properties.isEmpty()) null else ContentTab( + "Properties", + listOf( + BasicTabbedContentType.PROPERTY, + BasicTabbedContentType.EXTENSION_PROPERTY, + ) + ) + ) + } private fun TagConsumer.prepareForTemplates() = if (context.configuration.delayTemplateSubstitution || this is ImmediateResolutionTagConsumer) this else ImmediateResolutionTagConsumer(this, context) - private fun sortTabs(strategy: TabSortingStrategy, tabs: Collection): List { - val sorted = strategy.sort(tabs) - if (sorted.size != tabs.size) - context.logger.warn("Tab sorting strategy has changed number of tabs from ${tabs.size} to ${sorted.size}") - return sorted - } - override fun FlowContent.wrapGroup( node: ContentGroup, pageContext: ContentPage, @@ -66,20 +152,16 @@ open class HtmlRenderer( val additionalClasses = node.style.joinToString(" ") { it.toString().toLowerCase() } return when { node.hasStyle(ContentStyle.TabbedContent) -> div(additionalClasses) { - val secondLevel = node.children.filterIsInstance().flatMap { it.children } - .filterIsInstance().flatMap { it.children }.filterIsInstance() - val firstLevel = node.children.filterIsInstance().flatMap { it.children } - .filterIsInstance() - - val renderable = sortTabs(tabSortingStrategy, firstLevel.union(secondLevel)) + val contentTabs = createTabs(pageContext) div(classes = "tabs-section") { attributes["tabs-section"] = "tabs-section" - renderable.forEachIndexed { index, node -> + contentTabs.forEachIndexed { index, contentTab -> button(classes = "section-tab") { if (index == 0) attributes["data-active"] = "" - attributes["data-togglable"] = node.text - text(node.text) + attributes[TOGGLEABLE_CONTENT_TYPE_ATTR] = + contentTab.tabbedContentTypes.joinToString(",") { it.toHtmlAttribute() } + text(contentTab.text) } } } @@ -104,7 +186,9 @@ open class HtmlRenderer( span("breakable-word") { childrenCallback() } } node.hasStyle(TextStyle.Span) -> span { childrenCallback() } - node.dci.kind == ContentKind.Symbol -> div("symbol $additionalClasses") { childrenCallback() } + node.dci.kind == ContentKind.Symbol -> div("symbol $additionalClasses") { + childrenCallback() + } node.dci.kind == SymbolContentKind.Parameters -> { span("parameters $additionalClasses") { childrenCallback() @@ -122,7 +206,9 @@ open class HtmlRenderer( } node.dci.kind == ContentKind.Deprecation -> div("deprecation-content") { childrenCallback() } node.hasStyle(TextStyle.Paragraph) -> p(additionalClasses) { childrenCallback() } - node.hasStyle(TextStyle.Block) -> div(additionalClasses) { childrenCallback() } + node.hasStyle(TextStyle.Block) -> div(additionalClasses) { + childrenCallback() + } node.hasStyle(TextStyle.Quotation) -> blockQuote(additionalClasses) { childrenCallback() } node.hasStyle(TextStyle.FloatingRight) -> span("clearfix") { span("floating-right") { childrenCallback() } } node.hasStyle(TextStyle.Strikethrough) -> strike { childrenCallback() } @@ -139,6 +225,10 @@ open class HtmlRenderer( node.hasStyle(ListStyle.DescriptionDetails) -> DD(emptyMap(), consumer).visit { this@wrapGroup.childrenCallback() } + node.extra.extraTabbedContentType() != null -> div() { + node.extra.extraTabbedContentType()?.let { attributes[TOGGLEABLE_CONTENT_TYPE_ATTR] = it.value.toHtmlAttribute() } + this@wrapGroup.childrenCallback() + } else -> childrenCallback() } } @@ -433,6 +523,7 @@ open class HtmlRenderer( ) { buildAnchor(contextNode) div(classes = "table-row") { + contextNode.extra.extraTabbedContentType()?.let { attributes[TOGGLEABLE_CONTENT_TYPE_ATTR] = it.value.toHtmlAttribute() } addSourceSetFilteringAttributes(contextNode) div("main-subrow keyValue " + contextNode.style.joinToString(separator = " ")) { buildRowHeaderLink(toRender, pageContext, sourceSetRestriction, contextNode.anchor) @@ -786,11 +877,19 @@ open class HtmlRenderer( content(this, page) } + private fun PageNode.getDocumentableType(): String? = + when(this) { + is PackagePage -> "package" + is ClasslikePage -> "classlike" + is MemberPage -> "member" + else -> null + } open fun buildHtml(page: PageNode, resources: List, content: FlowContent.() -> Unit): String = templater.renderFromTemplate(DokkaTemplateTypes.BASE) { val generatedContent = createHTML().div("main-content") { + page.getDocumentableType()?.let { attributes["data-page-type"] = it } id = "content" (page as? ContentPage)?.let { attributes["pageIds"] = "${context.configuration.moduleName}::${page.pageId}" @@ -851,6 +950,28 @@ open class HtmlRenderer( private val isPartial = context.configuration.delayTemplateSubstitution } +private fun TabbedContentType.toHtmlAttribute(): String = + when(this) { + is BasicTabbedContentType -> + when(this) { + BasicTabbedContentType.ENTRY -> "ENTRY" + BasicTabbedContentType.TYPE -> "TYPE" + BasicTabbedContentType.CONSTRUCTOR -> "CONSTRUCTOR" + BasicTabbedContentType.FUNCTION -> "FUNCTION" + BasicTabbedContentType.PROPERTY -> "PROPERTY" + BasicTabbedContentType.EXTENSION_PROPERTY -> "EXTENSION_PROPERTY" + BasicTabbedContentType.EXTENSION_FUNCTION -> "EXTENSION_FUNCTION" + } + else -> throw IllegalStateException("Unknown TabbedContentType $this") + } + +/** + * Tabs for a content with [ContentStyle.TabbedContent]. + * + * @see ContentStyle.TabbedContent] + */ +private data class ContentTab(val text: String, val tabbedContentTypes: List) + fun List.joinAttr() = joinToString(" ") { it.extraKey + "=" + it.extraValue } private fun String.stripDiv() = drop(5).dropLast(6) // TODO: Find a way to do it without arbitrary trims @@ -859,6 +980,7 @@ private val PageNode.isNavigable: Boolean get() = this !is RendererSpecificPage || strategy != RenderingStrategy.DoNothing private fun PropertyContainer.extraHtmlAttributes() = allOfType() +private fun PropertyContainer.extraTabbedContentType() = this[TabbedContentTypeExtra] private val ContentNode.sourceSetsFilters: String get() = sourceSets.sourceSetIDs.joinToString(" ") { it.toString() } diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index 6006af5b61..a9c6dcca2d 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -18,6 +18,7 @@ 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 java.util.Comparator import kotlin.reflect.KClass internal typealias GroupedTags = Map, List>> @@ -90,7 +91,7 @@ open class DefaultPageCreator( } val constructors = - if (documentables.shouldRenderConstructors()) { + if (documentables.shouldDocumentConstructors()) { documentables.flatMap { (it as? WithConstructors)?.constructors ?: emptyList() } } else { emptyList() @@ -179,6 +180,22 @@ open class DefaultPageCreator( private val WithScope.filteredProperties: List get() = properties.filterNot { it.isInherited() } + private fun Collection.splitPropsAndFuns(): Pair, List> { + val first = ArrayList() + val second = ArrayList() + for (element in this) { + when (element) { + is DProperty -> first.add(element) + is DFunction -> second.add(element) + else -> throw IllegalStateException("Expected only properties or functions") + } + } + return Pair(first, second) + } + + private fun Collection.splitInheritedExtension(dri: Set): Pair, List> where T : org.jetbrains.dokka.model.Callable = + partition { it.receiver?.dri !in dri } + private fun Collection.splitInherited(): Pair, List> where T : Documentable, T : WithExtraProperties = partition { it.isInherited() } @@ -235,14 +252,15 @@ open class DefaultPageCreator( } } } - group(styles = setOf(ContentStyle.TabbedContent)) { + group(styles = setOf(ContentStyle.TabbedContent), extra = mainExtra) { +contentForScope(p, p.dri, p.sourceSets) } } protected open fun contentForScopes( scopes: List, - sourceSets: Set + sourceSets: Set, + extensions: List = emptyList() ): ContentGroup { val types = scopes.flatMap { it.classlikes } + scopes.filterIsInstance().flatMap { it.typealiases } return contentForScope( @@ -251,40 +269,59 @@ open class DefaultPageCreator( sourceSets, types, scopes.flatMap { it.functions }, - scopes.flatMap { it.properties } + scopes.flatMap { it.properties }, + extensions ) } protected open fun contentForScope( s: WithScope, dri: DRI, - sourceSets: Set + sourceSets: Set, + extensions: List = emptyList() ): ContentGroup { val types = listOf( s.classlikes, (s as? DPackage)?.typealiases ?: emptyList() ).flatten() - return contentForScope(setOf(dri), sourceSets, types, s.functions, s.properties) + return contentForScope(setOf(dri), sourceSets, types, s.functions, s.properties, extensions) } - protected open fun contentForScope( + private fun contentForScope( dri: Set, sourceSets: Set, types: List, functions: List, - properties: List + properties: List, + extensions: List ) = contentBuilder.contentFor(dri, sourceSets) { - divergentBlock("Types", types, ContentKind.Classlikes, extra = mainExtra + SimpleAttr.header("Types")) + divergentBlock( + "Types", + types, + ContentKind.Classlikes + ) + val (extensionProps, extensionFuns) = extensions.splitPropsAndFuns() if (separateInheritedMembers) { 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) + + val (inheritedExtensionFunctions, extensionFunctions) = extensionFuns.splitInheritedExtension(dri) + val (inheritedExtensionProperties, extensionProperties) = extensionProps.splitInheritedExtension(dri) + propertiesBlock( + "Properties", memberProperties + extensionProperties + ) + propertiesBlock( + "Inherited properties", inheritedProperties + inheritedExtensionProperties + ) + functionsBlock("Functions", memberFunctions + extensionFunctions) + functionsBlock( + "Inherited functions", inheritedFunctions + inheritedExtensionFunctions + ) } else { - functionsBlock("Functions", functions) - propertiesBlock("Properties", properties, sourceSets) + functionsBlock("Functions", functions + extensionFuns) + propertiesBlock( + "Properties", properties + extensionProps + ) } } @@ -302,7 +339,8 @@ open class DefaultPageCreator( val extensions = (classlikes as List>).flatMap { it.extra[CallableExtensions]?.extensions ?.filterIsInstance().orEmpty() - }.distinctBy{ it.sourceSets to it.dri} // [Documentable] has expensive equals/hashCode at the moment, see #2620 + } + .distinctBy { it.sourceSets to it.dri } // [Documentable] has expensive equals/hashCode at the moment, see #2620 // 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 @@ -315,78 +353,91 @@ open class DefaultPageCreator( } } } - group(styles = setOf(ContentStyle.TabbedContent), sourceSets = mainSourcesetData + extensions.sourceSets) { - val csWithConstructor = classlikes.filterIsInstance() - if (csWithConstructor.isNotEmpty() && documentables.shouldRenderConstructors()) { - val constructorsToDocumented = csWithConstructor.flatMap { it.constructors } - multiBlock( - "Constructors", - 2, - ContentKind.Constructors, - constructorsToDocumented.groupBy { it.name } - .map { (_, v) -> v.first().name to v }, - @Suppress("UNCHECKED_CAST") - (csWithConstructor as List).sourceSets, - needsAnchors = true, - extra = PropertyContainer.empty() + 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() - ) { - ds.forEach { - +buildSignature(it) - contentForBrief(it) - } - } - } + val csEnum = classlikes.filterIsInstance() + val csWithConstructor = classlikes.filterIsInstance() + val scopes = documentables.filterIsInstance() + val constructorsToDocumented = csWithConstructor.flatMap { it.constructors } + + group( + styles = setOf(ContentStyle.TabbedContent), + sourceSets = mainSourcesetData + extensions.sourceSets, + extra = mainExtra + ) { + if (constructorsToDocumented.isNotEmpty() && documentables.shouldDocumentConstructors()) { + +contentForConstructors(constructorsToDocumented, classlikes.dri, classlikes.sourceSets) } - val csEnum = classlikes.filterIsInstance() 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() - ) { - ds.forEach { - +buildSignature(it) - contentForBrief(it) - } - } - } + +contentForEntries(csEnum.flatMap { it.entries }, csEnum.dri, csEnum.sourceSets) } - +contentForScopes(documentables.filterIsInstance(), documentables.sourceSets) + +contentForScopes(scopes, documentables.sourceSets, extensions) + } + } + protected open fun contentForConstructors( + constructorsToDocumented: List, + dri: Set, + sourceSets: Set + ) = contentBuilder.contentFor(dri, sourceSets) { + multiBlock( + "Constructors", + 2, + ContentKind.Constructors, + constructorsToDocumented.groupBy { it.name } + .map { (_, v) -> v.first().name to v }, + @Suppress("UNCHECKED_CAST") + (constructorsToDocumented as List).sourceSets, + needsAnchors = true, + extra = PropertyContainer.empty() + TabbedContentTypeExtra( + BasicTabbedContentType.CONSTRUCTOR + ), + ) { 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() + ) { + ds.forEach { + +buildSignature(it) + contentForBrief(it) + } + } + } + } - divergentBlock( - "Extensions", - extensions, - ContentKind.Extensions, - extra = mainExtra + SimpleAttr.header("Extensions") - ) + protected open fun contentForEntries( + entries: List, + dri: Set, + sourceSets: Set + ) = contentBuilder.contentFor(dri, sourceSets) { + multiBlock( + "Entries", + 2, + ContentKind.Classlikes, + entries.groupBy { it.name }.toList(), + entries.sourceSets, + needsSorting = false, + needsAnchors = true, + extra = mainExtra + TabbedContentTypeExtra(BasicTabbedContentType.ENTRY), + styles = emptySet() + ) { key, ds -> + link(key, ds.first().dri) + sourceSetDependentHint( + ds.dri, + ds.sourceSets, + kind = ContentKind.SourceSetDependentHint, + extra = PropertyContainer.empty() + ) { + ds.forEach { + +buildSignature(it) + contentForBrief(it) + } } } + } + - // Annotations might have constructors to substitute reflection invocations - // and for internal/compiler purposes, but they are not expected to be documented - // and instantiated directly under normal circumstances, so constructors should not be rendered. - private fun List.shouldRenderConstructors() = !this.any { it is DAnnotation } protected open fun contentForDescription( d: Documentable @@ -410,7 +461,9 @@ open class DefaultPageCreator( }.children } - protected open fun DocumentableContentBuilder.contentForBrief(documentable: Documentable) { + protected open fun DocumentableContentBuilder.contentForBrief( + documentable: Documentable + ) { documentable.sourceSets.forEach { sourceSet -> documentable.documentation[sourceSet]?.let { /* @@ -469,48 +522,50 @@ open class DefaultPageCreator( } } - private fun DocumentableContentBuilder.functionsBlock(name: String, list: Collection) = divergentBlock( - name, - list.sorted(), - ContentKind.Functions, - extra = mainExtra + SimpleAttr.header(name) - ) + private fun DocumentableContentBuilder.functionsBlock( + name: String, + list: Collection + ) { + divergentBlock( + name, + list.sorted(), + ContentKind.Functions, + extra = mainExtra + ) + } private fun DocumentableContentBuilder.propertiesBlock( name: String, - list: Collection, - sourceSets: Set + list: Collection ) { - multiBlock( + divergentBlock( name, - 2, + list, ContentKind.Properties, - list.groupBy { it.name }.toList(), - sourceSets, - needsAnchors = true, - extra = mainExtra + SimpleAttr.header(name), - headers = listOf( - headers("Name", "Summary") - ) - ) { key, props -> - link( - text = key, - address = props.first().dri, - kind = ContentKind.Main, - styles = setOf(ContentStyle.RowTitle) - ) - sourceSetDependentHint(props.dri, props.sourceSets, kind = ContentKind.SourceSetDependentHint) { - props.forEach { - +buildSignature(it) - contentForBrief(it) - contentForCustomTagsBrief(it) - } - } - } - } + extra = mainExtra + ) - private val groupKeyComparator: Comparator> = - compareBy(nullsFirst(canonicalAlphabeticalOrder)) { it.key } + } + 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) + } protected open fun DocumentableContentBuilder.divergentBlock( name: String, @@ -519,60 +574,77 @@ open class DefaultPageCreator( extra: PropertyContainer = mainExtra ) { if (collection.any()) { - header(2, name, kind = kind) - table(kind, extra = extra, styles = emptySet()) { - header { - group { text("Name") } - group { text("Summary") } - } - collection - .groupBy { it.name } // 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) - .forEach { (elementName, elements) -> // This groupBy should probably use LocationProvider - val sortedElements = sortDivergentElementsDeterministically(elements) - row( - dri = sortedElements.map { it.dri }.toSet(), - sourceSets = sortedElements.flatMap { it.sourceSets }.toSet(), - kind = kind, - styles = emptySet(), - extra = elementName?.let { name -> extra + SymbolAnchorHint(name, kind) } ?: extra - ) { - link( - text = elementName.orEmpty(), - address = sortedElements.first().dri, - kind = kind, - styles = setOf(ContentStyle.RowTitle), - sourceSets = sortedElements.sourceSets.toSet(), - extra = extra - ) - divergentGroup( - ContentDivergentGroup.GroupID(name), - sortedElements.map { it.dri }.toSet(), - kind = kind, - extra = extra + 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 ) { - sortedElements.map { - instance( - setOf(it.dri), - it.sourceSets.toSet(), - extra = PropertyContainer.withAll(SymbolAnchorHint(it.name ?: "", kind)) - ) { - divergent(extra = PropertyContainer.empty()) { - group { - +buildSignature(it) + 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(), + extra = PropertyContainer.withAll( + SymbolAnchorHint(element.name ?: "", rowKind) + ) + ) { + divergent(extra = PropertyContainer.empty()) { + group { + +buildSignature(element) + } + } + after( + extra = PropertyContainer.empty() + ) { + contentForBrief(element) + contentForCustomTagsBrief(element) } - } - after(extra = PropertyContainer.empty()) { - contentForBrief(it) - contentForCustomTagsBrief(it) } } } } } - } + } } } } @@ -664,3 +736,8 @@ internal inline fun GroupedTags.withTypeNamed(): M ?.groupByTo(linkedMapOf()) { it.second.name } ?.mapValues { (_, v) -> v.toMap() } .orEmpty() + +// Annotations might have constructors to substitute reflection invocations +// and for internal/compiler purposes, but they are not expected to be documented +// and instantiated directly under normal circumstances, so constructors should not be rendered. +internal fun List.shouldDocumentConstructors() = !this.any { it is DAnnotation } \ 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 index 1720b1d0d8..8cd53525e5 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt @@ -229,7 +229,6 @@ internal fun PageContentBuilder.DocumentableContentBuilder.samplesSectionContent 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)) } @@ -333,7 +332,6 @@ private fun PageContentBuilder.DocumentableContentBuilder.tableSectionContentBlo table( kind = kind, sourceSets = sourceSets, - extra = mainExtra + SimpleAttr.header(blockName) ) { body() } diff --git a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt index 000c955fba..4eb1f06b3b 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt @@ -245,28 +245,40 @@ open class PageContentBuilder( needsAnchors: Boolean = false, operation: DocumentableContentBuilder.(String, List) -> 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) + group(extra = extra) { + 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 } - }, - dci = DCI(mainDRI, kind), - sourceSets = sourceSets.toDisplaySourceSets(), - style = styles, - extra = extra - ) + .map { + val documentables = it.second + val newExtra = if (needsAnchors) extra + SymbolAnchorHint( + it.first, + kind + ) else extra + 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 + ) + } } } diff --git a/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js b/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js index def9dae8bb..1f99ba1f98 100644 --- a/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js +++ b/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js @@ -129,22 +129,39 @@ function handleAnchor() { highlightedAnchor = null; } - let searchForTab = function (element) { + let searchForContentTarget = function (element) { if (element && element.hasAttribute) { - if (element.hasAttribute("data-togglable")) return element; - else return searchForTab(element.parentNode) + if (element.hasAttribute("data-togglable")) return element.getAttribute("data-togglable"); + else return searchForContentTarget(element.parentNode) } else return null } + + let findAnyTab = function (target) { + let result = null + document.querySelectorAll('div[tabs-section] > button[data-togglable]') + .forEach(node => { + if(node.getAttribute("data-togglable").split(",").includes(target)) { + result = node + } + }) + return result + } + let anchor = window.location.hash if (anchor != "") { anchor = anchor.substring(1) let element = document.querySelector('a[data-name="' + anchor + '"]') + if (element) { - let tab = searchForTab(element) - if (tab) { - toggleSections(tab) - } const content = element.nextElementSibling + const contentStyle = window.getComputedStyle(content) + if(contentStyle.display == 'none') { + let tab = findAnyTab(searchForContentTarget(content)) + if (tab) { + toggleSections(tab) + } + } + if (content) { content.classList.add('anchor-highlight') highlightedAnchor = content @@ -174,10 +191,7 @@ function initTabs() { function showCorrespondingTabBody(element) { const buttonWithKey = element.querySelector("button[data-active]") if (buttonWithKey) { - const key = buttonWithKey.getAttribute("data-togglable") - document.querySelector(".tabs-section-body") - .querySelector("div[data-togglable='" + key + "']") - .setAttribute("data-active", "") + toggleSections(buttonWithKey) } } @@ -249,7 +263,6 @@ function removeSourcesetFilterFromCache(sourceset) { } function toggleSections(target) { - localStorage.setItem('active-tab', JSON.stringify(target.getAttribute("data-togglable"))) const activateTabs = (containerClass) => { for (const element of document.getElementsByClassName(containerClass)) { for (const child of element.children) { @@ -261,13 +274,24 @@ function toggleSections(target) { } } } - + const toggleTargets = target.getAttribute("data-togglable").split(",") + const activateTabsBody = (containerClass) => { + document.querySelectorAll("." + containerClass + " *[data-togglable]") + .forEach(child => { + if (toggleTargets.includes(child.getAttribute("data-togglable"))) { + child.setAttribute("data-active", "") + } else if(!child.classList.contains("sourceset-dependent-content")) { // data-togglable is used to switch source set as well, ignore it + child.removeAttribute("data-active") + } + }) + } activateTabs("tabs-section") - activateTabs("tabs-section-body") + activateTabsBody("tabs-section-body") } function toggleSectionsEventHandler(evt) { if (!evt.target.getAttribute("data-togglable")) return + localStorage.setItem('active-tab', JSON.stringify(evt.target.getAttribute("data-togglable"))) toggleSections(evt.target) } diff --git a/plugins/base/src/main/resources/dokka/styles/style.css b/plugins/base/src/main/resources/dokka/styles/style.css index 43c8dde69c..e34b9fb446 100644 --- a/plugins/base/src/main/resources/dokka/styles/style.css +++ b/plugins/base/src/main/resources/dokka/styles/style.css @@ -947,7 +947,7 @@ td.content { } .platform-hinted > .content:not([data-active]), -.tabs-section-body > *:not([data-active]) { +.tabs-section-body *[data-togglable]:not([data-active]) { display: none; } @@ -1297,3 +1297,11 @@ div.runnablesample { .floating-right { float: right; } + +/* +the hack to hide the headers inside tabs for a package page because each tab +has only one header, and the header text is the same as the tab name, so no point in showing it +*/ +.main-content[data-page-type="package"] .tabs-section-body h2 { + display: none; +} \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt b/plugins/base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt index c8c088f730..0cf94c186e 100644 --- a/plugins/base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt +++ b/plugins/base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt @@ -2,6 +2,7 @@ package content.signatures import matchers.content.* import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.pages.BasicTabbedContentType import org.jetbrains.dokka.pages.ContentPage import org.junit.jupiter.api.Test @@ -189,27 +190,31 @@ class ConstructorsSignaturesTest : BaseAbstractTest() { } } } - group { - header { +"Constructors" } - table { - group { - link { +"SomeClass" } - platformHinted { - group { - +"constructor" - +"(" - +")" - } + tabbedGroup { + group { + tab(BasicTabbedContentType.CONSTRUCTOR) { + header { +"Constructors" } + table { group { - +"constructor" - +"(" - group { + link { +"SomeClass" } + platformHinted { + group { + +"constructor" + +"(" + +")" + } group { - +"a: " - group { link { +"String" } } + +"constructor" + +"(" + group { + group { + +"a: " + group { link { +"String" } } + } + } + +")" } } - +")" } } } @@ -268,56 +273,60 @@ class ConstructorsSignaturesTest : BaseAbstractTest() { skipAllNotMatching() } } - group { - header { +"Constructors" } - table { - group { - link { +"SomeClass" } - platformHinted { - group { - +"constructor" - +"(" - +")" - } - group { - group { - group { +"ctor one" } - } - } + tabbedGroup { + group { + tab(BasicTabbedContentType.CONSTRUCTOR) { + header { +"Constructors" } + table { group { - +"constructor" - +"(" - group { + link { +"SomeClass" } + platformHinted { + group { + +"constructor" + +"(" + +")" + } group { - +"b: " group { - link { +"Int" } + group { +"ctor one" } } } - } - +")" - } - group { - group { - group { +"ctor two" } - } - } - group { - +"constructor" - +"(" - group { group { - +"a: " + +"constructor" + +"(" group { - link { +"String" } + group { + +"b: " + group { + link { +"Int" } + } + } + } + +")" + } + group { + group { + group { +"ctor two" } + } + } + group { + +"constructor" + +"(" + group { + group { + +"a: " + group { + link { +"String" } + } + } + } + +")" + } + group { + group { + group { +"ctor comment" } } } - } - +")" - } - group { - group { - group { +"ctor comment" } } } } @@ -366,28 +375,32 @@ class ConstructorsSignaturesTest : BaseAbstractTest() { skipAllNotMatching() } } - group { - header { +"Constructors" } - table { - group { - link { +"SomeClass" } - platformHinted { + tabbedGroup { + group { + tab(BasicTabbedContentType.CONSTRUCTOR) { + header { +"Constructors" } + table { group { - +"constructor" - +"(" - group { + link { +"SomeClass" } + platformHinted { group { - +"a: " + +"constructor" + +"(" group { - link { +"String" } + group { + +"a: " + group { + link { +"String" } + } + } + } + +")" + } + group { + group { + group { +"ctor comment" } } } - } - +")" - } - group { - group { - group { +"ctor comment" } } } } @@ -434,9 +447,11 @@ class ConstructorsSignaturesTest : BaseAbstractTest() { } group { group { - header { +"Properties" } - table { - skipAllNotMatching() + group { + header { +"Properties" } + table { + skipAllNotMatching() + } } } } diff --git a/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt b/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt index 58cb3f9f39..3b2720c9a3 100644 --- a/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt +++ b/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt @@ -168,7 +168,7 @@ class KotlinEnumsTest : BaseAbstractTest() { ) { renderingStage = { _, _ -> val enumEntriesOnPage = writerPlugin.writer.renderedContent("root/testpackage/-test-enum/index.html") - .select("div.table[data-togglable=Entries]") + .select("div[data-togglable=ENTRY] .table") .select("div.table-row") .select("div.keyValue") .select("div.title") diff --git a/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt b/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt index 01fefd3c19..6e85fe01a1 100644 --- a/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt +++ b/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt @@ -190,7 +190,7 @@ class PageNodeMergerTest : BaseAbstractTest() { defaultConfiguration ) { renderingStage = { rootPageNode, _ -> - val extensions = rootPageNode.findExtensionsOfClass("ExtensionReceiver") + val extensions = rootPageNode.findDivergencesOfClass("ExtensionReceiver", ContentKind.Extensions) extensions.assertContainsKDocsInOrder( "Top level val extension", @@ -238,7 +238,7 @@ class PageNodeMergerTest : BaseAbstractTest() { defaultConfiguration ) { renderingStage = { rootPageNode, _ -> - val extensions = rootPageNode.findExtensionsOfClass("ExtensionReceiver") + val extensions = rootPageNode.findDivergencesOfClass("ExtensionReceiver", ContentKind.Extensions) extensions.assertContainsKDocsInOrder( "Top level fun extension", "Companion fun extension", @@ -300,7 +300,7 @@ class PageNodeMergerTest : BaseAbstractTest() { defaultConfiguration ) { renderingStage = { rootPageNode, _ -> - val extensions = rootPageNode.findExtensionsOfClass("ExtensionReceiver") + val extensions = rootPageNode.findDivergencesOfClass("ExtensionReceiver", ContentKind.Extensions) extensions.assertContainsKDocsInOrder( "Top level fun extension with one int param", "Top level fun extension with one string param", @@ -421,10 +421,10 @@ class PageNodeMergerTest : BaseAbstractTest() { } } - private fun RootPageNode.findExtensionsOfClass(name: String): ContentDivergentGroup { - val extensionReceiverPage = this.dfs { it is ClasslikePageNode && it.name == name } as ClasslikePageNode + private fun RootPageNode.findDivergencesOfClass(className: String, kind: ContentKind): ContentDivergentGroup { + val extensionReceiverPage = this.dfs { it is ClasslikePageNode && it.name == className } as ClasslikePageNode return extensionReceiverPage.content - .dfs { it is ContentDivergentGroup && it.groupID.name == "Extensions" } as ContentDivergentGroup + .dfs { it is ContentDivergentGroup && it.dci.kind == kind } as ContentDivergentGroup } private fun RootPageNode.findPackageFunctionBlocks(packageName: String): List { diff --git a/plugins/base/src/test/kotlin/renderers/html/CoverPageTest.kt b/plugins/base/src/test/kotlin/renderers/html/CoverPageTest.kt index 0e1f53ecec..522f90377e 100644 --- a/plugins/base/src/test/kotlin/renderers/html/CoverPageTest.kt +++ b/plugins/base/src/test/kotlin/renderers/html/CoverPageTest.kt @@ -38,7 +38,7 @@ class CoverPageTest : BaseAbstractTest() { ) { renderingStage = { _, _ -> val content = writerPlugin.writer.renderedContent("root/example/-result/index.html") - val tableInheritors = content.select("div.table[data-togglable=Inheritors]").single() + val tableInheritors = content.select("div.table").single { it.previousElementSibling()?.text() == "Inheritors" && it.childrenSize() == 2 } assertEquals(tableInheritors.getElementsContainingOwnText("Failed").singleOrNull()?.tagName(), "a") assertEquals(tableInheritors.getElementsContainingOwnText("Success").singleOrNull()?.tagName(), "a") } diff --git a/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt b/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt index 94fcd5bf09..5632994000 100644 --- a/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt +++ b/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt @@ -3,7 +3,6 @@ package renderers.html import org.jetbrains.dokka.DokkaConfigurationImpl import org.jetbrains.dokka.Platform import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.renderers.DefaultTabSortingStrategy import org.jetbrains.dokka.base.renderers.RootCreator import org.jetbrains.dokka.base.resolvers.external.DefaultExternalLocationProviderFactory import org.jetbrains.dokka.base.resolvers.external.javadoc.JavadocExternalLocationProviderFactory @@ -53,7 +52,6 @@ abstract class HtmlRenderingOnlyTestBase : RenderingOnlyTestBase() { DokkaBase().htmlPreprocessors to { RootCreator }, DokkaBase().externalLocationProviderFactory to ::JavadocExternalLocationProviderFactory, DokkaBase().externalLocationProviderFactory to ::DefaultExternalLocationProviderFactory, - DokkaBase().tabSortingStrategy to { DefaultTabSortingStrategy() }, testConfiguration = configuration ) diff --git a/plugins/base/src/test/kotlin/renderers/html/TabbedContentTest.kt b/plugins/base/src/test/kotlin/renderers/html/TabbedContentTest.kt new file mode 100644 index 0000000000..306925b35f --- /dev/null +++ b/plugins/base/src/test/kotlin/renderers/html/TabbedContentTest.kt @@ -0,0 +1,111 @@ +package renderers.html + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jsoup.nodes.Element +import org.junit.jupiter.api.Test +import signatures.renderedContent +import utils.TestOutputWriterPlugin +import kotlin.test.assertEquals + +class TabbedContentTest : BaseAbstractTest() { + + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOf(commonStdlibPath!!) + externalDocumentationLinks = listOf(stdlibExternalDocumentationLink) + } + } + } + + private fun Element.getTabbedRow(type: String) = select(".table-row[data-togglable=$type]") + private fun Element.getTabbedTable(type: String) = select("div[data-togglable=$type] .table") + + @Test + fun `should have correct tabbed content type`() { + val source = """ + |/src/main/kotlin/test/Test.kt + |package example + | + |val p = 0 + |fun foo() = 0 + | + | class A(val d: Int = 0) { + | class Success(): Result() + | class Failed(): Result() + | + | fun fn() = 0 + | } + | + | fun A.fn() = 0 + | fun A.fn2() = 0 + | fun A.fn3() = 0 + | val A.p = 0 + | val A.p2 = 0 + """ + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val classContent = writerPlugin.writer.renderedContent("root/example/-a/index.html") + assertEquals(1, classContent.getTabbedTable("CONSTRUCTOR").size) + assertEquals(1, classContent.getTabbedTable("PROPERTY").size) + assertEquals(1, classContent.getTabbedTable("CONSTRUCTOR").size) + assertEquals(1, classContent.getTabbedTable("FUNCTION").size) + assertEquals(1, classContent.getTabbedTable("TYPE").size) + assertEquals(3, classContent.getTabbedRow("EXTENSION_FUNCTION").size) + assertEquals(2, classContent.getTabbedRow("EXTENSION_PROPERTY").size) + + val packagePage = writerPlugin.writer.renderedContent("root/example/index.html") + assertEquals(1, packagePage.getTabbedTable("TYPE").size) + assertEquals(1, packagePage.getTabbedTable("PROPERTY").size) + assertEquals(1, packagePage.getTabbedTable("FUNCTION").size) + assertEquals(3, packagePage.getTabbedRow("EXTENSION_FUNCTION").size) + assertEquals(2, packagePage.getTabbedRow("EXTENSION_PROPERTY").size) + } + } + } + + + @Test + fun `should have correct order of members and extensions`() { + val source = """ + |/src/main/kotlin/test/Test.kt + |package example + | + |val p = 0 + |fun foo() = 0 + | + |class A(val d: Int = 0) { + | fun fn() = 0 + | fun a() = 0 + | fun g() = 0 + |} + | + | fun A.fn() = 0 + """ + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val classContent = writerPlugin.writer.renderedContent("root/example/-a/index.html") + val funTable = classContent.select("div[data-togglable=FUNCTION] .table") + val orders = + funTable.select(".table-row").map { it.attr("data-togglable") } + assertEquals(listOf("", "", "EXTENSION_FUNCTION", ""), orders) + val names = + funTable.select(".main-subrow .inline-flex a").map { it.text() } + assertEquals(listOf("a", "fn", "fn", "g"), names) + } + } + } +} \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/signatures/ObviousTypeSkippingTest.kt b/plugins/base/src/test/kotlin/signatures/ObviousTypeSkippingTest.kt index 839fb7fe91..5a6d95eb22 100644 --- a/plugins/base/src/test/kotlin/signatures/ObviousTypeSkippingTest.kt +++ b/plugins/base/src/test/kotlin/signatures/ObviousTypeSkippingTest.kt @@ -62,12 +62,12 @@ class ObviousTypeSkippingTest : BaseAbstractTest( forProperty("val underTest = if (true) println(5) else null", "val underTest: Unit?"), forProperty("val underTest: Any = if (true) println(5) else 5", "val underTest: Any"), forProperty("val underTest = if (true) println(5) else 5", "val underTest: Any"), - forFunction("fun > T.underTest() {}", "fun > T.underTest()"), - forFunction("fun > T.underTest() {}", "fun > T.underTest()"), - forFunction("fun ?> T.underTest() {}", "fun ?> T.underTest()"), - forFunction("fun T.underTest() {}", "fun T.underTest()"), - forFunction("fun T.underTest() {}", "fun T.underTest()"), - forFunction("fun T.underTest() {}", "fun T.underTest()"), + forExtension("fun > T.underTest() {}", "fun > T.underTest()"), + forExtension("fun > T.underTest() {}", "fun > T.underTest()"), + forExtension("fun ?> T.underTest() {}", "fun ?> T.underTest()"), + forExtension("fun T.underTest() {}", "fun T.underTest()"), + forExtension("fun T.underTest() {}", "fun T.underTest()"), + forExtension("fun T.underTest() {}", "fun T.underTest()"), forClass("class Testable", "class Testable"), forClass("class Testable", "class Testable"), forClass("class Testable(t: T)", "class Testable(t: T)"), @@ -167,6 +167,13 @@ private fun forFunction(codeFragment: String, expectedSignature: String, functio OnOwnPage(functionName) ) +private fun forExtension(codeFragment: String, expectedSignature: String, functionName: String = "underTest") = + TestData( + codeFragment, + expectedSignature, + OnParentPage(PackagePageNode::class, ContentKind.Extensions), + OnOwnPage(functionName) + ) private fun forMethod( codeFragment: String, expectedSignature: String, diff --git a/plugins/base/src/test/kotlin/signatures/SignatureTest.kt b/plugins/base/src/test/kotlin/signatures/SignatureTest.kt index f017c815a7..3a263fd077 100644 --- a/plugins/base/src/test/kotlin/signatures/SignatureTest.kt +++ b/plugins/base/src/test/kotlin/signatures/SignatureTest.kt @@ -829,7 +829,7 @@ class SignatureTest : BaseAbstractTest() { renderingStage = { _, _ -> val constructorTabFirstElement = writerPlugin.writer.renderedContent("root/example/-primary-constructor-class/index.html") - .tab("Constructors") + .tab("CONSTRUCTOR") .first() ?: throw NoSuchElementException("No Constructors tab found or it is empty") constructorTabFirstElement.firstSignature().match( @@ -926,7 +926,7 @@ class SignatureTest : BaseAbstractTest() { ) { renderingStage = { _, _ -> val enumEntrySignatures = writerPlugin.writer.renderedContent("root/example/-enum-class/index.html") - .select("div.table[data-togglable=Entries]") + .select("div[data-togglable=ENTRY] .table") .single() .signature() .select("div.block") diff --git a/plugins/base/src/test/kotlin/transformerBuilders/PageTransformerBuilderTest.kt b/plugins/base/src/test/kotlin/transformerBuilders/PageTransformerBuilderTest.kt index 0ed5af9f35..686b499028 100644 --- a/plugins/base/src/test/kotlin/transformerBuilders/PageTransformerBuilderTest.kt +++ b/plugins/base/src/test/kotlin/transformerBuilders/PageTransformerBuilderTest.kt @@ -4,6 +4,7 @@ import org.jetbrains.dokka.CoreExtensions import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaPlugin import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.dfs import org.jetbrains.dokka.transformers.pages.PageTransformer import org.jetbrains.dokka.transformers.pages.pageMapper import org.jetbrains.dokka.transformers.pages.pageScanner @@ -12,8 +13,7 @@ import org.jsoup.Jsoup import org.junit.jupiter.api.Test import utils.TestOutputWriterPlugin import utils.assertContains -import kotlin.test.assertEquals - +import utils.assertNotNull class PageTransformerBuilderTest : BaseAbstractTest() { class ProxyPlugin(transformer: PageTransformer) : DokkaPlugin() { @@ -136,7 +136,7 @@ class PageTransformerBuilderTest : BaseAbstractTest() { } @Test - fun `kotlin constructors tab should exist even though there is primary constructor only`() { + fun `kotlin constructors should exist even though there is primary constructor only`() { val configuration = dokkaConfiguration { sourceSets { sourceSet { @@ -160,13 +160,12 @@ class PageTransformerBuilderTest : BaseAbstractTest() { .filterIsInstance() .single { it.dci.kind == ContentKind.Main }.children - val constructorTabsCount = content.filter { it is ContentHeader }.flatMap { - it.children.filter { it is ContentText } - }.count { - (it as? ContentText)?.text == "Constructors" - } + val contentWithConstructorsHeader = content.find { tabContent -> tabContent.dfs { it is ContentText && (it as? ContentText)?.text == "Constructors"} != null } + + contentWithConstructorsHeader.assertNotNull("contentWithConstructorsHeader") - assertEquals(1, constructorTabsCount) + contentWithConstructorsHeader?.dfs { it.dci.kind == ContentKind.Constructors && it is ContentGroup } + .assertNotNull("constructor group") } } } diff --git a/plugins/base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt b/plugins/base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt index 33ec237220..5e3352095d 100644 --- a/plugins/base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt +++ b/plugins/base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt @@ -57,6 +57,12 @@ class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() { }?.children?.dropWhile { child -> child != sectionHeader }?.drop(1)?.firstOrNull() } + private fun ContentNode.findTabWithType(type: TabbedContentType): ContentNode? = dfs { node -> + node.children.filterIsInstance().any { gr -> + gr.extra[TabbedContentTypeExtra]?.value == type + } + } + @Test fun `should merge fun`() { testInline( @@ -173,7 +179,7 @@ class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() { assertEquals( 2, - prop1.firstChildOfType().inner.children.size, + prop1.firstChildOfType().children.size, "Incorrect number of divergent instances found" ) @@ -217,7 +223,7 @@ class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() { val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode assertNotNull(classPage, "Tested class not found!") - val entries = classPage.findSectionWithName("Entries").assertNotNull("Entries") + val entries = classPage.content.findTabWithType(BasicTabbedContentType.ENTRY).assertNotNull("Entries") entries.children.singleOrNull().assertNotNull("ENTRY") val props = classPage.findSectionWithName("Properties").assertNotNull("Properties") diff --git a/plugins/base/src/test/kotlin/utils/contentUtils.kt b/plugins/base/src/test/kotlin/utils/contentUtils.kt index d38af3f4c2..4fce1155e9 100644 --- a/plugins/base/src/test/kotlin/utils/contentUtils.kt +++ b/plugins/base/src/test/kotlin/utils/contentUtils.kt @@ -1,9 +1,7 @@ package utils import matchers.content.* -import org.jetbrains.dokka.pages.ContentGroup -import org.jetbrains.dokka.pages.ContentPage -import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.pages.* //TODO: Try to unify those functions after update to 1.4 fun ContentMatcherBuilder<*>.functionSignature( @@ -197,35 +195,43 @@ fun ContentMatcherBuilder<*>.propertySignature( header { +"Package-level declarations" } skipAllNotMatching() } - group { + tabbedGroup { group { skipAllNotMatching() - header { +"Properties" } - table { - group { - link { +name } - platformHinted { - group { - annotations.entries.forEach { - group { - unwrapAnnotation(it) - } - } - if (visibility.isNotBlank()) +"$visibility " - if (modifier.isNotBlank()) +"$modifier " - +("${keywords.joinToString("") { "$it " }}$preposition ") - link { +name } - if (type != null) { - +(": ") - group { - link { - +(type) + tab(BasicTabbedContentType.PROPERTY) { + header{ + "Properties" } + table { + group { + link { +name } + divergentGroup { + divergentInstance { + divergent { + group { + group { + annotations.entries.forEach { + group { + unwrapAnnotation(it) + } + } + if (visibility.isNotBlank()) +"$visibility " + if (modifier.isNotBlank()) +"$modifier " + +("${keywords.joinToString("") { "$it " }}$preposition ") + link { +name } + if (type != null) { + +(": ") + group { + link { + +(type) + } + } + } + if (value != null) { + +(" = $value") + } + } } } } - if (value != null) { - +(" = $value") - } } } } @@ -242,39 +248,40 @@ fun ContentMatcherBuilder<*>.typealiasSignature(name: String, expressionTarget: } group { group { - skipAllNotMatching() - header { +"Types" } - table { - group { - link { +name } - divergentGroup { - divergentInstance { - group { + tab(BasicTabbedContentType.TYPE) { + header{ + "Types" } + table { + group { + link { +name } + divergentGroup { + divergentInstance { group { group { group { - +"typealias " group { + +"typealias " group { - link { +name } + group { + link { +name } + } + skipAllNotMatching() + } + +" = " + group { + link { +expressionTarget } } - skipAllNotMatching() - } - +" = " - group { - link { +expressionTarget } } } } } } + skipAllNotMatching() } - skipAllNotMatching() } + skipAllNotMatching() } skipAllNotMatching() } - skipAllNotMatching() } } } diff --git a/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt b/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt index 24c58e8e6a..031f5ee8b2 100644 --- a/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt +++ b/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt @@ -9,23 +9,18 @@ import org.jetbrains.dokka.model.Annotations import org.jetbrains.dokka.model.GenericTypeConstructor import org.jetbrains.dokka.model.dfs import org.jetbrains.dokka.pages.* -import org.jetbrains.kotlin.utils.addToStdlib.cast import org.junit.Assert import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -import signatures.Parameter -import signatures.Parameters -import signatures.firstSignature -import signatures.renderedContent -import utils.A -import utils.TestOutputWriterPlugin -import utils.match +import signatures.* +import utils.* import kotlin.test.assertEquals +import kotlin.test.assertNotNull class KotlinAsJavaPluginTest : BaseAbstractTest() { @Test - fun topLevelTest() { + fun `top-level functions should be generated`() { val configuration = dokkaConfiguration { sourceSets { sourceSet { @@ -53,15 +48,19 @@ class KotlinAsJavaPluginTest : BaseAbstractTest() { pagesGenerationStage = { root -> val content = (root.children.single().children.first { it.name == "TestKt" } as ContentPage).content - val children = content.mainContents.first().cast() - .children.filterIsInstance() - .filter { it.children.isNotEmpty() } + val functionRows = content.findTableWithKind(kind = ContentKind.Functions).children + functionRows.assertCount(6) - children.assertCount(2) + val propRows = content.findTableWithKind(kind = ContentKind.Properties).children + propRows.assertCount(1) } } } + private fun ContentNode.findTableWithKind(kind: ContentKind): ContentNode = dfs { node -> + node is ContentTable && node.dci.kind === kind + }.let { assertNotNull(it, "the table with kind $kind") } + @Test fun topLevelWithClassTest() { val configuration = dokkaConfiguration { @@ -90,15 +89,17 @@ class KotlinAsJavaPluginTest : BaseAbstractTest() { pagesGenerationStage = { root -> val contentList = root.children .flatMap { it.children() } - .map { it.content } - val children = contentList.flatMap { content -> - content.mainContents.single { it is ContentGroup }.children - .filterIsInstance() - .filter { it.children.isNotEmpty() } + contentList.find {it.name == "Test"}.apply { + assertNotNull(this) + content.findTableWithKind(ContentKind.Functions).children.assertCount(2) + content.findTableWithKind(ContentKind.Properties).children.assertCount(1) + } + contentList.find {it.name == "TestKt"}.apply { + assertNotNull(this) + content.findTableWithKind(ContentKind.Functions).children.assertCount(2) + content.findTableWithKind(ContentKind.Properties).children.assertCount(1) } - - children.assertCount(4) } } } @@ -233,10 +234,9 @@ class KotlinAsJavaPluginTest : BaseAbstractTest() { pagesGenerationStage = { root -> val testClass = root.dfs { it.name == "TestJ" } as? ClasslikePageNode assert(testClass != null) - (testClass!!.content as ContentGroup).children.last().assertNode { - skipAllNotMatching() + (testClass!!.content as ContentGroup).children.last().children.last().assertNode { group { - header(2) { + header(2){ +"Properties" } table { @@ -244,11 +244,17 @@ class KotlinAsJavaPluginTest : BaseAbstractTest() { link { +"publicProperty" } - platformHinted { - group { - +"public Int" - link { - +"publicProperty" + divergentGroup { + divergentInstance { + divergent { + group { + group { + +"public Int" + link { + +"publicProperty" + } + } + } } } } @@ -602,4 +608,4 @@ class KotlinAsJavaPluginTest : BaseAbstractTest() { private val ContentNode.mainContents: List get() = (this as ContentGroup).children .filterIsInstance() - .single { it.dci.kind == ContentKind.Main }.children + .single { it.dci.kind == ContentKind.Main }.children[0].let { it.children[0] }.children