diff options
author | ilya-g <ilya.gorbunov@jetbrains.com> | 2023-02-17 16:42:28 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-17 16:42:28 +0100 |
commit | 1fd045904e94d68878b3a5f748bf20f7c61a1cf7 (patch) | |
tree | 6e5e63033f654abc4e5c61278a4eeb39330977be | |
parent | daed35f92b3b482688856d139da6849c8e6b4ab1 (diff) | |
download | dokka-1fd045904e94d68878b3a5f748bf20f7c61a1cf7.tar.gz dokka-1fd045904e94d68878b3a5f748bf20f7c61a1cf7.tar.bz2 dokka-1fd045904e94d68878b3a5f748bf20f7c61a1cf7.zip |
Consistent alphabetical order of element groups in index and navigation (#2861)
* Sort groups of divergent elements by their key first ignoring case, then preserving it
* Add tests for sorting groups and navigation
5 files changed, 132 insertions, 3 deletions
diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt index ecce70e8..4dae21c8 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<NavigationNode> = + compareBy(canonicalAlphabeticalOrder) { it.name } + private fun ContentPage.navigableChildren() = if (this is ClasslikePage) { this.navigableChildren() @@ -97,7 +101,7 @@ abstract class NavigationDataProvider { children .filterIsInstance<ContentPage>() .map { visit(it) } - .sortedBy { it.name.toLowerCase() } + .sortedWith(navigationNodeOrder) } private fun ClasslikePage.navigableChildren(): List<NavigationNode> { @@ -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 25f3c450..6006af5b 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<Map.Entry<String?, *>> = + compareBy(nullsFirst(canonicalAlphabeticalOrder)) { it.key } + protected open fun DocumentableContentBuilder.divergentBlock( name: String, collection: Collection<Documentable>, @@ -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 00000000..68b9cde8 --- /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<in String> = 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 05ff55ee..01fefd3c 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 <T> sequence(): Sequence<T> + | + |/** Sequence SAM constructor */ + |fun <T> Sequence(): Sequence<T> + | + |/** Sequence.any() */ + |fun <T> Sequence<T>.any() {} + | + |/** Sequence interface */ + |interface Sequence<T> + """.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 13a9e711..21b4ea3c 100644 --- a/plugins/base/src/test/kotlin/renderers/html/NavigationTest.kt +++ b/plugins/base/src/test/kotlin/renderers/html/NavigationTest.kt @@ -20,6 +20,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 <T> sequence(): Sequence<T> + | + |fun <T> Sequence(): Sequence<T> + | + |fun <T> Sequence<T>.any() {} + | + |interface Sequence<T> + """.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() testInline( |