Skip to content

Commit

Permalink
Add external documentable provider (#2307)
Browse files Browse the repository at this point in the history
* Add external documentable provider

* Update the api spec of base plugin

* Hide mistakenly exposed fields

* Add comments and fix naming
  • Loading branch information
Kordyjan committed Jan 18, 2022
1 parent 6b72925 commit 62f2919
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 4 deletions.
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 translateClassDescriptor (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 translateClassDescriptor (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 translateClassDescriptor(descriptor: ClassDescriptor, sourceSet: DokkaSourceSet): DClasslike {
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 =
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.translateClassDescriptor(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,12 @@
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

/**
* Service translating [ClassDescriptor]s of symbols defined outside of documented project to [DClasslike]s.
*/
interface ExternalClasslikesTranslator {
fun translateClassDescriptor(descriptor: ClassDescriptor, sourceSet: DokkaSourceSet): DClasslike
}
@@ -0,0 +1,22 @@
package org.jetbrains.dokka.base.translators.descriptors

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

/**
* Service that can be queried with [DRI] and source set to obtain a documentable for classlike.
*
* There are some cases when there is a need to process documentables of classlikes that were not defined
* in the project itself but are somehow related to the symbols defined in the documented project (e.g. are supertypes
* of classes defined in project).
*/
interface ExternalDocumentablesProvider {

/**
* Returns [DClasslike] matching provided [DRI] in specified source set.
*
* Result is null if compiler haven't generated matching class descriptor.
*/
fun findClasslike(dri: DRI, sourceSet: DokkaConfiguration.DokkaSourceSet): DClasslike?
}
139 changes: 139 additions & 0 deletions plugins/base/src/test/kotlin/translators/ExternalDocumentablesTest.kt
@@ -0,0 +1,139 @@
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 ExternalDocumentablesTest : 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)
assertEquals("java.util/ArrayList///PointingToDeclaration/", res?.dri?.toString())

val supertypes = res?.cast<DClass>()?.supertypes?.values?.single()
?.map { it.typeConstructor.dri.classNames }
assertEquals(
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)
assertEquals("kotlinx.coroutines/Job///PointingToDeclaration/", res?.dri?.toString())

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("kotlin.collections/Map.Entry///PointingToDeclaration/", res?.dri?.toString())
}
}
}
}

0 comments on commit 62f2919

Please sign in to comment.