diff options
author | Błażej Kardyś <bkardys@virtuslab.com> | 2020-07-29 19:34:12 +0200 |
---|---|---|
committer | Błażej Kardyś <bkardys@virtuslab.com> | 2020-08-04 00:47:36 +0200 |
commit | a673f77482fea4ba89f9fdf3a7544955cf8b8226 (patch) | |
tree | 2942bdbfc87168f96893a2b9d6da2f9fa649c94e /plugins/javadoc/src/main/kotlin/org/jetbrains/dokka | |
parent | 401e657eff30df5cdb4b0415b6218167ea08916b (diff) | |
download | dokka-a673f77482fea4ba89f9fdf3a7544955cf8b8226.tar.gz dokka-a673f77482fea4ba89f9fdf3a7544955cf8b8226.tar.bz2 dokka-a673f77482fea4ba89f9fdf3a7544955cf8b8226.zip |
Javadoc plugin package name fix
Diffstat (limited to 'plugins/javadoc/src/main/kotlin/org/jetbrains/dokka')
17 files changed, 2178 insertions, 0 deletions
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt new file mode 100644 index 00000000..f8723f33 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt @@ -0,0 +1,18 @@ +package org.jetbrains.dokka.javadoc + +import org.jetbrains.dokka.base.signatures.SignatureProvider +import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.pages.ModulePageNode +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator +import org.jetbrains.dokka.utilities.DokkaLogger + +class JavadocDocumentableToPageTranslator( + private val commentsToContentConverter: CommentsToContentConverter, + private val signatureProvider: SignatureProvider, + private val logger: DokkaLogger +) : DocumentableToPageTranslator { + override fun invoke(module: DModule): RootPageNode = + JavadocPageCreator(commentsToContentConverter, signatureProvider, logger).pageForModule(module) +}
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt new file mode 100644 index 00000000..8b86fab0 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt @@ -0,0 +1,238 @@ +package org.jetbrains.dokka.javadoc + +import org.jetbrains.dokka.javadoc.pages.* +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.signatures.SignatureProvider +import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter +import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.doc.Description +import org.jetbrains.dokka.model.doc.Index +import org.jetbrains.dokka.model.doc.Param +import org.jetbrains.dokka.model.doc.TagWrapper +import org.jetbrains.dokka.model.properties.PropertyContainer +import org.jetbrains.dokka.model.properties.WithExtraProperties +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.utilities.DokkaLogger +import kotlin.reflect.KClass + +open class JavadocPageCreator( + commentsToContentConverter: CommentsToContentConverter, + private val signatureProvider: SignatureProvider, + val logger: DokkaLogger +) { + + fun pageForModule(m: DModule): JavadocModulePageNode = + JavadocModulePageNode( + name = m.name.ifEmpty { "root" }, + content = contentForModule(m), + children = m.packages.map { pageForPackage(it) }, + dri = setOf(m.dri) + ) + + fun pageForPackage(p: DPackage) = + JavadocPackagePageNode(p.name, contentForPackage(p), setOf(p.dri), p, + p.classlikes.mapNotNull { pageForClasslike(it) } // TODO: nested classlikes + ) + + fun pageForClasslike(c: DClasslike): JavadocClasslikePageNode? = + c.highestJvmSourceSet?.let { jvm -> + JavadocClasslikePageNode( + name = c.name.orEmpty(), + content = contentForClasslike(c), + dri = setOf(c.dri), + 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 = c.classlikes.mapNotNull { pageForClasslike(it) }, + properties = c.properties.map { + JavadocPropertyNode( + it.dri, + it.name, + signatureForNode(it, jvm), + it.descriptionToContentNodes(jvm), + PropertyContainer.withAll(it.indexesInDocumentation()) + ) + }, + documentable = c, + extra = ((c as? WithExtraProperties<Documentable>)?.extra ?: PropertyContainer.empty()) + c.indexesInDocumentation() + ) + } + + private fun contentForModule(m: DModule): JavadocContentNode = + JavadocContentGroup( + setOf(m.dri), + JavadocContentKind.OverviewSummary, + m.jvmSourceSets.toSet() + ) { + title(m.name, m.brief(), "0.0.1", dri = setOf(m.dri), kind = ContentKind.Main) + leafList(setOf(m.dri), + ContentKind.Packages, JavadocList( + "Packages", "Package", + m.packages.sortedBy { it.name }.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.jvmSourceSets.toSet() + ) { + title(p.name, p.brief(), "0.0.1", dri = setOf(p.dri), kind = ContentKind.Packages) + val rootList = p.classlikes.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.jvmSourceSets.toSet() + ) { + title( + c.name.orEmpty(), + c.brief(), + "0.0.1", + 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), + 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()) + ) + } + }, + extra = extra + indexesInDocumentation() + ) + } + + private val Documentable.jvmSourceSets + get() = sourceSets.filter { it.analysisPlatform == Platform.jvm } + + private val Documentable.highestJvmSourceSet + get() = jvmSourceSets.let { sources -> + sources.firstOrNull { it != expectPresentInSet } ?: sources.firstOrNull() + } + + private val firstSentenceRegex = Regex("^((?:[^.?!]|[.!?](?!\\s))*[.!?])") + + private inline fun <reified T : TagWrapper> Documentable.findNodeInDocumentation(sourceSetData: DokkaSourceSet?): T? = + documentation[sourceSetData]?.firstChildOfTypeOrNull<T>() + + 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() + + fun List<ContentNode>.nodeForJvm(jvm: DokkaSourceSet): ContentNode = + first { it.sourceSets.contains(jvm) } + + private fun Documentable.brief(sourceSet: DokkaSourceSet? = highestJvmSourceSet): List<ContentNode> = + briefFromContentNodes(descriptionToContentNodes(sourceSet)) + + private fun briefFromContentNodes(description: List<ContentNode>): List<ContentNode> { + val contents = mutableListOf<ContentNode>() + for (node in description) { + if (node is ContentText && firstSentenceRegex.containsMatchIn(node.text)) { + contents.add(node.copy(text = firstSentenceRegex.find(node.text)?.value.orEmpty())) + break + } else { + contents.add(node) + } + } + return contents + } + + private fun DParameter.brief(sourceSet: DokkaSourceSet? = highestJvmSourceSet): List<ContentNode> = + briefFromContentNodes(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.toSet(), + style = emptySet(), + extra = PropertyContainer.empty() + ) + } + ) + } +} + diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt new file mode 100644 index 00000000..0c322a82 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt @@ -0,0 +1,53 @@ +package org.jetbrains.dokka.javadoc + +import org.jetbrains.dokka.javadoc.JavadocDocumentableToPageTranslator +import org.jetbrains.dokka.javadoc.location.JavadocLocationProviderFactory +import org.jetbrains.dokka.javadoc.renderer.KorteJavadocRenderer +import org.jetbrains.dokka.javadoc.signatures.JavadocSignatureProvider +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.kotlinAsJava.KotlinAsJavaPlugin +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.plugability.querySingle + +class JavadocPlugin : DokkaPlugin() { + + val dokkaBasePlugin by lazy { plugin<DokkaBase>() } + val kotinAsJavaPlugin by lazy { plugin<KotlinAsJavaPlugin>() } + + val locationProviderFactory by extensionPoint<JavadocLocationProviderFactory>() + + val dokkaJavadocPlugin by extending { + (CoreExtensions.renderer + providing { ctx -> KorteJavadocRenderer(dokkaBasePlugin.querySingle { outputWriter }, ctx, "views") } + override dokkaBasePlugin.htmlRenderer) + } + + val pageTranslator by extending { + CoreExtensions.documentableToPageTranslator providing { context -> + JavadocDocumentableToPageTranslator( + dokkaBasePlugin.querySingle { commentsToContentConverter }, + dokkaBasePlugin.querySingle { signatureProvider }, + context.logger + ) + } override dokkaBasePlugin.documentableToPageTranslator + } + + val javadocLocationProviderFactory by extending { + locationProviderFactory providing { context -> + JavadocLocationProviderFactory(context) + } + } + + val javadocSignatureProvider by extending { + val dokkaBasePlugin = plugin<DokkaBase>() + dokkaBasePlugin.signatureProvider providing { ctx -> + JavadocSignatureProvider( + ctx.single( + dokkaBasePlugin.commentsToContentConverter + ), ctx.logger + ) + } override kotinAsJavaPlugin.javaSignatureProvider + } +} + diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt new file mode 100644 index 00000000..7f27ff18 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt @@ -0,0 +1,129 @@ +package org.jetbrains.dokka.javadoc.location + +import org.jetbrains.dokka.javadoc.pages.* +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.base.resolvers.local.BaseLocationProvider +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.Nullable +import org.jetbrains.dokka.links.parent +import org.jetbrains.dokka.model.* +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.* + +class JavadocLocationProvider(pageRoot: RootPageNode, dokkaContext: DokkaContext) : + BaseLocationProvider(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 + 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 nodeIndex = HashMap<DRI, PageNode>().apply { + fun registerNode(node: PageNode) { + if (node is ContentPage) put(node.dri.first(), 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) + val commonPathElements = zip(contextPath).takeWhile { (a, b) -> a == b }.count() + return (List(contextPath.size - commonPathElements) { ".." } + this.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<DokkaSourceSet>, context: PageNode?): String { + return nodeIndex[dri]?.let { resolve(it, context) } + ?: nodeIndex[dri.parent]?.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" + } + ?: getExternalLocation(dri, sourceSets) + } + + private fun JavadocFunctionNode.getAnchor(): String = + "$name(${parameters.joinToString(",") { + when (val bound = if (it.typeBound is org.jetbrains.dokka.model.Nullable) it.typeBound.inner else it.typeBound) { + is TypeConstructor -> bound.dri.classNames.orEmpty() + is OtherParameter -> bound.name + is PrimitiveJavaType -> bound.name + is UnresolvedBound -> bound.name + is JavaObject -> "Object" + else -> bound.toString() + } + }})" + + fun anchorForFunctionNode(node: JavadocFunctionNode) = node.getAnchor() + + 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 = + 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") + } + + fun resolve(link: LinkJavadocListEntry, contextRoot: PageNode? = null, skipExtension: Boolean = true) = + 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" + else -> it + } + }?.relativeTo(pathIndex[contextRoot].orEmpty())?.let { if (skipExtension) "$it.html" else it }.orEmpty() + + override fun resolveRoot(node: PageNode): String { + TODO("Not yet implemented") + } + + override fun ancestors(node: PageNode): List<PageNode> { + TODO("Not yet implemented") + } +} diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt new file mode 100644 index 00000000..7dd8abe1 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt @@ -0,0 +1,11 @@ +package org.jetbrains.dokka.javadoc.location + +import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability.DokkaContext + +class JavadocLocationProviderFactory(private val context: DokkaContext) : LocationProviderFactory { + + override fun getLocationProvider(pageNode: RootPageNode) = + JavadocLocationProvider(pageNode, context) +}
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt new file mode 100644 index 00000000..d45837b7 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt @@ -0,0 +1,173 @@ +package org.jetbrains.dokka.javadoc.pages + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.model.properties.PropertyContainer +import org.jetbrains.dokka.pages.* + +enum class JavadocContentKind : Kind { + AllClasses, OverviewSummary, PackageSummary, Class, OverviewTree, PackageTree +} + +abstract class JavadocContentNode( + dri: Set<DRI>, + kind: Kind, + override val sourceSets: Set<DokkaSourceSet> +) : 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 +} + +interface JavadocList { + val tabTitle: String + val colTitle: String + val children: List<JavadocListEntry> +} + +interface JavadocListEntry { + val stringTag: String +} + +class EmptyNode( + dri: DRI, + kind: Kind, + override val sourceSets: Set<DokkaSourceSet>, + 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>): ContentNode = + EmptyNode(dci.dri.first(), dci.kind, sourceSets, newExtras) + + override fun hasAnyContent(): Boolean = false +} + +class JavadocContentGroup( + val dri: Set<DRI>, + val kind: Kind, + sourceSets: Set<DokkaSourceSet>, + override val children: List<JavadocContentNode> +) : JavadocContentNode(dri, kind, sourceSets) { + + companion object { + operator fun invoke( + dri: Set<DRI>, + kind: Kind, + sourceSets: Set<DokkaSourceSet>, + block: JavaContentGroupBuilder.() -> Unit + ): JavadocContentGroup = + JavadocContentGroup(dri, kind, sourceSets, JavaContentGroupBuilder(sourceSets).apply(block).list) + } + + override fun hasAnyContent(): Boolean = children.isNotEmpty() +} + +class JavaContentGroupBuilder(val sourceSets: Set<DokkaSourceSet>) { + val list = mutableListOf<JavadocContentNode>() +} + +class TitleNode( + val title: String, + val subtitle: List<ContentNode>, + val version: String, + val parent: String?, + val dri: Set<DRI>, + val kind: Kind, + sourceSets: Set<DokkaSourceSet> +) : JavadocContentNode(dri, kind, sourceSets) { + override fun hasAnyContent(): Boolean = !title.isBlank() || !version.isBlank() || subtitle.isNotEmpty() +} + +fun JavaContentGroupBuilder.title( + title: String, + subtitle: List<ContentNode>, + version: String, + parent: String? = null, + dri: Set<DRI>, + kind: Kind +) { + list.add(TitleNode(title, subtitle, version, parent, dri, kind, sourceSets)) +} + +class RootListNode( + val entries: List<LeafListNode>, + val dri: Set<DRI>, + val kind: Kind, + sourceSets: Set<DokkaSourceSet>, +) : JavadocContentNode(dri, kind, sourceSets) { + override fun hasAnyContent(): Boolean = children.isNotEmpty() +} + +class LeafListNode( + val tabTitle: String, + val colTitle: String, + val entries: List<JavadocListEntry>, + val dri: Set<DRI>, + val kind: Kind, + sourceSets: Set<DokkaSourceSet> +) : JavadocContentNode(dri, kind, sourceSets) { + override fun hasAnyContent(): Boolean = children.isNotEmpty() +} + + +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)) +} + +fun JavaContentGroupBuilder.leafList( + dri: Set<DRI>, + kind: Kind, + leafList: JavadocList +) { + list.add(LeafListNode(leafList.tabTitle, leafList.colTitle, leafList.children, dri, kind, sourceSets)) +} + +fun JavadocList(tabTitle: String, colTitle: String, children: List<JavadocListEntry>) = object : JavadocList { + override val tabTitle = tabTitle + override val colTitle = colTitle + override val children = children +} + +class LinkJavadocListEntry( + val name: String, + val dri: Set<DRI>, + val kind: Kind = ContentKind.Symbol, + val sourceSets: Set<DokkaSourceSet> +) : + JavadocListEntry { + override val stringTag: String + get() = if (builtString == null) + throw IllegalStateException("stringTag for LinkJavadocListEntry accessed before build() call") + else builtString!! + + private var builtString: String? = null + + fun build(body: (String, Set<DRI>, Kind, List<DokkaSourceSet>) -> String) { + builtString = body(name, dri, kind, sourceSets.toList()) + } +} + +data class RowJavadocListEntry(val link: LinkJavadocListEntry, val doc: List<ContentNode>) : JavadocListEntry { + override val stringTag: String = "" +} + +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 +} diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt new file mode 100644 index 00000000..08a90613 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt @@ -0,0 +1,10 @@ +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 + +data class JavadocIndexExtra(val index: List<ContentNode>) : ExtraProperty<Documentable> { + override val key: ExtraProperty.Key<Documentable, *> = JavadocIndexExtra + companion object : ExtraProperty.Key<Documentable, JavadocIndexExtra> +}
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt new file mode 100644 index 00000000..790e15c5 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt @@ -0,0 +1,436 @@ +package org.jetbrains.dokka.javadoc.pages + +import com.intellij.psi.PsiClass +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.analysis.DescriptorDocumentableSource +import org.jetbrains.dokka.analysis.PsiDocumentableSource +import org.jetbrains.dokka.analysis.from +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.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.resolve.DescriptorUtils.getClassDescriptorForType +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance + +interface JavadocPageNode : ContentPage + +interface WithJavadocExtra<T : Documentable> : WithExtraProperties<T> { + override fun withNewExtras(newExtras: PropertyContainer<T>): T = + throw IllegalStateException("Merging extras is not applicable for javadoc") +} + +class JavadocModulePageNode( + override val name: String, + override val content: JavadocContentNode, + override val children: List<PageNode>, + override val dri: Set<DRI> +) : + RootPageNode(), + JavadocPageNode { + + override val documentable: Documentable? = null + override val embeddedResources: List<String> = emptyList() + override fun modified(name: String, children: List<PageNode>): RootPageNode = + JavadocModulePageNode(name, content, children, dri) + + override fun modified( + name: String, + content: ContentNode, + dri: Set<DRI>, + embeddedResources: List<String>, + children: List<PageNode> + ): ContentPage = JavadocModulePageNode(name, content as JavadocContentNode, children, dri) +} + +class JavadocPackagePageNode( + override val name: String, + override val content: JavadocContentNode, + override val dri: Set<DRI>, + + override val documentable: Documentable? = null, + override val children: List<PageNode> = emptyList(), + override val embeddedResources: List<String> = listOf() +) : JavadocPageNode { + + override fun modified( + name: String, + children: List<PageNode> + ): PageNode = JavadocPackagePageNode( + name, + content, + dri, + documentable, + 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, + documentable, + children, + embeddedResources + ) +} + +sealed class AnchorableJavadocNode(open val dri: DRI) + +data class JavadocEntryNode( + override val dri: DRI, + val name: String, + val signature: JavadocSignatureContentNode, + val brief: List<ContentNode>, + override val extra: PropertyContainer<DEnumEntry> = PropertyContainer.empty() +): AnchorableJavadocNode(dri), WithJavadocExtra<DEnumEntry> + +data class JavadocParameterNode( + override val dri: DRI, + val name: String, + val type: ContentNode, + val description: List<ContentNode>, + val typeBound: Bound, + override val extra: PropertyContainer<DParameter> = PropertyContainer.empty() +): AnchorableJavadocNode(dri), WithJavadocExtra<DParameter> + +data class JavadocPropertyNode( + override val dri: DRI, + val name: String, + val signature: JavadocSignatureContentNode, + val brief: List<ContentNode>, + override val extra: PropertyContainer<DProperty> = PropertyContainer.empty() +): AnchorableJavadocNode(dri), WithJavadocExtra<DProperty> + +data class JavadocFunctionNode( + val signature: JavadocSignatureContentNode, + val brief: List<ContentNode>, + val parameters: List<JavadocParameterNode>, + val name: String, + override val dri: DRI, + override val extra: PropertyContainer<DFunction> = PropertyContainer.empty() +): AnchorableJavadocNode(dri), WithJavadocExtra<DFunction> { + val isInherited: Boolean + get() { + val extra = extra[InheritedFunction] + return extra?.inheritedFrom?.keys?.firstOrNull { it.analysisPlatform == Platform.jvm }?.let { jvm -> + extra.isInherited(jvm) + } ?: false + } +} + +class JavadocClasslikePageNode( + override val name: String, + override val content: JavadocContentNode, + override val dri: Set<DRI>, + val signature: JavadocSignatureContentNode, + val description: List<ContentNode>, + val constructors: List<JavadocFunctionNode>, + val methods: List<JavadocFunctionNode>, + val entries: List<JavadocEntryNode>, + val classlikes: List<JavadocClasslikePageNode>, + val properties: List<JavadocPropertyNode>, + override val documentable: Documentable? = null, + override val children: List<PageNode> = emptyList(), + override val embeddedResources: List<String> = listOf(), + override val extra: PropertyContainer<DClasslike> = PropertyContainer.empty(), +) : JavadocPageNode, WithJavadocExtra<DClasslike> { + + val kind: String? = documentable?.kind() + val packageName = dri.first().packageName + + override fun modified( + name: String, + children: List<PageNode> + ): PageNode = JavadocClasslikePageNode( + name, + content, + dri, + signature, + description, + constructors, + methods, + entries, + classlikes, + properties, + documentable, + 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, + documentable, + children, + embeddedResources, + extra + ) +} + +class AllClassesPage(val classes: List<JavadocClasslikePageNode>) : JavadocPageNode { + val classEntries = + 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 documentable: Documentable? = null + 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 = TODO() + + override fun modified(name: String, children: List<PageNode>): PageNode = + TODO() + + override val children: List<PageNode> = emptyList() + +} + +class TreeViewPage( + override val name: String, + val packages: List<JavadocPackagePageNode>?, + val classes: List<JavadocClasslikePageNode>?, + override val dri: Set<DRI>, + override val documentable: Documentable?, + val root: PageNode +) : JavadocPageNode { + init { + assert(packages == null || classes == null) + assert(packages != null || classes != null) + } + + private val documentables = root.children.filterIsInstance<ContentPage>().flatMap { node -> + getDocumentableEntries(node) + }.groupBy({ it.first }) { it.second }.map { (l, r) -> l to r.first() }.toMap() + + private val descriptorMap = getDescriptorMap() + private val inheritanceTuple = generateInheritanceTree() + internal val classGraph = inheritanceTuple.first + internal val interfaceGraph = inheritanceTuple.second + + override val children: List<PageNode> = emptyList() + + val title = when (documentable) { + is DPackage -> "$name Class Hierarchy" + else -> "All packages" + } + + val kind = when (documentable) { + 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, + documentable = documentable, + root = root + ) + + 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, + documentable = documentable, + root = root + ) + + 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() + + fun gatherPsiClasses(psi: PsiClass): List<Pair<PsiClass, List<PsiClass>>> = psi.supers.toList().let { l -> + listOf(psi to l) + l.flatMap { gatherPsiClasses(it) } + } + + val psiInheritanceTree = documentables.flatMap { (_, v) -> (v as? WithExpectActual)?.sources?.values.orEmpty() } + .filterIsInstance<PsiDocumentableSource>().mapNotNull { it.psi as? PsiClass }.flatMap(::gatherPsiClasses) + .flatMap { entry -> entry.second.map { it to entry.first } } + .let { + it + it.map { it.second to null } + } + .groupBy({ it.first }) { it.second } + .map { it.key to it.value.filterNotNull().distinct() } + .map { (k, v) -> + InheritanceNode( + DRI.from(k), + v.map { InheritanceNode(DRI.from(it)) }, + k.supers.filter { it.isInterface }.map { DRI.from(it) }, + k.isInterface + ) + + } + + val descriptorInheritanceTree = descriptorMap.flatMap { (_, v) -> + v.typeConstructor.supertypes + .map { getClassDescriptorForType(it) to v } + } + .let { + it + it.map { it.second to null } + } + .groupBy({ it.first }) { it.second } + .map { it.key to it.value.filterNotNull().distinct() } + .map { (k, v) -> + InheritanceNode( + DRI.from(k), + v.map { InheritanceNode(DRI.from(it)) }, + k.typeConstructor.supertypes.map { getClassDescriptorForType(it) } + .mapNotNull { cd -> cd.takeIf { it.kind == ClassKind.INTERFACE }?.let { DRI.from(it) } }, + isInterface = k.kind == ClassKind.INTERFACE + ) + } + + descriptorInheritanceTree.forEach { addToMap(it, mergeMap) } + psiInheritanceTree.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 generateInterfaceGraph() { + documentables.values.filterIsInstance<DInterface>() + } + + private fun getDocumentableEntries(node: ContentPage): List<Pair<DRI, Documentable>> = + listOfNotNull(node.documentable?.let { it.dri to it }) + + node.children.filterIsInstance<ContentPage>().flatMap(::getDocumentableEntries) + + private fun getDescriptorMap(): Map<DRI, ClassDescriptor> { + val map: MutableMap<DRI, ClassDescriptor> = mutableMapOf() + documentables + .mapNotNull { (k, v) -> + v.descriptorForPlatform()?.let { k to it }?.also { (k, v) -> map[k] = v } + }.map { it.second }.forEach { gatherSupertypes(it, map) } + + return map.toMap() + } + + private fun gatherSupertypes(descriptor: ClassDescriptor, map: MutableMap<DRI, ClassDescriptor>) { + map.putIfAbsent(DRI.from(descriptor), descriptor) + descriptor.typeConstructor.supertypes.map { getClassDescriptorForType(it) } + .forEach { gatherSupertypes(it, map) } + } + + private fun Documentable?.descriptorForPlatform(platform: Platform = Platform.jvm) = + (this as? WithExpectActual).descriptorForPlatform(platform) + + private fun WithExpectActual?.descriptorForPlatform(platform: Platform = Platform.jvm) = this?.let { + it.sources.entries.find { it.key.analysisPlatform == platform }?.value?.let { it as? DescriptorDocumentableSource }?.descriptor as? ClassDescriptor + } + + data class InheritanceNode( + val dri: DRI, + val children: List<InheritanceNode> = emptyList(), + val interfaces: List<DRI> = emptyList(), + val isInterface: Boolean = false + ) { + override fun equals(other: Any?): Boolean = other is InheritanceNode && other.dri == dri + override fun hashCode(): Int = dri.hashCode() + } +} + +private fun Documentable.kind(): String? = + when (this) { + is DClass -> "class" + is DEnum -> "enum" + is DAnnotation -> "annotation" + is DObject -> "object" + is DInterface -> "interface" + else -> null + }
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt new file mode 100644 index 00000000..42276dd4 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt @@ -0,0 +1,68 @@ +package org.jetbrains.dokka.javadoc.pages + +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.transformers.pages.PageTransformer + +val preprocessors = listOf(ResourcesInstaller, TreeViewInstaller, AllClassesPageInstaller) + +object ResourcesInstaller : PageTransformer { + override fun invoke(input: RootPageNode): RootPageNode = input.modified( + children = input.children + + RendererSpecificResourcePage( + "resourcePack", + emptyList(), + RenderingStrategy.Copy("/static_res") + ) + ) +} + +object TreeViewInstaller : 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, + documentable = node.documentable, + root = root + ) + + return node.modified(children = node.children.map { node -> + install( + node, + root + ) + } + 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, + documentable = node.documentable, + root = root + ) + + return node.modified(children = node.children + packageTree) as JavadocPackagePageNode + } +} + +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)) + } +}
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/pages.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/pages.kt new file mode 100644 index 00000000..14aa1903 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/pages.kt @@ -0,0 +1,17 @@ +package org.jetbrains.dokka.javadoc.pages + +internal const val jQueryVersion = "3.3.1" +internal const val jQueryMigrateVersion = "3.0.1" + +//class PackageSummary(val page: PageNode) : RendererSpecificPage { +// override val name = "package-summary" +// override val children = emptyList<PageNode>() +// override fun modified(name: String, children: List<PageNode>) = this +// +// override val strategy = RenderingStrategy.Write(content()) +// +// private fun content(): String = pageStart(page.name, "0.0.1", page.name, "../") + // TODO +// topNavbar(page, "???") +// +//} + diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt new file mode 100644 index 00000000..906c9d5a --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt @@ -0,0 +1,63 @@ +package org.jetbrains.dokka.javadoc.renderer + +import org.jetbrains.dokka.javadoc.location.JavadocLocationProvider +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: JavadocLocationProvider, + private val context: DokkaContext +) { + + fun htmlForContentNode(node: ContentNode, relative: PageNode?): String = + when (node) { + is ContentGroup -> htmlForContentNodes(node.children, node.style, relative) + is ContentText -> buildText(node) + is ContentDRILink -> buildLink( + locationProvider.resolve(node.address, node.sourceSets, relative), + htmlForContentNodes(node.children, node.style, relative) + ) + is ContentResolvedLink -> buildLink(node.address, htmlForContentNodes(node.children, node.style, relative)) + is ContentCode -> htmlForCode(node.children) + is JavadocSignatureContentNode -> htmlForSignature(node, relative) + else -> "" + } + + fun htmlForContentNodes(list: List<ContentNode>, styles: Set<Style>, relative: PageNode?) = + list.joinToString(separator = "") { htmlForContentNode(it, relative) } + + private fun buildText(node: ContentText): String { + val escapedText = node.text.htmlEscape() + return if (node.style.contains(ContentStyle.InDocumentationAnchor)) { + """<em><a id="$escapedText" class="searchTagResult">${escapedText}</a></em>""" + } else { + escapedText + } + } + + private fun htmlForCode(code: List<ContentNode>): String = code.map { element -> + when (element) { + is ContentText -> element.text + is ContentBreakLine -> "" + else -> run { context.logger.error("Cannot cast $element as ContentText!"); "" } + } + }.joinToString("<br>", """<span class="code">""", "</span>") { it } + + private fun htmlForSignature(node: JavadocSignatureContentNode, relative: PageNode?): String = + listOfNotNull( + node.annotations, + node.modifiers, + node.signatureWithoutModifiers, + node.supertypes + ).joinToString(separator = " ") { htmlForContentNode(it, relative) } + + companion object { + + fun buildLink(address: String, content: String) = + """<a href=${address.formatToEndWithHtml()}>$content</a>""" + + } +}
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt new file mode 100644 index 00000000..1a286095 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt @@ -0,0 +1,222 @@ +package org.jetbrains.dokka.javadoc.renderer + +import org.jetbrains.dokka.javadoc.location.JavadocLocationProvider +import org.jetbrains.dokka.javadoc.pages.* +import org.jetbrains.dokka.javadoc.toNormalized +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.resolvers.local.LocationProvider +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.sureClassNames +import org.jetbrains.dokka.model.ImplementedInterfaces +import org.jetbrains.dokka.model.InheritedFunction +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.plugability.DokkaContext +import java.nio.file.Paths + +internal class JavadocContentToTemplateMapTranslator( + private val locationProvider: JavadocLocationProvider, + private val context: DokkaContext, +) { + + fun templateMapForPageNode(node: JavadocPageNode): TemplateMap = + mapOf<String, Any?>( + "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) + 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("/").filter { it.isNotEmpty() }.joinToString("/") { ".." }.let { + if (it.isNotEmpty()) "$it/" else it + } + } + } + } + + private inner class InnerTranslator(val contextNode: PageNode) { + + private val htmlTranslator = JavadocContentToHtmlTranslator(locationProvider, context) + + fun templateMapForAllClassesPage(node: AllClassesPage): TemplateMap { + return mapOf( + "title" to "All Classes", + "list" to node.classEntries + ) + } + + fun templateMapForTreeViewPage(node: TreeViewPage): TemplateMap { + return 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 { + return mapOf( + "kind" to "package" + ) + templateMapForJavadocContentNode(node.content) + } + + fun templateMapForFunctionNode(node: JavadocFunctionNode): TemplateMap { + return mapOf( + "brief" to htmlForContentNodes(node.brief, contextNode), + "parameters" to node.parameters.map { templateMapForParameterNode(it) }, + "inlineParameters" to node.parameters.joinToString { renderInlineParameter(it) }, + "anchorLink" to locationProvider.anchorForFunctionNode(node), + "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(), + "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) } + ) + + 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() + } + + 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.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[InheritedFunction]?.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, + "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, emptySet(), relativeNode) + } + + private fun DRI.displayable(): String = "${packageName}.${sureClassNames}" +} + diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt new file mode 100644 index 00000000..092fed2c --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt @@ -0,0 +1,190 @@ +package org.jetbrains.dokka.javadoc.renderer + +import com.soywiz.korte.* +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 kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.jetbrains.dokka.base.renderers.OutputWriter +import org.jetbrains.dokka.javadoc.JavadocPlugin +import org.jetbrains.dokka.links.DRI +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.renderers.Renderer +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import java.nio.file.Path +import java.nio.file.Paths +import java.time.LocalDate + +typealias TemplateMap = Map<String, Any?> + +class KorteJavadocRenderer(private val outputWriter: OutputWriter, val context: DokkaContext, resourceDir: String) : + Renderer { + private lateinit var locationProvider: JavadocLocationProvider + + private val contentToHtmlTranslator by lazy { + JavadocContentToHtmlTranslator(locationProvider, context) + } + + private val contentToTemplateMapTranslator by lazy { + JavadocContentToTemplateMapTranslator(locationProvider, context) + } + + override fun render(root: RootPageNode) = root.let { preprocessors.fold(root) { r, t -> t.invoke(r) } }.let { newRoot -> + locationProvider = context.plugin<JavadocPlugin>().querySingle { locationProviderFactory }.getLocationProvider(newRoot) + runBlocking(Dispatchers.IO) { + renderModulePageNode(newRoot as JavadocModulePageNode) + 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" + else -> "" + } + + private fun CoroutineScope.renderNode(node: PageNode, path: String = "") { + if (node is JavadocPageNode) { + renderJavadocPageNode(node) + } else if (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 { renderNode(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 { renderNode(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 + } + } + + 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.1" }, + TeFunction("jQueryMigrateVersion") { "1.2.1" }, + 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, emptySet(), 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 -> + 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 -> + val rootNodes = args.first() as List<TreeViewPage.InheritanceNode> + + fun drawRec(node: TreeViewPage.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, + buildLink(n.toLink(), n.classNames.orEmpty()) + ).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 -> + args.first().safeAs<List<HashMap<String, String>>>() + ?.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")?.bufferedReader()?.lines()?.toArray() + ?.joinToString("\n") ?: throw IllegalStateException("Template not found: $basePath/$template") + } + +} diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt new file mode 100644 index 00000000..696c2e80 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt @@ -0,0 +1,261 @@ +package org.jetbrains.dokka.javadoc.renderer + +import org.jetbrains.dokka.javadoc.location.JavadocLocationProvider +import org.jetbrains.dokka.javadoc.pages.* +import org.jetbrains.dokka.javadoc.renderer.SearchRecord.Companion.allTypes +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.base.renderers.sourceSets +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.InheritedFunction +import org.jetbrains.dokka.model.doc.Index +import org.jetbrains.dokka.model.properties.WithExtraProperties +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.utilities.formatToEndWithHtml +import org.jetbrains.dokka.utilities.htmlEscape +import java.lang.StringBuilder + +class SearchScriptsCreator(private val locationProvider: JavadocLocationProvider) { + + fun invoke(input: RootPageNode): List<RendererSpecificPage> { + val data = when (input) { + is JavadocModulePageNode -> processModules(listOf(input)) + else -> SearchData() + } + 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.resolve(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.resolve(it).formatToEndWithHtml()) } + SearchRecord.allPackages + val types = input.flatMap { + it.children.filterIsInstance<JavadocClasslikePageNode>().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.resolve(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.resolve(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.resolve(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.resolve(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<DokkaConfiguration.DokkaSourceSet>, label: String): String = + locationProvider.resolve(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) } + }
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt new file mode 100644 index 00000000..a60d7711 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt @@ -0,0 +1,201 @@ +package org.jetbrains.dokka.javadoc.signatures + +import org.jetbrains.dokka.javadoc.translators.documentables.JavadocPageContentBuilder +import org.jetbrains.dokka.DokkaConfiguration +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.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.utilities.DokkaLogger + +class JavadocSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLogger) : SignatureProvider, + JvmSignatureUtils by JavaSignatureUtils { + + 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.name!!, 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.dri.sureClassNames, it.dri, sourceSets = setOf(p)) + } + list(interfaces, prefix = " implements ", sourceSets = setOf(p)){ + link(it.dri.sureClassNames, it.dri, sourceSets = setOf(p)) + } + } + } + } + } + + private fun signature(f: DFunction): List<ContentNode> = + javadocSignature(f) { + annotations { + annotationsBlock(f) + } + modifiers { + text(f.modifier[it]?.takeIf { it !in ignoredModifiers }?.name?.plus(" ") ?: "") + text(f.modifiers()[it]?.toSignatureString() ?: "") + list(f.generics, prefix = "<", suffix = "> ") { + +buildSignature(it) + } + signatureForProjection(f.type) + } + signatureWithoutModifiers { + link(f.name, f.dri) + text("(") + list(f.parameters) { + annotationsInline(it) + text(it.modifiers()[it]?.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 OtherParameter -> link(p.name, p.declarationDRI) + is TypeConstructor -> group { + link(p.dri.classNames.orEmpty(), p.dri) + list(p.projections, prefix = "<", suffix = ">") { + signatureForProjection(it) + } + } + is Variance -> group { + text(p.kind.toString() + " ") + signatureForProjection(p.inner) + } + is Star -> text("?") + is Nullable -> 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) + } + + private fun DRI.fqName(): String = "${packageName.orEmpty()}.${classNames.orEmpty()}" +} diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt new file mode 100644 index 00000000..e70f8370 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt @@ -0,0 +1,80 @@ +package org.jetbrains.dokka.javadoc.translators.documentables + +import org.jetbrains.dokka.javadoc.pages.JavadocSignatureContentNode +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.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 +import java.lang.IllegalStateException + +class JavadocPageContentBuilder( + commentsConverter: CommentsToContentConverter, + signatureProvider: SignatureProvider, + logger: DokkaLogger +) : PageContentBuilder(commentsConverter, signatureProvider, logger) { + + 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() + } + + open inner class JavadocContentBuilder( + private val mainDri: DRI, + private val mainExtra: PropertyContainer<ContentNode>, + private val mainSourceSet: Set<DokkaConfiguration.DokkaSourceSet>, + ) { + var annotations: ContentNode? = null + var modifiers: ContentNode? = null + var signatureWithoutModifiers: ContentNode? = null + var supertypes: ContentNode? = null + + fun annotations(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) { + val built = buildContentForBlock(block) + if(built.hasAnyContent()) annotations = built + } + + fun modifiers(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) { + val built = buildContentForBlock(block) + if(built.hasAnyContent()) modifiers = built + } + + fun signatureWithoutModifiers(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) { + signatureWithoutModifiers = buildContentForBlock(block) + } + + 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 + ) + + fun build(): JavadocSignatureContentNode = JavadocSignatureContentNode( + dri = mainDri, + annotations = annotations, + modifiers = modifiers, + signatureWithoutModifiers = signatureWithoutModifiers ?: throw IllegalStateException("JavadocSignatureContentNode should have at least a signature"), + supertypes = supertypes + ) + } +}
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt new file mode 100644 index 00000000..97c35b28 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt @@ -0,0 +1,8 @@ +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() |