diff options
author | Ignat Beresnev <ignat.beresnev@jetbrains.com> | 2023-11-10 11:46:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-10 11:46:54 +0100 |
commit | 8e5c63d035ef44a269b8c43430f43f5c8eebfb63 (patch) | |
tree | 1b915207b2b9f61951ddbf0ff2e687efd053d555 /dokka-subprojects/plugin-javadoc/src/main/kotlin | |
parent | a44efd4ba0c2e4ab921ff75e0f53fc9335aa79db (diff) | |
download | dokka-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')
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() } + } ?: " "), + "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" + } +} |