diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt index ecce70e8da..4dae21c8b4 100644 --- a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt +++ b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt @@ -6,6 +6,7 @@ import org.jetbrains.dokka.base.transformers.documentables.isDeprecated import org.jetbrains.dokka.base.transformers.documentables.isException import org.jetbrains.dokka.base.translators.documentables.DocumentableLanguage import org.jetbrains.dokka.base.translators.documentables.documentableLanguage +import org.jetbrains.dokka.base.utils.canonicalAlphabeticalOrder import org.jetbrains.dokka.model.* import org.jetbrains.dokka.pages.* @@ -90,6 +91,9 @@ abstract class NavigationDataProvider { } } + private val navigationNodeOrder: Comparator = + compareBy(canonicalAlphabeticalOrder) { it.name } + private fun ContentPage.navigableChildren() = if (this is ClasslikePage) { this.navigableChildren() @@ -97,7 +101,7 @@ abstract class NavigationDataProvider { children .filterIsInstance() .map { visit(it) } - .sortedBy { it.name.toLowerCase() } + .sortedWith(navigationNodeOrder) } private fun ClasslikePage.navigableChildren(): List { @@ -111,7 +115,7 @@ abstract class NavigationDataProvider { // no sorting for enum entries, should be the same order as in source code navigableChildren } else { - navigableChildren.sortedBy { it.name.toLowerCase() } + navigableChildren.sortedWith(navigationNodeOrder) } } } diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index 25f3c4502d..6006af5b61 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -9,6 +9,7 @@ import org.jetbrains.dokka.base.transformers.documentables.ClashingDriIdentifier import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter import org.jetbrains.dokka.base.transformers.pages.tags.CustomTagContentProvider import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder +import org.jetbrains.dokka.base.utils.canonicalAlphabeticalOrder import org.jetbrains.dokka.links.Callable import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* @@ -508,6 +509,9 @@ open class DefaultPageCreator( } } + private val groupKeyComparator: Comparator> = + compareBy(nullsFirst(canonicalAlphabeticalOrder)) { it.key } + protected open fun DocumentableContentBuilder.divergentBlock( name: String, collection: Collection, @@ -525,7 +529,7 @@ open class DefaultPageCreator( .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.sortedBy { it.key } + .entries.sortedWith(groupKeyComparator) .forEach { (elementName, elements) -> // This groupBy should probably use LocationProvider val sortedElements = sortDivergentElementsDeterministically(elements) row( diff --git a/plugins/base/src/main/kotlin/utils/alphabeticalOrder.kt b/plugins/base/src/main/kotlin/utils/alphabeticalOrder.kt new file mode 100644 index 0000000000..68b9cde8a6 --- /dev/null +++ b/plugins/base/src/main/kotlin/utils/alphabeticalOrder.kt @@ -0,0 +1,7 @@ +package org.jetbrains.dokka.base.utils + + +/** + * Canonical alphabetical order to sort named elements + */ +internal val canonicalAlphabeticalOrder: Comparator = String.CASE_INSENSITIVE_ORDER.thenBy { it } \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt b/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt index 05ff55ee4c..01fefd3c19 100644 --- a/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt +++ b/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt @@ -381,6 +381,46 @@ class PageNodeMergerTest : BaseAbstractTest() { } } + @Test + fun `should sort groups alphabetically ignoring case`() { + testInline( + """ + |/src/main/kotlin/test/Test.kt + |package test + | + |/** Sequence builder */ + |fun sequence(): Sequence + | + |/** Sequence SAM constructor */ + |fun Sequence(): Sequence + | + |/** Sequence.any() */ + |fun Sequence.any() {} + | + |/** Sequence interface */ + |interface Sequence + """.trimMargin(), + defaultConfiguration + ) { + renderingStage = { rootPageNode, _ -> + val packageFunctionBlocks = rootPageNode.findPackageFunctionBlocks(packageName = "test") + assertEquals(3, packageFunctionBlocks.size, "Expected 3 separate function groups") + + packageFunctionBlocks[0].assertContainsKDocsInOrder( + "Sequence.any()", + ) + + packageFunctionBlocks[1].assertContainsKDocsInOrder( + "Sequence SAM constructor", + ) + + packageFunctionBlocks[2].assertContainsKDocsInOrder( + "Sequence builder", + ) + } + } + } + private fun RootPageNode.findExtensionsOfClass(name: String): ContentDivergentGroup { val extensionReceiverPage = this.dfs { it is ClasslikePageNode && it.name == name } as ClasslikePageNode return extensionReceiverPage.content diff --git a/plugins/base/src/test/kotlin/renderers/html/NavigationTest.kt b/plugins/base/src/test/kotlin/renderers/html/NavigationTest.kt index 13a9e71147..21b4ea3c08 100644 --- a/plugins/base/src/test/kotlin/renderers/html/NavigationTest.kt +++ b/plugins/base/src/test/kotlin/renderers/html/NavigationTest.kt @@ -19,6 +19,80 @@ class NavigationTest : BaseAbstractTest() { } } + @Test + fun `should sort alphabetically ignoring case`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/com/example/Sequences.kt + |package com.example + | + |fun sequence(): Sequence + | + |fun Sequence(): Sequence + | + |fun Sequence.any() {} + | + |interface Sequence + """.trimMargin(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") + assertEquals(6, content.size) + + // Navigation menu should be the following: + // - root + // - com.example + // - any() + // - Sequence interface + // - Sequence() + // - sequence() + + content[0].assertNavigationLink( + id = "root-nav-submenu", + text = "root", + address = "index.html", + ) + + content[1].assertNavigationLink( + id = "root-nav-submenu-0", + text = "com.example", + address = "root/com.example/index.html", + ) + + content[2].assertNavigationLink( + id = "root-nav-submenu-0-0", + text = "any()", + address = "root/com.example/any.html", + icon = NavigationNodeIcon.FUNCTION + ) + + content[3].assertNavigationLink( + id = "root-nav-submenu-0-1", + text = "Sequence", + address = "root/com.example/-sequence/index.html", + icon = NavigationNodeIcon.INTERFACE_KT + ) + + content[4].assertNavigationLink( + id = "root-nav-submenu-0-2", + text = "Sequence()", + address = "root/com.example/-sequence.html", + icon = NavigationNodeIcon.FUNCTION + ) + + content[5].assertNavigationLink( + id = "root-nav-submenu-0-3", + text = "sequence()", + address = "root/com.example/sequence.html", + icon = NavigationNodeIcon.FUNCTION + ) + } + } + } + @Test fun `should strike deprecated class link`() { val writerPlugin = TestOutputWriterPlugin()