Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add external documentable provider #2307

Merged
merged 4 commits into from Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 19 additions & 1 deletion plugins/base/api/base.api
Expand Up @@ -5,6 +5,8 @@ public final class org/jetbrains/dokka/base/DokkaBase : org/jetbrains/dokka/plug
public final fun getBaseSearchbarDataInstaller ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getCommentsToContentConverter ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getCustomResourceInstaller ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getDefaultExternalClasslikesTranslator ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getDefaultExternalDocumentablesProvider ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getDefaultKotlinAnalysis ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getDefaultSamplesTransformer ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getDefaultTabSortingStrategy ()Lorg/jetbrains/dokka/plugability/Extension;
Expand All @@ -18,6 +20,8 @@ public final class org/jetbrains/dokka/base/DokkaBase : org/jetbrains/dokka/plug
public final fun getEmptyModulesFilter ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getEmptyPackagesFilter ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getExtensionsExtractor ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getExternalClasslikesTranslator ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getExternalDocumentablesProvider ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getExternalLocationProviderFactory ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getFallbackMerger ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getFileWriter ()Lorg/jetbrains/dokka/plugability/Extension;
Expand Down Expand Up @@ -1279,16 +1283,30 @@ public final class org/jetbrains/dokka/base/translators/descriptors/DRIWithPlatf
public fun toString ()Ljava/lang/String;
}

