diff options
Diffstat (limited to 'subprojects')
6 files changed, 178 insertions, 2 deletions
diff --git a/subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api b/subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api index a057470e..c65dfe5a 100644 --- a/subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api +++ b/subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api @@ -51,6 +51,7 @@ public final class org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAn public final fun getInheritanceBuilder ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; public final fun getKotlinToJavaService ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; public final fun getModuleAndPackageDocumentationReader ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; + public final fun getSampleProviderFactory ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; public final fun getSyntheticDocumentableDetector ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; } @@ -64,6 +65,20 @@ public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/Mod public abstract fun read (Lorg/jetbrains/dokka/model/DPackage;)Ljava/util/Map; } +public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider : java/lang/AutoCloseable { + public abstract fun getSample (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Ljava/lang/String;)Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider$SampleSnippet; +} + +public final class org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider$SampleSnippet { + public fun <init> (Ljava/lang/String;Ljava/lang/String;)V + public final fun getBody ()Ljava/lang/String; + public final fun getImports ()Ljava/lang/String; +} + +public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/SampleProviderFactory { + public abstract fun build ()Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider; +} + public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/SyntheticDocumentableDetector { public abstract fun isSynthetic (Lorg/jetbrains/dokka/model/Documentable;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Z } diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt index 330522f3..30c2c2f8 100644 --- a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt +++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt @@ -26,6 +26,8 @@ class InternalKotlinAnalysisPlugin : DokkaPlugin() { val documentableSourceLanguageParser by extensionPoint<DocumentableSourceLanguageParser>() + val sampleProviderFactory by extensionPoint<SampleProviderFactory>() + @OptIn(DokkaPluginApiPreview::class) override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement } diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider.kt new file mode 100644 index 00000000..195f3c35 --- /dev/null +++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider.kt @@ -0,0 +1,30 @@ +package org.jetbrains.dokka.analysis.kotlin.internal + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.InternalDokkaApi + +@InternalDokkaApi +interface SampleProviderFactory { + /** + * [SampleProvider] is a short-lived closeable instance. + * It assumes that [SampleProvider] scope of use is not big. + * Otherwise, it can lead to high memory consumption / leaks during Dokka running. + */ + fun build(): SampleProvider +} + +/** + * It is closeable. + * Otherwise, there is a chance of high memory consumption / leak. + * In general case, it creates a separate project to analysis samples directories. + */ +@InternalDokkaApi +interface SampleProvider: AutoCloseable { + class SampleSnippet(val imports: String, val body:String) + + + /** + * @return [SampleSnippet] or null if it has not found by [fqLink] + */ + fun getSample(sourceSet: DokkaConfiguration.DokkaSourceSet, fqLink: String): SampleSnippet? +}
\ No newline at end of file diff --git a/subprojects/analysis-kotlin-descriptors/compiler/api/compiler.api b/subprojects/analysis-kotlin-descriptors/compiler/api/compiler.api index 7d53418f..470def08 100644 --- a/subprojects/analysis-kotlin-descriptors/compiler/api/compiler.api +++ b/subprojects/analysis-kotlin-descriptors/compiler/api/compiler.api @@ -66,3 +66,18 @@ 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; +} + diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt index 6d63fe14..2d79affa 100644 --- a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt +++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt @@ -21,6 +21,7 @@ import org.jetbrains.dokka.renderers.PostAction import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation +@Suppress("unused") @InternalDokkaApi class CompilerDescriptorAnalysisPlugin : DokkaPlugin() { @@ -42,7 +43,7 @@ class CompilerDescriptorAnalysisPlugin : DokkaPlugin() { plugin<InternalKotlinAnalysisPlugin>().documentableSourceLanguageParser providing { CompilerDocumentableSourceLanguageParser() } } - internal val defaultKotlinAnalysis by extending { + internal val defaultKotlinAnalysis by extending { kotlinAnalysis providing { ctx -> ProjectKotlinAnalysis( sourceSets = ctx.configuration.sourceSets, @@ -51,7 +52,7 @@ class CompilerDescriptorAnalysisPlugin : DokkaPlugin() { } } - internal val descriptorToDocumentableTranslator by extending { + internal val descriptorToDocumentableTranslator by extending { CoreExtensions.sourceToDocumentableTranslator providing ::DefaultDescriptorToDocumentableTranslator } @@ -63,6 +64,10 @@ class CompilerDescriptorAnalysisPlugin : DokkaPlugin() { plugin<InternalKotlinAnalysisPlugin>().fullClassHierarchyBuilder providing { DescriptorFullClassHierarchyBuilder() } } + internal val kotlinSampleProviderFactory by extending { + plugin<InternalKotlinAnalysisPlugin>().sampleProviderFactory providing ::KotlinSampleProviderFactory + } + internal val descriptorSyntheticDocumentableDetector by extending { plugin<InternalKotlinAnalysisPlugin>().syntheticDocumentableDetector providing { DescriptorSyntheticDocumentableDetector() } } diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt new file mode 100644 index 00000000..73f70b9c --- /dev/null +++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/KotlinSampleProvider.kt @@ -0,0 +1,109 @@ +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.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 + +class KotlinSampleProviderFactory(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 +open class KotlinSampleProvider(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) { + 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) + } + } +}
\ No newline at end of file |