diff options
Diffstat (limited to 'dokka-subprojects/analysis-kotlin-descriptors-compiler')
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) - } - } -} |