public final class org/jetbrains/dokka/base/translators/descriptors/DefaultDescriptorToDocumentableTranslator : org/jetbrains/dokka/transformers/sources/AsyncSourceToDocumentableTranslator {
public final class org/jetbrains/dokka/base/translators/descriptors/DefaultDescriptorToDocumentableTranslator : org/jetbrains/dokka/base/translators/descriptors/ExternalClasslikesTranslator, org/jetbrains/dokka/transformers/sources/AsyncSourceToDocumentableTranslator {
public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
public fun invoke (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Lorg/jetbrains/dokka/plugability/DokkaContext;)Lorg/jetbrains/dokka/model/DModule;
public fun invokeSuspending (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Lorg/jetbrains/dokka/plugability/DokkaContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun translateDescriptor (Lorg/jetbrains/kotlin/descriptors/ClassDescriptor;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/model/DClasslike;
}

public final class org/jetbrains/dokka/base/translators/descriptors/DefaultDescriptorToDocumentableTranslatorKt {
public static final fun withEmptyInfo (Lorg/jetbrains/dokka/links/DRI;)Lorg/jetbrains/dokka/base/translators/descriptors/DRIWithPlatformInfo;
}

public final class org/jetbrains/dokka/base/translators/descriptors/DefaultExternalDocumentablesProvider : org/jetbrains/dokka/base/translators/descriptors/ExternalDocumentablesProvider {
public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
public fun findClasslike (Lorg/jetbrains/dokka/links/DRI;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/model/DClasslike;
}

public abstract interface class org/jetbrains/dokka/base/translators/descriptors/ExternalClasslikesTranslator {
public abstract fun translateDescriptor (Lorg/jetbrains/kotlin/descriptors/ClassDescriptor;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/model/DClasslike;
}

public abstract interface class org/jetbrains/dokka/base/translators/descriptors/ExternalDocumentablesProvider {
public abstract fun findClasslike (Lorg/jetbrains/dokka/links/DRI;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/model/DClasslike;
}

public final class org/jetbrains/dokka/base/translators/documentables/BriefFromContentNodesKt {
public static final fun firstParagraphBrief (Lorg/jetbrains/dokka/model/doc/DocTag;)Lorg/jetbrains/dokka/model/doc/DocTag;
public static final fun firstSentenceBriefFromContentNodes (Ljava/util/List;)Ljava/util/List;
Expand Down
13 changes: 13 additions & 0 deletions plugins/base/src/main/kotlin/DokkaBase.kt
Expand Up @@ -29,6 +29,9 @@ import org.jetbrains.dokka.base.translators.documentables.DefaultDocumentableToP
import org.jetbrains.dokka.base.translators.psi.DefaultPsiToDocumentableTranslator
import org.jetbrains.dokka.base.generation.SingleModuleGeneration
import org.jetbrains.dokka.base.renderers.html.command.consumers.ReplaceVersionsConsumer
import org.jetbrains.dokka.base.translators.descriptors.DefaultExternalDocumentablesProvider
import org.jetbrains.dokka.base.translators.descriptors.ExternalClasslikesTranslator
import org.jetbrains.dokka.base.translators.descriptors.ExternalDocumentablesProvider
import org.jetbrains.dokka.plugability.DokkaPlugin
import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer
import org.jetbrains.dokka.transformers.pages.PageTransformer
Expand All @@ -46,6 +49,8 @@ class DokkaBase : DokkaPlugin() {
val kotlinAnalysis by extensionPoint<KotlinAnalysis>()
val tabSortingStrategy by extensionPoint<TabSortingStrategy>()
val immediateHtmlCommandConsumer by extensionPoint<ImmediateHtmlCommandConsumer>()
val externalDocumentablesProvider by extensionPoint<ExternalDocumentablesProvider>()
val externalClasslikesTranslator by extensionPoint<ExternalClasslikesTranslator>()

val singleGeneration by extending {
CoreExtensions.generation providing ::SingleModuleGeneration
Expand Down Expand Up @@ -252,4 +257,12 @@ class DokkaBase : DokkaPlugin() {
val baseSearchbarDataInstaller by extending {
htmlPreprocessors providing ::SearchbarDataInstaller order { after(sourceLinksTransformer) }
}

val defaultExternalDocumentablesProvider by extending {
externalDocumentablesProvider providing ::DefaultExternalDocumentablesProvider
}

val defaultExternalClasslikesTranslator by extending {
externalClasslikesTranslator providing ::DefaultDescriptorToDocumentableTranslator
}
}
Expand Up @@ -2,6 +2,7 @@ package org.jetbrains.dokka.base.translators.descriptors

import com.intellij.psi.PsiNamedElement
import com.intellij.psi.util.PsiLiteralUtil.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -55,6 +56,7 @@ import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.LocalClass
import org.jetbrains.kotlin.resolve.constants.KClassValue.Value.NormalClass
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
Expand All @@ -79,8 +81,8 @@ import org.jetbrains.kotlin.resolve.constants.BooleanValue as ConstantsBooleanVa
import org.jetbrains.kotlin.resolve.constants.NullValue as ConstantsNullValue

class DefaultDescriptorToDocumentableTranslator(
context: DokkaContext
) : AsyncSourceToDocumentableTranslator {
private val context: DokkaContext
) : AsyncSourceToDocumentableTranslator, ExternalClasslikesTranslator {

private val kotlinAnalysis: KotlinAnalysis = context.plugin<DokkaBase>().querySingle { kotlinAnalysis }

Expand Down Expand Up @@ -109,6 +111,15 @@ class DefaultDescriptorToDocumentableTranslator(
)
}
}

override fun translateDescriptor(descriptor: ClassDescriptor, sourceSet: DokkaSourceSet): DClasslike {
Kordyjan marked this conversation as resolved.
Show resolved Hide resolved
val driInfo = DRI.from(descriptor.parents.first()).withEmptyInfo()

return runBlocking(Dispatchers.Default) {
DokkaDescriptorVisitor(sourceSet, kotlinAnalysis[sourceSet].facade, context.logger)
.visitClassDescriptor(descriptor, driInfo)
}
}
}

data class DRIWithPlatformInfo(
Expand Down Expand Up @@ -162,7 +173,7 @@ private class DokkaDescriptorVisitor(
}
}

private suspend fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DClasslike =
suspend fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DClasslike =
IgnatBeresnev marked this conversation as resolved.
Show resolved Hide resolved
when (descriptor.kind) {
ClassKind.ENUM_CLASS -> enumDescriptor(descriptor, parent)
ClassKind.OBJECT -> objectDescriptor(descriptor, parent)
Expand Down
@@ -0,0 +1,42 @@
package org.jetbrains.dokka.base.translators.descriptors

import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DClasslike
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.querySingle
import org.jetbrains.dokka.plugability.plugin
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered

class DefaultExternalDocumentablesProvider(context: DokkaContext) : ExternalDocumentablesProvider {
private val analysis = context.plugin<DokkaBase>().querySingle { kotlinAnalysis }

private val translator = context.plugin<DokkaBase>().querySingle { externalClasslikesTranslator }

override fun findClasslike(dri: DRI, sourceSet: DokkaSourceSet): DClasslike? {
val pkg = dri.packageName?.let { FqName(it) } ?: FqName.ROOT
val names = dri.classNames?.split('.') ?: return null

val packageDsc = analysis[sourceSet].facade.moduleDescriptor.getPackage(pkg)
val classDsc = names.fold<String, DeclarationDescriptor?>(packageDsc) { dsc, name ->
dsc?.scope?.getDescriptorsFiltered { it.identifier == name }
?.filterIsInstance<ClassDescriptor>()
?.firstOrNull()
}

return (classDsc as? ClassDescriptor)?.let { translator.translateDescriptor(it, sourceSet) }
}

private val DeclarationDescriptor.scope: MemberScope
get() = when (this) {
is PackageViewDescriptor -> memberScope
is ClassDescriptor -> unsubstitutedMemberScope
else -> throw IllegalArgumentException("Unexpected type of descriptor: ${this::class}")
}
}
@@ -0,0 +1,9 @@
package org.jetbrains.dokka.base.translators.descriptors

import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
import org.jetbrains.dokka.model.DClasslike
import org.jetbrains.kotlin.descriptors.ClassDescriptor

interface ExternalClasslikesTranslator {
fun translateDescriptor(descriptor: ClassDescriptor, sourceSet: DokkaSourceSet): DClasslike
}
@@ -0,0 +1,10 @@
package org.jetbrains.dokka.base.translators.descriptors

import org.jetbrains.dokka.DokkaConfiguration
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DClasslike


interface ExternalDocumentablesProvider {
fun findClasslike(dri: DRI, sourceSet: DokkaConfiguration.DokkaSourceSet): DClasslike?
}
Kordyjan marked this conversation as resolved.
Show resolved Hide resolved
137 changes: 137 additions & 0 deletions plugins/base/src/test/kotlin/translators/ExternalDocumentables.kt
@@ -0,0 +1,137 @@
package translators

import com.intellij.openapi.application.PathManager
import kotlinx.coroutines.Job
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
import org.jetbrains.dokka.base.translators.descriptors.ExternalDocumentablesProvider
import org.jetbrains.dokka.model.DClass
import org.jetbrains.dokka.model.DInterface
import org.jetbrains.dokka.plugability.plugin
import org.jetbrains.dokka.plugability.querySingle
import org.jetbrains.dokka.utilities.cast
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class ExternalDocumentables : BaseAbstractTest() {
@Test
fun `external documentable from java stdlib`() {
val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src")
analysisPlatform = "jvm"
classpath += jvmStdlibPath!!
}
}
}

testInline(
"""
/src/com/sample/MyList.kt
package com.sample
class MyList: ArrayList<Int>()
""".trimIndent(),
configuration
) {
lateinit var provider: ExternalDocumentablesProvider
pluginsSetupStage = {
provider = it.plugin<DokkaBase>().querySingle { externalDocumentablesProvider }
}
documentablesTransformationStage = { mod ->
val entry = mod.packages.single().classlikes.single().cast<DClass>().supertypes.entries.single()
val res = provider.findClasslike(
entry.value.single().typeConstructor.dri,
entry.key)
assertEquals("ArrayList", res?.name)

val supertypes = res?.cast<DClass>()?.supertypes?.values?.single()
?.map { it.typeConstructor.dri.classNames }
assertEquals(
Kordyjan marked this conversation as resolved.
Show resolved Hide resolved
listOf("AbstractList", "RandomAccess", "Cloneable", "Serializable", "MutableList"),
supertypes
)
}
}
}

@Test
fun `external documentable from dependency`() {
val coroutinesPath =
PathManager.getResourceRoot(Job::class.java, "/kotlinx/coroutines/Job.class")

val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src")
analysisPlatform = "jvm"
classpath += listOf(jvmStdlibPath!!, coroutinesPath!!)
}
}
}

testInline(
"""
/src/com/sample/MyJob.kt
package com.sample
import kotlinx.coroutines.Job
abstract class MyJob: Job
""".trimIndent(),
configuration
) {
lateinit var provider: ExternalDocumentablesProvider
pluginsSetupStage = {
provider = it.plugin<DokkaBase>().querySingle { externalDocumentablesProvider }
}
documentablesTransformationStage = { mod ->
val entry = mod.packages.single().classlikes.single().cast<DClass>().supertypes.entries.single()
val res = provider.findClasslike(
entry.value.single().typeConstructor.dri,
entry.key)
assertEquals("Job", res?.name)

val supertypes = res?.cast<DInterface>()?.supertypes?.values?.single()
?.map { it.typeConstructor.dri.classNames }
assertEquals(
listOf("CoroutineContext.Element"),
supertypes
)
}
}
}

@Test
fun `external documentable for nested class`() {
val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src")
analysisPlatform = "jvm"
classpath += jvmStdlibPath!!
}
}
}

testInline(
"""
/src/com/sample/MyList.kt
package com.sample
abstract class MyEntry: Map.Entry<Int, String>
""".trimIndent(),
configuration
) {
lateinit var provider: ExternalDocumentablesProvider
pluginsSetupStage = {
provider = it.plugin<DokkaBase>().querySingle { externalDocumentablesProvider }
}
documentablesTransformationStage = { mod ->
val entry = mod.packages.single().classlikes.single().cast<DClass>().supertypes.entries.single()
val res = provider.findClasslike(
entry.value.single().typeConstructor.dri,
entry.key)
assertEquals("Entry", res?.name)
assertEquals("Map.Entry", res?.dri?.classNames)
}
}
}
}