Support hiding extension functions from the TOC (side menu)
Is your feature request related to a problem? Please describe
Use case: TestBalloon's API consists of a large number of extension functions, mostly on TestConfig, which is a central configuration builder. These extension functions are not meaningful outside this builder, so seeing them in the de.infix.testBalloon.framework.core package's top-level navigation is just noise cluttering the signal (relevant content):
Describe the solution you'd like
Hiding such extension functions from the TOC shows more relevant content (declarations which are actually accessible at the top level):
Describe alternatives you've considered
This Dokka plugin hides the affected navigation nodes (and also fixes an empty package remaining after applying another plugin to hide internal-API-annotated symbols):
/**
* A Dokka plugin which hides extension functions and empty packages in the table of contents (side menu).
*/
class NavigationNodeHidingDokkaPlugin : DokkaPlugin() {
@Suppress("unused")
val myFilterExtension: Extension<PageTransformer, *, *> by extending {
val dokkaBase = plugin<DokkaBase>()
dokkaBase.htmlPreprocessors providing ::NavigationNodeHidingTransformer override
dokkaBase.navigationPageInstaller
}
@DokkaPluginApiPreview
override fun pluginApiPreviewAcknowledgement() = PluginApiPreviewAcknowledgement
}
private class NavigationNodeHidingTransformer(context: DokkaContext) : NavigationPageInstaller(context) {
override fun navigableChildren(input: RootPageNode): NavigationNode = super.navigableChildren(input).transform {
NavigationNode(
it.name,
it.dri,
it.sourceSets,
it.icon,
it.styles,
it.children.filterNot { child ->
// Drop extension functions and empty packages.
child.isExtensionFunction() || child.isEmptyPackage()
}
)
}
private fun NavigationNode.isExtensionFunction(): Boolean = dri.callable?.receiver != null
private fun NavigationNode.isEmptyPackage(): Boolean {
val isPackage = dri.classNames == null && dri.callable == null && dri.packageName != null
return isPackage && children.isEmpty()
}
}
Additional context
Slack discussion: https://kotlinlang.slack.com/archives/C0F4UNJET/p1761560392906549?thread_ts=1761330952.244459&cid=C0F4UNJET
Thanks! That's really a nice idea to explore!
Though, regarding your current implementation, you do drop all extensions even if they have different receivers. For example, if you had a function like fun String.xxx() at top-level, the declaration will be hidden and not shown in documentation at all.
To be correct, you might want to limit filtering to types coming from the current module only.
Thanks for the feedback! Yes you are right that the above plugin would potentially hide too much. It's not an issue now, as there are no public extensions on outside types, but this could change, of course. I'll take care of this.
Ideally, even with such outside types, I'd prefer to treat extensions as if they were members. Given your String.xxx() example, is there a way to make String appear as a top-level type in the navigation? I know it's not "mine" and it would be incomplete, showing only "my" extensions, but I'd say even then it provides better explorability than having the extension-less function xxx() on the top level.
Ideally, even with such outside types, I'd prefer to treat extensions as if they were members. Given your String.xxx() example, is there a way to make String appear as a top-level type in the navigation?
Theoretically, it's possible (as Dokka is very flexible :)). You will need to collect extensions (as in ExtensionExtractorTransformer) for external types and create additional ContentPages for them.
Additionally, Swift Docc utilizes this approach with Extended Modules / Extended Protocols.