aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src
diff options
context:
space:
mode:
authorVadim Mishenev <vad-mishenev@yandex.ru>2023-02-24 17:44:24 +0200
committerGitHub <noreply@github.com>2023-02-24 17:44:24 +0200
commit1040288ca76e070445f1400df2fcc5a56310be28 (patch)
tree52bed40e8a320f0835540b0cd38ea1899800e395 /plugins/base/src
parent8d23340d1c377b8f490cdee3c2c874453d321dd8 (diff)
downloaddokka-1040288ca76e070445f1400df2fcc5a56310be28.tar.gz
dokka-1040288ca76e070445f1400df2fcc5a56310be28.tar.bz2
dokka-1040288ca76e070445f1400df2fcc5a56310be28.zip
Reorganize tabs for Classlike (#2764)
Diffstat (limited to 'plugins/base/src')
-rw-r--r--plugins/base/src/main/kotlin/DokkaBase.kt5
-rw-r--r--plugins/base/src/main/kotlin/renderers/DefaultTabSortingStrategy.kt30
-rw-r--r--plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt160
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt405
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DescriptionSections.kt2
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt52
-rw-r--r--plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js52
-rw-r--r--plugins/base/src/main/resources/dokka/styles/style.css10
-rw-r--r--plugins/base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt171
-rw-r--r--plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt2
-rw-r--r--plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt12
-rw-r--r--plugins/base/src/test/kotlin/renderers/html/CoverPageTest.kt2
-rw-r--r--plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt2
-rw-r--r--plugins/base/src/test/kotlin/renderers/html/TabbedContentTest.kt111
-rw-r--r--plugins/base/src/test/kotlin/signatures/ObviousTypeSkippingTest.kt19
-rw-r--r--plugins/base/src/test/kotlin/signatures/SignatureTest.kt4
-rw-r--r--plugins/base/src/test/kotlin/transformerBuilders/PageTransformerBuilderTest.kt17
-rw-r--r--plugins/base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt10
-rw-r--r--plugins/base/src/test/kotlin/utils/contentUtils.kt95
19 files changed, 756 insertions, 405 deletions
diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt
index 456a587b..f76eef63 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<OutputWriter>()
val htmlPreprocessors by extensionPoint<PageTransformer>()
val kotlinAnalysis by extensionPoint<KotlinAnalysis>()
+ @Deprecated("It is not used anymore")
val tabSortingStrategy by extensionPoint<TabSortingStrategy>()
val immediateHtmlCommandConsumer by extensionPoint<ImmediateHtmlCommandConsumer>()
val externalDocumentablesProvider by extensionPoint<ExternalDocumentablesProvider>()
@@ -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 056e0f93..00000000
--- 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 <T: ContentNode> sort(tabs: Collection<T>): List<T> {
- val tabMap: Map<Kind, MutableList<T>> = kindsOrder.asSequence().map { it to mutableListOf<T>() }.toMap()
- val unrecognized: MutableList<T> = 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 b4d98b58..dfefbad8 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<DokkaBase>().query { htmlPreprocessors }
- private val tabSortingStrategy = context.plugin<DokkaBase>().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<ContentTab> {
+ 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<ContentTab> {
+ val documentables = page.documentables
+ fun List<Documentable>.shouldDocumentConstructors() = !this.any { it is DAnnotation }
+ val csEnum = documentables.filterIsInstance<DEnum>()
+ val csWithConstructor = documentables.filterIsInstance<WithConstructors>()
+ val scopes = documentables.filterIsInstance<WithScope>()
+ 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<WithExtraProperties<DClasslike>>).flatMap {
+ it.extra[CallableExtensions]?.extensions
+ ?.filterIsInstance<Documentable>().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<ContentTab> {
+ 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 <R> TagConsumer<R>.prepareForTemplates() =
if (context.configuration.delayTemplateSubstitution || this is ImmediateResolutionTagConsumer) this
else ImmediateResolutionTagConsumer(this, context)
- private fun <T : ContentNode> sortTabs(strategy: TabSortingStrategy, tabs: Collection<T>): List<T> {
- 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<ContentComposite>().flatMap { it.children }
- .filterIsInstance<ContentHeader>().flatMap { it.children }.filterIsInstance<ContentText>()
- val firstLevel = node.children.filterIsInstance<ContentHeader>().flatMap { it.children }
- .filterIsInstance<ContentText>()
-
- 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<String>, 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<TabbedContentType>)
+
fun List<SimpleAttr>.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<ContentNode>.extraHtmlAttributes() = allOfType<SimpleAttr>()
+private fun PropertyContainer<ContentNode>.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 6006af5b..a9c6dcca 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<KClass<out TagWrapper>, List<Pair<DokkaSourceSet?, TagWrapper>>>
@@ -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<DProperty>
get() = properties.filterNot { it.isInherited() }
+ private fun Collection<Documentable>.splitPropsAndFuns(): Pair<List<DProperty>, List<DFunction>> {
+ val first = ArrayList<DProperty>()
+ val second = ArrayList<DFunction>()
+ 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 <T> Collection<T>.splitInheritedExtension(dri: Set<DRI>): Pair<List<T>, List<T>> where T : org.jetbrains.dokka.model.Callable =
+ partition { it.receiver?.dri !in dri }
+
private fun <T> Collection<T>.splitInherited(): Pair<List<T>, List<T>> where T : Documentable, T : WithExtraProperties<T> =
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<WithScope>,
- sourceSets: Set<DokkaSourceSet>
+ sourceSets: Set<DokkaSourceSet>,
+ extensions: List<Documentable> = emptyList()
): ContentGroup {
val types = scopes.flatMap { it.classlikes } + scopes.filterIsInstance<DPackage>().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<DokkaSourceSet>
+ sourceSets: Set<DokkaSourceSet>,
+ extensions: List<Documentable> = 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<DRI>,
sourceSets: Set<DokkaSourceSet>,
types: List<Documentable>,
functions: List<DFunction>,
- properties: List<DProperty>
+ properties: List<DProperty>,
+ extensions: List<Documentable>
) = 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<WithExtraProperties<DClasslike>>).flatMap {
it.extra[CallableExtensions]?.extensions
?.filterIsInstance<Documentable>().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<WithConstructors>()
- 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<Documentable>).sourceSets,
- needsAnchors = true,
- extra = PropertyContainer.empty<ContentNode>() + SimpleAttr.header("Constructors")
- ) { key, ds ->
- link(key, ds.first().dri, kind = ContentKind.Main, styles = setOf(ContentStyle.RowTitle))
- sourceSetDependentHint(
- ds.dri,
- ds.sourceSets,
- kind = ContentKind.SourceSetDependentHint,
- styles = emptySet(),
- extra = PropertyContainer.empty<ContentNode>()
- ) {
- ds.forEach {
- +buildSignature(it)
- contentForBrief(it)
- }
- }
- }
+ val csEnum = classlikes.filterIsInstance<DEnum>()
+ val csWithConstructor = classlikes.filterIsInstance<WithConstructors>()
+ val scopes = documentables.filterIsInstance<WithScope>()
+ 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<DEnum>()
if (csEnum.isNotEmpty()) {
- multiBlock(
- "Entries",
- 2,
- ContentKind.Classlikes,
- csEnum.flatMap { it.entries }.groupBy { it.name }.toList(),
- csEnum.sourceSets,
- needsSorting = false,
- needsAnchors = true,
- extra = mainExtra + SimpleAttr.header("Entries"),
- styles = emptySet()
- ) { key, ds ->
- link(key, ds.first().dri)
- sourceSetDependentHint(
- ds.dri,
- ds.sourceSets,
- kind = ContentKind.SourceSetDependentHint,
- extra = PropertyContainer.empty<ContentNode>()
- ) {
- ds.forEach {
- +buildSignature(it)
- contentForBrief(it)
- }
- }
- }
+ +contentForEntries(csEnum.flatMap { it.entries }, csEnum.dri, csEnum.sourceSets)
}
- +contentForScopes(documentables.filterIsInstance<WithScope>(), documentables.sourceSets)
+ +contentForScopes(scopes, documentables.sourceSets, extensions)
+ }
+ }
+ protected open fun contentForConstructors(
+ constructorsToDocumented: List<DFunction>,
+ dri: Set<DRI>,
+ sourceSets: Set<DokkaSourceSet>
+ ) = 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<Documentable>).sourceSets,
+ needsAnchors = true,
+ extra = PropertyContainer.empty<ContentNode>() + 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<ContentNode>()
+ ) {
+ ds.forEach {
+ +buildSignature(it)
+ contentForBrief(it)
+ }
+ }
+ }
+ }
- divergentBlock(
- "Extensions",
- extensions,
- ContentKind.Extensions,
- extra = mainExtra + SimpleAttr.header("Extensions")
- )
+ protected open fun contentForEntries(
+ entries: List<DEnumEntry>,
+ dri: Set<DRI>,
+ sourceSets: Set<DokkaSourceSet>
+ ) = 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<ContentNode>()
+ ) {
+ 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<Documentable>.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<DFunction>) = divergentBlock(
- name,
- list.sorted(),
- ContentKind.Functions,
- extra = mainExtra + SimpleAttr.header(name)
- )
+ private fun DocumentableContentBuilder.functionsBlock(
+ name: String,
+ list: Collection<DFunction>
+ ) {
+ divergentBlock(
+ name,
+ list.sorted(),
+ ContentKind.Functions,
+ extra = mainExtra
+ )
+ }
private fun DocumentableContentBuilder.propertiesBlock(
name: String,
- list: Collection<DProperty>,
- sourceSets: Set<DokkaSourceSet>
+ list: Collection<DProperty>
) {
- 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<Map.Entry<String?, *>> =
- compareBy(nullsFirst(canonicalAlphabeticalOrder)) { it.key }
+ }
+ private data class NameAndIsExtension(val name:String?, val isExtension: Boolean)
+
+ private fun groupAndSortDivergentCollection(collection: Collection<Documentable>): List<Map.Entry<NameAndIsExtension, List<Documentable>>> {
+ val groupKeyComparator: Comparator<Map.Entry<NameAndIsExtension, List<Documentable>>> =
+ compareBy<Map.Entry<NameAndIsExtension, List<Documentable>>, 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<ContentNode> = 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 <reified T : NamedTagWrapper> 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<Documentable>.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 1720b1d0..8cd53525 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 000c955f..4eb1f06b 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<Documentable>) -> Unit
) {
+
if (renderWhenEmpty || groupedElements.any()) {
- header(level, name, kind = kind) { }
- contents += ContentTable(
- header = headers,
- children = groupedElements
- .let {
- if (needsSorting)
- it.sortedWith(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)) { it.first })
- else it
- }
- .map {
- val newExtra = if (needsAnchors) extra + SymbolAnchorHint(it.first, kind) else extra
- val documentables = it.second
- buildGroup(documentables.map { it.dri }.toSet(), documentables.flatMap { it.sourceSets }.toSet(), kind, styles, newExtra) {
- operation(it.first, documentables)
+ 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 def9dae8..1f99ba1f 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 43c8dde6..e34b9fb4 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 c8c088f7..0cf94c18 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 58cb3f9f..3b2720c9 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 01fefd3c..6e85fe01 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<ContentDivergentGroup> {
diff --git a/plugins/base/src/test/kotlin/renderers/html/CoverPageTest.kt b/plugins/base/src/test/kotlin/renderers/html/CoverPageTest.kt
index 0e1f53ec..522f9037 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 94fcd5bf..56329940 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<Element>() {
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 00000000..306925b3
--- /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 839fb7fe..5a6d95eb 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: Iterable<Any>> T.underTest() {}", "fun <T : Iterable<Any>> T.underTest()"),
- forFunction("fun <T: Iterable<Any?>> T.underTest() {}", "fun <T : Iterable<Any?>> T.underTest()"),
- forFunction("fun <T: Iterable<Any?>?> T.underTest() {}", "fun <T : Iterable<Any?>?> T.underTest()"),
- forFunction("fun <T: Any> T.underTest() {}", "fun <T : Any> T.underTest()"),
- forFunction("fun <T: Any?> T.underTest() {}", "fun <T> T.underTest()"),
- forFunction("fun <T> T.underTest() {}", "fun <T> T.underTest()"),
+ forExtension("fun <T: Iterable<Any>> T.underTest() {}", "fun <T : Iterable<Any>> T.underTest()"),
+ forExtension("fun <T: Iterable<Any?>> T.underTest() {}", "fun <T : Iterable<Any?>> T.underTest()"),
+ forExtension("fun <T: Iterable<Any?>?> T.underTest() {}", "fun <T : Iterable<Any?>?> T.underTest()"),
+ forExtension("fun <T: Any> T.underTest() {}", "fun <T : Any> T.underTest()"),
+ forExtension("fun <T: Any?> T.underTest() {}", "fun <T> T.underTest()"),
+ forExtension("fun <T> T.underTest() {}", "fun <T> T.underTest()"),
forClass("class Testable<T: Any>", "class Testable<T : Any>"),
forClass("class Testable<T: Any?>", "class Testable<T>"),
forClass("class Testable<T: Any?>(t: T)", "class Testable<T>(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 f017c815..3a263fd0 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 0ed5af9f..686b4990 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<ContentGroup>()
.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 33ec2372..5e335209 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<ContentGroup>().any { gr ->
+ gr.extra[TabbedContentTypeExtra]?.value == type
+ }
+ }
+
@Test
fun `should merge fun`() {
testInline(
@@ -173,7 +179,7 @@ class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() {
assertEquals(
2,
- prop1.firstChildOfType<PlatformHintedContent>().inner.children.size,
+ prop1.firstChildOfType<ContentDivergentGroup>().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 d38af3f4..4fce1155 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()
}
}
}