aboutsummaryrefslogtreecommitdiff
path: root/dokka-subprojects/analysis-kotlin-descriptors-compiler
diff options
context:
space:
mode:
Diffstat (limited to 'dokka-subprojects/analysis-kotlin-descriptors-compiler')
-rw-r--r--dokka-subprojects/analysis-kotlin-descriptors-compiler/api/analysis-kotlin-descriptors-compiler.api16
-rw-r--r--dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt11
-rw-r--r--dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSampleAnalysisEnvironment.kt195
-rw-r--r--dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt119
4 files changed, 198 insertions, 143 deletions
diff --git a/dokka-subprojects/analysis-kotlin-descriptors-compiler/api/analysis-kotlin-descriptors-compiler.api b/dokka-subprojects/analysis-kotlin-descriptors-compiler/api/analysis-kotlin-descriptors-compiler.api
index 373ec268..c08cbd62 100644
--- a/dokka-subprojects/analysis-kotlin-descriptors-compiler/api/analysis-kotlin-descriptors-compiler.api
+++ b/dokka-subprojects/analysis-kotlin-descriptors-compiler/api/analysis-kotlin-descriptors-compiler.api
@@ -10,7 +10,6 @@ public final class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/Comp
public final fun getKdocFinder ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getKlibService ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getKotlinAnalysis ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
- public final fun getKotlinSampleProviderFactory ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getMockApplicationHack ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
}
@@ -85,21 +84,6 @@ public abstract class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/c
public final fun get (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisContext;
}
-public class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider : org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider {
- public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
- public fun close ()V
- public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext;
- public fun getSample (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Ljava/lang/String;)Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider$SampleSnippet;
- protected fun processBody (Lcom/intellij/psi/PsiElement;)Ljava/lang/String;
- protected fun processImports (Lcom/intellij/psi/PsiElement;)Ljava/lang/String;
-}
-
-public final class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProviderFactory : org/jetbrains/dokka/analysis/kotlin/internal/SampleProviderFactory {
- public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
- public fun build ()Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider;
- public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext;
-}
-
public final class org/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl : com/intellij/core/CoreJavaFileManager, org/jetbrains/kotlin/resolve/jvm/KotlinCliJavaFileManager {
public static final field Companion Lorg/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl$Companion;
public fun <init> (Lcom/intellij/psi/PsiManager;)V
diff --git a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt
index c59a43b2..e8ebceb0 100644
--- a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt
+++ b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt
@@ -11,6 +11,7 @@ import org.jetbrains.dokka.InternalDokkaApi
import org.jetbrains.dokka.analysis.java.BreakingAbstractionKotlinLightMethodChecker
import org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin
import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.DokkaAnalysisConfiguration
+import org.jetbrains.dokka.analysis.kotlin.KotlinAnalysisPlugin
import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.KotlinAnalysis
import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.ProjectKotlinAnalysis
import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.*
@@ -20,7 +21,6 @@ import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator.Defau
import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator.DefaultExternalDocumentablesProvider
import org.jetbrains.dokka.renderers.PostAction
import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
-import org.jetbrains.dokka.analysis.kotlin.internal.SampleProviderFactory
import org.jetbrains.dokka.plugability.*
import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation
@@ -75,13 +75,8 @@ public class CompilerDescriptorAnalysisPlugin : DokkaPlugin() {
plugin<InternalKotlinAnalysisPlugin>().fullClassHierarchyBuilder providing { DescriptorFullClassHierarchyBuilder() }
}
- /**
- * StdLib has its own a sample provider
- * So it should have a possibility to override this extension
- */
- @InternalDokkaApi
- public val kotlinSampleProviderFactory: Extension<SampleProviderFactory, *, *> by extending {
- plugin<InternalKotlinAnalysisPlugin>().sampleProviderFactory providing ::KotlinSampleProviderFactory
+ internal val descriptorSampleAnalysisEnvironmentCreator by extending {
+ plugin<KotlinAnalysisPlugin>().sampleAnalysisEnvironmentCreator providing ::DescriptorSampleAnalysisEnvironmentCreator
}
internal val descriptorSyntheticDocumentableDetector by extending {
diff --git a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSampleAnalysisEnvironment.kt b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSampleAnalysisEnvironment.kt
new file mode 100644
index 00000000..3df3d22c
--- /dev/null
+++ b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSampleAnalysisEnvironment.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl
+
+import com.intellij.psi.PsiElement
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KDocFinder
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.KotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.SamplesKotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironment
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironmentCreator
+import org.jetbrains.dokka.analysis.kotlin.sample.SampleSnippet
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.DokkaPluginApiPreview
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.load.kotlin.toSourceElement
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.KtBlockExpression
+import org.jetbrains.kotlin.psi.KtDeclarationWithBody
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
+import org.jetbrains.kotlin.resolve.lazy.ResolveSession
+import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
+import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
+
+internal class DescriptorSampleAnalysisEnvironmentCreator(
+ private val context: DokkaContext,
+) : SampleAnalysisEnvironmentCreator {
+
+ private val descriptorAnalysisPlugin = context.plugin<CompilerDescriptorAnalysisPlugin>()
+
+ override fun <T> use(block: SampleAnalysisEnvironment.() -> T): T {
+ // Run from the thread of Dispatchers.Default as it can help
+ // avoid memory leaks through the compiler's ThreadLocals.
+ // Might not be relevant if the project stops using coroutines.
+ return runBlocking(Dispatchers.Default) {
+ @OptIn(DokkaPluginApiPreview::class)
+ SamplesKotlinAnalysis(
+ sourceSets = context.configuration.sourceSets,
+ context = context,
+ projectKotlinAnalysis = descriptorAnalysisPlugin.querySingle { kotlinAnalysis }
+ ).use { kotlinAnalysis ->
+ val sampleAnalysis = DescriptorSampleAnalysisEnvironment(
+ kdocFinder = descriptorAnalysisPlugin.querySingle { kdocFinder },
+ kotlinAnalysis = kotlinAnalysis,
+ dokkaLogger = context.logger
+ )
+ block(sampleAnalysis)
+ }
+ }
+ }
+}
+
+internal class DescriptorSampleAnalysisEnvironment(
+ private val kdocFinder: KDocFinder,
+ private val kotlinAnalysis: KotlinAnalysis,
+ private val dokkaLogger: DokkaLogger,
+) : SampleAnalysisEnvironment {
+
+ override fun resolveSample(
+ sourceSet: DokkaConfiguration.DokkaSourceSet,
+ fullyQualifiedLink: String,
+ ): SampleSnippet? {
+ val resolveSession = kotlinAnalysis[sourceSet].resolveSession
+
+ val samplePsiElement = resolveSession.resolveSamplePsiElement(sourceSet, fullyQualifiedLink)
+ if (samplePsiElement == null) {
+ dokkaLogger.debug("Cannot resolve sample element for: \"$fullyQualifiedLink\"")
+ return null
+ } else if (samplePsiElement.containingFile !is KtFile) {
+ dokkaLogger.warn("Unable to resolve non-Kotlin @sample links: \"$fullyQualifiedLink\"")
+ return null
+ }
+
+ return SampleSnippet(
+ imports = processImports(samplePsiElement),
+ body = processBody(samplePsiElement)
+ )
+ }
+
+ private fun ResolveSession.resolveSamplePsiElement(
+ dokkaSourceSet: DokkaConfiguration.DokkaSourceSet,
+ fqLink: String,
+ ): PsiElement? {
+ val packageDescriptor = resolveNearestPackageDescriptor(fqLink)
+ if (packageDescriptor == null) {
+ dokkaLogger.debug(
+ "Unable to resolve package descriptor for @sample: \"$fqLink\";"
+ )
+ return null
+ }
+
+ val kdocLink = kdocFinder.resolveKDocLink(
+ fromDescriptor = packageDescriptor,
+ qualifiedName = fqLink,
+ sourceSet = dokkaSourceSet,
+ emptyBindingContext = true
+ ).firstOrNull()
+
+ if (kdocLink == null) {
+ dokkaLogger.warn(
+ "Unable to resolve a @sample link: \"$fqLink\". Is it used correctly? " +
+ "Expecting a link to a reachable (resolvable) Kotlin function."
+ )
+ return null
+ } else if (kdocLink.toSourceElement !is KotlinSourceElement) {
+ dokkaLogger.warn("Unable to resolve non-Kotlin @sample links: \"$fqLink\"")
+ return null
+ } else if (kdocLink !is FunctionDescriptor) {
+ dokkaLogger.warn("Unable to process a @sample link: \"$fqLink\". Only function links allowed.")
+ return null
+ }
+ return DescriptorToSourceUtils.descriptorToDeclaration(kdocLink)
+ }
+
+ /**
+ * Tries to resolve [fqLink]'s package.
+ *
+ * Since [fqLink] can be both a link to a top-level function and a link to a function within a class,
+ * we cannot tell for sure if [fqLink] contains a class name or not (relying on case letters is error-prone,
+ * there are exceptions). But we know for sure that the last element in the link is the function.
+ *
+ * So we start with what we think is the deepest package path, and if we cannot find a package descriptor
+ * for it - we drop one level and try again, until we find something or reach root.
+ *
+ * This function should also account for links to declarations within the root package (`""`).
+ *
+ * Here are some examples:
+ *
+ * Given [fqLink] = `com.example.ClassName.functionName`:
+ * 1) First pass, trying to resolve package `com.example.ClassName`. Failure.
+ * 2) Second pass, trying to resolve package `com.example`. Success.
+ *
+ * Given [fqLink] = `com.example.functionName`:
+ * 1) First pass, trying to resolve package `com.example`. Success.
+ *
+ * Given [fqLink] = `ClassName.functionName` (root package):
+ * 1) First pass, trying to resolve package `ClassName`. Failure.
+ * 2) Second pass, trying to resolve package `""`. Success.
+ */
+ private fun ResolveSession.resolveNearestPackageDescriptor(fqLink: String): LazyPackageDescriptor? {
+ val isRootPackage = !fqLink.contains('.')
+ val supposedPackageName = if (isRootPackage) "" else fqLink.substringBeforeLast(".")
+
+ val packageDescriptor = this.getPackageFragment(FqName(supposedPackageName))
+ if (packageDescriptor != null) {
+ return packageDescriptor
+ }
+ dokkaLogger.debug("Failed to resolve package \"$supposedPackageName\" for sample \"$fqLink\"")
+
+ if (isRootPackage) {
+ // cannot go any deeper
+ return null
+ }
+
+ return resolveNearestPackageDescriptor(supposedPackageName.substringBeforeLast("."))
+ }
+
+ private fun processImports(sampleElement: PsiElement): List<String> {
+ val psiFile = sampleElement.containingFile
+
+ val importsList = (psiFile as? KtFile)?.importList ?: return emptyList()
+ return importsList.imports
+ .map { it.text.removePrefix("import ") }
+ .filter { it.isNotBlank() }
+ }
+
+ private fun processBody(sampleElement: PsiElement): String {
+ return getSampleBody(sampleElement)
+ .trim { it == '\n' || it == '\r' }
+ .trimEnd()
+ .trimIndent()
+ }
+
+ private fun getSampleBody(sampleElement: PsiElement): String {
+ return when (sampleElement) {
+ is KtDeclarationWithBody -> {
+ when (val bodyExpression = sampleElement.bodyExpression) {
+ is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}")
+ else -> bodyExpression!!.text
+ }
+ }
+
+ else -> sampleElement.text
+ }
+ }
+}
diff --git a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt
deleted file mode 100644
index 5199abf5..00000000
--- a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl
-
-import com.intellij.psi.PsiElement
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.jetbrains.dokka.DokkaConfiguration
-import org.jetbrains.dokka.InternalDokkaApi
-import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
-import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KDocFinder
-import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.SamplesKotlinAnalysis
-import org.jetbrains.dokka.plugability.DokkaContext
-import org.jetbrains.dokka.plugability.plugin
-import org.jetbrains.dokka.plugability.querySingle
-import org.jetbrains.dokka.analysis.kotlin.internal.SampleProvider
-import org.jetbrains.dokka.analysis.kotlin.internal.SampleProviderFactory
-import org.jetbrains.dokka.plugability.DokkaPluginApiPreview
-import org.jetbrains.kotlin.name.FqName
-import org.jetbrains.kotlin.psi.KtBlockExpression
-import org.jetbrains.kotlin.psi.KtDeclarationWithBody
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.resolve.lazy.ResolveSession
-
-public class KotlinSampleProviderFactory(
- public val context: DokkaContext
-): SampleProviderFactory {
- override fun build(): SampleProvider {
- return KotlinSampleProvider(context)
- }
-
-}
-/**
- * It's declared as open since StdLib has its own sample transformer
- * with [processBody] and [processImports]
- */
-@InternalDokkaApi
-public open class KotlinSampleProvider(
- public val context: DokkaContext
-): SampleProvider {
- private val kDocFinder: KDocFinder = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kdocFinder }
- private val analysis = lazy {
- /**
- * Run from the thread of [Dispatchers.Default]. It can help to avoid a memory leaks in `ThreadLocal`s (that keep `URLCLassLoader`)
- * since we shut down Dispatchers.Default at the end of each task (see [org.jetbrains.dokka.DokkaConfiguration.finalizeCoroutines]).
- * Currently, all `ThreadLocal`s are in a compiler/IDE codebase.
- */
- runBlocking(Dispatchers.Default) {
- @OptIn(DokkaPluginApiPreview::class)
- SamplesKotlinAnalysis(
- sourceSets = context.configuration.sourceSets,
- context = context,
- projectKotlinAnalysis = context.plugin<CompilerDescriptorAnalysisPlugin>()
- .querySingle { kotlinAnalysis }
- )
- }
- }
- protected open fun processBody(psiElement: PsiElement): String {
- val text = processSampleBody(psiElement).trim { it == '\n' || it == '\r' }.trimEnd()
- val lines = text.split("\n")
- val indent = lines.filter(String::isNotBlank).map { it.takeWhile(Char::isWhitespace).count() }.minOrNull() ?: 0
- return lines.joinToString("\n") { it.drop(indent) }
- }
-
- private fun processSampleBody(psiElement: PsiElement): String = when (psiElement) {
- is KtDeclarationWithBody -> {
- when (val bodyExpression = psiElement.bodyExpression) {
- is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}")
- else -> bodyExpression!!.text
- }
- }
- else -> psiElement.text
- }
-
- protected open fun processImports(psiElement: PsiElement): String {
- val psiFile = psiElement.containingFile
- return when(val text = (psiFile as? KtFile)?.importList?.text) {
- is String -> text
- else -> ""
- }
- }
-
- /**
- * @return [SampleProvider.SampleSnippet] or null if it has not found by [fqLink]
- */
- override fun getSample(sourceSet: DokkaConfiguration.DokkaSourceSet, fqLink: String): SampleProvider.SampleSnippet? {
- return runBlocking(Dispatchers.Default) {
- val resolveSession = analysis.value[sourceSet].resolveSession
- val psiElement = fqNameToPsiElement(resolveSession, fqLink, sourceSet)
- ?: return@runBlocking null.also { context.logger.warn("Cannot find PsiElement corresponding to $fqLink") }
- val imports =
- processImports(psiElement)
- val body = processBody(psiElement)
- return@runBlocking SampleProvider.SampleSnippet(imports, body)
- }
- }
- override fun close() {
- if(analysis.isInitialized())
- analysis.value.close()
- }
-
- private fun fqNameToPsiElement(resolveSession: ResolveSession, functionName: String, dokkaSourceSet: DokkaConfiguration.DokkaSourceSet): PsiElement? {
- val packageName = functionName.takeWhile { it != '.' }
- val descriptor = resolveSession.getPackageFragment(FqName(packageName))
- ?: return null.also { context.logger.warn("Cannot find descriptor for package $packageName") }
-
- with (kDocFinder) {
- val symbol = resolveKDocLink(
- descriptor,
- functionName,
- dokkaSourceSet,
- emptyBindingContext = true
- ).firstOrNull() ?: return null.also { context.logger.warn("Unresolved function $functionName in @sample") }
- return org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration(symbol)
- }
- }
-}