aboutsummaryrefslogtreecommitdiff
path: root/dokka-subprojects/plugin-javadoc/src/main/kotlin
diff options
context:
space:
mode:
authorIgnat Beresnev <ignat.beresnev@jetbrains.com>2023-11-10 11:46:54 +0100
committerGitHub <noreply@github.com>2023-11-10 11:46:54 +0100
commit8e5c63d035ef44a269b8c43430f43f5c8eebfb63 (patch)
tree1b915207b2b9f61951ddbf0ff2e687efd053d555 /dokka-subprojects/plugin-javadoc/src/main/kotlin
parenta44efd4ba0c2e4ab921ff75e0f53fc9335aa79db (diff)
downloaddokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.gz
dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.bz2
dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.zip
Restructure the project to utilize included builds (#3174)
* Refactor and simplify artifact publishing * Update Gradle to 8.4 * Refactor and simplify convention plugins and build scripts Fixes #3132 --------- Co-authored-by: Adam <897017+aSemy@users.noreply.github.com> Co-authored-by: Oleg Yukhnevich <whyoleg@gmail.com>
Diffstat (limited to 'dokka-subprojects/plugin-javadoc/src/main/kotlin')
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt16
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt261
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt113
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt147
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt17
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt206
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt14
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt529
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt200
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/utils.kt42
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt90
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt312
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt213
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt280
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt225
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/transformers/documentables/JavadocDocumentableJVMSourceSetFilter.kt27
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt83
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt12
-rw-r--r--dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/validity/MultiplatformConfiguredChecker.kt28
19 files changed, 2815 insertions, 0 deletions
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt
new file mode 100644
index 00000000..595c307a
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc
+
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.pages.RootPageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator
+
+public class JavadocDocumentableToPageTranslator(
+ private val context: DokkaContext
+) : DocumentableToPageTranslator {
+ override fun invoke(module: DModule): RootPageNode = JavadocPageCreator(context).pageForModule(module)
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt
new file mode 100644
index 00000000..cfdda649
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc
+
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter
+import org.jetbrains.dokka.base.translators.documentables.firstSentenceBriefFromContentNodes
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import kotlin.reflect.KClass
+
+public open class JavadocPageCreator(context: DokkaContext) {
+ private val signatureProvider: SignatureProvider = context.plugin<DokkaBase>().querySingle { signatureProvider }
+ private val documentationVersion = context.configuration.moduleVersion
+
+ public fun pageForModule(m: DModule): JavadocModulePageNode {
+ return JavadocModulePageNode(
+ name = m.name.ifEmpty { "root" },
+ content = contentForModule(m),
+ children = m.packages.map { pageForPackage(it) },
+ dri = setOf(m.dri),
+ extra = ((m as? WithExtraProperties<DModule>)?.extra ?: PropertyContainer.empty())
+ )
+ }
+
+ public fun pageForPackage(p: DPackage): JavadocPackagePageNode {
+ return JavadocPackagePageNode(p.name, contentForPackage(p), setOf(p.dri), listOf(p),
+ p.classlikes.mapNotNull { pageForClasslike(it) }
+ )
+ }
+
+ public fun pageForClasslike(c: DClasslike): JavadocClasslikePageNode? {
+ return c.highestJvmSourceSet?.let { jvm ->
+ @Suppress("UNCHECKED_CAST")
+ val extra = ((c as? WithExtraProperties<Documentable>)?.extra ?: PropertyContainer.empty())
+ val children = c.classlikes.mapNotNull { pageForClasslike(it) }
+
+ JavadocClasslikePageNode(
+ name = c.dri.classNames.orEmpty(),
+ content = contentForClasslike(c),
+ dri = setOf(c.dri),
+ brief = c.brief(),
+ signature = signatureForNode(c, jvm),
+ description = c.descriptionToContentNodes(),
+ constructors = (c as? WithConstructors)?.constructors?.mapNotNull { it.toJavadocFunction() }.orEmpty(),
+ methods = c.functions.mapNotNull { it.toJavadocFunction() },
+ entries = (c as? DEnum)?.entries?.map {
+ JavadocEntryNode(
+ it.dri,
+ it.name,
+ signatureForNode(it, jvm),
+ it.descriptionToContentNodes(jvm),
+ PropertyContainer.withAll(it.indexesInDocumentation())
+ )
+ }.orEmpty(),
+ classlikes = children,
+ properties = c.properties.map {
+ JavadocPropertyNode(
+ it.dri,
+ it.name,
+ signatureForNode(it, jvm),
+ it.descriptionToContentNodes(jvm),
+ PropertyContainer.withAll(it.indexesInDocumentation()),
+ )
+ },
+ sinceTagContent = c.sinceToContentNodes(jvm),
+ authorTagContent = c.authorsToContentNodes(jvm),
+ documentables = listOf(c),
+ children = children,
+ extra = extra + c.indexesInDocumentation()
+ )
+ }
+ }
+
+ private fun contentForModule(m: DModule): JavadocContentNode =
+ JavadocContentGroup(
+ setOf(m.dri),
+ JavadocContentKind.OverviewSummary,
+ m.sourceSets.toDisplaySourceSets()
+ ) {
+ title(m.name, m.descriptionToContentNodes(), documentationVersion, dri = setOf(m.dri), kind = ContentKind.Main)
+ leafList(setOf(m.dri),
+ ContentKind.Packages, JavadocList(
+ "Packages", "Package",
+ m.packages.sortedBy { it.packageName }.map { p ->
+ RowJavadocListEntry(
+ LinkJavadocListEntry(p.name, setOf(p.dri), JavadocContentKind.PackageSummary, sourceSets),
+ p.brief()
+ )
+ }
+ ))
+ }
+
+ private fun contentForPackage(p: DPackage): JavadocContentNode =
+ JavadocContentGroup(
+ setOf(p.dri),
+ JavadocContentKind.PackageSummary,
+ p.sourceSets.toDisplaySourceSets()
+ ) {
+ title("Package ${p.name}", p.descriptionToContentNodes(), dri = setOf(p.dri), kind = ContentKind.Packages)
+ fun allClasslikes(c: DClasslike): List<DClasslike> = c.classlikes.flatMap { allClasslikes(it) } + c
+ val rootList = p.classlikes.map { allClasslikes(it) }.flatten().groupBy { it::class }.map { (key, value) ->
+ JavadocList(key.tabTitle, key.colTitle, value.map { c ->
+ RowJavadocListEntry(
+ LinkJavadocListEntry(c.name ?: "", setOf(c.dri), JavadocContentKind.Class, sourceSets),
+ c.brief()
+ )
+ })
+ }
+ rootList(setOf(p.dri), JavadocContentKind.Class, rootList)
+ }
+
+ private val KClass<out DClasslike>.colTitle: String
+ get() = when (this) {
+ DClass::class -> "Class"
+ DObject::class -> "Object"
+ DAnnotation::class -> "Annotation"
+ DEnum::class -> "Enum"
+ DInterface::class -> "Interface"
+ else -> ""
+ }
+
+ private val KClass<out DClasslike>.tabTitle: String
+ get() = "$colTitle Summary"
+
+ private fun contentForClasslike(c: DClasslike): JavadocContentNode =
+ JavadocContentGroup(
+ setOf(c.dri),
+ JavadocContentKind.Class,
+ c.sourceSets.toDisplaySourceSets()
+ ) {
+ title(
+ c.name.orEmpty(),
+ c.brief(),
+ documentationVersion,
+ parent = c.dri.packageName,
+ dri = setOf(c.dri),
+ kind = JavadocContentKind.Class
+ )
+ }
+
+ private fun DFunction.toJavadocFunction() = highestJvmSourceSet?.let { jvm ->
+ JavadocFunctionNode(
+ name = name,
+ dri = dri,
+ signature = signatureForNode(this, jvm),
+ brief = brief(jvm),
+ description = descriptionToContentNodes(jvm),
+ parameters = parameters.mapNotNull {
+ val signature = signatureForNode(it, jvm)
+ signature.modifiers?.let { type ->
+ JavadocParameterNode(
+ name = it.name.orEmpty(),
+ type = type,
+ description = it.brief(),
+ typeBound = it.type,
+ dri = it.dri,
+ extra = PropertyContainer.withAll(it.indexesInDocumentation())
+ )
+ }
+ },
+ returnTagContent = returnToContentNodes(jvm),
+ sinceTagContent = sinceToContentNodes(jvm),
+ extra = extra + indexesInDocumentation()
+ )
+ }
+
+ private val Documentable.highestJvmSourceSet
+ get() = sourceSets.let { sources ->
+ sources.firstOrNull { it != expectPresentInSet } ?: sources.firstOrNull()
+ }
+
+ private inline fun <reified T : TagWrapper> Documentable.findNodeInDocumentation(sourceSetData: DokkaSourceSet?): T? =
+ documentation[sourceSetData]?.firstChildOfTypeOrNull<T>()
+
+ private inline fun <reified T : TagWrapper> Documentable.findAllNodesInDocumentation(sourceSetData: DokkaSourceSet?): List<T> =
+ documentation[sourceSetData]?.childrenOfType<T>() ?: emptyList()
+
+ private fun Documentable.descriptionToContentNodes(sourceSet: DokkaSourceSet? = highestJvmSourceSet) =
+ contentNodesFromType<Description>(sourceSet)
+
+ private fun DParameter.paramsToContentNodes(sourceSet: DokkaSourceSet? = highestJvmSourceSet) =
+ contentNodesFromType<Param>(sourceSet)
+
+ private inline fun <reified T : TagWrapper> Documentable.contentNodesFromType(sourceSet: DokkaSourceSet?) =
+ findNodeInDocumentation<T>(sourceSet)?.let {
+ DocTagToContentConverter().buildContent(
+ it.root,
+ DCI(setOf(dri), JavadocContentKind.OverviewSummary),
+ sourceSets.toSet()
+ )
+ }.orEmpty()
+
+ private inline fun <reified T : TagWrapper> Documentable.allContentNodesFromType(sourceSet: DokkaSourceSet?) =
+ findAllNodesInDocumentation<T>(sourceSet).map {
+ DocTagToContentConverter().buildContent(
+ it.root,
+ DCI(setOf(dri), JavadocContentKind.OverviewSummary),
+ sourceSets.toSet()
+ )
+ }
+
+ public fun List<ContentNode>.nodeForJvm(jvm: DokkaSourceSet): ContentNode {
+ return firstOrNull { jvm.sourceSetID in it.sourceSets.computeSourceSetIds() }
+ ?: throw IllegalStateException("No source set found for ${jvm.sourceSetID} ")
+ }
+
+ private fun Documentable.brief(sourceSet: DokkaSourceSet? = highestJvmSourceSet): List<ContentNode> =
+ firstSentenceBriefFromContentNodes(descriptionToContentNodes(sourceSet))
+
+ private fun DParameter.brief(sourceSet: DokkaSourceSet? = highestJvmSourceSet): List<ContentNode> =
+ firstSentenceBriefFromContentNodes(paramsToContentNodes(sourceSet).dropWhile { it is ContentDRILink })
+
+ private fun ContentNode.asJavadocNode(): JavadocSignatureContentNode =
+ (this as ContentGroup).firstChildOfTypeOrNull<JavadocSignatureContentNode>()
+ ?: throw IllegalStateException("No content for javadoc signature found")
+
+ private fun signatureForNode(documentable: Documentable, sourceSet: DokkaSourceSet): JavadocSignatureContentNode =
+ signatureProvider.signature(documentable).nodeForJvm(sourceSet).asJavadocNode()
+
+ private fun Documentable.indexesInDocumentation(): JavadocIndexExtra {
+ val indexes =
+ documentation[highestJvmSourceSet]?.withDescendants()?.filterIsInstance<Index>()?.toList().orEmpty()
+ return JavadocIndexExtra(
+ indexes.map {
+ ContentGroup(
+ children = DocTagToContentConverter().buildContent(
+ it,
+ DCI(setOf(dri), JavadocContentKind.OverviewSummary),
+ sourceSets.toSet()
+ ),
+ dci = DCI(setOf(dri), JavadocContentKind.OverviewSummary),
+ sourceSets = sourceSets.toDisplaySourceSets(),
+ style = emptySet(),
+ extra = PropertyContainer.empty()
+ )
+ }
+ )
+ }
+
+ private fun Documentable.authorsToContentNodes(sourceSet: DokkaSourceSet? = highestJvmSourceSet) =
+ allContentNodesFromType<Author>(sourceSet)
+
+ private fun Documentable.sinceToContentNodes(sourceSet: DokkaSourceSet? = highestJvmSourceSet) =
+ allContentNodesFromType<Since>(sourceSet)
+
+ private fun Documentable.returnToContentNodes(sourceSet: DokkaSourceSet? = highestJvmSourceSet) =
+ contentNodesFromType<Return>(sourceSet)
+}
+
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt
new file mode 100644
index 00000000..6a5749ab
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc
+
+import org.jetbrains.dokka.CoreExtensions
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.base.renderers.PackageListCreator
+import org.jetbrains.dokka.base.renderers.RootCreator
+import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory
+import org.jetbrains.dokka.base.resolvers.shared.PackageList.Companion.PACKAGE_LIST_NAME
+import org.jetbrains.dokka.base.resolvers.shared.RecognizedLinkFormat
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.javadoc.location.JavadocLocationProviderFactory
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.javadoc.renderer.KorteJavadocRenderer
+import org.jetbrains.dokka.javadoc.signatures.JavadocSignatureProvider
+import org.jetbrains.dokka.javadoc.transformers.documentables.JavadocDocumentableJVMSourceSetFilter
+import org.jetbrains.dokka.javadoc.validity.MultiplatformConfiguredChecker
+import org.jetbrains.dokka.kotlinAsJava.KotlinAsJavaPlugin
+import org.jetbrains.dokka.plugability.*
+import org.jetbrains.dokka.renderers.PostAction
+import org.jetbrains.dokka.renderers.Renderer
+import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator
+import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer
+import org.jetbrains.dokka.transformers.pages.PageTransformer
+import org.jetbrains.dokka.validity.PreGenerationChecker
+
+public class JavadocPlugin : DokkaPlugin() {
+
+ private val dokkaBasePlugin: DokkaBase by lazy { plugin<DokkaBase>() }
+ private val kotinAsJavaPlugin: KotlinAsJavaPlugin by lazy { plugin<KotlinAsJavaPlugin>() }
+
+ public val locationProviderFactory: ExtensionPoint<LocationProviderFactory> by lazy { dokkaBasePlugin.locationProviderFactory }
+ public val javadocPreprocessors: ExtensionPoint<PageTransformer> by extensionPoint<PageTransformer>()
+
+ public val dokkaJavadocPlugin: Extension<Renderer, *, *> by extending {
+ CoreExtensions.renderer providing { ctx -> KorteJavadocRenderer(ctx, "views") } override dokkaBasePlugin.htmlRenderer
+ }
+
+ public val javadocMultiplatformCheck: Extension<PreGenerationChecker, *, *> by extending {
+ CoreExtensions.preGenerationCheck providing ::MultiplatformConfiguredChecker
+ }
+
+ public val pageTranslator: Extension<DocumentableToPageTranslator, *, *> by extending {
+ CoreExtensions.documentableToPageTranslator providing ::JavadocDocumentableToPageTranslator override
+ kotinAsJavaPlugin.kotlinAsJavaDocumentableToPageTranslator
+ }
+
+ public val documentableSourceSetFilter: Extension<PreMergeDocumentableTransformer, *, *> by extending {
+ dokkaBasePlugin.preMergeDocumentableTransformer providing ::JavadocDocumentableJVMSourceSetFilter
+ }
+
+ public val javadocLocationProviderFactory: Extension<LocationProviderFactory, *, *> by extending {
+ dokkaBasePlugin.locationProviderFactory providing ::JavadocLocationProviderFactory override dokkaBasePlugin.locationProvider
+ }
+
+ public val javadocSignatureProvider: Extension<SignatureProvider, *, *> by extending {
+ dokkaBasePlugin.signatureProvider providing ::JavadocSignatureProvider override kotinAsJavaPlugin.javaSignatureProvider
+ }
+
+ public val rootCreator: Extension<PageTransformer, *, *> by extending {
+ javadocPreprocessors with RootCreator
+ }
+
+ public val packageListCreator: Extension<PageTransformer, *, *> by extending {
+ javadocPreprocessors providing {
+ PackageListCreator(
+ context = it,
+ format = RecognizedLinkFormat.DokkaJavadoc,
+ outputFilesNames = listOf(PACKAGE_LIST_NAME, "element-list")
+ )
+ } order { after(rootCreator) }
+ }
+
+ public val resourcesInstaller: Extension<PageTransformer, *, *> by extending {
+ javadocPreprocessors with ResourcesInstaller order { after(rootCreator) }
+ }
+
+ public val treeViewInstaller: Extension<PageTransformer, *, *> by extending {
+ javadocPreprocessors providing ::TreeViewInstaller order { after(rootCreator) }
+ }
+
+ public val allClassessPageInstaller: Extension<PageTransformer, *, *> by extending {
+ javadocPreprocessors with AllClassesPageInstaller order { before(rootCreator) }
+ }
+
+ public val indexGenerator: Extension<PageTransformer, *, *> by extending {
+ javadocPreprocessors with IndexGenerator order { before(rootCreator) }
+ }
+
+ public val deprecatedPageCreator: Extension<PageTransformer, *, *> by extending {
+ javadocPreprocessors with DeprecatedPageCreator order { before(rootCreator) }
+ }
+
+ internal val alphaVersionNotifier by extending {
+ CoreExtensions.postActions providing { ctx ->
+ PostAction {
+ ctx.logger.info(
+ "The Javadoc output format is still in Alpha so you may find bugs and experience migration " +
+ "issues when using it. Successful integration with tools that accept Java's Javadoc " +
+ "HTML as input is not guaranteed. You use it at your own risk."
+ )
+ }
+ }
+ }
+
+ @OptIn(DokkaPluginApiPreview::class)
+ override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement =
+ PluginApiPreviewAcknowledgement
+}
+
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt
new file mode 100644
index 00000000..6de4c808
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.location
+
+import org.jetbrains.dokka.base.resolvers.local.DefaultLocationProvider
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.Nullable
+import org.jetbrains.dokka.links.PointingToDeclaration
+import org.jetbrains.dokka.model.DisplaySourceSet
+import org.jetbrains.dokka.pages.ContentPage
+import org.jetbrains.dokka.pages.PageNode
+import org.jetbrains.dokka.pages.RootPageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import java.util.*
+
+public class JavadocLocationProvider(
+ pageRoot: RootPageNode,
+ dokkaContext: DokkaContext
+) : DefaultLocationProvider(pageRoot, dokkaContext) {
+
+ private val pathIndex = IdentityHashMap<PageNode, List<String>>().apply {
+ fun registerPath(page: PageNode, prefix: List<String> = emptyList()) {
+ val packagePath = page.takeIf { it is JavadocPackagePageNode }?.name.orEmpty()
+ .replace(".", "/")
+ val newPathPrefix = prefix + packagePath
+
+ val path = (prefix + when (page) {
+ is AllClassesPage -> listOf("allclasses")
+ is TreeViewPage -> if (page.classes == null)
+ listOf("overview-tree")
+ else
+ listOf("package-tree")
+ is ContentPage -> if (page.dri.isNotEmpty() && page.dri.first().classNames != null)
+ listOfNotNull(page.dri.first().classNames)
+ else if (page is JavadocPackagePageNode)
+ listOf(packagePath, "package-summary")
+ else if (page is IndexPage)
+ listOf("index-files", page.name)
+ else if (page is DeprecatedPage)
+ listOf("deprecated")
+ else
+ listOf("index")
+ else -> emptyList()
+ }).filterNot { it.isEmpty() }
+
+ put(page, path)
+ page.children.forEach { registerPath(it, newPathPrefix) }
+
+ }
+ put(pageRoot, listOf("index"))
+ pageRoot.children.forEach { registerPath(it) }
+ }
+
+ private val parentPageIndex = HashMap<DRI, PageNode>()
+ private val nodeIndex = HashMap<DRI, PageNode>().apply {
+ fun registerNode(node: PageNode) {
+ if (node is ContentPage) put(node.dri.first(), node)
+ (node as? JavadocClasslikePageNode)?.getAnchorables()?.forEach { navigableNode ->
+ parentPageIndex[navigableNode.getDRI()] = node
+ }
+ node.children.forEach(::registerNode)
+ }
+ registerNode(pageRoot)
+ }
+
+ private operator fun IdentityHashMap<PageNode, List<String>>.get(dri: DRI) = this[nodeIndex[dri]]
+
+ private fun List<String>.relativeTo(context: List<String>): String {
+ val contextPath = context.dropLast(1).flatMap { it.split("/") }
+ val commonPathElements = flatMap { it.split("/") }.zip(contextPath).takeWhile { (a, b) -> a == b }.count()
+ return (List(contextPath.size - commonPathElements) { ".." } + this.flatMap { it.split("/") }.drop(commonPathElements)).joinToString("/")
+ }
+
+ private fun JavadocClasslikePageNode.findAnchorableByDRI(dri: DRI): AnchorableJavadocNode? =
+ (constructors + methods + entries + properties).firstOrNull { it.dri == dri }
+
+ override fun resolve(dri: DRI, sourceSets: Set<DisplaySourceSet>, context: PageNode?): String? =
+ getLocalLocation(dri, context)
+ ?: getLocalLocation(dri.copy(target = PointingToDeclaration), context)
+ // Not found in PageGraph, that means it's an external link
+ ?: getExternalLocation(dri, sourceSets)
+ ?: getExternalLocation(dri.copy(target = PointingToDeclaration), sourceSets)
+
+ private fun getLocalLocation(dri: DRI, context: PageNode?): String? =
+ nodeIndex[dri]?.let { resolve(it, context) }
+ ?: parentPageIndex[dri]?.let {
+ val anchor = when (val anchorElement = (it as? JavadocClasslikePageNode)?.findAnchorableByDRI(dri)) {
+ is JavadocFunctionNode -> anchorElement.getAnchor()
+ is JavadocEntryNode -> anchorElement.name
+ is JavadocPropertyNode -> anchorElement.name
+ else -> anchorForDri(dri)
+ }
+ "${resolve(it, context, skipExtension = true)}.html#$anchor"
+ }
+
+ private fun anchorForDri(dri: DRI): String =
+ dri.callable?.let { callable ->
+ "${callable.name}(${
+ callable.params.joinToString(",") {
+ ((it as? Nullable)?.wrapped ?: it).toString()
+ }
+ })"
+ } ?: dri.classNames.orEmpty()
+
+ override fun resolve(node: PageNode, context: PageNode?, skipExtension: Boolean): String {
+ return pathIndex[node]?.relativeTo(pathIndex[context].orEmpty())?.let {
+ if (skipExtension) it.removeSuffix(".html") else it
+ } ?: run {
+ throw IllegalStateException("Path for ${node::class.java.canonicalName}:${node.name} not found")
+ }
+ }
+
+ public fun resolve(link: LinkJavadocListEntry, contextRoot: PageNode? = null, skipExtension: Boolean = true): String {
+ return pathIndex[link.dri.first()]?.let {
+ when (link.kind) {
+ JavadocContentKind.Class -> it
+ JavadocContentKind.OverviewSummary -> it.dropLast(1) + "index"
+ JavadocContentKind.PackageSummary -> it.dropLast(1) + "package-summary"
+ JavadocContentKind.AllClasses -> it.dropLast(1) + "allclasses"
+ JavadocContentKind.OverviewTree -> it.dropLast(1) + "overview-tree"
+ JavadocContentKind.PackageTree -> it.dropLast(1) + "package-tree"
+ JavadocContentKind.IndexPage -> it.dropLast(1) + "index-1"
+ else -> it
+ }
+ }?.relativeTo(pathIndex[contextRoot].orEmpty())?.let { if (skipExtension) "$it.html" else it }.orEmpty()
+ }
+
+ override fun pathToRoot(from: PageNode): String {
+ TODO("Not yet implemented")
+ }
+
+ override fun ancestors(node: PageNode): List<PageNode> {
+ TODO("Not yet implemented")
+ }
+
+ override fun expectedLocationForDri(dri: DRI): String {
+ if (dri.packageName?.isNotEmpty() == true && dri.classNames == null)
+ return (dri.packageName?.split(".").orEmpty() + "package-summary").joinToString("/")
+
+ return (dri.packageName?.split(".").orEmpty() +
+ dri.classNames?.split(".").orEmpty() // Top-level methods will always be relocated which is fine
+ ).joinToString("/")
+ }
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt
new file mode 100644
index 00000000..10e7d416
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.location
+
+import org.jetbrains.dokka.base.resolvers.local.LocationProvider
+import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory
+import org.jetbrains.dokka.pages.RootPageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+
+public class JavadocLocationProviderFactory(
+ private val context: DokkaContext
+) : LocationProviderFactory {
+ override fun getLocationProvider(pageNode: RootPageNode): LocationProvider =
+ JavadocLocationProvider(pageNode, context)
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt
new file mode 100644
index 00000000..427ad98e
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.pages
+
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.DisplaySourceSet
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.pages.*
+
+public enum class JavadocContentKind : Kind {
+ AllClasses, OverviewSummary, PackageSummary, Class, OverviewTree, PackageTree, IndexPage
+}
+
+public abstract class JavadocContentNode(
+ dri: Set<DRI>,
+ kind: Kind,
+ override val sourceSets: Set<DisplaySourceSet>
+) : ContentNode {
+ override val dci: DCI = DCI(dri, kind)
+ override val style: Set<Style> = emptySet()
+ override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
+ override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentNode = this
+}
+
+public interface JavadocList {
+ public val tabTitle: String
+ public val colTitle: String
+ public val children: List<JavadocListEntry>
+}
+
+public interface JavadocListEntry {
+ public val stringTag: String
+}
+
+public data class EmptyNode(
+ val dri: DRI,
+ val kind: Kind,
+ override val sourceSets: Set<DisplaySourceSet>,
+ override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
+) : ContentNode {
+ override val dci: DCI = DCI(setOf(dri), kind)
+ override val style: Set<Style> = emptySet()
+
+ override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): EmptyNode = copy(extra = newExtras)
+
+ override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): EmptyNode =
+ copy(sourceSets = sourceSets)
+
+ override fun hasAnyContent(): Boolean = false
+}
+
+public data class JavadocContentGroup(
+ val dri: Set<DRI>,
+ val kind: Kind,
+ override val sourceSets: Set<DisplaySourceSet>,
+ override val children: List<JavadocContentNode>
+) : JavadocContentNode(dri, kind, sourceSets) {
+
+ public companion object {
+ public operator fun invoke(
+ dri: Set<DRI>,
+ kind: Kind,
+ sourceSets: Set<DisplaySourceSet>,
+ block: JavaContentGroupBuilder.() -> Unit
+ ): JavadocContentGroup =
+ JavadocContentGroup(dri, kind, sourceSets, JavaContentGroupBuilder(sourceSets).apply(block).list)
+ }
+
+ override fun hasAnyContent(): Boolean = children.isNotEmpty()
+
+ override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): JavadocContentGroup = this
+
+ override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): JavadocContentGroup =
+ copy(sourceSets = sourceSets)
+}
+
+public class JavaContentGroupBuilder(
+ public val sourceSets: Set<DisplaySourceSet>
+) {
+ public val list: MutableList<JavadocContentNode> = mutableListOf<JavadocContentNode>()
+}
+
+public data class TitleNode(
+ val title: String,
+ val subtitle: List<ContentNode>,
+ val version: String?,
+ val parent: String?,
+ val dri: Set<DRI>,
+ val kind: Kind,
+ override val sourceSets: Set<DisplaySourceSet>
+) : JavadocContentNode(dri, kind, sourceSets) {
+ override fun hasAnyContent(): Boolean = title.isNotBlank() || !version.isNullOrBlank() || subtitle.isNotEmpty()
+
+ override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): TitleNode = this
+
+ override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): TitleNode =
+ copy(sourceSets = sourceSets)
+}
+
+public fun JavaContentGroupBuilder.title(
+ title: String,
+ subtitle: List<ContentNode>,
+ version: String? = null,
+ parent: String? = null,
+ dri: Set<DRI>,
+ kind: Kind
+) {
+ list.add(TitleNode(title, subtitle, version, parent, dri, kind, sourceSets))
+}
+
+public data class RootListNode(
+ val entries: List<LeafListNode>,
+ val dri: Set<DRI>,
+ val kind: Kind,
+ override val sourceSets: Set<DisplaySourceSet>,
+) : JavadocContentNode(dri, kind, sourceSets) {
+ override fun hasAnyContent(): Boolean = children.isNotEmpty()
+
+
+ override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): RootListNode =
+ copy(sourceSets = sourceSets)
+}
+
+public data class LeafListNode(
+ val tabTitle: String,
+ val colTitle: String,
+ val entries: List<JavadocListEntry>,
+ val dri: Set<DRI>,
+ val kind: Kind,
+ override val sourceSets: Set<DisplaySourceSet>
+) : JavadocContentNode(dri, kind, sourceSets) {
+ override fun hasAnyContent(): Boolean = children.isNotEmpty()
+
+ override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): LeafListNode = copy(sourceSets = sourceSets)
+}
+
+
+public fun JavaContentGroupBuilder.rootList(
+ dri: Set<DRI>,
+ kind: Kind,
+ rootList: List<JavadocList>
+) {
+ val children = rootList.map {
+ LeafListNode(it.tabTitle, it.colTitle, it.children, dri, kind, sourceSets)
+ }
+ list.add(RootListNode(children, dri, kind, sourceSets))
+}
+
+public fun JavaContentGroupBuilder.leafList(
+ dri: Set<DRI>,
+ kind: Kind,
+ leafList: JavadocList
+) {
+ list.add(LeafListNode(leafList.tabTitle, leafList.colTitle, leafList.children, dri, kind, sourceSets))
+}
+
+public fun JavadocList(tabTitle: String, colTitle: String, children: List<JavadocListEntry>): JavadocList {
+ return object : JavadocList {
+ override val tabTitle = tabTitle
+ override val colTitle = colTitle
+ override val children = children
+ }
+}
+
+public class LinkJavadocListEntry(
+ public val name: String,
+ public val dri: Set<DRI>,
+ public val kind: Kind = ContentKind.Symbol,
+ public val sourceSets: Set<DisplaySourceSet>
+) : JavadocListEntry {
+ override val stringTag: String
+ get() {
+ return if (builtString == null) {
+ throw IllegalStateException("stringTag for LinkJavadocListEntry accessed before build() call")
+ } else {
+ builtString!!
+ }
+ }
+
+ private var builtString: String? = null
+
+ public fun build(body: (String, Set<DRI>, Kind, List<DisplaySourceSet>) -> String) {
+ builtString = body(name, dri, kind, sourceSets.toList())
+ }
+}
+
+public data class RowJavadocListEntry(val link: LinkJavadocListEntry, val doc: List<ContentNode>) : JavadocListEntry {
+ override val stringTag: String = ""
+}
+
+public data class JavadocSignatureContentNode(
+ val dri: DRI,
+ val kind: Kind = ContentKind.Symbol,
+ val annotations: ContentNode?,
+ val modifiers: ContentNode?,
+ val signatureWithoutModifiers: ContentNode,
+ val supertypes: ContentNode?
+) : JavadocContentNode(setOf(dri), kind, signatureWithoutModifiers.sourceSets) {
+ override fun hasAnyContent(): Boolean = true
+
+ override fun withSourceSets(sourceSets: Set<DisplaySourceSet>): JavadocSignatureContentNode {
+ return copy(signatureWithoutModifiers = signatureWithoutModifiers.withSourceSets(sourceSets))
+ }
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt
new file mode 100644
index 00000000..4ed0864a
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.pages
+
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.properties.ExtraProperty
+import org.jetbrains.dokka.pages.ContentNode
+
+public data class JavadocIndexExtra(val index: List<ContentNode>) : ExtraProperty<Documentable> {
+ override val key: ExtraProperty.Key<Documentable, *> = JavadocIndexExtra
+ public companion object : ExtraProperty.Key<Documentable, JavadocIndexExtra>
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt
new file mode 100644
index 00000000..3b5b63cc
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt
@@ -0,0 +1,529 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.pages
+
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.base.renderers.sourceSets
+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.analysis.kotlin.internal.InheritanceBuilder
+import org.jetbrains.dokka.analysis.kotlin.internal.InheritanceNode
+
+public interface JavadocPageNode : ContentPage, WithDocumentables
+
+public interface WithJavadocExtra<T : Documentable> : WithExtraProperties<T> {
+ override fun withNewExtras(newExtras: PropertyContainer<T>): T =
+ throw IllegalStateException("Merging extras is not applicable for javadoc")
+}
+
+public fun interface WithNavigable {
+ public fun getAllNavigables(): List<NavigableJavadocNode>
+}
+
+public interface WithBrief {
+ public val brief: List<ContentNode>
+}
+
+public class JavadocModulePageNode(
+ override val name: String,
+ override val content: JavadocContentNode,
+ override val children: List<PageNode>,
+ override val dri: Set<DRI>,
+ override val extra: PropertyContainer<DModule> = PropertyContainer.empty()
+) :
+ RootPageNode(),
+ WithJavadocExtra<DModule>,
+ NavigableJavadocNode,
+ JavadocPageNode,
+ ModulePage {
+
+ override val documentables: List<Documentable> = emptyList()
+ override val embeddedResources: List<String> = emptyList()
+ override fun modified(name: String, children: List<PageNode>): RootPageNode =
+ JavadocModulePageNode(name, content, children, dri, extra)
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage = JavadocModulePageNode(name, content as JavadocContentNode, children, dri, extra)
+
+ override fun getId(): String = name
+
+ override fun getDRI(): DRI = dri.first()
+}
+
+public class JavadocPackagePageNode(
+ override val name: String,
+ override val content: JavadocContentNode,
+ override val dri: Set<DRI>,
+
+ override val documentables: List<Documentable> = emptyList(),
+ override val children: List<PageNode> = emptyList(),
+ override val embeddedResources: List<String> = listOf()
+) : JavadocPageNode,
+ WithNavigable,
+ NavigableJavadocNode,
+ PackagePage {
+
+ init {
+ require(name.isNotBlank()) { "Empty name is not supported " }
+ }
+
+ override fun getAllNavigables(): List<NavigableJavadocNode> =
+ children.filterIsInstance<NavigableJavadocNode>().flatMap {
+ if (it is WithNavigable) it.getAllNavigables()
+ else listOf(it)
+ }
+
+ override fun modified(
+ name: String,
+ children: List<PageNode>
+ ): PageNode = JavadocPackagePageNode(
+ name,
+ content,
+ dri,
+ documentables,
+ children,
+ embeddedResources
+ )
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage =
+ JavadocPackagePageNode(
+ name,
+ content as JavadocContentNode,
+ dri,
+ documentables,
+ children,
+ embeddedResources
+ )
+
+ override fun getId(): String = name
+
+ override fun getDRI(): DRI = dri.first()
+}
+
+public interface NavigableJavadocNode {
+ public fun getId(): String
+ public fun getDRI(): DRI
+}
+
+public sealed class AnchorableJavadocNode(
+ public open val name: String,
+ public open val dri: DRI
+) : NavigableJavadocNode {
+ override fun getId(): String = name
+ override fun getDRI(): DRI = dri
+}
+
+public data class JavadocEntryNode(
+ override val dri: DRI,
+ override val name: String,
+ val signature: JavadocSignatureContentNode,
+ override val brief: List<ContentNode>,
+ override val extra: PropertyContainer<DEnumEntry> = PropertyContainer.empty()
+) : AnchorableJavadocNode(name, dri), WithJavadocExtra<DEnumEntry>, WithBrief
+
+public data class JavadocParameterNode(
+ override val dri: DRI,
+ override val name: String,
+ val type: ContentNode,
+ val description: List<ContentNode>,
+ val typeBound: Bound,
+ override val extra: PropertyContainer<DParameter> = PropertyContainer.empty()
+) : AnchorableJavadocNode(name, dri), WithJavadocExtra<DParameter>
+
+public data class JavadocPropertyNode(
+ override val dri: DRI,
+ override val name: String,
+ val signature: JavadocSignatureContentNode,
+ override val brief: List<ContentNode>,
+
+ override val extra: PropertyContainer<DProperty> = PropertyContainer.empty()
+) : AnchorableJavadocNode(name, dri), WithJavadocExtra<DProperty>, WithBrief
+
+public data class JavadocFunctionNode(
+ val signature: JavadocSignatureContentNode,
+ override val brief: List<ContentNode>,
+ val description: List<ContentNode>,
+ val parameters: List<JavadocParameterNode>,
+
+ val returnTagContent: List<ContentNode>,
+ val sinceTagContent: List<List<ContentNode>>,
+
+ override val name: String,
+ override val dri: DRI,
+ override val extra: PropertyContainer<DFunction> = PropertyContainer.empty()
+) : AnchorableJavadocNode(name, dri), WithJavadocExtra<DFunction>, WithBrief {
+ val isInherited: Boolean
+ get() {
+ val extra = extra[InheritedMember]
+ return extra?.inheritedFrom?.keys?.firstOrNull { it.analysisPlatform == Platform.jvm }?.let { jvm ->
+ extra.isInherited(jvm)
+ } ?: false
+ }
+}
+
+public class JavadocClasslikePageNode(
+ override val name: String,
+ override val content: JavadocContentNode,
+ override val dri: Set<DRI>,
+ public val signature: JavadocSignatureContentNode,
+ public val description: List<ContentNode>,
+ public val constructors: List<JavadocFunctionNode>,
+ public val methods: List<JavadocFunctionNode>,
+ public val entries: List<JavadocEntryNode>,
+ public val classlikes: List<JavadocClasslikePageNode>,
+ public val properties: List<JavadocPropertyNode>,
+ override val brief: List<ContentNode>,
+
+ public val sinceTagContent: List<List<ContentNode>>,
+ public val authorTagContent: List<List<ContentNode>>,
+
+ override val documentables: List<Documentable> = emptyList(),
+ override val children: List<PageNode> = emptyList(),
+ override val embeddedResources: List<String> = listOf(),
+ override val extra: PropertyContainer<DClasslike> = PropertyContainer.empty(),
+) : JavadocPageNode, WithJavadocExtra<DClasslike>, NavigableJavadocNode, WithNavigable, WithBrief, ClasslikePage {
+
+ override fun getAllNavigables(): List<NavigableJavadocNode> =
+ methods + entries + classlikes.map { it.getAllNavigables() }.flatten() + this
+
+ public fun getAnchorables(): List<AnchorableJavadocNode> =
+ constructors + methods + entries + properties
+
+ public val kind: String? = documentables.firstOrNull()?.kind()
+ public val packageName: String? = dri.first().packageName
+
+ override fun getId(): String = name
+ override fun getDRI(): DRI = dri.first()
+
+ override fun modified(
+ name: String,
+ children: List<PageNode>
+ ): PageNode = JavadocClasslikePageNode(
+ name,
+ content,
+ dri,
+ signature,
+ description,
+ constructors,
+ methods,
+ entries,
+ classlikes,
+ properties,
+ brief,
+ sinceTagContent,
+ authorTagContent,
+ documentables,
+ children,
+ embeddedResources,
+ extra
+ )
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage =
+ JavadocClasslikePageNode(
+ name,
+ content as JavadocContentNode,
+ dri,
+ signature,
+ description,
+ constructors,
+ methods,
+ entries,
+ classlikes,
+ properties,
+ brief,
+ sinceTagContent,
+ authorTagContent,
+ documentables,
+ children,
+ embeddedResources,
+ extra
+ )
+}
+
+public class AllClassesPage(
+ public val classes: List<JavadocClasslikePageNode>
+) : JavadocPageNode {
+ public val classEntries: List<LinkJavadocListEntry> =
+ classes.map { LinkJavadocListEntry(it.name, it.dri, ContentKind.Classlikes, it.sourceSets().toSet()) }
+
+ override val name: String = "All Classes"
+ override val dri: Set<DRI> = setOf(DRI.topLevel)
+
+ override val documentables: List<Documentable> = emptyList()
+ override val embeddedResources: List<String> = emptyList()
+
+ override val content: ContentNode =
+ EmptyNode(
+ DRI.topLevel,
+ ContentKind.Classlikes,
+ classes.flatMap { it.sourceSets() }.toSet()
+ )
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage = this
+
+ override fun modified(name: String, children: List<PageNode>): PageNode =
+ this
+
+ override val children: List<PageNode> = emptyList()
+
+}
+
+public class DeprecatedPage(
+ public val elements: Map<DeprecatedPageSection, Set<DeprecatedNode>>,
+ sourceSet: Set<DisplaySourceSet>
+) : JavadocPageNode {
+ override val name: String = "deprecated"
+ override val dri: Set<DRI> = setOf(DRI.topLevel)
+ override val documentables: List<Documentable> = emptyList()
+ override val children: List<PageNode> = emptyList()
+ override val embeddedResources: List<String> = listOf()
+
+ override val content: ContentNode = EmptyNode(
+ DRI.topLevel,
+ ContentKind.Main,
+ sourceSet
+ )
+
+ override fun modified(
+ name: String,
+ children: List<PageNode>
+ ): PageNode = this
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage = this
+
+}
+
+public class DeprecatedNode(
+ public val name: String,
+ public val address: DRI,
+ public val description: List<ContentNode>
+) {
+ override fun equals(other: Any?): Boolean =
+ (other as? DeprecatedNode)?.address == address
+
+ override fun hashCode(): Int = address.hashCode()
+}
+
+public enum class DeprecatedPageSection(
+ public val id: String,
+ public val caption: String,
+ public val header: String,
+) {
+ DeprecatedModules("module", "Modules", "Module"),
+ DeprecatedInterfaces("interface", "Interfaces", "Interface"),
+ DeprecatedClasses("class", "Classes", "Class"),
+ DeprecatedExceptions("exception", "Exceptions", "Exceptions"),
+ DeprecatedFields("field", "Fields", "Field"),
+ DeprecatedMethods("method", "Methods", "Method"),
+ DeprecatedConstructors("constructor", "Constructors", "Constructor"),
+ DeprecatedEnums("enum", "Enums", "Enum"),
+ DeprecatedEnumConstants("enum.constant", "Enum Constants", "Enum Constant"),
+ DeprecatedForRemoval("forRemoval", "For Removal", "Element");
+
+ internal fun getPosition() = ordinal
+}
+
+public class IndexPage(
+ public val id: Int,
+ public val elements: List<NavigableJavadocNode>,
+ public val keys: List<Char>,
+
+ sourceSet: Set<DisplaySourceSet>
+
+) : JavadocPageNode {
+ override val name: String = "index-$id"
+ override val dri: Set<DRI> = setOf(DRI.topLevel)
+ override val documentables: List<Documentable> = emptyList()
+ override val children: List<PageNode> = emptyList()
+ override val embeddedResources: List<String> = listOf()
+ public val title: String = "${keys[id - 1]}-index"
+
+ override val content: ContentNode = EmptyNode(
+ DRI.topLevel,
+ ContentKind.Classlikes,
+ sourceSet
+ )
+
+ override fun modified(
+ name: String,
+ children: List<PageNode>
+ ): PageNode = this
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage = this
+
+}
+
+public class TreeViewPage(
+ override val name: String,
+ public val packages: List<JavadocPackagePageNode>?,
+ public val classes: List<JavadocClasslikePageNode>?,
+ override val dri: Set<DRI>,
+ override val documentables: List<Documentable> = emptyList(),
+ public val root: PageNode,
+ public val inheritanceBuilder: InheritanceBuilder
+) : JavadocPageNode {
+ init {
+ assert(packages == null || classes == null)
+ assert(packages != null || classes != null)
+ }
+
+ private val childrenDocumentables = root.children.filterIsInstance<WithDocumentables>().flatMap { node ->
+ getDocumentableEntries(node)
+ }.groupBy({ it.first }) { it.second }.map { (l, r) -> l to r.first() }.toMap()
+
+ private val inheritanceTuple = generateInheritanceTree()
+ internal val classGraph = inheritanceTuple.first
+ internal val interfaceGraph = inheritanceTuple.second
+
+ override val children: List<PageNode> = emptyList()
+
+ public val title: String = when (documentables.firstOrNull()) {
+ is DPackage -> "$name Class Hierarchy"
+ else -> "All packages"
+ }
+
+ public val kind: String = when (documentables.firstOrNull()) {
+ is DPackage -> "package"
+ else -> "main"
+ }
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage =
+ TreeViewPage(
+ name,
+ packages = children.filterIsInstance<JavadocPackagePageNode>().takeIf { it.isNotEmpty() },
+ classes = children.filterIsInstance<JavadocClasslikePageNode>().takeIf { it.isNotEmpty() },
+ dri = dri,
+ documentables,
+ root = root,
+ inheritanceBuilder
+ )
+
+ override fun modified(name: String, children: List<PageNode>): PageNode =
+ TreeViewPage(
+ name,
+ packages = children.filterIsInstance<JavadocPackagePageNode>().takeIf { it.isNotEmpty() },
+ classes = children.filterIsInstance<JavadocClasslikePageNode>().takeIf { it.isNotEmpty() },
+ dri = dri,
+ documentables,
+ root = root,
+ inheritanceBuilder
+ )
+
+ override val embeddedResources: List<String> = emptyList()
+
+ override val content: ContentNode = EmptyNode(
+ DRI.topLevel,
+ ContentKind.Classlikes,
+ emptySet()
+ )
+
+ private fun generateInheritanceTree(): Pair<List<InheritanceNode>, List<InheritanceNode>> {
+ val mergeMap = mutableMapOf<DRI, InheritanceNode>()
+
+ fun addToMap(info: InheritanceNode, map: MutableMap<DRI, InheritanceNode>) {
+ if (map.containsKey(info.dri))
+ map.computeIfPresent(info.dri) { _, info2 ->
+ info.copy(children = (info.children + info2.children).distinct())
+ }!!.children.forEach { addToMap(it, map) }
+ else
+ map[info.dri] = info
+ }
+
+ fun collect(dri: DRI): InheritanceNode =
+ InheritanceNode(
+ dri,
+ mergeMap[dri]?.children.orEmpty().map { collect(it.dri) },
+ mergeMap[dri]?.interfaces.orEmpty(),
+ mergeMap[dri]?.isInterface ?: false
+ )
+
+ fun classTreeRec(node: InheritanceNode): List<InheritanceNode> = if (node.isInterface) {
+ node.children.flatMap(::classTreeRec)
+ } else {
+ listOf(node.copy(children = node.children.flatMap(::classTreeRec)))
+ }
+
+ fun classTree(node: InheritanceNode) = classTreeRec(node).singleOrNull()
+
+ fun interfaceTreeRec(node: InheritanceNode): List<InheritanceNode> = if (node.isInterface) {
+ listOf(node.copy(children = node.children.filter { it.isInterface }))
+ } else {
+ node.children.flatMap(::interfaceTreeRec)
+ }
+
+ fun interfaceTree(node: InheritanceNode) = interfaceTreeRec(node).firstOrNull() // TODO.single()
+
+ val inheritanceNodes = inheritanceBuilder.build(childrenDocumentables)
+ inheritanceNodes.forEach { addToMap(it, mergeMap) }
+
+ val rootNodes = mergeMap.entries.filter {
+ it.key.classNames in setOf("Any", "Object") //TODO: Probably should be matched by DRI, not just className
+ }.map {
+ collect(it.value.dri)
+ }
+
+ return rootNodes.let { Pair(it.mapNotNull(::classTree), it.mapNotNull(::interfaceTree)) }
+ }
+
+ private fun getDocumentableEntries(node: WithDocumentables): List<Pair<DRI, Documentable>> =
+ node.documentables.map { it.dri to it } +
+ (node as? ContentPage)?.children?.filterIsInstance<WithDocumentables>()
+ ?.flatMap(::getDocumentableEntries).orEmpty()
+
+}
+
+private fun Documentable.kind(): String? =
+ when (this) {
+ is DClass -> "class"
+ is DEnum -> "enum"
+ is DAnnotation -> "annotation"
+ is DObject -> "object"
+ is DInterface -> "interface"
+ else -> null
+ }
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt
new file mode 100644
index 00000000..f54c41fd
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.pages
+
+import org.jetbrains.dokka.base.renderers.sourceSets
+import org.jetbrains.dokka.base.transformers.documentables.deprecatedAnnotation
+import org.jetbrains.dokka.base.transformers.documentables.isDeprecated
+import org.jetbrains.dokka.base.transformers.documentables.isException
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.BooleanValue
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.transformers.pages.PageTransformer
+import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
+
+public object ResourcesInstaller : PageTransformer {
+
+ override fun invoke(input: RootPageNode): RootPageNode = input.modified(
+ children = input.children +
+ RendererSpecificResourcePage(
+ "resourcePack",
+ emptyList(),
+ RenderingStrategy.Copy("/static_res")
+ )
+ )
+}
+
+public class TreeViewInstaller(
+ private val context: DokkaContext
+) : PageTransformer {
+
+ override fun invoke(input: RootPageNode): RootPageNode = install(input, input) as RootPageNode
+
+ private fun install(node: PageNode, root: RootPageNode): PageNode = when (node) {
+ is JavadocModulePageNode -> installOverviewTreeNode(node, root)
+ is JavadocPackagePageNode -> installPackageTreeNode(node, root)
+ else -> node
+ }
+
+ private fun installOverviewTreeNode(node: JavadocModulePageNode, root: RootPageNode): JavadocModulePageNode {
+ val overviewTree = TreeViewPage(
+ name = "Class Hierarchy",
+ packages = node.children<JavadocPackagePageNode>().map { installPackageTreeNode(it, root) },
+ classes = null,
+ dri = node.dri,
+ documentables = node.documentables,
+ root = root,
+ inheritanceBuilder = context.plugin<InternalKotlinAnalysisPlugin>().querySingle { inheritanceBuilder }
+ )
+
+ val nodeChildren = node.children.map { childNode ->
+ install(
+ childNode,
+ root
+ )
+ }
+ return node.modified(children = nodeChildren + overviewTree) as JavadocModulePageNode
+ }
+
+ private fun installPackageTreeNode(node: JavadocPackagePageNode, root: RootPageNode): JavadocPackagePageNode {
+ val packageTree = TreeViewPage(
+ name = node.name,
+ packages = null,
+ classes = node.children.filterIsInstance<JavadocClasslikePageNode>(),
+ dri = node.dri,
+ documentables = node.documentables,
+ root = root,
+ inheritanceBuilder = context.plugin<InternalKotlinAnalysisPlugin>().querySingle { inheritanceBuilder }
+ )
+
+ return node.modified(children = node.children + packageTree) as JavadocPackagePageNode
+ }
+}
+
+public object AllClassesPageInstaller : PageTransformer {
+
+ override fun invoke(input: RootPageNode): RootPageNode {
+ val classes = (input as JavadocModulePageNode).children.filterIsInstance<JavadocPackagePageNode>().flatMap {
+ it.children.filterIsInstance<JavadocClasslikePageNode>()
+ }
+
+ return input.modified(children = input.children + AllClassesPage(classes))
+ }
+}
+
+public object IndexGenerator : PageTransformer {
+
+ override fun invoke(input: RootPageNode): RootPageNode {
+ val elements = HashMap<Char, MutableSet<NavigableJavadocNode>>()
+ (input as JavadocModulePageNode).children.filterIsInstance<JavadocPackagePageNode>().forEach {
+ it.getAllNavigables().forEach { d ->
+ val name = when (d) {
+ is JavadocPageNode -> d.name
+ is AnchorableJavadocNode -> d.name
+ else -> null
+ }
+ if (name != null && name.isNotBlank()) {
+ elements.getOrPut(name[0].toUpperCase(), ::mutableSetOf).add(d)
+ }
+ }
+ elements.getOrPut(it.name[0].toUpperCase(), ::mutableSetOf).add(it)
+ }
+ val keys = elements.keys.sortedBy { it }
+ val sortedElements = elements.entries.sortedBy { (a, _) -> a }
+
+ val indexNodeComparator = getIndexNodeComparator()
+ val indexPages = sortedElements.mapIndexed { idx, (_, set) ->
+ IndexPage(
+ id = idx + 1,
+ elements = set.sortedWith(indexNodeComparator),
+ keys = keys,
+ sourceSet = input.sourceSets()
+ )
+ }
+ return input.modified(children = input.children + indexPages)
+ }
+
+ private fun getIndexNodeComparator(): Comparator<NavigableJavadocNode> {
+ val driComparator = compareBy<DRI> { it.packageName }
+ .thenBy { it.classNames }
+ .thenBy { it.callable?.name.orEmpty() }
+ .thenBy { it.callable?.signature().orEmpty() }
+
+ return compareBy<NavigableJavadocNode> { it.getId().toLowerCase() }
+ .thenBy(driComparator) { it.getDRI() }
+ }
+}
+
+public object DeprecatedPageCreator : PageTransformer {
+
+ override fun invoke(input: RootPageNode): RootPageNode {
+ val elements = HashMap<DeprecatedPageSection, MutableSet<DeprecatedNode>>().apply {
+
+ fun <T, V> T.putAs(deprecatedPageSection: DeprecatedPageSection) where
+ T : NavigableJavadocNode,
+ V : Documentable,
+ T : WithJavadocExtra<V> {
+ val deprecatedNode = DeprecatedNode(
+ listOfNotNull(
+ getDRI().packageName?.takeUnless { it.isBlank() },
+ if (this is JavadocFunctionNode) getAnchor() else getId()
+ ).joinToString("."),
+ getDRI(),
+ (this as? WithBrief)?.brief.orEmpty()
+ )
+ getOrPut(deprecatedPageSection) { mutableSetOf() }.add(deprecatedNode)
+ if (deprecatedAnnotation?.params?.get("forRemoval") == BooleanValue(true)) {
+ getOrPut(DeprecatedPageSection.DeprecatedForRemoval) { mutableSetOf() }.add(deprecatedNode)
+ }
+ }
+
+ fun verifyDeprecation(node: NavigableJavadocNode) {
+ when (node) {
+ is JavadocModulePageNode -> {
+ node.children.filterIsInstance<JavadocPackagePageNode>().forEach(::verifyDeprecation)
+ node.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedModules)
+ }
+ is JavadocPackagePageNode ->
+ node.children.filterIsInstance<NavigableJavadocNode>().forEach(::verifyDeprecation)
+ is JavadocClasslikePageNode -> {
+ node.classlikes.forEach(::verifyDeprecation)
+ node.methods.forEach {
+ it.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedMethods)
+ }
+ node.constructors.forEach {
+ it.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedConstructors)
+ }
+ node.properties.forEach {
+ it.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedFields)
+ }
+ node.entries.forEach {
+ it.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedEnumConstants)
+ }
+ node.takeIf { it.isDeprecated() }?.putAs(
+ if ((node as? WithJavadocExtra<*>)?.isException == true) DeprecatedPageSection.DeprecatedExceptions
+ else when (node.kind) {
+ "enum" -> DeprecatedPageSection.DeprecatedEnums
+ "interface" -> DeprecatedPageSection.DeprecatedInterfaces
+ else -> DeprecatedPageSection.DeprecatedClasses
+ }
+ )
+ }
+ }
+ }
+
+ verifyDeprecation(input as JavadocModulePageNode)
+ }
+ return input.modified(
+ children = input.children + DeprecatedPage(
+ elements,
+ (input as JavadocModulePageNode).sourceSets()
+ )
+ )
+ }
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/utils.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/utils.kt
new file mode 100644
index 00000000..70f71fab
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/utils.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.pages
+
+import org.jetbrains.dokka.model.*
+
+/**
+ * Returns an unencoded, unescaped function anchor.
+ *
+ * Should be URL encoded / HTML escaped at call site,
+ * depending on usage.
+ */
+// see the discussion in #2813 related to encoding/escaping this value for ids/hrefs
+internal fun JavadocFunctionNode.getAnchor(): String {
+ val parameters = parameters.joinToString(",") { it.typeBound.asString() }
+ return "$name($parameters)"
+}
+
+private fun Bound.asString(): String = when (this) {
+ is Nullable -> this.inner.asString()
+ is DefinitelyNonNullable -> this.inner.asString()
+ is TypeConstructor -> listOf(this.dri.packageName, this.dri.classNames).joinToString(".")
+ is TypeParameter -> this.name
+ is PrimitiveJavaType -> this.name
+ is UnresolvedBound -> this.name
+ is TypeAliased -> this.typeAlias.asString()
+ is JavaObject -> "Object"
+
+ // Void bound is currently used for return type only,
+ // which is not used in the anchor generation, but
+ // the handling for it is added regardless, just in case.
+ // Note: if you accept `Void` as a param, it'll be a TypeConstructor
+ Void -> "void"
+
+ // Javadoc format currently does not support multiplatform projects,
+ // so in an ideal world we should not see Dynamic here, but someone
+ // might disable the checker or the support for it might be added
+ // by Dokka or another plugin, so the handling is added just in case.
+ Dynamic -> "dynamic"
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt
new file mode 100644
index 00000000..7b54488a
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.renderer
+
+import org.jetbrains.dokka.base.resolvers.local.LocationProvider
+import org.jetbrains.dokka.javadoc.pages.JavadocSignatureContentNode
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.utilities.formatToEndWithHtml
+import org.jetbrains.dokka.utilities.htmlEscape
+
+internal class JavadocContentToHtmlTranslator(
+ private val locationProvider: LocationProvider,
+ private val context: DokkaContext
+) {
+
+ fun htmlForContentNode(node: ContentNode, relative: PageNode?): String =
+ when (node) {
+ is ContentGroup ->
+ if (node.style.contains(TextStyle.Paragraph)) htmlForParagraph(node.children, relative)
+ else htmlForContentNodes(node.children, relative)
+ is ContentText -> htmlForText(node)
+ is ContentDRILink -> buildLinkFromNode(node, relative)
+ is ContentResolvedLink -> buildLinkFromNode(node, relative)
+ is ContentCode -> htmlForCode(node, relative)
+ is ContentList -> htmlForList(node.children, relative)
+ is JavadocSignatureContentNode -> htmlForSignature(node, relative)
+ is ContentBreakLine -> "<br>"
+ else -> ""
+ }
+
+ private fun htmlForText(node: ContentText): String {
+ val escapedText = node.text.htmlEscape()
+ return when {
+ node.style.contains(ContentStyle.InDocumentationAnchor) -> """<em><a id="$escapedText" class="searchTagResult">${escapedText}</a></em>"""
+ node.style.contains(TextStyle.Bold) -> "<b>$escapedText</b>"
+ node.style.contains(TextStyle.Italic) -> "<i>$escapedText</i>"
+ node.style.contains(TextStyle.Strikethrough) -> "<del>$escapedText</del>"
+ else -> node.text.htmlEscape()
+ }
+ }
+
+ fun htmlForContentNodes(list: List<ContentNode>, relative: PageNode?) =
+ list.joinToString(separator = "") { htmlForContentNode(it, relative) }
+
+ private fun htmlForParagraph(nodes: List<ContentNode>, relative: PageNode?) =
+ "<p>${htmlForContentNodes(nodes, relative)}</p>"
+
+ private fun htmlForCode(code: ContentCode, relative: PageNode?): String {
+ fun nodeToText(node: ContentNode, insidePre: Boolean = false): String = when (node) {
+ is ContentText -> node.text.htmlEscape()
+ is ContentBreakLine -> if(insidePre) "\n" else "<br>"
+ is ContentDRILink -> buildLinkFromNode(node, relative)
+ is ContentResolvedLink -> buildLinkFromNode(node, relative)
+ is ContentCodeBlock -> "<pre><code>${node.children.joinToString("") { nodeToText(it, insidePre = true) }}</code></pre>"
+ is ContentCodeInline -> "<code>${node.children.joinToString("") { nodeToText(it) }}</code>"
+ else -> run { context.logger.error("Cannot cast $node as ContentText!"); "" }
+ }
+ return nodeToText(code)
+ }
+
+ private fun htmlForList(elements: List<ContentNode>, relative: PageNode?) =
+ elements.filterIsInstance<ContentGroup>()
+ .joinToString("", "<ul>", "</ul>") { "<li>${htmlForContentNode(it, relative)}</li>" }
+
+ private fun htmlForSignature(node: JavadocSignatureContentNode, relative: PageNode?): String =
+ listOfNotNull(
+ node.annotations,
+ node.modifiers,
+ node.signatureWithoutModifiers,
+ node.supertypes
+ ).joinToString(separator = " ") { htmlForContentNode(it, relative) }
+
+ private fun buildLinkFromNode(node: ContentDRILink, relative: PageNode?) =
+ locationProvider.resolve(node.address, node.sourceSets, relative)?.let {
+ buildLink(it, htmlForContentNodes(node.children, relative))
+ } ?: htmlForContentNodes(node.children, relative)
+
+ private fun buildLinkFromNode(node: ContentResolvedLink, relative: PageNode?) =
+ buildLink(node.address, htmlForContentNodes(node.children, relative))
+
+ companion object {
+
+ fun buildLink(address: String, content: String) =
+ """<a href=${address.formatToEndWithHtml()}>$content</a>"""
+
+ }
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt
new file mode 100644
index 00000000..445a7708
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.renderer
+
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.base.renderers.sourceSets
+import org.jetbrains.dokka.javadoc.location.JavadocLocationProvider
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.javadoc.toNormalized
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.parent
+import org.jetbrains.dokka.links.sureClassNames
+import org.jetbrains.dokka.model.ImplementedInterfaces
+import org.jetbrains.dokka.model.InheritedMember
+import org.jetbrains.dokka.pages.ContentNode
+import org.jetbrains.dokka.pages.PageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.utilities.formatToEndWithHtml
+import java.io.File
+import java.nio.file.Paths
+
+internal class JavadocContentToTemplateMapTranslator(
+ private val locationProvider: JavadocLocationProvider,
+ private val context: DokkaContext,
+) {
+
+ fun templateMapForPageNode(node: JavadocPageNode): TemplateMap =
+ mapOf(
+ "docName" to "docName", // todo docname
+ "pathToRoot" to pathToRoot(node),
+ "contextRoot" to node,
+ "kind" to "main",
+ ) + templateMapForNode(node)
+
+
+ private fun templateMapForNode(node: JavadocPageNode): TemplateMap =
+ when (node) {
+ is JavadocModulePageNode -> InnerTranslator(node).templateMapForJavadocContentNode(node.content)
+ is JavadocClasslikePageNode -> InnerTranslator(node).templateMapForClasslikeNode(node)
+ is JavadocPackagePageNode -> InnerTranslator(node).templateMapForPackagePageNode(node)
+ is TreeViewPage -> InnerTranslator(node).templateMapForTreeViewPage(node)
+ is AllClassesPage -> InnerTranslator(node).templateMapForAllClassesPage(node)
+ is IndexPage -> InnerTranslator(node).templateMapForIndexPage(node)
+ is DeprecatedPage -> InnerTranslator(node).templateMapForDeprecatedPage(node)
+ else -> emptyMap()
+ }
+
+ private fun pathToRoot(node: JavadocPageNode): String {
+ return when (node) {
+ is JavadocModulePageNode -> ""
+ else -> run {
+ val link = locationProvider.resolve(node, skipExtension = true)
+ val dir = Paths.get(link).parent?.toNormalized().orEmpty()
+ return dir.split(File.separator).filter { it.isNotEmpty() }.joinToString("/") { ".." }.let {
+ if (it.isNotEmpty()) "$it/" else it
+ }
+ }
+ }
+ }
+
+ private inner class InnerTranslator(val contextNode: JavadocPageNode) {
+
+ private val htmlTranslator = JavadocContentToHtmlTranslator(locationProvider, context)
+
+ fun templateMapForAllClassesPage(node: AllClassesPage): TemplateMap =
+ mapOf(
+ "title" to "All Classes",
+ "list" to node.classEntries
+ )
+
+ fun templateMapForIndexPage(node: IndexPage): TemplateMap =
+ mapOf(
+ "id" to node.id,
+ "title" to node.title,
+ "kind" to "indexPage",
+ "prevLetter" to if (node.id > 1) "index-${node.id - 1}" else "",
+ "nextLetter" to if (node.id < node.keys.size) "index-${node.id + 1}" else "",
+ "dictionary" to node.keys,
+ "elements" to node.elements.map { templateMapForIndexableNode(it) }
+ )
+
+ fun templateMapForDeprecatedPage(node: DeprecatedPage): TemplateMap =
+ mapOf(
+ "id" to node.name,
+ "title" to "Deprecated",
+ "kind" to "deprecated",
+ "sections" to node.elements.toList().sortedBy { (section, _) -> section.getPosition() }
+ .map { (s, e) -> templateMapForDeprecatedPageSection(s, e) }
+ )
+
+ fun templateMapForDeprecatedPageSection(
+ section: DeprecatedPageSection,
+ elements: Set<DeprecatedNode>
+ ): TemplateMap =
+ mapOf(
+ "id" to section.id,
+ "header" to section.header,
+ "caption" to section.caption,
+ "elements" to elements.map { node ->
+ mapOf(
+ "name" to node.name,
+ "address" to locationProvider.resolve(node.address, contextNode.sourceSets(), contextNode)
+ ?.formatToEndWithHtml().orEmpty(),
+ "description" to htmlForContentNodes(node.description, contextNode)
+ )
+ }
+ )
+
+ fun templateMapForTreeViewPage(node: TreeViewPage): TemplateMap =
+ mapOf(
+ "title" to node.title,
+ "name" to node.name,
+ "kind" to node.kind,
+ "list" to node.packages.orEmpty() + node.classes.orEmpty(),
+ "classGraph" to node.classGraph,
+ "interfaceGraph" to node.interfaceGraph
+ )
+
+ fun templateMapForPackagePageNode(node: JavadocPackagePageNode): TemplateMap = mapOf(
+ "kind" to "package"
+ ) + templateMapForJavadocContentNode(node.content)
+
+ fun templateMapForFunctionNode(node: JavadocFunctionNode): TemplateMap = mapOf(
+ "brief" to htmlForContentNodes(node.brief, contextNode),
+ "description" to htmlForContentNodes(node.description,contextNode),
+ "parameters" to node.parameters.map { templateMapForParameterNode(it) },
+ "inlineParameters" to node.parameters.joinToString { renderInlineParameter(it) },
+ "returnTagContent" to htmlForContentNodes(node.returnTagContent, contextNode),
+ "sinceTagContent" to node.sinceTagContent.map { htmlForContentNodes(it, contextNode) },
+ "anchorLink" to node.getAnchor(),
+ "signature" to templateMapForSignatureNode(node.signature),
+ "name" to node.name
+ )
+
+ fun templateMapForClasslikeNode(node: JavadocClasslikePageNode): TemplateMap =
+ mapOf(
+ "constructors" to node.constructors.map { templateMapForFunctionNode(it) },
+ "signature" to templateMapForSignatureNode(node.signature),
+ "methods" to templateMapForClasslikeMethods(node.methods),
+ "classlikeDocumentation" to htmlForContentNodes(node.description, node),
+ "entries" to node.entries.map { templateMapForEntryNode(it) },
+ "properties" to node.properties.map { templateMapForPropertyNode(it) },
+ "classlikes" to node.classlikes.map { templateMapForNestedClasslikeNode(it) },
+ "implementedInterfaces" to templateMapForImplementedInterfaces(node).sorted(),
+ "sinceTagContent" to node.sinceTagContent.map { htmlForContentNodes(it, contextNode) },
+ "authorTagContent" to node.authorTagContent.map { htmlForContentNodes(it, contextNode) },
+ "kind" to node.kind,
+ "packageName" to node.packageName,
+ "name" to node.name
+ ) + templateMapForJavadocContentNode(node.content)
+
+ fun templateMapForSignatureNode(node: JavadocSignatureContentNode): TemplateMap =
+ mapOf(
+ "annotations" to node.annotations?.let { htmlForContentNode(it, contextNode) },
+ "signatureWithoutModifiers" to htmlForContentNode(node.signatureWithoutModifiers, contextNode),
+ "modifiers" to node.modifiers?.let { htmlForContentNode(it, contextNode) },
+ "supertypes" to node.supertypes?.let { htmlForContentNode(it, contextNode) }
+ )
+
+ private fun NavigableJavadocNode.typeForIndexable() = when (this) {
+ is JavadocClasslikePageNode -> "class"
+ is JavadocFunctionNode -> "function"
+ is JavadocEntryNode -> "enum entry"
+ is JavadocParameterNode -> "parameter"
+ is JavadocPropertyNode -> "property"
+ is JavadocPackagePageNode -> "package"
+ else -> ""
+ }
+
+ fun templateMapForIndexableNode(node: NavigableJavadocNode): TemplateMap {
+ val origin = node.getDRI().parent
+ return mapOf(
+ "address" to locationProvider.resolve(node.getDRI(), contextNode.sourceSets(), contextNode)
+ ?.formatToEndWithHtml().orEmpty(),
+ "type" to node.typeForIndexable(),
+ "isMember" to (node !is JavadocPackagePageNode),
+ "name" to if (node is JavadocFunctionNode) node.getAnchor() else node.getId(),
+ "description" to ((node as? WithBrief)?.let {
+ htmlForContentNodes(
+ it.brief,
+ contextNode
+ ).takeIf { desc -> desc.isNotBlank() }
+ } ?: "&nbsp;"),
+ "origin" to origin.indexableOriginSignature(),
+ )
+ }
+
+ private fun DRI.indexableOriginSignature(): String {
+ val packageName = packageName?.takeIf { it.isNotBlank() }
+ val className = classNames?.let {
+ "<a href=${locationProvider.resolve(this, contextNode.sourceSets(), contextNode)
+ ?.formatToEndWithHtml().orEmpty()}>$it</a>"
+ }
+ return listOfNotNull(packageName, className).joinToString(".")
+ }
+
+ fun templateMapForJavadocContentNode(node: JavadocContentNode): TemplateMap =
+ when (node) {
+ is TitleNode -> templateMapForTitleNode(node)
+ is JavadocContentGroup -> templateMapForJavadocContentGroup(node)
+ is LeafListNode -> templateMapForLeafListNode(node)
+ is RootListNode -> templateMapForRootListNode(node)
+ else -> emptyMap()
+ }
+
+ fun templateMapForJavadocContentNode(node: ContentNode): TemplateMap = (node as? JavadocContentNode)?.let { templateMapForJavadocContentNode(it) } ?: emptyMap()
+
+ private fun templateMapForParameterNode(node: JavadocParameterNode): TemplateMap =
+ mapOf(
+ "description" to htmlForContentNodes(node.description, contextNode),
+ "name" to node.name,
+ "type" to htmlForContentNode(node.type, contextNode)
+ )
+
+ private fun templateMapForImplementedInterfaces(node: JavadocClasslikePageNode) =
+ node.extra[ImplementedInterfaces]?.interfaces?.entries?.firstOrNull { it.key.analysisPlatform == Platform.jvm }?.value?.map { it.dri.displayable() } // TODO: REMOVE HARDCODED JVM DEPENDENCY
+ .orEmpty()
+
+ private fun templateMapForClasslikeMethods(nodes: List<JavadocFunctionNode>): TemplateMap {
+ val (inherited, own) = nodes.partition { it.isInherited }
+ return mapOf(
+ "own" to own.map { templateMapForFunctionNode(it) },
+ "inherited" to inherited.map { templateMapForInheritedMethod(it) }
+ .groupBy { it["inheritedFrom"] as String }.entries.map {
+ mapOf(
+ "inheritedFrom" to it.key,
+ "names" to it.value.map { it["name"] as String }.sorted().joinToString()
+ )
+ }
+ )
+ }
+
+ private fun templateMapForInheritedMethod(node: JavadocFunctionNode): TemplateMap {
+ val inheritedFrom = node.extra[InheritedMember]?.inheritedFrom
+ return mapOf(
+ "inheritedFrom" to inheritedFrom?.entries?.firstOrNull { it.key.analysisPlatform == Platform.jvm }?.value?.displayable() // TODO: REMOVE HARDCODED JVM DEPENDENCY
+ .orEmpty(),
+ "name" to node.name
+ )
+ }
+
+ private fun templateMapForNestedClasslikeNode(node: JavadocClasslikePageNode): TemplateMap {
+ return mapOf(
+ "modifiers" to node.signature.modifiers?.let { htmlForContentNode(it, contextNode) },
+ "signature" to node.name,
+ "address" to locationProvider.resolve(
+ contextNode.children.first { (it as? JavadocClasslikePageNode)?.dri?.first() == node.dri.first() },
+ contextNode
+ ).formatToEndWithHtml(),
+ "description" to htmlForContentNodes(node.description, node)
+ )
+ }
+
+ private fun templateMapForPropertyNode(node: JavadocPropertyNode): TemplateMap {
+ return mapOf(
+ "modifiers" to node.signature.modifiers?.let { htmlForContentNode(it, contextNode) },
+ "signature" to htmlForContentNode(node.signature.signatureWithoutModifiers, contextNode),
+ "description" to htmlForContentNodes(node.brief, contextNode)
+ )
+ }
+
+ private fun templateMapForEntryNode(node: JavadocEntryNode): TemplateMap {
+ return mapOf(
+ "signature" to templateMapForSignatureNode(node.signature),
+ "brief" to htmlForContentNodes(node.brief, contextNode)
+ )
+ }
+
+ private fun templateMapForTitleNode(node: TitleNode): TemplateMap {
+ return mapOf(
+ "title" to node.title,
+ "subtitle" to htmlForContentNodes(node.subtitle, contextNode),
+ "version" to node.version,
+ "packageName" to node.parent
+ )
+ }
+
+ private fun templateMapForJavadocContentGroup(note: JavadocContentGroup): TemplateMap {
+ return note.children.fold(emptyMap()) { map, child ->
+ map + templateMapForJavadocContentNode(child)
+ }
+ }
+
+ private fun templateMapForLeafListNode(node: LeafListNode): TemplateMap {
+ return mapOf(
+ "tabTitle" to node.tabTitle,
+ "colTitle" to node.colTitle,
+ "list" to node.entries
+ )
+ }
+
+ private fun templateMapForRootListNode(node: RootListNode): TemplateMap {
+ return mapOf(
+ "lists" to node.entries.map { templateMapForLeafListNode(it) }
+ )
+ }
+
+ private fun renderInlineParameter(parameter: JavadocParameterNode): String =
+ htmlForContentNode(parameter.type, contextNode) + " ${parameter.name}"
+
+ private fun htmlForContentNode(node: ContentNode, relativeNode: PageNode) =
+ htmlTranslator.htmlForContentNode(node, relativeNode)
+
+ private fun htmlForContentNodes(nodes: List<ContentNode>, relativeNode: PageNode) =
+ htmlTranslator.htmlForContentNodes(nodes, relativeNode)
+ }
+
+ private fun DRI.displayable(): String = "${packageName}.${sureClassNames}"
+}
+
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt
new file mode 100644
index 00000000..cfc4bb2a
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.renderer
+
+import korlibs.template.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.base.renderers.OutputWriter
+import org.jetbrains.dokka.javadoc.JavadocPlugin
+import org.jetbrains.dokka.javadoc.location.JavadocLocationProvider
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.javadoc.renderer.JavadocContentToHtmlTranslator.Companion.buildLink
+import org.jetbrains.dokka.javadoc.toNormalized
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.pages.PageNode
+import org.jetbrains.dokka.pages.RendererSpecificPage
+import org.jetbrains.dokka.pages.RenderingStrategy
+import org.jetbrains.dokka.pages.RootPageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.query
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.renderers.Renderer
+import org.jetbrains.dokka.analysis.kotlin.internal.InheritanceNode
+import java.time.LocalDate
+
+public typealias TemplateMap = Map<String, Any?>
+
+public class KorteJavadocRenderer(
+ public val context: DokkaContext, resourceDir: String
+) : Renderer {
+ private val outputWriter: OutputWriter = context.plugin<DokkaBase>().querySingle { outputWriter }
+ private lateinit var locationProvider: JavadocLocationProvider
+ private val registeredPreprocessors = context.plugin<JavadocPlugin>().query { javadocPreprocessors }
+
+ private val contentToHtmlTranslator by lazy {
+ JavadocContentToHtmlTranslator(locationProvider, context)
+ }
+
+ private val contentToTemplateMapTranslator by lazy {
+ JavadocContentToTemplateMapTranslator(locationProvider, context)
+ }
+
+ override fun render(root: RootPageNode) {
+ root.let { registeredPreprocessors.fold(root) { r, t -> t.invoke(r) } }.let { newRoot ->
+ locationProvider = context.plugin<JavadocPlugin>().querySingle { locationProviderFactory }.getLocationProvider(newRoot) as JavadocLocationProvider
+ runBlocking(Dispatchers.IO) {
+ renderPage(newRoot)
+ SearchScriptsCreator(locationProvider).invoke(newRoot).forEach { renderSpecificPage(it, "") }
+ }
+ }
+ }
+
+ private fun templateForNode(node: JavadocPageNode) = when (node) {
+ is JavadocModulePageNode,
+ is JavadocPackagePageNode -> "tabPage.korte"
+ is JavadocClasslikePageNode -> "class.korte"
+ is AllClassesPage -> "listPage.korte"
+ is TreeViewPage -> "treePage.korte"
+ is IndexPage -> "indexPage.korte"
+ is DeprecatedPage -> "deprecated.korte"
+ else -> ""
+ }
+
+ private fun CoroutineScope.renderPage(node: PageNode, path: String = "") {
+ when(node){
+ is JavadocModulePageNode -> renderModulePageNode(node)
+ is JavadocPageNode -> renderJavadocPageNode(node)
+ is RendererSpecificPage -> renderSpecificPage(node, path)
+ }
+ }
+
+ private fun CoroutineScope.renderModulePageNode(node: JavadocModulePageNode) {
+ val link = "."
+ val name = "index"
+
+ val contentMap = contentToTemplateMapTranslator.templateMapForPageNode(node)
+
+ writeFromTemplate(outputWriter, "$link/$name".toNormalized(), "tabPage.korte", contentMap.toList())
+ node.children.forEach { renderPage(it, link) }
+ }
+
+ private fun CoroutineScope.renderJavadocPageNode(node: JavadocPageNode) {
+ val link = locationProvider.resolve(node, skipExtension = true)
+ val contentMap = contentToTemplateMapTranslator.templateMapForPageNode(node)
+ writeFromTemplate(outputWriter, link, templateForNode(node), contentMap.toList())
+ node.children.forEach { renderPage(it, link.toNormalized()) }
+ }
+
+ private fun CoroutineScope.renderSpecificPage(node: RendererSpecificPage, path: String) = launch {
+ when (val strategy = node.strategy) {
+ is RenderingStrategy.Copy -> outputWriter.writeResources(strategy.from, "")
+ is RenderingStrategy.Write -> outputWriter.writeHtml(node.name, strategy.text)
+ is RenderingStrategy.Callback -> outputWriter.writeResources(
+ path,
+ strategy.instructions(this@KorteJavadocRenderer, node)
+ )
+ RenderingStrategy.DoNothing -> Unit
+ }
+ node.children.forEach { renderPage(it, locationProvider.resolve(node, skipExtension = true).toNormalized()) }
+ }
+
+ private fun Pair<String, String>.pairToTag() =
+ """<th class="colFirst" scope="row">${first}</th><td class="colLast">${second}</td>"""
+
+ private fun DRI.toLink(context: PageNode? = null) = locationProvider.resolve(this, emptySet(), context)
+
+ private suspend fun OutputWriter.writeHtml(path: String, text: String) = write(path, text, "")
+ private fun CoroutineScope.writeFromTemplate(
+ writer: OutputWriter,
+ path: String,
+ template: String,
+ args: List<Pair<String, *>>
+ ) = launch {
+ val tmp = templateRenderer.render(template, *(args.toTypedArray()))
+ writer.writeHtml("$path.html", tmp)
+ }
+
+ private fun getTemplateConfig() = TemplateConfig().also { config ->
+ listOf(
+ TeFunction("curDate") { LocalDate.now() },
+ TeFunction("jQueryVersion") { "3.6.0" },
+ TeFunction("jQueryMigrateVersion") { "3.4.0" },
+ TeFunction("rowColor") { args -> if ((args.first() as Int) % 2 == 0) "altColor" else "rowColor" },
+ TeFunction("h1Title") { args -> if ((args.first() as? String) == "package") "title=\"Package\" " else "" },
+ TeFunction("createTabRow") { args ->
+ val (link, doc) = args.first() as RowJavadocListEntry
+ val contextRoot = args[1] as PageNode?
+ (buildLink(
+ locationProvider.resolve(link, contextRoot),
+ link.name
+ ) to contentToHtmlTranslator.htmlForContentNodes(doc, contextRoot)).pairToTag().trim()
+ },
+ TeFunction("createListRow") { args ->
+ val link = args.first() as LinkJavadocListEntry
+ val contextRoot = args[1] as PageNode?
+ buildLink(
+ locationProvider.resolve(link, contextRoot),
+ link.name
+ )
+ },
+ TeFunction("createPackageHierarchy") { args ->
+ @Suppress("UNCHECKED_CAST")
+ val list = args.first() as List<JavadocPackagePageNode>
+
+ list.mapIndexed { i, p ->
+ val content = if (i + 1 == list.size) "" else ", "
+ val name = p.name
+ "<li><a href=\"$name/package-tree.html\">$name</a>$content</li>"
+ }.joinToString("\n")
+ },
+ TeFunction("renderInheritanceGraph") { args ->
+ @Suppress("UNCHECKED_CAST")
+ val rootNodes = args.first() as List<InheritanceNode>
+
+ fun drawRec(node: InheritanceNode): String =
+ "<li class=\"circle\">" + node.dri.let { dri ->
+ listOfNotNull(
+ dri.packageName,
+ dri.classNames
+ ).joinToString(".") + node.interfaces.takeUnless { node.isInterface || it.isEmpty() }
+ ?.let {
+ " implements " + it.joinToString(", ") { n ->
+ listOfNotNull(
+ n.packageName,
+ n.toLink()?.let{ buildLink(it, n.classNames.orEmpty()) } ?: n
+ ).joinToString(".")
+ }
+ }.orEmpty()
+ } + node.children.filterNot { it.isInterface }.takeUnless { it.isEmpty() }?.let {
+ "<ul>" + it.joinToString("\n", transform = ::drawRec) + "</ul>"
+ }.orEmpty() + "</li>"
+
+ rootNodes.joinToString { drawRec(it) }
+ },
+ Filter("length") { subject.dynamicLength() },
+ TeFunction("hasAnyDescription") { args ->
+ @Suppress("UNCHECKED_CAST")
+ val map = args.first() as? List<HashMap<String, String>>
+ map?.any { it["description"]?.trim()?.isNotEmpty() ?: false }
+ }
+ ).forEach {
+ when (it) {
+ is TeFunction -> config.register(it)
+ is Filter -> config.register(it)
+ is Tag -> config.register(it)
+ }
+ }
+ }
+
+ private val config = getTemplateConfig()
+ private val templateRenderer = Templates(
+ ResourceTemplateProvider(
+ resourceDir
+ ), config = config, cache = true
+ )
+
+ private class ResourceTemplateProvider(val basePath: String) : TemplateProvider {
+ override suspend fun get(template: String): String =
+ javaClass.classLoader.getResourceAsStream("$basePath/$template")?.use { stream ->
+ stream.bufferedReader()
+ .lines()
+ .toArray()
+ .joinToString("\n")
+ } ?: throw IllegalStateException("Template not found: $basePath/$template")
+ }
+
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt
new file mode 100644
index 00000000..af91a01d
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.renderer
+
+import org.jetbrains.dokka.base.renderers.sourceSets
+import org.jetbrains.dokka.base.resolvers.local.LocationProvider
+import org.jetbrains.dokka.base.resolvers.local.resolveOrThrow
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.javadoc.renderer.SearchRecord.Companion.allTypes
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.DisplaySourceSet
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.utilities.formatToEndWithHtml
+
+public class SearchScriptsCreator(
+ private val locationProvider: LocationProvider
+) {
+
+ public fun invoke(input: RootPageNode): List<RendererSpecificPage> {
+ val data = when (input) {
+ is JavadocModulePageNode -> processModules(listOf(input))
+ else -> processModules(input.children.filterIsInstance<JavadocModulePageNode>())
+ }
+ val serializer = SearchRecordJsonSerializer()
+
+ val modules = RendererSpecificResourcePage(
+ name = "module-search-index.js",
+ children = emptyList(),
+ strategy = RenderingStrategy.Write(serializer.serialize(data.moduleRecords, "moduleSearchIndex"))
+ )
+
+ val packages = RendererSpecificResourcePage(
+ name = "package-search-index.js",
+ children = emptyList(),
+ strategy = RenderingStrategy.Write(serializer.serialize(data.packageRecords, "packageSearchIndex"))
+ )
+
+ val types = RendererSpecificResourcePage(
+ name = "type-search-index.js",
+ children = emptyList(),
+ strategy = RenderingStrategy.Write(serializer.serialize(data.typeRecords, "typeSearchIndex"))
+ )
+
+ val members = RendererSpecificResourcePage(
+ name = "member-search-index.js",
+ children = emptyList(),
+ strategy = RenderingStrategy.Write(serializer.serialize(data.memberRecords, "memberSearchIndex"))
+ )
+
+ val indexes = RendererSpecificResourcePage(
+ name = "tag-search-index.js",
+ children = emptyList(),
+ strategy = RenderingStrategy.Write(serializer.serialize(data.searchIndexes, "tagSearchIndex"))
+ )
+
+ return listOf(modules, packages, types, members, indexes)
+ }
+
+ private fun processModules(input: List<JavadocModulePageNode>): SearchData {
+ val modules = SearchData(moduleRecords = input.map {
+ SearchRecord(
+ l = it.name,
+ url = locationProvider.resolveOrThrow(it).formatToEndWithHtml()
+ )
+ })
+ val processablePackages = input.flatMap { it.children.filterIsInstance<JavadocPackagePageNode>() }
+ return processPackages(processablePackages, modules)
+ }
+
+ private fun processPackages(input: List<JavadocPackagePageNode>, accumulator: SearchData): SearchData {
+ val packages = input.map {
+ SearchRecord(
+ l = it.name,
+ url = locationProvider.resolveOrThrow(it).formatToEndWithHtml()
+ )
+ } + SearchRecord.allPackages
+ fun allClasses(c: JavadocClasslikePageNode): List<JavadocClasslikePageNode> =
+ c.children.filterIsInstance<JavadocClasslikePageNode>().flatMap { allClasses(it) } + c
+ val types = input.flatMap {
+ it.children.filterIsInstance<JavadocClasslikePageNode>().flatMap { allClasses(it) }.map { classlike -> it to classlike }
+ }
+ val updated = accumulator.copy(packageRecords = packages)
+ return processTypes(types, updated)
+ }
+
+ private fun processTypes(
+ input: List<Pair<JavadocPackagePageNode, JavadocClasslikePageNode>>,
+ accumulator: SearchData
+ ): SearchData {
+ val types = input.map {
+ SearchRecord(
+ p = it.first.name,
+ l = it.second.name,
+ url = locationProvider.resolveOrThrow(it.second).formatToEndWithHtml()
+ )
+ } + allTypes
+ val updated = accumulator.copy(typeRecords = types)
+ return processMembers(input, updated).copy(searchIndexes = indexSearchForClasslike(input))
+ }
+
+ private fun processMembers(
+ input: List<Pair<JavadocPackagePageNode, JavadocClasslikePageNode>>,
+ accumulator: SearchData
+ ): SearchData {
+ val functions = input.flatMap {
+ (it.second.constructors + it.second.methods).withoutInherited().map { function ->
+ SearchRecordCreator.function(
+ packageName = it.first.name,
+ classlikeName = it.second.name,
+ input = function,
+ url = locationProvider.resolveOrThrow(function.dri, it.first.sourceSets())
+ )
+ }
+ }
+
+ val properties = input.flatMap {
+ it.second.properties.map { property ->
+ SearchRecordCreator.property(
+ packageName = it.first.name,
+ classlikeName = it.second.name,
+ property,
+ locationProvider.resolveOrThrow(property.dri, it.first.sourceSets())
+ )
+ }
+ }
+
+ val entries = input.flatMap {
+ it.second.entries.map { entry ->
+ SearchRecordCreator.entry(
+ packageName = it.first.name,
+ classlikeName = it.second.name,
+ entry,
+ locationProvider.resolveOrThrow(entry.dri, it.first.sourceSets())
+ )
+ }
+ }
+
+ return accumulator.copy(memberRecords = functions + properties + entries)
+ }
+
+ private fun indexSearchForClasslike(
+ input: List<Pair<JavadocPackagePageNode, JavadocClasslikePageNode>>,
+ ): List<SearchRecord> {
+ val indexesForClasslike = input.flatMap {
+ val indexes = it.second.indexes()
+ indexes.map { index ->
+ val label = renderNode(index)
+ SearchRecord(
+ p = it.first.name,
+ c = it.second.name,
+ l = label,
+ url = resolveUrlForSearchIndex(it.second.dri.first(), it.second.sourceSets(), label)
+ )
+ }
+ }
+
+ val indexesForMemberNodes = input.flatMap { packageWithClasslike ->
+ (packageWithClasslike.second.constructors +
+ packageWithClasslike.second.methods.withoutInherited() +
+ packageWithClasslike.second.properties +
+ packageWithClasslike.second.entries
+ ).map { it to it.indexes() }
+ .flatMap { entryWithIndex ->
+ entryWithIndex.second.map {
+ val label = renderNode(it)
+ SearchRecord(
+ p = packageWithClasslike.first.name,
+ c = packageWithClasslike.second.name,
+ l = label,
+ url = resolveUrlForSearchIndex(
+ entryWithIndex.first.dri,
+ packageWithClasslike.second.sourceSets(),
+ label
+ )
+ )
+ }
+ }
+ }
+
+ return indexesForClasslike + indexesForMemberNodes
+ }
+
+ private fun <T : Documentable> WithJavadocExtra<T>.indexes(): List<ContentNode> =
+ extra[JavadocIndexExtra]?.index.orEmpty()
+
+ private fun List<JavadocFunctionNode>.withoutInherited(): List<JavadocFunctionNode> = filter { !it.isInherited }
+
+ private fun resolveUrlForSearchIndex(
+ dri: DRI,
+ sourceSets: Set<DisplaySourceSet>,
+ label: String
+ ): String =
+ locationProvider.resolveOrThrow(dri, sourceSets).formatToEndWithHtml() + "#" + label
+}
+
+private data class SearchRecord(
+ val p: String? = null,
+ val c: String? = null,
+ val l: String,
+ val url: String? = null
+) {
+ companion object {
+ val allPackages = SearchRecord(l = "All packages", url = "index.html")
+ val allTypes = SearchRecord(l = "All classes", url = "allclasses.html")
+ }
+}
+
+private object SearchRecordCreator {
+ fun function(
+ packageName: String,
+ classlikeName: String,
+ input: JavadocFunctionNode,
+ url: String
+ ): SearchRecord =
+ SearchRecord(
+ p = packageName,
+ c = classlikeName,
+ l = input.name + input.parameters.joinToString(
+ prefix = "(",
+ postfix = ")"
+ ) { renderNode(it.type) },
+ url = url.formatToEndWithHtml()
+ )
+
+ fun property(
+ packageName: String,
+ classlikeName: String,
+ input: JavadocPropertyNode,
+ url: String
+ ): SearchRecord =
+ SearchRecord(
+ p = packageName,
+ c = classlikeName,
+ l = input.name,
+ url = url.formatToEndWithHtml()
+ )
+
+ fun entry(packageName: String, classlikeName: String, input: JavadocEntryNode, url: String): SearchRecord =
+ SearchRecord(
+ p = packageName,
+ c = classlikeName,
+ l = input.name,
+ url = url.formatToEndWithHtml()
+ )
+}
+
+private data class SearchData(
+ val moduleRecords: List<SearchRecord> = emptyList(),
+ val packageRecords: List<SearchRecord> = emptyList(),
+ val typeRecords: List<SearchRecord> = emptyList(),
+ val memberRecords: List<SearchRecord> = emptyList(),
+ val searchIndexes: List<SearchRecord> = emptyList()
+)
+
+private class SearchRecordJsonSerializer {
+ fun serialize(record: SearchRecord): String {
+ val serialized = StringBuilder()
+ serialized.append("{")
+ with(record) {
+ if (p != null) serialized.append("\"p\":\"$p\",")
+ if (c != null) serialized.append("\"c\":\"$c\",")
+ serialized.append("\"l\":\"$l\"")
+ if (url != null) serialized.append(",\"url\":\"$url\"")
+ }
+ serialized.append("}")
+ return serialized.toString()
+ }
+
+ fun serialize(records: List<SearchRecord>, variable: String): String =
+ "var " + variable + " = " + records.joinToString(prefix = "[", postfix = "]") { serialize(it) }
+}
+
+private fun renderNode(node: ContentNode): String =
+ when (node) {
+ is ContentText -> node.text
+ else -> node.children.joinToString(separator = "") { renderNode(it) }
+ }
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt
new file mode 100644
index 00000000..e3510686
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.signatures
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.base.signatures.JvmSignatureUtils
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder
+import org.jetbrains.dokka.javadoc.translators.documentables.JavadocPageContentBuilder
+import org.jetbrains.dokka.kotlinAsJava.signatures.JavaSignatureUtils
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.sureClassNames
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.pages.ContentKind
+import org.jetbrains.dokka.pages.ContentNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.utilities.DokkaLogger
+
+public class JavadocSignatureProvider(
+ ctcc: CommentsToContentConverter,
+ logger: DokkaLogger
+) : SignatureProvider, JvmSignatureUtils by JavaSignatureUtils {
+
+ public constructor(context: DokkaContext) : this(
+ context.plugin<DokkaBase>().querySingle { commentsToContentConverter },
+ context.logger
+ )
+
+ private val contentBuilder = JavadocPageContentBuilder(ctcc, this, logger)
+
+ private val ignoredVisibilities = setOf(JavaVisibility.Default)
+
+ private val ignoredModifiers =
+ setOf(KotlinModifier.Open, JavaModifier.Empty, KotlinModifier.Empty, KotlinModifier.Sealed)
+
+ override fun signature(documentable: Documentable): List<ContentNode> = when (documentable) {
+ is DFunction -> signature(documentable)
+ is DProperty -> signature(documentable)
+ is DClasslike -> signature(documentable)
+ is DEnumEntry -> signature(documentable)
+ is DTypeParameter -> signature(documentable)
+ is DParameter -> signature(documentable)
+ else -> throw NotImplementedError(
+ "Cannot generate signature for ${documentable::class.qualifiedName} ${documentable.name}"
+ )
+ }
+
+ private fun signature(c: DClasslike): List<ContentNode> =
+ javadocSignature(c) {
+ annotations {
+ annotationsBlock(c)
+ }
+ modifiers {
+ text(c.visibility[it]?.takeIf { it !in ignoredVisibilities }?.name?.plus(" ") ?: "")
+
+ if (c is DClass) {
+ text(c.modifier[it]?.takeIf { it !in ignoredModifiers }?.name?.plus(" ") ?: "")
+ text(c.modifiers()[it]?.toSignatureString() ?: "")
+ }
+
+ when (c) {
+ is DClass -> text("class")
+ is DInterface -> text("interface")
+ is DEnum -> text("enum")
+ is DObject -> text("class")
+ is DAnnotation -> text("@interface")
+ }
+ }
+ signatureWithoutModifiers {
+ link(c.dri.classNames!!, c.dri)
+ if (c is WithGenerics) {
+ list(c.generics, prefix = "<", suffix = ">") {
+ +buildSignature(it)
+ }
+ }
+ }
+ supertypes {
+ if (c is WithSupertypes) {
+ c.supertypes.map { (p, dris) ->
+ val (classes, interfaces) = dris.partition { it.kind == JavaClassKindTypes.CLASS }
+ list(classes, prefix = "extends ", sourceSets = setOf(p)) {
+ link(it.typeConstructor.dri.sureClassNames, it.typeConstructor.dri, sourceSets = setOf(p))
+ list(it.typeConstructor.projections, prefix = "<", suffix = ">", sourceSets = setOf(p)) {
+ signatureForProjection(it)
+ }
+ }
+ list(interfaces, prefix = " implements ", sourceSets = setOf(p)){
+ link(it.typeConstructor.dri.sureClassNames, it.typeConstructor.dri, sourceSets = setOf(p))
+ list(it.typeConstructor.projections, prefix = "<", suffix = ">", sourceSets = setOf(p)) {
+ signatureForProjection(it)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun signature(f: DFunction): List<ContentNode> =
+ javadocSignature(f) { sourceSet ->
+ annotations {
+ annotationsBlock(f)
+ }
+ modifiers {
+ text(f.modifier[sourceSet]?.takeIf { it !in ignoredModifiers }?.name?.plus(" ") ?: "")
+ text(f.modifiers()[sourceSet]?.toSignatureString() ?: "")
+ val usedGenerics = if (f.isConstructor) f.generics.filter { f uses it } else f.generics
+ list(usedGenerics, prefix = "<", suffix = "> ") {
+ +buildSignature(it)
+ }
+ signatureForProjection(f.type)
+ }
+ signatureWithoutModifiers {
+ link(f.name, f.dri)
+ text("(")
+ list(f.parameters) {
+ annotationsInline(it)
+ text(it.modifiers()[sourceSet]?.toSignatureString().orEmpty())
+ signatureForProjection(it.type)
+ text(Typography.nbsp.toString())
+ text(it.name.orEmpty())
+ }
+ text(")")
+ }
+ }
+
+ private fun signature(p: DProperty): List<ContentNode> =
+ javadocSignature(p) {
+ annotations {
+ annotationsBlock(p)
+ }
+ modifiers {
+ text(p.visibility[it]?.takeIf { it !in ignoredVisibilities }?.name?.plus(" ") ?: "")
+ text(p.modifier[it]?.name + " ")
+ text(p.modifiers()[it]?.toSignatureString() ?: "")
+ signatureForProjection(p.type)
+ }
+ signatureWithoutModifiers {
+ link(p.name, p.dri)
+ }
+ }
+
+ private fun signature(e: DEnumEntry): List<ContentNode> =
+ javadocSignature(e) {
+ annotations {
+ annotationsBlock(e)
+ }
+ modifiers {
+ text(e.modifiers()[it]?.toSignatureString() ?: "")
+ }
+ signatureWithoutModifiers {
+ link(e.name, e.dri)
+ }
+ }
+
+ private fun signature(t: DTypeParameter): List<ContentNode> =
+ javadocSignature(t) {
+ annotations {
+ annotationsBlock(t)
+ }
+ signatureWithoutModifiers {
+ text(t.name)
+ }
+ supertypes {
+ list(t.bounds, prefix = "extends ") {
+ signatureForProjection(it)
+ }
+ }
+ }
+
+ private fun signature(p: DParameter): List<ContentNode> =
+ javadocSignature(p) {
+ modifiers {
+ signatureForProjection(p.type)
+ }
+ signatureWithoutModifiers {
+ link(p.name.orEmpty(), p.dri)
+ }
+ }
+
+ private fun javadocSignature(
+ d: Documentable,
+ extra: PropertyContainer<ContentNode> = PropertyContainer.empty(),
+ block: JavadocPageContentBuilder.JavadocContentBuilder.(DokkaConfiguration.DokkaSourceSet) -> Unit
+ ): List<ContentNode> =
+ d.sourceSets.map { sourceSet ->
+ contentBuilder.contentFor(d, ContentKind.Main) {
+ with(contentBuilder) {
+ javadocGroup(d.dri, d.sourceSets, extra) {
+ block(sourceSet)
+ }
+ }
+ }
+ }
+
+ private fun PageContentBuilder.DocumentableContentBuilder.signatureForProjection(p: Projection): Unit = when (p) {
+ is TypeParameter -> link(p.name, p.dri)
+ is TypeConstructor -> group {
+ link(p.dri.classNames.orEmpty(), p.dri)
+ list(p.projections, prefix = "<", suffix = ">") {
+ signatureForProjection(it)
+ }
+ }
+ is Variance<*> -> group {
+ text("$p ".takeIf { it.isNotBlank() } ?: "")
+ signatureForProjection(p.inner)
+ }
+ is Star -> text("?")
+ is Nullable -> signatureForProjection(p.inner)
+ is DefinitelyNonNullable -> signatureForProjection(p.inner)
+ is JavaObject, is Dynamic -> link("Object", DRI("java.lang", "Object"))
+ is Void -> text("void")
+ is PrimitiveJavaType -> text(p.name)
+ is UnresolvedBound -> text(p.name)
+ is TypeAliased -> signatureForProjection(p.inner)
+ }
+
+ private fun DRI.fqName(): String = "${packageName.orEmpty()}.${classNames.orEmpty()}"
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/transformers/documentables/JavadocDocumentableJVMSourceSetFilter.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/transformers/documentables/JavadocDocumentableJVMSourceSetFilter.kt
new file mode 100644
index 00000000..0ab5280e
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/transformers/documentables/JavadocDocumentableJVMSourceSetFilter.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.transformers.documentables
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer
+
+public class JavadocDocumentableJVMSourceSetFilter(
+ public val context: DokkaContext
+) : PreMergeDocumentableTransformer {
+
+ private val allowedSourceSets = context.configuration.sourceSets.filter { it.analysisPlatform == Platform.jvm }
+ .flatMap { it.getAllDependentSourceSets() }.distinct()
+
+ private fun DokkaConfiguration.DokkaSourceSet.getAllDependentSourceSets(): List<DokkaConfiguration.DokkaSourceSet> =
+ dependentSourceSets.flatMap { setId ->
+ context.configuration.sourceSets.find { it.sourceSetID == setId }?.getAllDependentSourceSets().orEmpty()
+ } + this
+
+ override fun invoke(modules: List<DModule>): List<DModule> =
+ modules.filter { module -> allowedSourceSets.containsAll(module.sourceSets) }
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt
new file mode 100644
index 00000000..9746376b
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.translators.documentables
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder
+import org.jetbrains.dokka.javadoc.pages.JavadocSignatureContentNode
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.pages.ContentKind
+import org.jetbrains.dokka.pages.ContentNode
+import org.jetbrains.dokka.utilities.DokkaLogger
+
+public class JavadocPageContentBuilder(
+ commentsConverter: CommentsToContentConverter,
+ signatureProvider: SignatureProvider,
+ logger: DokkaLogger
+) : PageContentBuilder(commentsConverter, signatureProvider, logger) {
+
+ public fun PageContentBuilder.DocumentableContentBuilder.javadocGroup(
+ dri: DRI = mainDRI.first(),
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet> = mainSourcesetData,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: JavadocContentBuilder.() -> Unit
+ ) {
+ +JavadocContentBuilder(
+ mainDri = dri,
+ mainExtra = extra,
+ mainSourceSet = sourceSets,
+ ).apply(block).build()
+ }
+
+ public open inner class JavadocContentBuilder(
+ private val mainDri: DRI,
+ private val mainExtra: PropertyContainer<ContentNode>,
+ private val mainSourceSet: Set<DokkaConfiguration.DokkaSourceSet>,
+ ) {
+ public var annotations: ContentNode? = null
+ public var modifiers: ContentNode? = null
+ public var signatureWithoutModifiers: ContentNode? = null
+ public var supertypes: ContentNode? = null
+
+ public fun annotations(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) {
+ val built = buildContentForBlock(block)
+ if(built.hasAnyContent()) annotations = built
+ }
+
+ public fun modifiers(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) {
+ val built = buildContentForBlock(block)
+ if(built.hasAnyContent()) modifiers = built
+ }
+
+ public fun signatureWithoutModifiers(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) {
+ signatureWithoutModifiers = buildContentForBlock(block)
+ }
+
+ public fun supertypes(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) {
+ val built = buildContentForBlock(block)
+ if(built.hasAnyContent()) supertypes = built
+ }
+
+ private fun buildContentForBlock(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) =
+ contentFor(
+ dri = mainDri,
+ sourceSets = mainSourceSet,
+ kind = ContentKind.Symbol,
+ extra = mainExtra,
+ block = block
+ )
+
+ public fun build(): JavadocSignatureContentNode = JavadocSignatureContentNode(
+ dri = mainDri,
+ annotations = annotations,
+ modifiers = modifiers,
+ signatureWithoutModifiers = signatureWithoutModifiers ?: throw IllegalStateException("JavadocSignatureContentNode should have at least a signature"),
+ supertypes = supertypes
+ )
+ }
+}
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt
new file mode 100644
index 00000000..654f4391
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc
+
+import java.nio.file.Path
+import java.nio.file.Paths
+
+internal fun Path.toNormalized() = this.normalize().toFile().toString()
+
+internal fun String.toNormalized() = Paths.get(this).toNormalized()
diff --git a/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/validity/MultiplatformConfiguredChecker.kt b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/validity/MultiplatformConfiguredChecker.kt
new file mode 100644
index 00000000..7ecd6757
--- /dev/null
+++ b/dokka-subprojects/plugin-javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/validity/MultiplatformConfiguredChecker.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.javadoc.validity
+
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.validity.PreGenerationChecker
+import org.jetbrains.dokka.validity.PreGenerationCheckerOutput
+
+public class MultiplatformConfiguredChecker(
+ public val context: DokkaContext
+) : PreGenerationChecker {
+
+ override fun invoke(): PreGenerationCheckerOutput {
+ val isSinglePlatform = context.configuration.sourceSets.all { sourceSet ->
+ val platform = sourceSet.analysisPlatform
+ (platform == Platform.jvm || platform == Platform.common)
+ }
+ return PreGenerationCheckerOutput(isSinglePlatform, listOfNotNull(errorMessage.takeUnless { isSinglePlatform }))
+ }
+
+ public companion object {
+ public const val errorMessage: String =
+ "Dokka Javadoc plugin currently does not support generating documentation for multiplatform project. Please, adjust your configuration"
+ }
+}