diff options
Diffstat (limited to 'plugins/javadoc/src')
64 files changed, 29632 insertions, 0 deletions
diff --git a/plugins/javadoc/src/main/kotlin/javadoc/JavadocDocumentableToPageTranslator.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocDocumentableToPageTranslator.kt new file mode 100644 index 00000000..840bdc55 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocDocumentableToPageTranslator.kt @@ -0,0 +1,18 @@ +package 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/javadoc/JavadocPageCreator.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt new file mode 100644 index 00000000..7420f78e --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt @@ -0,0 +1,238 @@ +package javadoc + +import 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/javadoc/JavadocPlugin.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt new file mode 100644 index 00000000..8283bd78 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt @@ -0,0 +1,53 @@ +package org.jetbrains.dokka.javadoc + +import javadoc.JavadocDocumentableToPageTranslator +import javadoc.location.JavadocLocationProviderFactory +import javadoc.renderer.KorteJavadocRenderer +import 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/javadoc/location/JavadocLocationProvider.kt b/plugins/javadoc/src/main/kotlin/javadoc/location/JavadocLocationProvider.kt new file mode 100644 index 00000000..49278b06 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/location/JavadocLocationProvider.kt @@ -0,0 +1,126 @@ +package javadoc.location + +import 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 newPrefix = prefix + page.takeIf { it is JavadocPackagePageNode }?.name.orEmpty() + 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(page.name, "package-summary") + else + listOf("index") + else -> emptyList() + }).filterNot { it.isEmpty() } + + put(page, path) + page.children.forEach { registerPath(it, newPrefix) } + + } + 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" |
