aboutsummaryrefslogtreecommitdiff
path: root/subprojects/analysis-kotlin-descriptors/compiler
diff options
context:
space:
mode:
authorIgnat Beresnev <ignat.beresnev@jetbrains.com>2023-07-05 10:04:55 +0200
committerGitHub <noreply@github.com>2023-07-05 10:04:55 +0200
commit9559158bfeeb274e9ccf1b4563f1b23b42afc493 (patch)
tree3ece0887623cfe2b7148af23001867a1dd5e6597 /subprojects/analysis-kotlin-descriptors/compiler
parentcbd9733d3dd2f52992e98e7cebd072091a572529 (diff)
downloaddokka-9559158bfeeb274e9ccf1b4563f1b23b42afc493.tar.gz
dokka-9559158bfeeb274e9ccf1b4563f1b23b42afc493.tar.bz2
dokka-9559158bfeeb274e9ccf1b4563f1b23b42afc493.zip
Decompose Kotlin/Java analysis (#3034)
* Extract analysis into separate modules
Diffstat (limited to 'subprojects/analysis-kotlin-descriptors/compiler')
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/README.md9
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/api/compiler.api68
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/build.gradle.kts21
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/AnalysisContextCreator.kt20
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt135
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDocumentableSourceLanguageParser.kt23
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerExtensionPointProvider.kt14
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/DescriptorFinder.kt10
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KDocFinder.kt30
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KLibService.kt23
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/MockApplicationHack.kt9
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AbsolutePathString.kt3
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisContext.kt94
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisEnvironment.kt599
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/CallableFactory.kt31
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/DRIFactory.kt49
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/DRITargetFactory.kt42
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/Documentable.kt24
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/JvmDependenciesIndexImpl.kt264
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinAnalysis.kt94
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinCliJavaFileManagerImpl.kt304
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/TypeReferenceFactory.kt68
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/CommonKlibModuleInfo.kt25
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaJsKlibLibraryInfo.kt30
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaJsResolverForModuleFactory.kt125
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibLibraryDependencyResolver.kt17
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibLibraryInfo.kt10
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibMetadataCommonDependencyContainer.kt139
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaNativeKlibLibraryInfo.kt50
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaNativeResolverForModuleFactory.kt79
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DefaultSamplesTransformer.kt35
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorFullClassHierarchyBuilder.kt85
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorInheritanceBuilder.kt91
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorKotlinToJavaMapper.kt31
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSyntheticDocumentableDetector.kt33
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/SamplesTransformerImpl.kt153
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/IllegalModuleAndPackageDocumentation.kt7
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentation.kt11
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationFragment.kt9
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationParsingContext.kt71
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationReader.kt113
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationSource.kt14
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/parseModuleAndPackageDocumentation.kt12
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/parseModuleAndPackageDocumentationFragments.kt55
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorDocumentationContent.kt16
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocComment.kt79
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocCommentCreator.kt26
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocCommentParser.kt54
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinAnalysisProjectProvider.kt16
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinAnalysisSourceRootsExtractor.kt27
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinInheritDocTagContentProvider.kt31
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/CollectionExtensions.kt12
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultDescriptorToDocumentableTranslator.kt1275
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultExternalDocumentablesProvider.kt43
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DescriptorAccessorConventionUtil.kt144
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/ExternalClasslikesTranslator.kt12
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/KdocMarkdownParser.kt101
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/SyntheticDescriptorDocumentationProvider.kt46
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/annotationsValue.kt3
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/isException.kt18
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin1
-rw-r--r--subprojects/analysis-kotlin-descriptors/compiler/src/test/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ParseModuleAndPackageDocumentationFragmentsTest.kt282
62 files changed, 5315 insertions, 0 deletions
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/README.md b/subprojects/analysis-kotlin-descriptors/compiler/README.md
new file mode 100644
index 00000000..5676fbf2
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/README.md
@@ -0,0 +1,9 @@
+# Descriptors: compiler
+
+An internal module that encapsulates external compiler (`org.jetbrains.kotlin:kotlin-compiler`) dependencies.
+
+Parses Kotlin sources.
+
+Exists primarily to make sure that unreliable and coupled external dependencies are somewhat abstracted away,
+otherwise everything gets tangled together and breaking changes in such dependencies become very
+difficult to resolve.
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/api/compiler.api b/subprojects/analysis-kotlin-descriptors/compiler/api/compiler.api
new file mode 100644
index 00000000..6c642ad6
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/api/compiler.api
@@ -0,0 +1,68 @@
+public abstract interface class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/AnalysisContextCreator {
+ public abstract fun create (Lcom/intellij/mock/MockProject;Lorg/jetbrains/kotlin/descriptors/ModuleDescriptor;Lorg/jetbrains/kotlin/analyzer/ResolverForModule;Lorg/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment;Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisEnvironment;)Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisContext;
+}
+
+public final class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin {
+ public fun <init> ()V
+ public final fun getAnalysisContextCreator ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getCompilerExtensionPointProvider ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getDescriptorFinder ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ 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 getMockApplicationHack ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerExtensionPointProvider {
+ public abstract fun get ()Ljava/util/List;
+}
+
+public final class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerExtensionPointProvider$CompilerExtensionPoint {
+ public fun <init> (Lorg/jetbrains/kotlin/extensions/ApplicationExtensionDescriptor;Ljava/util/List;)V
+ public final fun getExtensionDescriptor ()Lorg/jetbrains/kotlin/extensions/ApplicationExtensionDescriptor;
+ public final fun getExtensions ()Ljava/util/List;
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/DescriptorFinder {
+ public abstract fun findDescriptor (Lorg/jetbrains/kotlin/psi/KtDeclaration;)Lorg/jetbrains/kotlin/descriptors/DeclarationDescriptor;
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KDocFinder {
+ public abstract fun find (Lorg/jetbrains/kotlin/descriptors/DeclarationDescriptor;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlin/kdoc/psi/impl/KDocTag;
+ public abstract fun findKDoc (Lorg/jetbrains/kotlin/psi/KtElement;)Lorg/jetbrains/kotlin/kdoc/psi/impl/KDocTag;
+ public abstract fun resolveKDocLink (Lorg/jetbrains/kotlin/descriptors/DeclarationDescriptor;Ljava/lang/String;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Z)Ljava/util/Collection;
+}
+
+public final class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KDocFinder$DefaultImpls {
+ public static synthetic fun find$default (Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KDocFinder;Lorg/jetbrains/kotlin/descriptors/DeclarationDescriptor;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/kotlin/kdoc/psi/impl/KDocTag;
+ public static synthetic fun resolveKDocLink$default (Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KDocFinder;Lorg/jetbrains/kotlin/descriptors/DeclarationDescriptor;Ljava/lang/String;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;ZILjava/lang/Object;)Ljava/util/Collection;
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KLibService {
+ public abstract fun createPackageFragmentProvider (Lorg/jetbrains/kotlin/library/KotlinLibrary;Lorg/jetbrains/kotlin/storage/StorageManager;Lorg/jetbrains/kotlin/backend/common/serialization/metadata/KlibMetadataModuleDescriptorFactory;Lorg/jetbrains/kotlin/config/LanguageVersionSettings;Lorg/jetbrains/kotlin/descriptors/ModuleDescriptor;Lorg/jetbrains/kotlin/incremental/components/LookupTracker;)Lorg/jetbrains/kotlin/descriptors/PackageFragmentProvider;
+ public abstract fun isAnalysisCompatible (Lorg/jetbrains/kotlin/library/KotlinLibrary;)Z
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/MockApplicationHack {
+ public abstract fun hack (Lcom/intellij/mock/MockApplication;)V
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisContext : java/io/Closeable {
+ public abstract fun getEnvironment ()Lorg/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment;
+ public abstract fun getModuleDescriptor ()Lorg/jetbrains/kotlin/descriptors/ModuleDescriptor;
+ public abstract fun getProject ()Lcom/intellij/openapi/project/Project;
+ public abstract fun getResolveSession ()Lorg/jetbrains/kotlin/resolve/lazy/ResolveSession;
+}
+
+public final class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisEnvironment : com/intellij/openapi/Disposable {
+ public fun <init> (Lorg/jetbrains/kotlin/cli/common/messages/MessageCollector;Lorg/jetbrains/dokka/Platform;Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerExtensionPointProvider;Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/MockApplicationHack;Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KLibService;)V
+ public fun dispose ()V
+}
+
+public abstract class org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinAnalysis : java/io/Closeable {
+ public fun <init> ()V
+ public fun <init> (Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinAnalysis;)V
+ public synthetic fun <init> (Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinAnalysis;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun get (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisContext;
+}
+
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/build.gradle.kts b/subprojects/analysis-kotlin-descriptors/compiler/build.gradle.kts
new file mode 100644
index 00000000..1b40027d
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ id("org.jetbrains.conventions.kotlin-jvm")
+}
+
+dependencies {
+ compileOnly(projects.core)
+ compileOnly(projects.subprojects.analysisKotlinApi)
+
+ api(libs.kotlin.compiler)
+
+ implementation(projects.subprojects.analysisMarkdownJb)
+ implementation(projects.subprojects.analysisJavaPsi)
+
+ testImplementation(projects.core.contentMatcherTestUtils)
+ testImplementation(projects.core.testApi)
+ testImplementation(platform(libs.junit.bom))
+ testImplementation(libs.junit.jupiter)
+
+ // TODO [beresnev] get rid of it
+ compileOnly(libs.kotlinx.coroutines.core)
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/AnalysisContextCreator.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/AnalysisContextCreator.kt
new file mode 100644
index 00000000..dfba2b3a
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/AnalysisContextCreator.kt
@@ -0,0 +1,20 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler
+
+import com.intellij.mock.MockProject
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.AnalysisContext
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.AnalysisEnvironment
+import org.jetbrains.kotlin.analyzer.ResolverForModule
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+
+@InternalDokkaApi
+interface AnalysisContextCreator {
+ fun create(
+ project: MockProject,
+ moduleDescriptor: ModuleDescriptor,
+ moduleResolver: ResolverForModule,
+ kotlinEnvironment: KotlinCoreEnvironment,
+ analysisEnvironment: AnalysisEnvironment,
+ ): AnalysisContext
+}
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
new file mode 100644
index 00000000..535e628e
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt
@@ -0,0 +1,135 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler
+
+import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute
+import com.intellij.psi.PsiAnnotation
+import org.jetbrains.dokka.CoreExtensions
+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.KotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.ProjectKotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.*
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs.ModuleAndPackageDocumentationReader
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.java.*
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator.DefaultDescriptorToDocumentableTranslator
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator.DefaultExternalDocumentablesProvider
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import org.jetbrains.dokka.plugability.DokkaPluginApiPreview
+import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.renderers.PostAction
+import org.jetbrains.kotlin.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
+import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation
+
+@InternalDokkaApi
+class CompilerDescriptorAnalysisPlugin : DokkaPlugin() {
+
+ val kdocFinder by extensionPoint<KDocFinder>()
+
+ val descriptorFinder by extensionPoint<DescriptorFinder>()
+
+ val klibService by extensionPoint<KLibService>()
+
+ val compilerExtensionPointProvider by extensionPoint<CompilerExtensionPointProvider>()
+
+ val mockApplicationHack by extensionPoint<MockApplicationHack>()
+
+ val analysisContextCreator by extensionPoint<AnalysisContextCreator>()
+
+ val kotlinAnalysis by extensionPoint<KotlinAnalysis>()
+
+ internal val documentableAnalyzerImpl by extending {
+ plugin<InternalKotlinAnalysisPlugin>().documentableSourceLanguageParser providing { CompilerDocumentableSourceLanguageParser() }
+ }
+
+ internal val defaultKotlinAnalysis by extending {
+ kotlinAnalysis providing { ctx ->
+ ProjectKotlinAnalysis(
+ sourceSets = ctx.configuration.sourceSets,
+ context = ctx
+ )
+ }
+ }
+
+ internal val descriptorToDocumentableTranslator by extending {
+ CoreExtensions.sourceToDocumentableTranslator providing ::DefaultDescriptorToDocumentableTranslator
+ }
+
+ internal val defaultSamplesTransformer by extending {
+ CoreExtensions.pageTransformer providing ::DefaultSamplesTransformer
+ }
+
+ internal val descriptorFullClassHierarchyBuilder by extending {
+ plugin<InternalKotlinAnalysisPlugin>().fullClassHierarchyBuilder providing { DescriptorFullClassHierarchyBuilder() }
+ }
+
+ internal val descriptorSyntheticDocumentableDetector by extending {
+ plugin<InternalKotlinAnalysisPlugin>().syntheticDocumentableDetector providing { DescriptorSyntheticDocumentableDetector() }
+ }
+
+ internal val moduleAndPackageDocumentationReader by extending {
+ plugin<InternalKotlinAnalysisPlugin>().moduleAndPackageDocumentationReader providing ::ModuleAndPackageDocumentationReader
+ }
+
+ internal val kotlinToJavaMapper by extending {
+ plugin<InternalKotlinAnalysisPlugin>().kotlinToJavaService providing { DescriptorKotlinToJavaMapper() }
+ }
+
+ internal val descriptorInheritanceBuilder by extending {
+ plugin<InternalKotlinAnalysisPlugin>().inheritanceBuilder providing { DescriptorInheritanceBuilder() }
+ }
+
+ internal val defaultExternalDocumentablesProvider by extending {
+ plugin<InternalKotlinAnalysisPlugin>().externalDocumentablesProvider providing ::DefaultExternalDocumentablesProvider
+ }
+
+ private val javaAnalysisPlugin by lazy { plugin<JavaAnalysisPlugin>() }
+
+ internal val projectProvider by extending {
+ javaAnalysisPlugin.projectProvider providing { KotlinAnalysisProjectProvider() }
+ }
+
+ internal val sourceRootsExtractor by extending {
+ javaAnalysisPlugin.sourceRootsExtractor providing { KotlinAnalysisSourceRootsExtractor() }
+ }
+
+ internal val kotlinDocCommentCreator by extending {
+ javaAnalysisPlugin.docCommentCreators providing {
+ DescriptorKotlinDocCommentCreator(querySingle { kdocFinder }, querySingle { descriptorFinder })
+ }
+ }
+
+ internal val kotlinDocCommentParser by extending {
+ javaAnalysisPlugin.docCommentParsers providing { context ->
+ DescriptorKotlinDocCommentParser(
+ context,
+ context.logger
+ )
+ }
+ }
+
+ internal val inheritDocTagProvider by extending {
+ javaAnalysisPlugin.inheritDocTagContentProviders providing ::KotlinInheritDocTagContentProvider
+ }
+
+ internal val kotlinLightMethodChecker by extending {
+ javaAnalysisPlugin.kotlinLightMethodChecker providing {
+ object : BreakingAbstractionKotlinLightMethodChecker {
+ override fun isLightAnnotation(annotation: PsiAnnotation): Boolean {
+ return annotation is KtLightAbstractAnnotation
+ }
+
+ override fun isLightAnnotationAttribute(attribute: JvmAnnotationAttribute): Boolean {
+ return attribute is KtLightAbstractAnnotation
+ }
+ }
+ }
+ }
+
+ internal val disposeKotlinAnalysisPostAction by extending {
+ CoreExtensions.postActions with PostAction { querySingle { kotlinAnalysis }.close() }
+ }
+
+ @OptIn(DokkaPluginApiPreview::class)
+ override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDocumentableSourceLanguageParser.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDocumentableSourceLanguageParser.kt
new file mode 100644
index 00000000..888ccfa9
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDocumentableSourceLanguageParser.kt
@@ -0,0 +1,23 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.java.util.PsiDocumentableSource
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.DescriptorDocumentableSource
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.WithSources
+import org.jetbrains.kotlin.analysis.kotlin.internal.DocumentableLanguage
+import org.jetbrains.kotlin.analysis.kotlin.internal.DocumentableSourceLanguageParser
+
+internal class CompilerDocumentableSourceLanguageParser : DocumentableSourceLanguageParser {
+ override fun getLanguage(
+ documentable: Documentable,
+ sourceSet: DokkaConfiguration.DokkaSourceSet,
+ ): DocumentableLanguage? {
+ val documentableSource = (documentable as? WithSources)?.sources?.get(sourceSet) ?: return null
+ return when (documentableSource) {
+ is PsiDocumentableSource -> DocumentableLanguage.JAVA
+ is DescriptorDocumentableSource -> DocumentableLanguage.KOTLIN
+ else -> error("Unknown language sources: ${documentableSource::class}")
+ }
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerExtensionPointProvider.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerExtensionPointProvider.kt
new file mode 100644
index 00000000..50bdbb2c
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerExtensionPointProvider.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.kotlin.extensions.ApplicationExtensionDescriptor
+
+@InternalDokkaApi
+interface CompilerExtensionPointProvider {
+ fun get(): List<CompilerExtensionPoint>
+
+ class CompilerExtensionPoint(
+ val extensionDescriptor: ApplicationExtensionDescriptor<Any>,
+ val extensions: List<Any>
+ )
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/DescriptorFinder.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/DescriptorFinder.kt
new file mode 100644
index 00000000..eed62fa3
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/DescriptorFinder.kt
@@ -0,0 +1,10 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.psi.KtDeclaration
+
+@InternalDokkaApi
+interface DescriptorFinder {
+ fun KtDeclaration.findDescriptor(): DeclarationDescriptor?
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KDocFinder.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KDocFinder.kt
new file mode 100644
index 00000000..23d1acfe
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KDocFinder.kt
@@ -0,0 +1,30 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
+
+@InternalDokkaApi
+interface KDocFinder {
+ fun KtElement.findKDoc(): KDocTag?
+
+ fun DeclarationDescriptor.find(
+ descriptorToPsi: (DeclarationDescriptorWithSource) -> PsiElement? = {
+ DescriptorToSourceUtils.descriptorToDeclaration(
+ it
+ )
+ }
+ ): KDocTag?
+
+ fun resolveKDocLink(
+ fromDescriptor: DeclarationDescriptor,
+ qualifiedName: String,
+ sourceSet: DokkaConfiguration.DokkaSourceSet,
+ emptyBindingContext: Boolean = false
+ ): Collection<DeclarationDescriptor>
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KLibService.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KLibService.kt
new file mode 100644
index 00000000..ceb5536a
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KLibService.kt
@@ -0,0 +1,23 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataModuleDescriptorFactory
+import org.jetbrains.kotlin.config.LanguageVersionSettings
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
+import org.jetbrains.kotlin.incremental.components.LookupTracker
+import org.jetbrains.kotlin.library.KotlinLibrary
+import org.jetbrains.kotlin.storage.StorageManager
+
+@InternalDokkaApi
+interface KLibService {
+ fun KotlinLibrary.createPackageFragmentProvider(
+ storageManager: StorageManager,
+ metadataModuleDescriptorFactory: KlibMetadataModuleDescriptorFactory,
+ languageVersionSettings: LanguageVersionSettings,
+ moduleDescriptor: ModuleDescriptor,
+ lookupTracker: LookupTracker
+ ): PackageFragmentProvider?
+
+ fun isAnalysisCompatible(kotlinLibrary: KotlinLibrary): Boolean
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/MockApplicationHack.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/MockApplicationHack.kt
new file mode 100644
index 00000000..77a4e083
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/MockApplicationHack.kt
@@ -0,0 +1,9 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler
+
+import com.intellij.mock.MockApplication
+import org.jetbrains.dokka.InternalDokkaApi
+
+@InternalDokkaApi
+interface MockApplicationHack { // ¯\_(ツ)_/¯
+ fun hack(mockApplication: MockApplication)
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AbsolutePathString.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AbsolutePathString.kt
new file mode 100644
index 00000000..f1d35752
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AbsolutePathString.kt
@@ -0,0 +1,3 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+internal typealias AbsolutePathString = String
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisContext.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisContext.kt
new file mode 100644
index 00000000..89ae8810
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisContext.kt
@@ -0,0 +1,94 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+import com.intellij.openapi.project.Project
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.AnalysisContextCreator
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
+import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.resolve.lazy.ResolveSession
+import java.io.Closeable
+import java.io.File
+
+internal fun createAnalysisContext(
+ context: DokkaContext,
+ sourceSets: List<DokkaConfiguration.DokkaSourceSet>,
+ sourceSet: DokkaConfiguration.DokkaSourceSet,
+ analysisConfiguration: DokkaAnalysisConfiguration
+): AnalysisContext {
+ val parentSourceSets = sourceSets.filter { it.sourceSetID in sourceSet.dependentSourceSets }
+ val classpath = sourceSet.classpath + parentSourceSets.flatMap { it.classpath }
+ val sources = sourceSet.sourceRoots + parentSourceSets.flatMap { it.sourceRoots }
+
+ return createAnalysisContext(
+ context = context,
+ classpath = classpath,
+ sourceRoots = sources,
+ sourceSet = sourceSet,
+ analysisConfiguration = analysisConfiguration
+ )
+}
+
+internal fun createAnalysisContext(
+ context: DokkaContext,
+ classpath: List<File>,
+ sourceRoots: Set<File>,
+ sourceSet: DokkaConfiguration.DokkaSourceSet,
+ analysisConfiguration: DokkaAnalysisConfiguration
+): AnalysisContext {
+ val analysisEnvironment = AnalysisEnvironment(
+ DokkaMessageCollector(context.logger),
+ sourceSet.analysisPlatform,
+ context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { compilerExtensionPointProvider },
+ context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { mockApplicationHack },
+ context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { klibService },
+ ).apply {
+ if (analysisPlatform == Platform.jvm) {
+ configureJdkClasspathRoots()
+ }
+ addClasspath(classpath)
+ addSources(sourceRoots)
+
+ loadLanguageVersionSettings(sourceSet.languageVersion, sourceSet.apiVersion)
+ }
+
+ val environment = analysisEnvironment.createCoreEnvironment()
+ return analysisEnvironment.createResolutionFacade(
+ environment,
+ context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle<CompilerDescriptorAnalysisPlugin, AnalysisContextCreator> { analysisContextCreator },
+ analysisConfiguration.ignoreCommonBuiltIns
+ )
+}
+
+internal class DokkaMessageCollector(private val logger: DokkaLogger) : MessageCollector {
+ override fun clear() {
+ seenErrors = false
+ }
+
+ private var seenErrors = false
+
+ override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageSourceLocation?) {
+ if (severity == CompilerMessageSeverity.ERROR) {
+ seenErrors = true
+ }
+ logger.info(MessageRenderer.PLAIN_FULL_PATHS.render(severity, message, location))
+ }
+
+ override fun hasErrors() = seenErrors
+}
+
+interface AnalysisContext : Closeable {
+ val environment: KotlinCoreEnvironment
+ val resolveSession: ResolveSession
+ val moduleDescriptor: ModuleDescriptor
+ val project: Project
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisEnvironment.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisEnvironment.kt
new file mode 100644
index 00000000..611d1325
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisEnvironment.kt
@@ -0,0 +1,599 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+import com.intellij.core.CoreApplicationEnvironment
+import com.intellij.mock.MockApplication
+import com.intellij.mock.MockComponentManager
+import com.intellij.mock.MockProject
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.extensions.Extensions
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.Disposer
+import com.intellij.openapi.vfs.StandardFileSystems
+import com.intellij.psi.PsiNameHelper
+import com.intellij.psi.impl.PsiNameHelperImpl
+import com.intellij.psi.impl.source.javadoc.JavadocManagerImpl
+import com.intellij.psi.javadoc.CustomJavadocTagProvider
+import com.intellij.psi.javadoc.JavadocManager
+import com.intellij.psi.javadoc.JavadocTagInfo
+import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.AnalysisContextCreator
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerExtensionPointProvider
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KLibService
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.MockApplicationHack
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.resolve.*
+import org.jetbrains.kotlin.analyzer.*
+import org.jetbrains.kotlin.analyzer.common.CommonAnalysisParameters
+import org.jetbrains.kotlin.analyzer.common.CommonDependenciesContainer
+import org.jetbrains.kotlin.analyzer.common.CommonPlatformAnalyzerServices
+import org.jetbrains.kotlin.analyzer.common.CommonResolverForModuleFactory
+import org.jetbrains.kotlin.builtins.DefaultBuiltIns
+import org.jetbrains.kotlin.builtins.KotlinBuiltIns
+import org.jetbrains.kotlin.builtins.jvm.JvmBuiltIns
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
+import org.jetbrains.kotlin.cli.common.config.ContentRoot
+import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
+import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
+import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
+import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
+import org.jetbrains.kotlin.cli.jvm.config.*
+import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
+import org.jetbrains.kotlin.config.*
+import org.jetbrains.kotlin.context.ProjectContext
+import org.jetbrains.kotlin.context.withModule
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
+import org.jetbrains.kotlin.extensions.ApplicationExtensionDescriptor
+import org.jetbrains.kotlin.js.config.JSConfigurationKeys
+import org.jetbrains.kotlin.js.resolve.JsPlatformAnalyzerServices
+import org.jetbrains.kotlin.library.KLIB_FILE_EXTENSION
+import org.jetbrains.kotlin.library.KotlinLibrary
+import org.jetbrains.kotlin.library.ToolingSingleFileKlibResolveStrategy
+import org.jetbrains.kotlin.library.resolveSingleFileKlib
+import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
+import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.platform.CommonPlatforms
+import org.jetbrains.kotlin.platform.TargetPlatform
+import org.jetbrains.kotlin.platform.js.JsPlatforms
+import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
+import org.jetbrains.kotlin.platform.jvm.JvmPlatforms.unspecifiedJvmPlatform
+import org.jetbrains.kotlin.platform.konan.NativePlatforms
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.resolve.CliSealedClassInheritorsProvider
+import org.jetbrains.kotlin.resolve.CompilerEnvironment
+import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
+import org.jetbrains.kotlin.resolve.jvm.JvmPlatformParameters
+import org.jetbrains.kotlin.resolve.jvm.JvmResolverForModuleFactory
+import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices
+import org.jetbrains.kotlin.resolve.konan.platform.NativePlatformAnalyzerServices
+import org.jetbrains.kotlin.storage.LockBasedStorageManager
+import java.io.File
+import org.jetbrains.kotlin.konan.file.File as KFile
+
+internal const val JAR_SEPARATOR = "!/"
+
+/**
+ * Kotlin as a service entry point
+ *
+ * Configures environment, analyses files and provides facilities to perform code processing without emitting bytecode
+ *
+ * $messageCollector: required by compiler infrastructure and will receive all compiler messages
+ * $body: optional and can be used to configure environment without creating local variable
+ */
+@InternalDokkaApi
+class AnalysisEnvironment(
+ private val messageCollector: MessageCollector,
+ internal val analysisPlatform: Platform,
+ private val compilerExtensionPointProvider: CompilerExtensionPointProvider,
+ private val mockApplicationHack: MockApplicationHack,
+ private val kLibService: KLibService,
+) : Disposable {
+ private val configuration = CompilerConfiguration()
+
+ init {
+ configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
+ }
+
+ internal fun createCoreEnvironment(): KotlinCoreEnvironment {
+ System.setProperty("idea.io.use.nio2", "true")
+ System.setProperty("idea.ignore.disabled.plugins", "true")
+
+ val configFiles = when (analysisPlatform) {
+ Platform.jvm, Platform.common -> EnvironmentConfigFiles.JVM_CONFIG_FILES
+ Platform.native -> EnvironmentConfigFiles.NATIVE_CONFIG_FILES
+ Platform.js, Platform.wasm -> EnvironmentConfigFiles.JS_CONFIG_FILES
+ }
+
+ val environment = KotlinCoreEnvironment.createForProduction(this, configuration, configFiles)
+ val projectComponentManager = environment.project as MockComponentManager
+
+ CoreApplicationEnvironment.registerExtensionPoint(
+ environment.project.extensionArea,
+ JavadocTagInfo.EP_NAME, JavadocTagInfo::class.java
+ )
+
+ @Suppress("DEPRECATION")
+ val extensionArea = Extensions.getRootArea()
+
+ CoreApplicationEnvironment.registerExtensionPoint(
+ extensionArea,
+ CustomJavadocTagProvider.EP_NAME, CustomJavadocTagProvider::class.java
+ )
+
+ // TODO: figure out why compilation fails with unresolved `CoreApplicationEnvironment.registerApplicationService(...)`
+ // call, fix it appropriately
+ with(ApplicationManager.getApplication() as MockApplication) {
+ mockApplicationHack.hack(this)
+ }
+
+ projectComponentManager.registerService(
+ JavadocManager::class.java,
+ JavadocManagerImpl(environment.project)
+ )
+
+ projectComponentManager.registerService(
+ PsiNameHelper::class.java,
+ PsiNameHelperImpl(environment.project)
+ )
+
+ projectComponentManager.registerService(
+ CustomJavadocTagProvider::class.java,
+ CustomJavadocTagProvider { emptyList() }
+ )
+
+ compilerExtensionPointProvider.get().forEach { extension ->
+ registerExtensionPoint(extension.extensionDescriptor, extension.extensions, this)
+ }
+ return environment
+ }
+
+ private fun createSourceModuleSearchScope(project: Project, sourceFiles: List<KtFile>): GlobalSearchScope =
+ when (analysisPlatform) {
+ Platform.jvm -> TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, sourceFiles)
+ Platform.js, Platform.common, Platform.native, Platform.wasm -> GlobalSearchScope.filesScope(
+ project,
+ sourceFiles.map { it.virtualFile }.toSet()
+ )
+ }
+
+ internal fun createResolutionFacade(
+ environment: KotlinCoreEnvironment,
+ analysisContextCreator: AnalysisContextCreator,
+ ignoreCommonBuiltIns: Boolean = false
+ ): AnalysisContext {
+ val projectContext = ProjectContext(environment.project, "Dokka")
+ val sourceFiles = environment.getSourceFiles()
+
+ val targetPlatform = when (analysisPlatform) {
+ Platform.js, Platform.wasm -> JsPlatforms.defaultJsPlatform
+ Platform.common -> CommonPlatforms.defaultCommonPlatform
+ Platform.native -> NativePlatforms.unspecifiedNativePlatform
+ Platform.jvm -> JvmPlatforms.defaultJvmPlatform
+ }
+
+ val kotlinLibraries: Map<AbsolutePathString, KotlinLibrary> = resolveKotlinLibraries()
+
+ val commonDependencyContainer = if (analysisPlatform == Platform.common) DokkaKlibMetadataCommonDependencyContainer(
+ kotlinLibraries.values.toList(),
+ environment.configuration,
+ LockBasedStorageManager("DokkaKlibMetadata")
+ ) else null
+
+ val extraModuleDependencies = kotlinLibraries.values.registerLibraries() + commonDependencyContainer?.moduleInfos.orEmpty()
+
+ val library = object : LibraryModuleInfo {
+ override val analyzerServices: PlatformDependentAnalyzerServices =
+ analysisPlatform.analyzerServices()
+ override val name: Name = Name.special("<library>")
+ override val platform: TargetPlatform = targetPlatform
+ override fun dependencies(): List<ModuleInfo> = listOf(this)
+ override fun getLibraryRoots(): Collection<String> = classpath
+ .map { libraryFile -> libraryFile.absolutePath }
+ .filter { path -> path !in kotlinLibraries }
+ }
+
+ val module = object : ModuleInfo {
+ override val analyzerServices: PlatformDependentAnalyzerServices =
+ analysisPlatform.analyzerServices()
+ override val name: Name = Name.special("<module>")
+ override val platform: TargetPlatform = targetPlatform
+ override fun dependencies(): List<ModuleInfo> =
+ listOf(this, library) + extraModuleDependencies
+
+ /**
+ * Only for common platform ignore BuiltIns for StdLib since it can cause a conflict
+ * between BuiltIns from a compiler and ones from source code.
+ */
+ override fun dependencyOnBuiltIns(): ModuleInfo.DependencyOnBuiltIns {
+ return if (analysisPlatform == Platform.common && ignoreCommonBuiltIns) ModuleInfo.DependencyOnBuiltIns.NONE
+ else super.dependencyOnBuiltIns()
+ }
+ }
+
+ val sourcesScope = createSourceModuleSearchScope(environment.project, sourceFiles)
+ val modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo> = {
+ when (it) {
+ library -> ModuleContent(it, emptyList(), GlobalSearchScope.notScope(sourcesScope))
+ module -> ModuleContent(it, emptyList(), GlobalSearchScope.allScope(environment.project))
+ is DokkaKlibLibraryInfo -> {
+ if (it.libraryRoot in kotlinLibraries)
+ ModuleContent(it, emptyList(), GlobalSearchScope.notScope(sourcesScope))
+ else null
+ }
+ is CommonKlibModuleInfo -> ModuleContent(it, emptyList(), GlobalSearchScope.notScope(sourcesScope))
+ else -> null
+ } ?: throw IllegalArgumentException("Unexpected module info")
+ }
+
+ var builtIns: JvmBuiltIns? = null
+
+ val resolverForProject = when (analysisPlatform) {
+ Platform.jvm -> {
+ builtIns = JvmBuiltIns(
+ projectContext.storageManager,
+ JvmBuiltIns.Kind.FROM_CLASS_LOADER
+ ) // TODO we should use FROM_DEPENDENCIES
+ createJvmResolverForProject(
+ projectContext,
+ module,
+ library,
+ modulesContent,
+ sourcesScope,
+ builtIns
+ )
+ }
+ Platform.common -> createCommonResolverForProject(
+ projectContext,
+ module,
+ modulesContent,
+ environment,
+ commonDependencyContainer
+ )
+ Platform.js, Platform.wasm -> createJsResolverForProject(projectContext, module, modulesContent)
+ Platform.native -> createNativeResolverForProject(projectContext, module, modulesContent)
+
+ }
+ @Suppress("UNUSED_VARIABLE") // BEWARE!!!! IT's UNUSED, but without it some things don't work
+ val libraryModuleDescriptor = resolverForProject.descriptorForModule(library)
+
+ val moduleDescriptor = resolverForProject.descriptorForModule(module)
+ builtIns?.initialize(moduleDescriptor, true)
+
+ @Suppress("UNUSED_VARIABLE") // BEWARE!!!! IT's UNUSED, but without it some things don't work
+ val resolverForLibrary = resolverForProject.resolverForModule(library) // Required before module to initialize library properly
+
+ val resolverForModule = resolverForProject.resolverForModule(module)
+
+ return analysisContextCreator.create(
+ environment.project as MockProject,
+ moduleDescriptor,
+ resolverForModule,
+ environment,
+ this
+ )
+ }
+
+ private fun Platform.analyzerServices() = when (this) {
+ Platform.js, Platform.wasm -> JsPlatformAnalyzerServices
+ Platform.common -> CommonPlatformAnalyzerServices
+ Platform.native -> NativePlatformAnalyzerServices
+ Platform.jvm -> JvmPlatformAnalyzerServices
+ }
+
+ private fun Collection<KotlinLibrary>.registerLibraries(): List<DokkaKlibLibraryInfo> {
+ if (analysisPlatform != Platform.native && analysisPlatform != Platform.js && analysisPlatform != Platform.wasm) return emptyList()
+ val dependencyResolver = DokkaKlibLibraryDependencyResolver()
+ val analyzerServices = analysisPlatform.analyzerServices()
+
+ return map { kotlinLibrary ->
+ if (analysisPlatform == org.jetbrains.dokka.Platform.native) DokkaNativeKlibLibraryInfo(
+ kotlinLibrary,
+ analyzerServices,
+ dependencyResolver
+ )
+ else DokkaJsKlibLibraryInfo(kotlinLibrary, analyzerServices, dependencyResolver)
+ }
+ }
+
+ @OptIn(ExperimentalStdlibApi::class)
+ private fun resolveKotlinLibraries(): Map<AbsolutePathString, KotlinLibrary> {
+ return if (analysisPlatform == Platform.jvm) emptyMap() else buildMap {
+ classpath
+ .filter { it.isDirectory || it.extension == KLIB_FILE_EXTENSION }
+ .forEach { libraryFile ->
+ try {
+ val kotlinLibrary = resolveSingleFileKlib(
+ libraryFile = KFile(libraryFile.absolutePath),
+ strategy = ToolingSingleFileKlibResolveStrategy
+ )
+ if (kLibService.isAnalysisCompatible(kotlinLibrary)) {
+ // exists, is KLIB, has compatible format
+ put(
+ libraryFile.absolutePath,
+ kotlinLibrary
+ )
+ }
+ } catch (e: Throwable) {
+ configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
+ .report(CompilerMessageSeverity.WARNING, "Can not resolve KLIB. " + e.message)
+ }
+ }
+ }
+ }
+
+ private fun createCommonResolverForProject(
+ projectContext: ProjectContext,
+ module: ModuleInfo,
+ modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo>,
+ environment: KotlinCoreEnvironment,
+ dependencyContainer: CommonDependenciesContainer?
+ ): ResolverForProject<ModuleInfo> {
+ return object : AbstractResolverForProject<ModuleInfo>(
+ "Dokka",
+ projectContext,
+ modules = module.dependencies()
+ ) {
+ override fun modulesContent(module: ModuleInfo): ModuleContent<ModuleInfo> = modulesContent(module)
+
+ override fun builtInsForModule(module: ModuleInfo): KotlinBuiltIns = DefaultBuiltIns.Instance
+
+ override fun createResolverForModule(
+ descriptor: ModuleDescriptor,
+ moduleInfo: ModuleInfo
+ ): ResolverForModule =
+ CommonResolverForModuleFactory(
+ CommonAnalysisParameters(
+ metadataPartProviderFactory = { content ->
+ environment.createPackagePartProvider(content.moduleContentScope)
+ }
+ ),
+ CompilerEnvironment,
+ unspecifiedJvmPlatform,
+ true,
+ dependencyContainer
+ ).createResolverForModule(
+ descriptor as ModuleDescriptorImpl,
+ projectContext.withModule(descriptor),
+ modulesContent(moduleInfo),
+ this,
+ LanguageVersionSettingsImpl.DEFAULT,
+ CliSealedClassInheritorsProvider,
+ )
+
+ override fun sdkDependency(module: ModuleInfo): ModuleInfo? = null
+ }
+ }
+
+ private fun createJsResolverForProject(
+ projectContext: ProjectContext,
+ module: ModuleInfo,
+ modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo>
+ ): ResolverForProject<ModuleInfo> {
+ return object : AbstractResolverForProject<ModuleInfo>(
+ "Dokka",
+ projectContext,
+ modules = module.dependencies()
+ ) {
+ override fun modulesContent(module: ModuleInfo): ModuleContent<ModuleInfo> = modulesContent(module)
+ override fun createResolverForModule(
+ descriptor: ModuleDescriptor,
+ moduleInfo: ModuleInfo
+ ): ResolverForModule = DokkaJsResolverForModuleFactory(CompilerEnvironment, kLibService).createResolverForModule(
+ descriptor as ModuleDescriptorImpl,
+ projectContext.withModule(descriptor),
+ modulesContent(moduleInfo),
+ this,
+ LanguageVersionSettingsImpl.DEFAULT,
+ CliSealedClassInheritorsProvider,
+ )
+
+ override fun builtInsForModule(module: ModuleInfo): KotlinBuiltIns = DefaultBuiltIns.Instance
+
+ override fun sdkDependency(module: ModuleInfo): ModuleInfo? = null
+ }
+ }
+
+ private fun createNativeResolverForProject(
+ projectContext: ProjectContext,
+ module: ModuleInfo,
+ modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo>
+ ): ResolverForProject<ModuleInfo> {
+ return object : AbstractResolverForProject<ModuleInfo>(
+ "Dokka",
+ projectContext,
+ modules = module.dependencies()
+ ) {
+ override fun modulesContent(module: ModuleInfo): ModuleContent<ModuleInfo> = modulesContent(module)
+ override fun createResolverForModule(
+ descriptor: ModuleDescriptor,
+ moduleInfo: ModuleInfo
+ ): ResolverForModule {
+
+ return DokkaNativeResolverForModuleFactory(CompilerEnvironment, kLibService).createResolverForModule(
+ descriptor as ModuleDescriptorImpl,
+ projectContext.withModule(descriptor),
+ modulesContent(moduleInfo),
+ this,
+ LanguageVersionSettingsImpl.DEFAULT,
+ CliSealedClassInheritorsProvider,
+ )
+ }
+
+ override fun builtInsForModule(module: ModuleInfo): KotlinBuiltIns = DefaultBuiltIns.Instance
+
+ override fun sdkDependency(module: ModuleInfo): ModuleInfo? = null
+ }
+ }
+
+ private fun createJvmResolverForProject(
+ projectContext: ProjectContext,
+ module: ModuleInfo,
+ library: LibraryModuleInfo,
+ modulesContent: (ModuleInfo) -> ModuleContent<ModuleInfo>,
+ sourcesScope: GlobalSearchScope,
+ builtIns: KotlinBuiltIns
+ ): ResolverForProject<ModuleInfo> {
+ val javaRoots = classpath
+ .mapNotNull { file ->
+ val rootFile = when (file.extension) {
+ "jar" -> StandardFileSystems.jar().findFileByPath("${file.absolutePath}$JAR_SEPARATOR")
+ else -> StandardFileSystems.local().findFileByPath(file.absolutePath)
+ }
+ rootFile?.let { JavaRoot(it, JavaRoot.RootType.BINARY) }
+ }
+
+ return object : AbstractResolverForProject<ModuleInfo>(
+ "Dokka",
+ projectContext,
+ modules = listOf(module, library)
+ ) {
+ override fun modulesContent(module: ModuleInfo): ModuleContent<ModuleInfo> =
+ when (module) {
+ library -> ModuleContent(module, emptyList(), GlobalSearchScope.notScope(sourcesScope))
+ module -> ModuleContent(module, emptyList(), sourcesScope)
+ else -> throw IllegalArgumentException("Unexpected module info")
+ }
+
+ override fun builtInsForModule(module: ModuleInfo): KotlinBuiltIns = builtIns
+
+ override fun createResolverForModule(
+ descriptor: ModuleDescriptor,
+ moduleInfo: ModuleInfo
+ ): ResolverForModule = JvmResolverForModuleFactory(
+ JvmPlatformParameters(packagePartProviderFactory = { content ->
+ JvmPackagePartProvider(
+ configuration.languageVersionSettings,
+ content.moduleContentScope
+ )
+ .apply {
+ addRoots(javaRoots, messageCollector)
+ }
+ }, moduleByJavaClass = {
+ val file =
+ (it as? BinaryJavaClass)?.virtualFile ?: (it as JavaClassImpl).psi.containingFile.virtualFile
+ if (file in sourcesScope)
+ module
+ else
+ library
+ }, resolverForReferencedModule = null,
+ useBuiltinsProviderForModule = { false }),
+ CompilerEnvironment,
+ unspecifiedJvmPlatform
+ ).createResolverForModule(
+ descriptor as ModuleDescriptorImpl,
+ projectContext.withModule(descriptor),
+ modulesContent(moduleInfo),
+ this,
+ configuration.languageVersionSettings,
+ CliSealedClassInheritorsProvider,
+ )
+
+ override fun sdkDependency(module: ModuleInfo): ModuleInfo? = null
+ }
+ }
+
+ internal fun loadLanguageVersionSettings(languageVersionString: String?, apiVersionString: String?) {
+ val languageVersion = LanguageVersion.fromVersionString(languageVersionString) ?: LanguageVersion.LATEST_STABLE
+ val apiVersion =
+ apiVersionString?.let { ApiVersion.parse(it) } ?: ApiVersion.createByLanguageVersion(languageVersion)
+ configuration.languageVersionSettings = LanguageVersionSettingsImpl(
+ languageVersion = languageVersion,
+ apiVersion = apiVersion, analysisFlags = hashMapOf(
+ // force to resolve light classes (lazily by default)
+ AnalysisFlags.eagerResolveOfLightClasses to true
+ )
+ )
+ }
+
+ /**
+ * Classpath for this environment.
+ */
+ private val classpath: List<File>
+ get() = configuration.jvmClasspathRoots + configuration.getList(JSConfigurationKeys.LIBRARIES)
+ .mapNotNull { File(it) }
+
+ /**
+ * Adds list of paths to classpath.
+ * $paths: collection of files to add
+ */
+ internal fun addClasspath(paths: List<File>) {
+ if (analysisPlatform == Platform.js || analysisPlatform == Platform.wasm) {
+ configuration.addAll(JSConfigurationKeys.LIBRARIES, paths.map { it.absolutePath })
+ } else {
+ configuration.addJvmClasspathRoots(paths)
+ }
+ }
+
+ // Set up JDK classpath roots explicitly because of https://github.com/JetBrains/kotlin/commit/f89765eb33dd95c8de33a919cca83651b326b246
+ internal fun configureJdkClasspathRoots() = configuration.configureJdkClasspathRoots()
+ /**
+ * Adds path to classpath.
+ * $path: path to add
+ */
+ internal fun addClasspath(path: File) {
+ if (analysisPlatform == Platform.js || analysisPlatform == Platform.wasm) {
+ configuration.add(JSConfigurationKeys.LIBRARIES, path.absolutePath)
+ } else {
+ configuration.addJvmClasspathRoot(path)
+ }
+ }
+
+ /**
+ * List of source roots for this environment.
+ */
+ internal val sources: List<String>
+ get() = configuration.get(CLIConfigurationKeys.CONTENT_ROOTS)
+ ?.filterIsInstance<KotlinSourceRoot>()
+ ?.map { it.path } ?: emptyList()
+
+ /**
+ * Adds list of paths to source roots.
+ * $list: collection of files to add
+ */
+ internal fun addSources(sourceDirectories: Iterable<File>) {
+ sourceDirectories.forEach { directory ->
+ configuration.addKotlinSourceRoot(directory.path)
+ if (directory.isDirectory || directory.extension == "java") {
+ configuration.addJavaSourceRoot(directory)
+ }
+ }
+ }
+
+ internal fun addRoots(list: List<ContentRoot>) {
+ configuration.addAll(CLIConfigurationKeys.CONTENT_ROOTS, list)
+ }
+
+ /**
+ * Disposes the environment and frees all associated resources.
+ */
+ override fun dispose() {
+ Disposer.dispose(this)
+ }
+
+ private companion object {
+ private fun <T : Any> registerExtensionPoint(
+ appExtension: ApplicationExtensionDescriptor<T>,
+ instances: List<T>,
+ disposable: Disposable
+ ) {
+ @Suppress("DEPRECATION")
+ val extensionArea = Extensions.getRootArea()
+
+ if (extensionArea.hasExtensionPoint(appExtension.extensionPointName)) {
+ return
+ }
+
+ appExtension.registerExtensionPoint()
+ instances.forEach { extension -> appExtension.registerExtension(extension, disposable) }
+ }
+ }
+}
+
+
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/CallableFactory.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/CallableFactory.kt
new file mode 100644
index 00000000..0cc17219
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/CallableFactory.kt
@@ -0,0 +1,31 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+import com.intellij.psi.PsiField
+import com.intellij.psi.PsiMethod
+import org.jetbrains.dokka.links.Callable
+import org.jetbrains.dokka.links.JavaClassReference
+import org.jetbrains.dokka.links.TypeReference
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
+
+internal fun Callable.Companion.from(descriptor: CallableDescriptor, name: String? = null) = with(descriptor) {
+ Callable(
+ name ?: descriptor.name.asString(),
+ extensionReceiverParameter?.let { TypeReference.from(it) },
+ valueParameters.mapNotNull { TypeReference.from(it) }
+ )
+}
+
+internal fun Callable.Companion.from(psi: PsiMethod) = with(psi) {
+ Callable(
+ name,
+ null,
+ parameterList.parameters.map { param -> JavaClassReference(param.type.canonicalText) })
+}
+
+internal fun Callable.Companion.from(psi: PsiField): Callable {
+ return Callable(
+ name = psi.name,
+ receiver = null,
+ params = emptyList()
+ )
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/DRIFactory.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/DRIFactory.kt
new file mode 100644
index 00000000..726d6dbc
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/DRIFactory.kt
@@ -0,0 +1,49 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+import com.intellij.psi.*
+import org.jetbrains.dokka.links.*
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor
+import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
+import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
+
+internal fun DRI.Companion.from(descriptor: DeclarationDescriptor) = descriptor.parentsWithSelf.run {
+ val parameter = firstIsInstanceOrNull<ValueParameterDescriptor>()
+ val callable = parameter?.containingDeclaration ?: firstIsInstanceOrNull<CallableDescriptor>()
+
+ DRI(
+ packageName = firstIsInstanceOrNull<PackageFragmentDescriptor>()?.fqName?.asString() ?: "",
+ classNames = (filterIsInstance<ClassDescriptor>() + filterIsInstance<TypeAliasDescriptor>()).toList()
+ .takeIf { it.isNotEmpty() }
+ ?.asReversed()
+ ?.joinToString(separator = ".") { it.name.asString() },
+ callable = callable?.let { Callable.from(it) },
+ target = DriTarget.from(parameter ?: descriptor),
+ extra = if (descriptor is EnumEntrySyntheticClassDescriptor || (descriptor as? ClassDescriptor)?.kind == ClassKind.ENUM_ENTRY)
+ DRIExtraContainer().also { it[EnumEntryDRIExtra] = EnumEntryDRIExtra }.encode()
+ else null
+ )
+}
+
+internal fun DRI.Companion.from(psi: PsiElement) = psi.parentsWithSelf.run {
+ val psiMethod = firstIsInstanceOrNull<PsiMethod>()
+ val psiField = firstIsInstanceOrNull<PsiField>()
+ val classes = filterIsInstance<PsiClass>().filterNot { it is PsiTypeParameter }
+ .toList() // We only want exact PsiClass types, not PsiTypeParameter subtype
+ val additionalClasses = if (psi is PsiEnumConstant) listOfNotNull(psiField?.name) else emptyList()
+ DRI(
+ packageName = classes.lastOrNull()?.qualifiedName?.substringBeforeLast('.', "") ?: "",
+ classNames = (additionalClasses + classes.mapNotNull { it.name }).takeIf { it.isNotEmpty() }
+ ?.asReversed()?.joinToString("."),
+ // The fallback strategy test whether psi is not `PsiEnumConstant`. The reason behind this is that
+ // we need unified DRI for both Java and Kotlin enums, so we can link them properly and treat them alike.
+ // To achieve that, we append enum name to classNames list and leave the callable part set to null. For Kotlin enums
+ // it is by default, while for Java enums we have to explicitly test for that in this `takeUnless` condition.
+ callable = psiMethod?.let { Callable.from(it) } ?: psiField?.takeUnless { psi is PsiEnumConstant }?.let { Callable.from(it) },
+ target = DriTarget.from(psi),
+ extra = if (psi is PsiEnumConstant)
+ DRIExtraContainer().also { it[EnumEntryDRIExtra] = EnumEntryDRIExtra }.encode()
+ else null
+ )
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/DRITargetFactory.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/DRITargetFactory.kt
new file mode 100644
index 00000000..7f39cf94
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/DRITargetFactory.kt
@@ -0,0 +1,42 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiParameter
+import com.intellij.psi.PsiTypeParameter
+import org.jetbrains.dokka.links.DriTarget
+import org.jetbrains.dokka.links.PointingToCallableParameters
+import org.jetbrains.dokka.links.PointingToDeclaration
+import org.jetbrains.dokka.links.PointingToGenericParameters
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
+import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
+
+internal fun DriTarget.Companion.from(descriptor: DeclarationDescriptor): DriTarget = descriptor.parentsWithSelf.run {
+ return when (descriptor) {
+ is TypeParameterDescriptor -> PointingToGenericParameters(descriptor.index)
+ is ValueParameterDescriptor -> PointingToCallableParameters(descriptor.index)
+ else -> {
+ val callable = firstIsInstanceOrNull<CallableDescriptor>()
+ val params =
+ callable?.let { listOfNotNull(it.extensionReceiverParameter) + it.valueParameters }.orEmpty()
+ val parameterDescriptor = firstIsInstanceOrNull<ParameterDescriptor>()
+
+ parameterDescriptor?.let { PointingToCallableParameters(params.indexOf(it)) }
+ ?: PointingToDeclaration
+ }
+ }
+}
+
+
+internal fun DriTarget.Companion.from(psi: PsiElement): DriTarget = psi.parentsWithSelf.run {
+ return when (psi) {
+ is PsiTypeParameter -> PointingToGenericParameters(psi.index)
+ else -> firstIsInstanceOrNull<PsiParameter>()?.let {
+ val callable = firstIsInstanceOrNull<PsiMethod>()
+ val params = (callable?.parameterList?.parameters).orEmpty()
+ PointingToCallableParameters(params.indexOf(it))
+ } ?: PointingToDeclaration
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/Documentable.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/Documentable.kt
new file mode 100644
index 00000000..55a93ad5
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/Documentable.kt
@@ -0,0 +1,24 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+import com.intellij.psi.PsiDocumentManager
+import org.jetbrains.dokka.model.DocumentableSource
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.load.kotlin.toSourceElement
+import org.jetbrains.kotlin.resolve.source.getPsi
+
+internal class DescriptorDocumentableSource(val descriptor: DeclarationDescriptor) : DocumentableSource {
+ override val path = descriptor.toSourceElement.containingFile.toString()
+
+ override fun computeLineNumber(): Int? {
+ return (this.descriptor as DeclarationDescriptorWithSource)
+ .source.getPsi()
+ ?.let {
+ val range = it.node?.findChildByType(KtTokens.IDENTIFIER)?.textRange ?: it.textRange
+ val doc = PsiDocumentManager.getInstance(it.project).getDocument(it.containingFile)
+ // IJ uses 0-based line-numbers; external source browsers use 1-based
+ doc?.getLineNumber(range.startOffset)?.plus(1)
+ }
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/JvmDependenciesIndexImpl.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/JvmDependenciesIndexImpl.kt
new file mode 100644
index 00000000..42fda615
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/JvmDependenciesIndexImpl.kt
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2010-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+import com.intellij.ide.highlighter.JavaClassFileType
+import com.intellij.ide.highlighter.JavaFileType
+import com.intellij.openapi.vfs.VfsUtilCore
+import com.intellij.openapi.vfs.VirtualFile
+import gnu.trove.THashMap
+import it.unimi.dsi.fastutil.ints.IntArrayList
+import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
+import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+
+// speeds up finding files/classes in classpath/java source roots
+// NOT THREADSAFE, needs to be adapted/removed if we want compiler to be multithreaded
+// the main idea of this class is for each package to store roots which contains it to avoid excessive file system traversal
+internal class JvmDependenciesIndexImpl(_roots: List<JavaRoot>) : JvmDependenciesIndex {
+ //these fields are computed based on _roots passed to constructor which are filled in later
+ private val roots: List<JavaRoot> by lazy { _roots.toList() }
+
+ private val maxIndex: Int
+ get() = roots.size
+
+ // each "Cache" object corresponds to a package
+ private class Cache {
+ private val innerPackageCaches = HashMap<String, Cache>()
+
+ operator fun get(name: String) = innerPackageCaches.getOrPut(name, ::Cache)
+
+ // indices of roots that are known to contain this package
+ // if this list contains [1, 3, 5] then roots with indices 1, 3 and 5 are known to contain this package, 2 and 4 are known not to (no information about roots 6 or higher)
+ // if this list contains maxIndex that means that all roots containing this package are known
+ val rootIndices = IntArrayList(2)
+ }
+
+ // root "Cache" object corresponds to DefaultPackage which exists in every root. Roots with non-default fqname are also listed here but
+ // they will be ignored on requests with invalid fqname prefix.
+ private val rootCache: Cache by lazy {
+ Cache().apply {
+ roots.indices.forEach(rootIndices::add)
+ rootIndices.add(maxIndex)
+ rootIndices.trimToSize(0)
+ }
+ }
+
+ // holds the request and the result last time we searched for class
+ // helps improve several scenarios, LazyJavaResolverContext.findClassInJava being the most important
+ private var lastClassSearch: Pair<FindClassRequest, SearchResult>? = null
+
+ override val indexedRoots by lazy { roots.asSequence() }
+
+ private val packageCache: Array<out MutableMap<String, VirtualFile?>> by lazy {
+ Array(roots.size) { THashMap<String, VirtualFile?>() }
+ }
+
+ override fun traverseDirectoriesInPackage(
+ packageFqName: FqName,
+ acceptedRootTypes: Set<JavaRoot.RootType>,
+ continueSearch: (VirtualFile, JavaRoot.RootType) -> Boolean
+ ) {
+ search(TraverseRequest(packageFqName, acceptedRootTypes)) { dir, rootType ->
+ if (continueSearch(dir, rootType)) null else Unit
+ }
+ }
+
+ // findClassGivenDirectory MUST check whether the class with this classId exists in given package
+ override fun <T : Any> findClass(
+ classId: ClassId,
+ acceptedRootTypes: Set<JavaRoot.RootType>,
+ findClassGivenDirectory: (VirtualFile, JavaRoot.RootType) -> T?
+ ): T? {
+ // make a decision based on information saved from last class search
+ if (lastClassSearch?.first?.classId != classId) {
+ return search(FindClassRequest(classId, acceptedRootTypes), findClassGivenDirectory)
+ }
+
+ val (cachedRequest, cachedResult) = lastClassSearch!!
+ return when (cachedResult) {
+ is SearchResult.NotFound -> {
+ val limitedRootTypes = acceptedRootTypes - cachedRequest.acceptedRootTypes
+ if (limitedRootTypes.isEmpty()) {
+ null
+ } else {
+ search(FindClassRequest(classId, limitedRootTypes), findClassGivenDirectory)
+ }
+ }
+ is SearchResult.Found -> {
+ if (cachedRequest.acceptedRootTypes == acceptedRootTypes) {
+ findClassGivenDirectory(cachedResult.packageDirectory, cachedResult.root.type)
+ } else {
+ search(FindClassRequest(classId, acceptedRootTypes), findClassGivenDirectory)
+ }
+ }
+ }
+ }
+
+ private fun <T : Any> search(request: SearchRequest, handler: (VirtualFile, JavaRoot.RootType) -> T?): T? {
+ // a list of package sub names, ["org", "jb", "kotlin"]
+ val packagesPath = request.packageFqName.pathSegments().map { it.identifier }
+ // a list of caches corresponding to packages, [default, "org", "org.jb", "org.jb.kotlin"]
+ val caches = cachesPath(packagesPath)
+
+ var processedRootsUpTo = -1
+ // traverse caches starting from last, which contains most specific information
+
+ // NOTE: indices manipulation instead of using caches.reversed() is here for performance reasons
+ for (cacheIndex in caches.lastIndex downTo 0) {
+ val cacheRootIndices = caches[cacheIndex].rootIndices
+ for (i in 0 until cacheRootIndices.size) {
+ val rootIndex = cacheRootIndices.getInt(i)
+ if (rootIndex <= processedRootsUpTo) continue // roots with those indices have been processed by now
+
+ val directoryInRoot =
+ travelPath(rootIndex, request.packageFqName, packagesPath, cacheIndex, caches) ?: continue
+ val root = roots[rootIndex]
+ if (root.type in request.acceptedRootTypes) {
+ val result = handler(directoryInRoot, root.type)
+ if (result != null) {
+ if (request is FindClassRequest) {
+ lastClassSearch = Pair(request, SearchResult.Found(directoryInRoot, root))
+ }
+ return result
+ }
+ }
+ }
+ processedRootsUpTo =
+ if (cacheRootIndices.isEmpty()) {
+ processedRootsUpTo
+ } else {
+ cacheRootIndices.getInt(cacheRootIndices.size - 1)
+ }
+ }
+
+ if (request is FindClassRequest) {
+ lastClassSearch = Pair(request, SearchResult.NotFound)
+ }
+ return null
+ }
+
+ // try to find a target directory corresponding to package represented by packagesPath in a given root represented by index
+ // possibly filling "Cache" objects with new information
+ private fun travelPath(
+ rootIndex: Int,
+ packageFqName: FqName,
+ packagesPath: List<String>,
+ fillCachesAfter: Int,
+ cachesPath: List<Cache>
+ ): VirtualFile? {
+ if (rootIndex >= maxIndex) {
+ for (i in (fillCachesAfter + 1) until cachesPath.size) {
+ // we all know roots that contain this package by now
+ cachesPath[i].rootIndices.add(maxIndex)
+ cachesPath[i].rootIndices.trimToSize(0)
+ }
+ return null
+ }
+
+ return synchronized(packageCache) {
+ packageCache[rootIndex].getOrPut(packageFqName.asString()) {
+ doTravelPath(rootIndex, packagesPath, fillCachesAfter, cachesPath)
+ }
+ }
+ }
+
+ private fun doTravelPath(rootIndex: Int, packagesPath: List<String>, fillCachesAfter: Int, cachesPath: List<Cache>): VirtualFile? {
+ val pathRoot = roots[rootIndex]
+ val prefixPathSegments = pathRoot.prefixFqName?.pathSegments()
+
+ var currentFile = pathRoot.file
+
+ for (pathIndex in packagesPath.indices) {
+ val subPackageName = packagesPath[pathIndex]
+ if (prefixPathSegments != null && pathIndex < prefixPathSegments.size) {
+ // Traverse prefix first instead of traversing real directories
+ if (prefixPathSegments[pathIndex].identifier != subPackageName) {
+ return null
+ }
+ } else {
+ currentFile = currentFile.findChildPackage(subPackageName, pathRoot.type) ?: return null
+ }
+
+ val correspondingCacheIndex = pathIndex + 1
+ if (correspondingCacheIndex > fillCachesAfter) {
+ // subPackageName exists in this root
+ cachesPath[correspondingCacheIndex].rootIndices.add(rootIndex)
+ }
+ }
+
+ return currentFile
+ }
+
+ private fun VirtualFile.findChildPackage(subPackageName: String, rootType: JavaRoot.RootType): VirtualFile? {
+ val childDirectory = findChild(subPackageName) ?: return null
+
+ val fileExtension = when (rootType) {
+ JavaRoot.RootType.BINARY -> JavaClassFileType.INSTANCE.defaultExtension
+ JavaRoot.RootType.BINARY_SIG -> "sig"
+ JavaRoot.RootType.SOURCE -> JavaFileType.INSTANCE.defaultExtension
+ }
+
+ // If in addition to a directory "foo" there's a class file "foo.class" AND there are no classes anywhere in the directory "foo",
+ // then we ignore the directory and let the resolution choose the class "foo" instead.
+ if (findChild("$subPackageName.$fileExtension")?.isDirectory == false) {
+ if (VfsUtilCore.processFilesRecursively(childDirectory) { file -> file.extension != fileExtension }) {
+ return null
+ }
+ }
+
+ return childDirectory
+ }
+
+ private fun cachesPath(path: List<String>): List<Cache> {
+ val caches = ArrayList<Cache>(path.size + 1)
+ caches.add(rootCache)
+ var currentCache = rootCache
+ for (subPackageName in path) {
+ currentCache = currentCache[subPackageName]
+ caches.add(currentCache)
+ }
+ return caches
+ }
+
+ private fun <E> MutableList<E>.trimToSize(newSize: Int) {
+ subList(newSize, size).clear()
+ }
+
+ private data class FindClassRequest(val classId: ClassId, override val acceptedRootTypes: Set<JavaRoot.RootType>) :
+ SearchRequest {
+ override val packageFqName: FqName
+ get() = classId.packageFqName
+ }
+
+ private data class TraverseRequest(
+ override val packageFqName: FqName,
+ override val acceptedRootTypes: Set<JavaRoot.RootType>
+ ) : SearchRequest
+
+ private interface SearchRequest {
+ val packageFqName: FqName
+ val acceptedRootTypes: Set<JavaRoot.RootType>
+ }
+
+ private sealed class SearchResult {
+ class Found(val packageDirectory: VirtualFile, val root: JavaRoot) : SearchResult()
+
+ object NotFound : SearchResult()
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinAnalysis.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinAnalysis.kt
new file mode 100644
index 00000000..b4a1b8f7
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinAnalysis.kt
@@ -0,0 +1,94 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.model.SourceSetDependent
+import org.jetbrains.dokka.plugability.DokkaContext
+import java.io.Closeable
+
+@Suppress("FunctionName")
+internal fun ProjectKotlinAnalysis(
+ sourceSets: List<DokkaSourceSet>,
+ context: DokkaContext,
+ analysisConfiguration: DokkaAnalysisConfiguration = DokkaAnalysisConfiguration()
+): KotlinAnalysis {
+ val environments = sourceSets.associateWith { sourceSet ->
+ createAnalysisContext(
+ context = context,
+ sourceSets = sourceSets,
+ sourceSet = sourceSet,
+ analysisConfiguration = analysisConfiguration
+ )
+ }
+ return EnvironmentKotlinAnalysis(environments)
+}
+
+/**
+ * [projectKotlinAnalysis] needs to be closed separately
+ * Usually the analysis created for samples is short-lived and can be closed right after
+ * it's been used, there's no need to wait for [projectKotlinAnalysis] to be closed as it must be handled separately.
+ */
+@Suppress("FunctionName")
+internal fun SamplesKotlinAnalysis(
+ sourceSets: List<DokkaSourceSet>,
+ context: DokkaContext,
+ projectKotlinAnalysis: KotlinAnalysis,
+ analysisConfiguration: DokkaAnalysisConfiguration = DokkaAnalysisConfiguration()
+): KotlinAnalysis {
+ val environments = sourceSets
+ .filter { it.samples.isNotEmpty() }
+ .associateWith { sourceSet ->
+ createAnalysisContext(
+ context = context,
+ classpath = sourceSet.classpath,
+ sourceRoots = sourceSet.samples,
+ sourceSet = sourceSet,
+ analysisConfiguration = analysisConfiguration
+ )
+ }
+
+ return EnvironmentKotlinAnalysis(environments, projectKotlinAnalysis)
+}
+
+internal class DokkaAnalysisConfiguration(
+ /**
+ * Only for common platform ignore BuiltIns for StdLib since it can cause a conflict
+ * between BuiltIns from a compiler and ones from source code.
+ */
+ val ignoreCommonBuiltIns: Boolean = false
+)
+
+/**
+ * First child delegation. It does not close [parent].
+ */
+@InternalDokkaApi
+abstract class KotlinAnalysis(
+ private val parent: KotlinAnalysis? = null
+) : Closeable {
+
+ operator fun get(key: DokkaSourceSet): AnalysisContext {
+ return get(key.sourceSetID)
+ }
+
+ internal operator fun get(key: DokkaSourceSetID): AnalysisContext {
+ return find(key)
+ ?: parent?.get(key)
+ ?: throw IllegalStateException("Missing EnvironmentAndFacade for sourceSet $key")
+ }
+
+ internal abstract fun find(sourceSetID: DokkaSourceSetID): AnalysisContext?
+}
+
+internal open class EnvironmentKotlinAnalysis(
+ private val environments: SourceSetDependent<AnalysisContext>,
+ parent: KotlinAnalysis? = null,
+) : KotlinAnalysis(parent = parent) {
+
+ override fun find(sourceSetID: DokkaSourceSetID): AnalysisContext? =
+ environments.entries.firstOrNull { (sourceSet, _) -> sourceSet.sourceSetID == sourceSetID }?.value
+
+ override fun close() {
+ environments.values.forEach(AnalysisContext::close)
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinCliJavaFileManagerImpl.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinCliJavaFileManagerImpl.kt
new file mode 100644
index 00000000..ac120b62
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinCliJavaFileManagerImpl.kt
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+import com.intellij.core.CoreJavaFileManager
+import com.intellij.openapi.diagnostic.Logger
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.*
+import com.intellij.psi.impl.file.PsiPackageImpl
+import com.intellij.psi.search.GlobalSearchScope
+import gnu.trove.THashMap
+import gnu.trove.THashSet
+import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider
+import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
+import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex
+import org.jetbrains.kotlin.cli.jvm.index.SingleJavaFileRootsIndex
+import org.jetbrains.kotlin.load.java.JavaClassFinder
+import org.jetbrains.kotlin.load.java.structure.JavaClass
+import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
+import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryClassSignatureParser
+import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
+import org.jetbrains.kotlin.load.java.structure.impl.classFiles.ClassifierResolutionContext
+import org.jetbrains.kotlin.load.java.structure.impl.classFiles.isNotTopLevelClass
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
+import org.jetbrains.kotlin.util.PerformanceCounter
+
+// TODO: do not inherit from CoreJavaFileManager to avoid accidental usage of its methods which do not use caches/indices
+// Currently, the only relevant usage of this class as CoreJavaFileManager is at CoreJavaDirectoryService.getPackage,
+// which is indirectly invoked from PsiPackage.getSubPackages
+internal class KotlinCliJavaFileManagerImpl(private val myPsiManager: PsiManager) : CoreJavaFileManager(myPsiManager), KotlinCliJavaFileManager {
+ private val perfCounter = PerformanceCounter.create("Find Java class")
+ private lateinit var index: JvmDependenciesIndex
+ private lateinit var singleJavaFileRootsIndex: SingleJavaFileRootsIndex
+ private lateinit var packagePartProviders: List<JvmPackagePartProvider>
+ private val topLevelClassesCache: MutableMap<FqName, VirtualFile?> = THashMap()
+ private val allScope = GlobalSearchScope.allScope(myPsiManager.project)
+ private var usePsiClassFilesReading = false
+
+ fun initialize(
+ index: JvmDependenciesIndex,
+ packagePartProviders: List<JvmPackagePartProvider>,
+ singleJavaFileRootsIndex: SingleJavaFileRootsIndex,
+ usePsiClassFilesReading: Boolean
+ ) {
+ this.index = index
+ this.packagePartProviders = packagePartProviders
+ this.singleJavaFileRootsIndex = singleJavaFileRootsIndex
+ this.usePsiClassFilesReading = usePsiClassFilesReading
+ }
+
+ private fun findPsiClass(classId: ClassId, searchScope: GlobalSearchScope): PsiClass? = perfCounter.time {
+ findVirtualFileForTopLevelClass(classId, searchScope)?.findPsiClassInVirtualFile(classId.relativeClassName.asString())
+ }
+
+ private fun findVirtualFileForTopLevelClass(classId: ClassId, searchScope: GlobalSearchScope): VirtualFile? {
+ val relativeClassName = classId.relativeClassName.asString()
+ synchronized(topLevelClassesCache) {
+ return topLevelClassesCache.getOrPut(classId.packageFqName.child(classId.relativeClassName.pathSegments().first())) {
+ index.findClass(classId) { dir, type ->
+ findVirtualFileGivenPackage(dir, relativeClassName, type)
+ } ?: singleJavaFileRootsIndex.findJavaSourceClass(classId)
+ }?.takeIf { it in searchScope }
+ }
+ }
+
+ private val binaryCache: MutableMap<ClassId, JavaClass?> = THashMap()
+ private val signatureParsingComponent = BinaryClassSignatureParser()
+
+ fun findClass(classId: ClassId, searchScope: GlobalSearchScope): JavaClass? = findClass(JavaClassFinder.Request(classId), searchScope)
+
+ override fun findClass(request: JavaClassFinder.Request, searchScope: GlobalSearchScope): JavaClass? {
+ val (classId, classFileContentFromRequest, outerClassFromRequest) = request
+ val virtualFile = findVirtualFileForTopLevelClass(classId, searchScope) ?: return null
+
+ if (!usePsiClassFilesReading && (virtualFile.extension == "class" || virtualFile.extension == "sig")) {
+ synchronized(binaryCache){
+ // We return all class files' names in the directory in knownClassNamesInPackage method, so one may request an inner class
+ return binaryCache.getOrPut(classId) {
+ // Note that currently we implicitly suppose that searchScope for binary classes is constant and we do not use it
+ // as a key in cache
+ // This is a true assumption by now since there are two search scopes in compiler: one for sources and another one for binary
+ // When it become wrong because we introduce the modules into CLI, it's worth to consider
+ // having different KotlinCliJavaFileManagerImpl's for different modules
+
+ classId.outerClassId?.let { outerClassId ->
+ val outerClass = outerClassFromRequest ?: findClass(outerClassId, searchScope)
+
+ return if (outerClass is BinaryJavaClass)
+ outerClass.findInnerClass(classId.shortClassName, classFileContentFromRequest)
+ else
+ outerClass?.findInnerClass(classId.shortClassName)
+ }
+
+ // Here, we assume the class is top-level
+ val classContent = classFileContentFromRequest ?: virtualFile.contentsToByteArray()
+ if (virtualFile.nameWithoutExtension.contains("$") && isNotTopLevelClass(classContent)) return@getOrPut null
+
+ val resolver = ClassifierResolutionContext { findClass(it, allScope) }
+
+ BinaryJavaClass(
+ virtualFile, classId.asSingleFqName(), resolver, signatureParsingComponent,
+ outerClass = null, classContent = classContent
+ )
+ }
+ }
+ }
+
+ return virtualFile.findPsiClassInVirtualFile(classId.relativeClassName.asString())?.let(::JavaClassImpl)
+ }
+
+ // this method is called from IDEA to resolve dependencies in Java code
+ // which supposedly shouldn't have errors so the dependencies exist in general
+ override fun findClass(qName: String, scope: GlobalSearchScope): PsiClass? {
+ // String cannot be reliably converted to ClassId because we don't know where the package name ends and class names begin.
+ // For example, if qName is "a.b.c.d.e", we should either look for a top level class "e" in the package "a.b.c.d",
+ // or, for example, for a nested class with the relative qualified name "c.d.e" in the package "a.b".
+ // Below, we start by looking for the top level class "e" in the package "a.b.c.d" first, then for the class "d.e" in the package
+ // "a.b.c", and so on, until we find something. Most classes are top level, so most of the times the search ends quickly
+
+ forEachClassId(qName) { classId ->
+ findPsiClass(classId, scope)?.let { return it }
+ }
+
+ return null
+ }
+
+ private inline fun forEachClassId(fqName: String, block: (ClassId) -> Unit) {
+ var classId = fqName.toSafeTopLevelClassId() ?: return
+
+ while (true) {
+ block(classId)
+
+ val packageFqName = classId.packageFqName
+ if (packageFqName.isRoot) break
+
+ classId = ClassId(
+ packageFqName.parent(),
+ FqName(packageFqName.shortName().asString() + "." + classId.relativeClassName.asString()),
+ false
+ )
+ }
+ }
+
+ override fun findClasses(qName: String, scope: GlobalSearchScope): Array<PsiClass> = perfCounter.time {
+ val result = ArrayList<PsiClass>(1)
+ forEachClassId(qName) { classId ->
+ val relativeClassName = classId.relativeClassName.asString()
+ index.traverseDirectoriesInPackage(classId.packageFqName) { dir, rootType ->
+ val psiClass =
+ findVirtualFileGivenPackage(dir, relativeClassName, rootType)
+ ?.takeIf { it in scope }
+ ?.findPsiClassInVirtualFile(relativeClassName)
+ if (psiClass != null) {
+ result.add(psiClass)
+ }
+ // traverse all
+ true
+ }
+
+ singleJavaFileRootsIndex.findJavaSourceClass(classId)
+ ?.takeIf { it in scope }
+ ?.findPsiClassInVirtualFile(relativeClassName)
+ ?.let { result.add(it) }
+
+ if (result.isNotEmpty()) {
+ return@time result.toTypedArray()
+ }
+ }
+
+ PsiClass.EMPTY_ARRAY
+ }
+
+ override fun findPackage(packageName: String): PsiPackage? {
+ var found = false
+ val packageFqName = packageName.toSafeFqName() ?: return null
+ index.traverseDirectoriesInPackage(packageFqName) { _, _ ->
+ found = true
+ //abort on first found
+ false
+ }
+ if (!found) {
+ found = packagePartProviders.any { it.findPackageParts(packageName).isNotEmpty() }
+ }
+ if (!found) {
+ found = singleJavaFileRootsIndex.findJavaSourceClasses(packageFqName).isNotEmpty()
+ }
+ return if (found) PsiPackageImpl(myPsiManager, packageName) else null
+ }
+
+ private fun findVirtualFileGivenPackage(
+ packageDir: VirtualFile,
+ classNameWithInnerClasses: String,
+ rootType: JavaRoot.RootType
+ ): VirtualFile? {
+ val topLevelClassName = classNameWithInnerClasses.substringBefore('.')
+
+ val vFile = when (rootType) {
+ JavaRoot.RootType.BINARY -> packageDir.findChild("$topLevelClassName.class")
+ JavaRoot.RootType.BINARY_SIG -> packageDir.findChild("$topLevelClassName.sig")
+ JavaRoot.RootType.SOURCE -> packageDir.findChild("$topLevelClassName.java")
+ } ?: return null
+
+ if (!vFile.isValid) {
+ LOG.error("Invalid child of valid parent: ${vFile.path}; ${packageDir.isValid} path=${packageDir.path}")
+ return null
+ }
+
+ return vFile
+ }
+
+ private fun VirtualFile.findPsiClassInVirtualFile(classNameWithInnerClasses: String): PsiClass? {
+ val file = myPsiManager.findFile(this) as? PsiClassOwner ?: return null
+ return findClassInPsiFile(classNameWithInnerClasses, file)
+ }
+
+ override fun knownClassNamesInPackage(packageFqName: FqName): Set<String> {
+ val result = THashSet<String>()
+ index.traverseDirectoriesInPackage(packageFqName, continueSearch = { dir, _ ->
+ for (child in dir.children) {
+ if (child.extension == "class" || child.extension == "java" || child.extension == "sig") {
+ result.add(child.nameWithoutExtension)
+ }
+ }
+
+ true
+ })
+
+ for (classId in singleJavaFileRootsIndex.findJavaSourceClasses(packageFqName)) {
+ assert(!classId.isNestedClass) { "ClassId of a single .java source class should not be nested: $classId" }
+ result.add(classId.shortClassName.asString())
+ }
+
+ return result
+ }
+
+ override fun findModules(moduleName: String, scope: GlobalSearchScope): Collection<PsiJavaModule> {
+ // TODO
+ return emptySet()
+ }
+
+ override fun getNonTrivialPackagePrefixes(): Collection<String> = emptyList()
+
+ companion object {
+ private val LOG = Logger.getInstance(KotlinCliJavaFileManagerImpl::class.java)
+
+ private fun findClassInPsiFile(classNameWithInnerClassesDotSeparated: String, file: PsiClassOwner): PsiClass? {
+ for (topLevelClass in file.classes) {
+ val candidate = findClassByTopLevelClass(classNameWithInnerClassesDotSeparated, topLevelClass)
+ if (candidate != null) {
+ return candidate
+ }
+ }
+ return null
+ }
+
+ private fun findClassByTopLevelClass(className: String, topLevelClass: PsiClass): PsiClass? {
+ if (className.indexOf('.') < 0) {
+ return if (className == topLevelClass.name) topLevelClass else null
+ }
+
+ val segments = StringUtil.split(className, ".").iterator()
+ if (!segments.hasNext() || segments.next() != topLevelClass.name) {
+ return null
+ }
+ var curClass = topLevelClass
+ while (segments.hasNext()) {
+ val innerClassName = segments.next()
+ val innerClass = curClass.findInnerClassByName(innerClassName, false) ?: return null
+ curClass = innerClass
+ }
+ return curClass
+ }
+ }
+}
+
+// a sad workaround to avoid throwing exception when called from inside IDEA code
+private fun <T : Any> safely(compute: () -> T): T? =
+ try {
+ compute()
+ } catch (e: IllegalArgumentException) {
+ null
+ } catch (e: AssertionError) {
+ null
+ }
+
+private fun String.toSafeFqName(): FqName? = safely { FqName(this) }
+private fun String.toSafeTopLevelClassId(): ClassId? = safely { ClassId.topLevel(FqName(this)) }
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/TypeReferenceFactory.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/TypeReferenceFactory.kt
new file mode 100644
index 00000000..f271e1f1
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/TypeReferenceFactory.kt
@@ -0,0 +1,68 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration
+
+import com.intellij.psi.PsiClass
+import org.jetbrains.dokka.links.*
+import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
+import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
+import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.types.TypeProjection
+import org.jetbrains.kotlin.types.error.ErrorType
+import org.jetbrains.kotlin.types.error.ErrorTypeConstructor
+import org.jetbrains.kotlin.types.error.ErrorTypeKind
+
+internal fun TypeReference.Companion.from(d: ReceiverParameterDescriptor): TypeReference? =
+ when (d.value) {
+ is ExtensionReceiver -> fromPossiblyNullable(d.type, emptyList())
+ else -> run {
+ println("Unknown value type for $d")
+ null
+ }
+ }
+
+internal fun TypeReference.Companion.from(d: ValueParameterDescriptor): TypeReference =
+ fromPossiblyNullable(d.type, emptyList())
+
+internal fun TypeReference.Companion.from(@Suppress("UNUSED_PARAMETER") p: PsiClass) = TypeReference
+
+private fun TypeReference.Companion.fromPossiblyNullable(t: KotlinType, paramTrace: List<KotlinType>): TypeReference =
+ fromPossiblyRecursive(t, paramTrace).let { if (t.isMarkedNullable) Nullable(it) else it }
+
+private fun TypeReference.Companion.fromPossiblyRecursive(t: KotlinType, paramTrace: List<KotlinType>): TypeReference =
+ paramTrace.indexOfFirst { it.constructor == t.constructor && it.arguments == t.arguments }
+ .takeIf { it >= 0 }
+ ?.let(::RecursiveType)
+ ?: from(t, paramTrace)
+
+private fun TypeReference.Companion.from(t: KotlinType, paramTrace: List<KotlinType>): TypeReference {
+ if (t is ErrorType) {
+ val errorConstructor = t.constructor as? ErrorTypeConstructor
+ val presentableName =
+ if (errorConstructor?.kind == ErrorTypeKind.UNRESOLVED_TYPE && errorConstructor.parameters.isNotEmpty())
+ errorConstructor.getParam(0)
+ else
+ t.constructor.toString()
+ return TypeConstructor(presentableName, t.arguments.map { fromProjection(it, paramTrace) })
+ }
+ return when (val d = t.constructor.declarationDescriptor) {
+ is TypeParameterDescriptor -> TypeParam(
+ d.upperBounds.map { fromPossiblyNullable(it, paramTrace + t) }
+ )
+ else -> TypeConstructor(
+ t.constructorName.orEmpty(),
+ t.arguments.map { fromProjection(it, paramTrace) }
+ )
+ }
+}
+
+private fun TypeReference.Companion.fromProjection(t: TypeProjection, paramTrace: List<KotlinType>): TypeReference =
+ if (t.isStarProjection) {
+ StarProjection
+ } else {
+ fromPossiblyNullable(t.type, paramTrace)
+ }
+
+private val KotlinType.constructorName
+ get() = constructor.declarationDescriptor?.fqNameSafe?.asString()
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/CommonKlibModuleInfo.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/CommonKlibModuleInfo.kt
new file mode 100644
index 00000000..a7667009
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/CommonKlibModuleInfo.kt
@@ -0,0 +1,25 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.resolve
+
+import org.jetbrains.kotlin.analyzer.ModuleInfo
+import org.jetbrains.kotlin.analyzer.common.CommonPlatformAnalyzerServices
+import org.jetbrains.kotlin.library.KotlinLibrary
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.platform.CommonPlatforms
+import org.jetbrains.kotlin.platform.TargetPlatform
+import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
+
+internal class CommonKlibModuleInfo(
+ override val name: Name,
+ val kotlinLibrary: KotlinLibrary,
+ private val dependOnModules: List<ModuleInfo>
+) : ModuleInfo {
+ override fun dependencies(): List<ModuleInfo> = dependOnModules
+
+ override fun dependencyOnBuiltIns(): ModuleInfo.DependencyOnBuiltIns = ModuleInfo.DependencyOnBuiltIns.LAST
+
+ override val platform: TargetPlatform
+ get() = CommonPlatforms.defaultCommonPlatform
+
+ override val analyzerServices: PlatformDependentAnalyzerServices
+ get() = CommonPlatformAnalyzerServices
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaJsKlibLibraryInfo.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaJsKlibLibraryInfo.kt
new file mode 100644
index 00000000..675bf28e
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaJsKlibLibraryInfo.kt
@@ -0,0 +1,30 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.resolve
+
+import org.jetbrains.kotlin.analyzer.ModuleInfo
+import org.jetbrains.kotlin.library.KotlinLibrary
+import org.jetbrains.kotlin.library.shortName
+import org.jetbrains.kotlin.library.uniqueName
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.platform.TargetPlatform
+import org.jetbrains.kotlin.platform.js.JsPlatforms
+import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
+
+/** TODO: replace by [org.jetbrains.kotlin.caches.resolve.JsKlibLibraryInfo] after fix of KT-40734 */
+internal class DokkaJsKlibLibraryInfo(
+ override val kotlinLibrary: KotlinLibrary,
+ override val analyzerServices: PlatformDependentAnalyzerServices,
+ private val dependencyResolver: DokkaKlibLibraryDependencyResolver
+) : DokkaKlibLibraryInfo() {
+ init {
+ dependencyResolver.registerLibrary(this)
+ }
+
+ override val name: Name by lazy {
+ val libraryName = kotlinLibrary.shortName ?: kotlinLibrary.uniqueName
+ Name.special("<$libraryName>")
+ }
+
+ override val platform: TargetPlatform = JsPlatforms.defaultJsPlatform
+ override fun dependencies(): List<ModuleInfo> = listOf(this) + dependencyResolver.resolveDependencies(this)
+ override fun getLibraryRoots(): Collection<String> = listOf(libraryRoot)
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaJsResolverForModuleFactory.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaJsResolverForModuleFactory.kt
new file mode 100644
index 00000000..b409441b
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaJsResolverForModuleFactory.kt
@@ -0,0 +1,125 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.resolve
+
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KLibService
+import org.jetbrains.kotlin.analyzer.*
+import org.jetbrains.kotlin.builtins.DefaultBuiltIns
+import org.jetbrains.kotlin.config.LanguageVersionSettings
+import org.jetbrains.kotlin.container.StorageComponentContainer
+import org.jetbrains.kotlin.container.get
+import org.jetbrains.kotlin.context.ModuleContext
+import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
+import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider
+import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
+import org.jetbrains.kotlin.frontend.di.createContainerForLazyResolve
+import org.jetbrains.kotlin.incremental.components.LookupTracker
+import org.jetbrains.kotlin.js.resolve.JsPlatformAnalyzerServices
+import org.jetbrains.kotlin.konan.util.KlibMetadataFactories
+import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer
+import org.jetbrains.kotlin.resolve.SealedClassInheritorsProvider
+import org.jetbrains.kotlin.resolve.TargetEnvironment
+import org.jetbrains.kotlin.resolve.lazy.ResolveSession
+import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService
+import org.jetbrains.kotlin.serialization.js.DynamicTypeDeserializer
+import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil
+import org.jetbrains.kotlin.serialization.js.createKotlinJavascriptPackageFragmentProvider
+import org.jetbrains.kotlin.serialization.konan.impl.KlibMetadataModuleDescriptorFactoryImpl
+import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils
+import java.io.File
+
+/** TODO: replace by [org.jetbrains.kotlin.caches.resolve.JsResolverForModuleFactory] after fix of KT-40734 */
+internal class DokkaJsResolverForModuleFactory(
+ private val targetEnvironment: TargetEnvironment,
+ private val kLibService: KLibService
+) : ResolverForModuleFactory() {
+ companion object {
+ private val metadataFactories = KlibMetadataFactories({ DefaultBuiltIns.Instance }, DynamicTypeDeserializer)
+
+ private val metadataModuleDescriptorFactory = KlibMetadataModuleDescriptorFactoryImpl(
+ metadataFactories.DefaultDescriptorFactory,
+ metadataFactories.DefaultPackageFragmentsFactory,
+ metadataFactories.flexibleTypeDeserializer,
+ metadataFactories.platformDependentTypeTransformer
+ )
+ }
+
+ override fun <M : ModuleInfo> createResolverForModule(
+ moduleDescriptor: ModuleDescriptorImpl,
+ moduleContext: ModuleContext,
+ moduleContent: ModuleContent<M>,
+ resolverForProject: ResolverForProject<M>,
+ languageVersionSettings: LanguageVersionSettings,
+ sealedInheritorsProvider: SealedClassInheritorsProvider
+ ): ResolverForModule {
+ val declarationProviderFactory = DeclarationProviderFactoryService.createDeclarationProviderFactory(
+ moduleContext.project,
+ moduleContext.storageManager,
+ moduleContent.syntheticFiles,
+ moduleContent.moduleContentScope,
+ moduleContent.moduleInfo
+ )
+
+ val container = createContainerForLazyResolve(
+ moduleContext,
+ declarationProviderFactory,
+ CodeAnalyzerInitializer.getInstance(moduleContext.project).createTrace(), // BindingTraceContext(/* allowSliceRewrite = */ true),
+ moduleDescriptor.platform!!,
+ JsPlatformAnalyzerServices,
+ targetEnvironment,
+ languageVersionSettings
+ )
+
+ var packageFragmentProvider = container.get<ResolveSession>().packageFragmentProvider
+
+ val libraryProviders = createPackageFragmentProvider(moduleContent.moduleInfo, container, moduleContext, moduleDescriptor, languageVersionSettings)
+
+ if (libraryProviders.isNotEmpty()) {
+ packageFragmentProvider =
+ CompositePackageFragmentProvider(listOf(packageFragmentProvider) + libraryProviders, "DokkaCompositePackageFragmentProvider")
+ }
+ return ResolverForModule(packageFragmentProvider, container)
+ }
+
+ internal fun <M : ModuleInfo> createPackageFragmentProvider(
+ moduleInfo: M,
+ container: StorageComponentContainer,
+ moduleContext: ModuleContext,
+ moduleDescriptor: ModuleDescriptorImpl,
+ languageVersionSettings: LanguageVersionSettings
+ ): List<PackageFragmentProvider> = when (moduleInfo) {
+ is DokkaJsKlibLibraryInfo -> {
+ with(kLibService) {
+ listOfNotNull(
+ moduleInfo.kotlinLibrary
+ .createPackageFragmentProvider(
+ storageManager = moduleContext.storageManager,
+ metadataModuleDescriptorFactory = metadataModuleDescriptorFactory,
+ languageVersionSettings = languageVersionSettings,
+ moduleDescriptor = moduleDescriptor,
+ lookupTracker = LookupTracker.DO_NOTHING
+ )
+ )
+ }
+ }
+ is LibraryModuleInfo -> {
+ moduleInfo.getLibraryRoots()
+ .flatMap {
+ if (File(it).exists()) {
+ KotlinJavascriptMetadataUtils.loadMetadata(it)
+ } else {
+ // TODO can/should we warn a user about a problem in a library root? If so how?
+ emptyList()
+ }
+ }
+ .filter { it.version.isCompatible() }
+ .map { metadata ->
+ val (header, packageFragmentProtos) =
+ KotlinJavascriptSerializationUtil.readModuleAsProto(metadata.body, metadata.version)
+ createKotlinJavascriptPackageFragmentProvider(
+ moduleContext.storageManager, moduleDescriptor, header, packageFragmentProtos, metadata.version,
+ container.get(), LookupTracker.DO_NOTHING
+ )
+ }
+ }
+ else -> emptyList()
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibLibraryDependencyResolver.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibLibraryDependencyResolver.kt
new file mode 100644
index 00000000..a07bdc35
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibLibraryDependencyResolver.kt
@@ -0,0 +1,17 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.resolve
+
+import org.jetbrains.kotlin.library.uniqueName
+import org.jetbrains.kotlin.library.unresolvedDependencies
+
+/** TODO: replace by [NativeKlibLibraryInfo] after fix of KT-40734 */
+internal class DokkaKlibLibraryDependencyResolver {
+ private val cachedDependencies = mutableMapOf</* libraryName */String, DokkaKlibLibraryInfo>()
+
+ fun registerLibrary(libraryInfo: DokkaKlibLibraryInfo) {
+ cachedDependencies[libraryInfo.kotlinLibrary.uniqueName] = libraryInfo
+ }
+
+ fun resolveDependencies(libraryInfo: DokkaKlibLibraryInfo): List<DokkaKlibLibraryInfo> {
+ return libraryInfo.kotlinLibrary.unresolvedDependencies.mapNotNull { cachedDependencies[it.path] }
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibLibraryInfo.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibLibraryInfo.kt
new file mode 100644
index 00000000..3362633d
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibLibraryInfo.kt
@@ -0,0 +1,10 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.resolve
+
+import org.jetbrains.kotlin.analyzer.LibraryModuleInfo
+import org.jetbrains.kotlin.library.KotlinLibrary
+
+internal abstract class DokkaKlibLibraryInfo : LibraryModuleInfo {
+ abstract val kotlinLibrary: KotlinLibrary
+ internal val libraryRoot: String
+ get() = kotlinLibrary.libraryFile.path
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibMetadataCommonDependencyContainer.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibMetadataCommonDependencyContainer.kt
new file mode 100644
index 00000000..95f5486d
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibMetadataCommonDependencyContainer.kt
@@ -0,0 +1,139 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.resolve
+
+import org.jetbrains.kotlin.analyzer.ModuleInfo
+import org.jetbrains.kotlin.analyzer.common.CommonDependenciesContainer
+import org.jetbrains.kotlin.builtins.DefaultBuiltIns
+import org.jetbrains.kotlin.config.CompilerConfiguration
+import org.jetbrains.kotlin.config.languageVersionSettings
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
+import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
+import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin
+import org.jetbrains.kotlin.incremental.components.LookupTracker
+import org.jetbrains.kotlin.konan.util.KlibMetadataFactories
+import org.jetbrains.kotlin.library.KotlinLibrary
+import org.jetbrains.kotlin.library.metadata.NativeTypeTransformer
+import org.jetbrains.kotlin.library.metadata.NullFlexibleTypeDeserializer
+import org.jetbrains.kotlin.library.metadata.parseModuleHeader
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration
+import org.jetbrains.kotlin.serialization.konan.impl.KlibMetadataModuleDescriptorFactoryImpl
+import org.jetbrains.kotlin.storage.LockBasedStorageManager
+import org.jetbrains.kotlin.storage.StorageManager
+
+/**
+ * Adapted from org.jetbrains.kotlin.cli.metadata.KlibMetadataDependencyContainer
+ */
+internal class DokkaKlibMetadataCommonDependencyContainer(
+ kotlinLibraries: List<KotlinLibrary>,
+ private val configuration: CompilerConfiguration,
+ private val storageManager: StorageManager
+) : CommonDependenciesContainer {
+
+ private val builtIns
+ get() = DefaultBuiltIns.Instance
+
+ private val mutableDependenciesForAllModuleDescriptors = mutableListOf<ModuleDescriptorImpl>().apply {
+ add(builtIns.builtInsModule)
+ }
+
+ private val mutableDependenciesForAllModules = mutableListOf<ModuleInfo>()
+
+ private val moduleDescriptorsForKotlinLibraries: Map<KotlinLibrary, ModuleDescriptorImpl> =
+ kotlinLibraries.associateBy({ it }) { library ->
+ val moduleHeader = parseModuleHeader(library.moduleHeaderData)
+ val moduleName = Name.special(moduleHeader.moduleName)
+ val moduleOrigin = DeserializedKlibModuleOrigin(library)
+ MetadataFactories.DefaultDescriptorFactory.createDescriptor(
+ moduleName, storageManager, builtIns, moduleOrigin
+ )
+ }.also { result ->
+ val resultValues = result.values
+ resultValues.forEach { module ->
+ module.setDependencies(mutableDependenciesForAllModuleDescriptors)
+ }
+ mutableDependenciesForAllModuleDescriptors.addAll(resultValues)
+ }
+
+ private val moduleInfosImpl: List<CommonKlibModuleInfo> = mutableListOf<CommonKlibModuleInfo>().apply {
+ addAll(
+ moduleDescriptorsForKotlinLibraries.map { (kotlinLibrary, moduleDescriptor) ->
+ CommonKlibModuleInfo(moduleDescriptor.name, kotlinLibrary, mutableDependenciesForAllModules)
+ }
+ )
+ mutableDependenciesForAllModules.addAll(this@apply)
+ }
+
+ override val moduleInfos: List<ModuleInfo> get() = moduleInfosImpl
+
+ /* not used in Dokka */
+ override val friendModuleInfos: List<ModuleInfo> = emptyList()
+
+ /* not used in Dokka */
+ override val refinesModuleInfos: List<ModuleInfo> = emptyList()
+
+ override fun moduleDescriptorForModuleInfo(moduleInfo: ModuleInfo): ModuleDescriptor {
+ if (moduleInfo !in moduleInfos)
+ error("Unknown module info $moduleInfo")
+
+ // Ensure that the package fragment provider has been created and the module descriptor has been
+ // initialized with the package fragment provider:
+ packageFragmentProviderForModuleInfo(moduleInfo)
+
+ return moduleDescriptorsForKotlinLibraries.getValue((moduleInfo as CommonKlibModuleInfo).kotlinLibrary)
+ }
+
+ override fun registerDependencyForAllModules(
+ moduleInfo: ModuleInfo,
+ descriptorForModule: ModuleDescriptorImpl
+ ) {
+ mutableDependenciesForAllModules.add(moduleInfo)
+ mutableDependenciesForAllModuleDescriptors.add(descriptorForModule)
+ }
+
+ override fun packageFragmentProviderForModuleInfo(
+ moduleInfo: ModuleInfo
+ ): PackageFragmentProvider? {
+ if (moduleInfo !in moduleInfos)
+ return null
+ return packageFragmentProviderForKotlinLibrary((moduleInfo as CommonKlibModuleInfo).kotlinLibrary)
+ }
+
+ private val klibMetadataModuleDescriptorFactory by lazy {
+ KlibMetadataModuleDescriptorFactoryImpl(
+ MetadataFactories.DefaultDescriptorFactory,
+ MetadataFactories.DefaultPackageFragmentsFactory,
+ MetadataFactories.flexibleTypeDeserializer,
+ MetadataFactories.platformDependentTypeTransformer
+ )
+ }
+
+ private fun packageFragmentProviderForKotlinLibrary(
+ library: KotlinLibrary
+ ): PackageFragmentProvider {
+ val languageVersionSettings = configuration.languageVersionSettings
+
+ val libraryModuleDescriptor = moduleDescriptorsForKotlinLibraries.getValue(library)
+ val packageFragmentNames = parseModuleHeader(library.moduleHeaderData).packageFragmentNameList
+
+ return klibMetadataModuleDescriptorFactory.createPackageFragmentProvider(
+ library,
+ packageAccessHandler = null,
+ packageFragmentNames = packageFragmentNames,
+ storageManager = LockBasedStorageManager("KlibMetadataPackageFragmentProvider"),
+ moduleDescriptor = libraryModuleDescriptor,
+ configuration = CompilerDeserializationConfiguration(languageVersionSettings),
+ compositePackageFragmentAddend = null,
+ lookupTracker = LookupTracker.DO_NOTHING
+ ).also {
+ libraryModuleDescriptor.initialize(it)
+ }
+ }
+}
+
+private val MetadataFactories =
+ KlibMetadataFactories(
+ { DefaultBuiltIns.Instance },
+ NullFlexibleTypeDeserializer,
+ NativeTypeTransformer()
+ )
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaNativeKlibLibraryInfo.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaNativeKlibLibraryInfo.kt
new file mode 100644
index 00000000..526815d3
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaNativeKlibLibraryInfo.kt
@@ -0,0 +1,50 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.resolve
+
+import org.jetbrains.kotlin.analyzer.ModuleInfo
+import org.jetbrains.kotlin.descriptors.ModuleCapability
+import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin
+import org.jetbrains.kotlin.descriptors.konan.KlibModuleOrigin
+import org.jetbrains.kotlin.library.KotlinLibrary
+import org.jetbrains.kotlin.library.isInterop
+import org.jetbrains.kotlin.library.shortName
+import org.jetbrains.kotlin.library.uniqueName
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.platform.TargetPlatform
+import org.jetbrains.kotlin.platform.konan.NativePlatforms
+import org.jetbrains.kotlin.resolve.ImplicitIntegerCoercion
+import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
+import java.io.IOException
+
+/** TODO: replace by [NativeKlibLibraryInfo] after fix of KT-40734 */
+internal class DokkaNativeKlibLibraryInfo(
+ override val kotlinLibrary: KotlinLibrary,
+ override val analyzerServices: PlatformDependentAnalyzerServices,
+ private val dependencyResolver: DokkaKlibLibraryDependencyResolver
+) : DokkaKlibLibraryInfo() {
+ init {
+ dependencyResolver.registerLibrary(this)
+ }
+
+ override val name: Name by lazy {
+ val libraryName = kotlinLibrary.shortName ?: kotlinLibrary.uniqueName
+ Name.special("<$libraryName>")
+ }
+
+ override val platform: TargetPlatform = NativePlatforms.unspecifiedNativePlatform
+ override fun dependencies(): List<ModuleInfo> = listOf(this) + dependencyResolver.resolveDependencies(this)
+ override fun getLibraryRoots(): Collection<String> = listOf(libraryRoot)
+
+ override val capabilities: Map<ModuleCapability<*>, Any?>
+ get() {
+ val capabilities = super.capabilities.toMutableMap()
+ capabilities[KlibModuleOrigin.CAPABILITY] = DeserializedKlibModuleOrigin(kotlinLibrary)
+ capabilities[ImplicitIntegerCoercion.MODULE_CAPABILITY] = kotlinLibrary.safeRead(false) { isInterop }
+ return capabilities
+ }
+
+ private fun <T> KotlinLibrary.safeRead(defaultValue: T, action: KotlinLibrary.() -> T) = try {
+ action()
+ } catch (_: IOException) {
+ defaultValue
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaNativeResolverForModuleFactory.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaNativeResolverForModuleFactory.kt
new file mode 100644
index 00000000..db86b82f
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaNativeResolverForModuleFactory.kt
@@ -0,0 +1,79 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.resolve
+
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KLibService
+import org.jetbrains.kotlin.analyzer.*
+import org.jetbrains.kotlin.builtins.konan.KonanBuiltIns
+import org.jetbrains.kotlin.config.LanguageVersionSettings
+import org.jetbrains.kotlin.container.get
+import org.jetbrains.kotlin.context.ModuleContext
+import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider
+import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
+import org.jetbrains.kotlin.frontend.di.createContainerForLazyResolve
+import org.jetbrains.kotlin.incremental.components.LookupTracker
+import org.jetbrains.kotlin.konan.util.KlibMetadataFactories
+import org.jetbrains.kotlin.library.metadata.NullFlexibleTypeDeserializer
+import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer
+import org.jetbrains.kotlin.resolve.SealedClassInheritorsProvider
+import org.jetbrains.kotlin.resolve.TargetEnvironment
+import org.jetbrains.kotlin.resolve.konan.platform.NativePlatformAnalyzerServices
+import org.jetbrains.kotlin.resolve.lazy.ResolveSession
+import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService
+
+/** TODO: replace by [NativeResolverForModuleFactory] after fix of KT-40734 */
+internal class DokkaNativeResolverForModuleFactory(
+ private val targetEnvironment: TargetEnvironment,
+ private val kLibService: KLibService,
+) : ResolverForModuleFactory() {
+ companion object {
+ private val metadataFactories = KlibMetadataFactories(::KonanBuiltIns, NullFlexibleTypeDeserializer)
+ }
+
+ override fun <M : ModuleInfo> createResolverForModule(
+ moduleDescriptor: ModuleDescriptorImpl,
+ moduleContext: ModuleContext,
+ moduleContent: ModuleContent<M>,
+ resolverForProject: ResolverForProject<M>,
+ languageVersionSettings: LanguageVersionSettings,
+ sealedInheritorsProvider: SealedClassInheritorsProvider
+ ): ResolverForModule {
+
+ val declarationProviderFactory = DeclarationProviderFactoryService.createDeclarationProviderFactory(
+ moduleContext.project,
+ moduleContext.storageManager,
+ moduleContent.syntheticFiles,
+ moduleContent.moduleContentScope,
+ moduleContent.moduleInfo
+ )
+
+ val container = createContainerForLazyResolve(
+ moduleContext,
+ declarationProviderFactory,
+ CodeAnalyzerInitializer.getInstance(moduleContext.project).createTrace(),
+ moduleDescriptor.platform!!,
+ NativePlatformAnalyzerServices,
+ targetEnvironment,
+ languageVersionSettings
+ )
+
+ var packageFragmentProvider = container.get<ResolveSession>().packageFragmentProvider
+
+ val klibPackageFragmentProvider = with(kLibService) {
+ (moduleContent.moduleInfo as? DokkaNativeKlibLibraryInfo)
+ ?.kotlinLibrary
+ ?.createPackageFragmentProvider(
+ storageManager = moduleContext.storageManager,
+ metadataModuleDescriptorFactory = metadataFactories.DefaultDeserializedDescriptorFactory,
+ languageVersionSettings = languageVersionSettings,
+ moduleDescriptor = moduleDescriptor,
+ lookupTracker = LookupTracker.DO_NOTHING
+ )
+ }
+
+ if (klibPackageFragmentProvider != null) {
+ packageFragmentProvider =
+ CompositePackageFragmentProvider(listOf(packageFragmentProvider, klibPackageFragmentProvider), "DokkaCompositePackageFragmentProvider")
+ }
+
+ return ResolverForModule(packageFragmentProvider, container)
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DefaultSamplesTransformer.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DefaultSamplesTransformer.kt
new file mode 100644
index 00000000..22fecdf8
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DefaultSamplesTransformer.kt
@@ -0,0 +1,35 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.kotlin.psi.KtBlockExpression
+import org.jetbrains.kotlin.psi.KtDeclarationWithBody
+import org.jetbrains.kotlin.psi.KtFile
+
+internal class DefaultSamplesTransformer(context: DokkaContext) : SamplesTransformerImpl(context) {
+
+ override 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
+ }
+
+ override fun processImports(psiElement: PsiElement): String {
+ val psiFile = psiElement.containingFile
+ return when(val text = (psiFile as? KtFile)?.importList?.text) {
+ is String -> text
+ else -> ""
+ }
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorFullClassHierarchyBuilder.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorFullClassHierarchyBuilder.kt
new file mode 100644
index 00000000..13645762
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorFullClassHierarchyBuilder.kt
@@ -0,0 +1,85 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl
+
+import com.intellij.psi.PsiClass
+import kotlinx.coroutines.coroutineScope
+import org.jetbrains.dokka.analysis.java.util.PsiDocumentableSource
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.DescriptorDocumentableSource
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.from
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.utilities.parallelForEach
+import org.jetbrains.kotlin.analysis.kotlin.internal.ClassHierarchy
+import org.jetbrains.kotlin.analysis.kotlin.internal.FullClassHierarchyBuilder
+import org.jetbrains.kotlin.analysis.kotlin.internal.Supertypes
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes
+import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny
+import java.util.concurrent.ConcurrentHashMap
+
+internal class DescriptorFullClassHierarchyBuilder : FullClassHierarchyBuilder {
+
+ override suspend fun build(module: DModule): ClassHierarchy = coroutineScope {
+ val map = module.sourceSets.associateWith { ConcurrentHashMap<DRI, List<DRI>>() }
+ module.packages.parallelForEach { visitDocumentable(it, map) }
+ map
+ }
+
+ private suspend fun collectSupertypesFromKotlinType(
+ driWithKType: Pair<DRI, KotlinType>,
+ supersMap: MutableMap<DRI, Supertypes>
+ ): Unit = coroutineScope {
+ val (dri, kotlinType) = driWithKType
+ val supertypes = kotlinType.immediateSupertypes().filterNot { it.isAnyOrNullableAny() }
+ val supertypesDriWithKType = supertypes.mapNotNull { supertype ->
+ supertype.constructor.declarationDescriptor?.let {
+ DRI.from(it) to supertype
+ }
+ }
+
+ if (supersMap[dri] == null) {
+ // another thread can rewrite the same value, but it isn't a problem
+ supersMap[dri] = supertypesDriWithKType.map { it.first }
+ supertypesDriWithKType.parallelForEach { collectSupertypesFromKotlinType(it, supersMap) }
+ }
+ }
+
+ private suspend fun collectSupertypesFromPsiClass(
+ driWithPsiClass: Pair<DRI, PsiClass>,
+ supersMap: MutableMap<DRI, Supertypes>
+ ): Unit = coroutineScope {
+ val (dri, psiClass) = driWithPsiClass
+ val supertypes = psiClass.superTypes.mapNotNull { it.resolve() }
+ .filterNot { it.qualifiedName == "java.lang.Object" }
+ val supertypesDriWithPsiClass = supertypes.map { DRI.from(it) to it }
+
+ if (supersMap[dri] == null) {
+ // another thread can rewrite the same value, but it isn't a problem
+ supersMap[dri] = supertypesDriWithPsiClass.map { it.first }
+ supertypesDriWithPsiClass.parallelForEach { collectSupertypesFromPsiClass(it, supersMap) }
+ }
+ }
+
+ private suspend fun visitDocumentable(
+ documentable: Documentable,
+ hierarchy: SourceSetDependent<MutableMap<DRI, List<DRI>>>
+ ): Unit = coroutineScope {
+ if (documentable is WithScope) {
+ documentable.classlikes.parallelForEach { visitDocumentable(it, hierarchy) }
+ }
+ if (documentable is DClasslike) {
+ // to build a full class graph, using supertypes from Documentable
+ // is not enough since it keeps only one level of hierarchy
+ documentable.sources.forEach { (sourceSet, source) ->
+ if (source is DescriptorDocumentableSource) {
+ val descriptor = source.descriptor as ClassDescriptor
+ val type = descriptor.defaultType
+ hierarchy[sourceSet]?.let { collectSupertypesFromKotlinType(documentable.dri to type, it) }
+ } else if (source is PsiDocumentableSource) {
+ val psi = source.psi as PsiClass
+ hierarchy[sourceSet]?.let { collectSupertypesFromPsiClass(documentable.dri to psi, it) }
+ }
+ }
+ }
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorInheritanceBuilder.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorInheritanceBuilder.kt
new file mode 100644
index 00000000..15dd8f1d
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorInheritanceBuilder.kt
@@ -0,0 +1,91 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl
+
+import com.intellij.psi.PsiClass
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.java.util.PsiDocumentableSource
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.DescriptorDocumentableSource
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.from
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.WithSources
+import org.jetbrains.kotlin.analysis.kotlin.internal.InheritanceBuilder
+import org.jetbrains.kotlin.analysis.kotlin.internal.InheritanceNode
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.descriptors.ClassKind
+import org.jetbrains.kotlin.resolve.DescriptorUtils.getClassDescriptorForType
+
+internal class DescriptorInheritanceBuilder : InheritanceBuilder {
+
+ override fun build(documentables: Map<DRI, Documentable>): List<InheritanceNode> {
+ val descriptorMap = getDescriptorMap(documentables)
+
+ val psiInheritanceTree =
+ documentables.flatMap { (_, v) -> (v as? WithSources)?.sources?.values.orEmpty() }
+ .filterIsInstance<PsiDocumentableSource>().mapNotNull { it.psi as? PsiClass }
+ .flatMap(::gatherPsiClasses)
+ .flatMap { entry -> entry.second.map { it to entry.first } }
+ .let {
+ it + it.map { it.second to null }
+ }
+ .groupBy({ it.first }) { it.second }
+ .map { it.key to it.value.filterNotNull().distinct() }
+ .map { (k, v) ->
+ InheritanceNode(
+ DRI.from(k),
+ v.map { InheritanceNode(DRI.from(it)) },
+ k.supers.filter { it.isInterface }.map { DRI.from(it) },
+ k.isInterface
+ )
+
+ }
+
+ val descriptorInheritanceTree = descriptorMap.flatMap { (_, v) ->
+ v.typeConstructor.supertypes
+ .map { getClassDescriptorForType(it) to v }
+ }
+ .let {
+ it + it.map { it.second to null }
+ }
+ .groupBy({ it.first }) { it.second }
+ .map { it.key to it.value.filterNotNull().distinct() }
+ .map { (k, v) ->
+ InheritanceNode(
+ DRI.from(k),
+ v.map { InheritanceNode(DRI.from(it)) },
+ k.typeConstructor.supertypes.map { getClassDescriptorForType(it) }
+ .mapNotNull { cd -> cd.takeIf { it.kind == ClassKind.INTERFACE }?.let { DRI.from(it) } },
+ isInterface = k.kind == ClassKind.INTERFACE
+ )
+ }
+
+ return psiInheritanceTree + descriptorInheritanceTree
+ }
+
+ private fun gatherPsiClasses(psi: PsiClass): List<Pair<PsiClass, List<PsiClass>>> = psi.supers.toList().let { l ->
+ listOf(psi to l) + l.flatMap { gatherPsiClasses(it) }
+ }
+
+ private fun getDescriptorMap(documentables: Map<DRI, Documentable>): Map<DRI, ClassDescriptor> {
+ val map: MutableMap<DRI, ClassDescriptor> = mutableMapOf()
+ documentables
+ .mapNotNull { (k, v) ->
+ v.descriptorForPlatform()?.let { k to it }?.also { (k, v) -> map[k] = v }
+ }.map { it.second }.forEach { gatherSupertypes(it, map) }
+
+ return map.toMap()
+ }
+
+ private fun gatherSupertypes(descriptor: ClassDescriptor, map: MutableMap<DRI, ClassDescriptor>) {
+ map.putIfAbsent(DRI.from(descriptor), descriptor)
+ descriptor.typeConstructor.supertypes.map { getClassDescriptorForType(it) }
+ .forEach { gatherSupertypes(it, map) }
+ }
+
+
+ private fun Documentable?.descriptorForPlatform(platform: Platform = Platform.jvm) =
+ (this as? WithSources).descriptorForPlatform(platform)
+
+ private fun WithSources?.descriptorForPlatform(platform: Platform = Platform.jvm) = this?.let {
+ it.sources.entries.find { it.key.analysisPlatform == platform }?.value?.let { it as? DescriptorDocumentableSource }?.descriptor as? ClassDescriptor
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorKotlinToJavaMapper.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorKotlinToJavaMapper.kt
new file mode 100644
index 00000000..394a9863
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorKotlinToJavaMapper.kt
@@ -0,0 +1,31 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl
+
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.PointingToDeclaration
+import org.jetbrains.kotlin.analysis.kotlin.internal.KotlinToJavaService
+import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+
+internal class DescriptorKotlinToJavaMapper : KotlinToJavaService {
+
+ override fun findAsJava(kotlinDri: DRI): DRI? {
+ return kotlinDri.partialFqName().mapToJava()?.toDRI(kotlinDri)
+ }
+
+ private fun DRI.partialFqName() = packageName?.let { "$it." } + classNames
+
+ private fun String.mapToJava(): ClassId? =
+ JavaToKotlinClassMap.mapKotlinToJava(FqName(this).toUnsafe())
+
+ private fun ClassId.toDRI(dri: DRI?): DRI = DRI(
+ packageName = packageFqName.asString(),
+ classNames = classNames(),
+ callable = dri?.callable,//?.asJava(), TODO: check this
+ extra = null,
+ target = PointingToDeclaration
+ )
+
+ private fun ClassId.classNames(): String =
+ shortClassName.identifier + (outerClassId?.classNames()?.let { ".$it" } ?: "")
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSyntheticDocumentableDetector.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSyntheticDocumentableDetector.kt
new file mode 100644
index 00000000..34f9bba1
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSyntheticDocumentableDetector.kt
@@ -0,0 +1,33 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.DescriptorDocumentableSource
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.WithSources
+import org.jetbrains.kotlin.analysis.kotlin.internal.SyntheticDocumentableDetector
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
+
+internal class DescriptorSyntheticDocumentableDetector : SyntheticDocumentableDetector {
+ override fun isSynthetic(documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet): Boolean {
+ return isFakeOverride(documentable, sourceSet) || isSynthesized(documentable, sourceSet)
+ }
+
+ private fun isFakeOverride(documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet): Boolean {
+ return callableMemberDescriptorOrNull(documentable, sourceSet)?.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE
+ }
+
+ private fun isSynthesized(documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet): Boolean {
+ return callableMemberDescriptorOrNull(documentable, sourceSet)?.kind == CallableMemberDescriptor.Kind.SYNTHESIZED
+ }
+
+ private fun callableMemberDescriptorOrNull(
+ documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet
+ ): CallableMemberDescriptor? {
+ if (documentable is WithSources) {
+ return documentable.sources[sourceSet]
+ .let { it as? DescriptorDocumentableSource }?.descriptor as? CallableMemberDescriptor
+ }
+
+ return null
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/SamplesTransformerImpl.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/SamplesTransformerImpl.kt
new file mode 100644
index 00000000..f1924708
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/SamplesTransformerImpl.kt
@@ -0,0 +1,153 @@
+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.DokkaSourceSet
+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.links.DRI
+import org.jetbrains.dokka.model.DisplaySourceSet
+import org.jetbrains.dokka.model.doc.Sample
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.transformers.pages.PageTransformer
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
+import org.jetbrains.kotlin.resolve.lazy.ResolveSession
+
+internal const val KOTLIN_PLAYGROUND_SCRIPT = "<script src=\"`https://unpkg.com/kotlin-playground@1`\"></script>"
+
+internal abstract class SamplesTransformerImpl(val context: DokkaContext) : PageTransformer {
+
+ private val kDocFinder: KDocFinder = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kdocFinder }
+
+ abstract fun processBody(psiElement: PsiElement): String
+ abstract fun processImports(psiElement: PsiElement): String
+
+ final override fun invoke(input: RootPageNode): RootPageNode =
+ /**
+ * 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) {
+ val analysis = SamplesKotlinAnalysis(
+ sourceSets = context.configuration.sourceSets,
+ context = context,
+ projectKotlinAnalysis = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kotlinAnalysis }
+ )
+ analysis.use {
+ input.transformContentPagesTree { page ->
+ val samples = (page as? WithDocumentables)?.documentables?.flatMap {
+ it.documentation.entries.flatMap { entry ->
+ entry.value.children.filterIsInstance<Sample>().map { entry.key to it }
+ }
+ }
+
+ samples?.fold(page as ContentPage) { acc, (sampleSourceSet, sample) ->
+ acc.modified(
+ content = acc.content.addSample(page, sampleSourceSet, sample.name, it),
+ embeddedResources = acc.embeddedResources + KOTLIN_PLAYGROUND_SCRIPT
+ )
+ } ?: page
+ }
+ }
+ }
+
+ private fun ContentNode.addSample(
+ contentPage: ContentPage,
+ sourceSet: DokkaSourceSet,
+ fqName: String,
+ analysis: KotlinAnalysis
+ ): ContentNode {
+ val resolveSession = analysis[sourceSet].resolveSession
+ val psiElement = fqNameToPsiElement(resolveSession, fqName, sourceSet)
+ ?: return this.also { context.logger.warn("Cannot find PsiElement corresponding to $fqName") }
+ val imports =
+ processImports(psiElement)
+ val body = processBody(psiElement)
+ val node = contentCode(contentPage.content.sourceSets, contentPage.dri, createSampleBody(imports, body), "kotlin")
+
+ return dfs(fqName, node)
+ }
+
+ protected open fun createSampleBody(imports: String, body: String) =
+ """ |$imports
+ |fun main() {
+ | //sampleStart
+ | $body
+ | //sampleEnd
+ |}""".trimMargin()
+
+ private fun ContentNode.dfs(fqName: String, node: ContentCodeBlock): ContentNode {
+ return when (this) {
+ is ContentHeader -> copy(children.map { it.dfs(fqName, node) })
+ is ContentDivergentGroup -> @Suppress("UNCHECKED_CAST") copy(children.map {
+ it.dfs(fqName, node)
+ } as List<ContentDivergentInstance>)
+ is ContentDivergentInstance -> copy(
+ before.let { it?.dfs(fqName, node) },
+ divergent.dfs(fqName, node),
+ after.let { it?.dfs(fqName, node) })
+ is ContentCodeBlock -> copy(children.map { it.dfs(fqName, node) })
+ is ContentCodeInline -> copy(children.map { it.dfs(fqName, node) })
+ is ContentDRILink -> copy(children.map { it.dfs(fqName, node) })
+ is ContentResolvedLink -> copy(children.map { it.dfs(fqName, node) })
+ is ContentEmbeddedResource -> copy(children.map { it.dfs(fqName, node) })
+ is ContentTable -> copy(children = children.map { it.dfs(fqName, node) as ContentGroup })
+ is ContentList -> copy(children.map { it.dfs(fqName, node) })
+ is ContentGroup -> copy(children.map { it.dfs(fqName, node) })
+ is PlatformHintedContent -> copy(inner.dfs(fqName, node))
+ is ContentText -> if (text == fqName) node else this
+ is ContentBreakLine -> this
+ else -> this.also { context.logger.error("Could not recognize $this ContentNode in SamplesTransformer") }
+ }
+ }
+
+ private fun fqNameToPsiElement(resolveSession: ResolveSession, functionName: String, dokkaSourceSet: 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 DescriptorToSourceUtils.descriptorToDeclaration(symbol)
+ }
+ }
+
+ private fun contentCode(
+ sourceSets: Set<DisplaySourceSet>,
+ dri: Set<DRI>,
+ content: String,
+ language: String,
+ styles: Set<Style> = emptySet(),
+ extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
+ ) =
+ ContentCodeBlock(
+ children = listOf(
+ ContentText(
+ text = content,
+ dci = DCI(dri, ContentKind.Sample),
+ sourceSets = sourceSets,
+ style = emptySet(),
+ extra = PropertyContainer.empty()
+ )
+ ),
+ language = language,
+ dci = DCI(dri, ContentKind.Sample),
+ sourceSets = sourceSets,
+ style = styles + ContentStyle.RunnableSample + TextStyle.Monospace,
+ extra = extra
+ )
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/IllegalModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/IllegalModuleAndPackageDocumentation.kt
new file mode 100644
index 00000000..0d5fe5c5
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/IllegalModuleAndPackageDocumentation.kt
@@ -0,0 +1,7 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs
+
+import org.jetbrains.dokka.DokkaException
+
+internal class IllegalModuleAndPackageDocumentation(
+ source: ModuleAndPackageDocumentationSource, message: String
+) : DokkaException("[$source] $message")
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentation.kt
new file mode 100644
index 00000000..0aaea9c8
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentation.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs
+
+import org.jetbrains.dokka.model.doc.DocumentationNode
+
+internal data class ModuleAndPackageDocumentation(
+ val name: String,
+ val classifier: Classifier,
+ val documentation: DocumentationNode
+) {
+ enum class Classifier { Module, Package }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationFragment.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationFragment.kt
new file mode 100644
index 00000000..c0df713b
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationFragment.kt
@@ -0,0 +1,9 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs
+
+
+internal data class ModuleAndPackageDocumentationFragment(
+ val name: String,
+ val classifier: ModuleAndPackageDocumentation.Classifier,
+ val documentation: String,
+ val source: ModuleAndPackageDocumentationSource
+)
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationParsingContext.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationParsingContext.kt
new file mode 100644
index 00000000..f6ce66d6
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationParsingContext.kt
@@ -0,0 +1,71 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KDocFinder
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.from
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs.ModuleAndPackageDocumentation.Classifier.Module
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs.ModuleAndPackageDocumentation.Classifier.Package
+import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
+
+internal fun interface ModuleAndPackageDocumentationParsingContext {
+ fun markdownParserFor(fragment: ModuleAndPackageDocumentationFragment, location: String): MarkdownParser
+}
+
+internal fun ModuleAndPackageDocumentationParsingContext.parse(
+ fragment: ModuleAndPackageDocumentationFragment
+): DocumentationNode {
+ return markdownParserFor(fragment, fragment.source.sourceDescription).parse(fragment.documentation)
+}
+
+internal fun ModuleAndPackageDocumentationParsingContext(
+ logger: DokkaLogger,
+ moduleDescriptor: ModuleDescriptor? = null,
+ kDocFinder: KDocFinder? = null,
+ sourceSet: DokkaConfiguration.DokkaSourceSet? = null
+) = ModuleAndPackageDocumentationParsingContext { fragment, sourceLocation ->
+ val descriptor = when (fragment.classifier) {
+ Module -> moduleDescriptor?.getPackage(FqName.topLevel(Name.identifier("")))
+ Package -> moduleDescriptor?.getPackage(FqName(fragment.name))
+ }
+
+ val externalDri = { link: String ->
+ try {
+ if (kDocFinder != null && descriptor != null && sourceSet != null) {
+ with(kDocFinder) {
+ resolveKDocLink(
+ descriptor,
+ link,
+ sourceSet
+ ).sorted().firstOrNull()?.let {
+ DRI.from(
+ it
+ )
+ }
+ }
+ } else null
+ } catch (e1: IllegalArgumentException) {
+ logger.warn("Couldn't resolve link for $link")
+ null
+ }
+ }
+
+ MarkdownParser(externalDri = externalDri, sourceLocation)
+}
+
+private fun Collection<DeclarationDescriptor>.sorted() = sortedWith(
+ compareBy(
+ { it is ClassDescriptor },
+ { (it as? FunctionDescriptor)?.name },
+ { (it as? FunctionDescriptor)?.valueParameters?.size },
+ { (it as? FunctionDescriptor)?.valueParameters?.joinToString { it.type.toString() } }
+ )
+)
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationReader.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationReader.kt
new file mode 100644
index 00000000..66bbf1a8
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationReader.kt
@@ -0,0 +1,113 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs
+
+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.impl.moduledocs.ModuleAndPackageDocumentation.Classifier
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.model.DPackage
+import org.jetbrains.dokka.model.SourceSetDependent
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.doc.Deprecated
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.utilities.associateWithNotNull
+import org.jetbrains.kotlin.analysis.kotlin.internal.ModuleAndPackageDocumentationReader
+
+internal fun ModuleAndPackageDocumentationReader(context: DokkaContext): ModuleAndPackageDocumentationReader =
+ ContextModuleAndPackageDocumentationReader(context)
+
+private class ContextModuleAndPackageDocumentationReader(
+ private val context: DokkaContext
+) : ModuleAndPackageDocumentationReader {
+
+ private val kotlinAnalysis: KotlinAnalysis = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kotlinAnalysis }
+ private val kdocFinder: KDocFinder = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kdocFinder }
+
+ private val documentationFragments: SourceSetDependent<List<ModuleAndPackageDocumentationFragment>> =
+ context.configuration.sourceSets.associateWith { sourceSet ->
+ sourceSet.includes.flatMap { include -> parseModuleAndPackageDocumentationFragments(include) }
+ }
+
+ private fun findDocumentationNodes(
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet>,
+ predicate: (ModuleAndPackageDocumentationFragment) -> Boolean
+ ): SourceSetDependent<DocumentationNode> {
+ return sourceSets.associateWithNotNull { sourceSet ->
+ val fragments = documentationFragments[sourceSet].orEmpty().filter(predicate)
+ val moduleDescriptor = kotlinAnalysis[sourceSet].moduleDescriptor
+ val documentations = fragments.map { fragment ->
+ parseModuleAndPackageDocumentation(
+ context = ModuleAndPackageDocumentationParsingContext(context.logger, moduleDescriptor, kdocFinder, sourceSet),
+ fragment = fragment
+ )
+ }
+ when (documentations.size) {
+ 0 -> null
+ 1 -> documentations.single().documentation
+ else -> DocumentationNode(documentations.flatMap { it.documentation.children }
+ .mergeDocumentationNodes())
+ }
+ }
+ }
+
+ private val ModuleAndPackageDocumentationFragment.canonicalPackageName: String
+ get() {
+ check(classifier == Classifier.Package)
+ if (name == "[root]") return ""
+ return name
+ }
+
+ override fun read(module: DModule): SourceSetDependent<DocumentationNode> {
+ return findDocumentationNodes(module.sourceSets) { fragment ->
+ fragment.classifier == Classifier.Module && (fragment.name == module.name)
+ }
+ }
+
+ override fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> {
+ return findDocumentationNodes(pkg.sourceSets) { fragment ->
+ fragment.classifier == Classifier.Package && fragment.canonicalPackageName == pkg.dri.packageName
+ }
+ }
+
+ override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode? {
+ val parsingContext = ModuleAndPackageDocumentationParsingContext(context.logger)
+
+ val documentationFragment = module.includes
+ .flatMap { include -> parseModuleAndPackageDocumentationFragments(include) }
+ .firstOrNull { fragment -> fragment.classifier == Classifier.Module && fragment.name == module.name }
+ ?: return null
+
+ val moduleDocumentation = parseModuleAndPackageDocumentation(parsingContext, documentationFragment)
+ return moduleDocumentation.documentation
+ }
+
+ private fun List<TagWrapper>.mergeDocumentationNodes(): List<TagWrapper> =
+ groupBy { it::class }.values.map {
+ it.reduce { acc, tagWrapper ->
+ val newRoot = CustomDocTag(
+ acc.children + tagWrapper.children,
+ name = (tagWrapper as? NamedTagWrapper)?.name.orEmpty()
+ )
+ when (acc) {
+ is See -> acc.copy(newRoot)
+ is Param -> acc.copy(newRoot)
+ is Throws -> acc.copy(newRoot)
+ is Sample -> acc.copy(newRoot)
+ is Property -> acc.copy(newRoot)
+ is CustomTagWrapper -> acc.copy(newRoot)
+ is Description -> acc.copy(newRoot)
+ is Author -> acc.copy(newRoot)
+ is Version -> acc.copy(newRoot)
+ is Since -> acc.copy(newRoot)
+ is Return -> acc.copy(newRoot)
+ is Receiver -> acc.copy(newRoot)
+ is Constructor -> acc.copy(newRoot)
+ is Deprecated -> acc.copy(newRoot)
+ is org.jetbrains.dokka.model.doc.Suppress -> acc.copy(newRoot)
+ }
+ }
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationSource.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationSource.kt
new file mode 100644
index 00000000..18105be0
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationSource.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs
+
+import java.io.File
+
+internal abstract class ModuleAndPackageDocumentationSource {
+ abstract val sourceDescription: String
+ abstract val documentation: String
+ override fun toString(): String = sourceDescription
+}
+
+internal data class ModuleAndPackageDocumentationFile(private val file: File) : ModuleAndPackageDocumentationSource() {
+ override val sourceDescription: String = file.path
+ override val documentation: String by lazy(LazyThreadSafetyMode.PUBLICATION) { file.readText() }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/parseModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/parseModuleAndPackageDocumentation.kt
new file mode 100644
index 00000000..59b7d2e9
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/parseModuleAndPackageDocumentation.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs
+
+internal fun parseModuleAndPackageDocumentation(
+ context: ModuleAndPackageDocumentationParsingContext,
+ fragment: ModuleAndPackageDocumentationFragment
+): ModuleAndPackageDocumentation {
+ return ModuleAndPackageDocumentation(
+ name = fragment.name,
+ classifier = fragment.classifier,
+ documentation = context.parse(fragment)
+ )
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/parseModuleAndPackageDocumentationFragments.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/parseModuleAndPackageDocumentationFragments.kt
new file mode 100644
index 00000000..32f636ff
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/parseModuleAndPackageDocumentationFragments.kt
@@ -0,0 +1,55 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs
+
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs.ModuleAndPackageDocumentation.Classifier.Module
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs.ModuleAndPackageDocumentation.Classifier.Package
+import java.io.File
+
+internal fun parseModuleAndPackageDocumentationFragments(source: File): List<ModuleAndPackageDocumentationFragment> {
+ return parseModuleAndPackageDocumentationFragments(ModuleAndPackageDocumentationFile(source))
+}
+
+internal fun parseModuleAndPackageDocumentationFragments(
+ source: ModuleAndPackageDocumentationSource
+): List<ModuleAndPackageDocumentationFragment> {
+ val fragmentStrings = source.documentation.split(Regex("(|^)#\\s*(?=(Module|Package))"))
+ return fragmentStrings
+ .filter(String::isNotBlank)
+ .map { fragmentString -> parseModuleAndPackageDocFragment(source, fragmentString) }
+}
+
+private fun parseModuleAndPackageDocFragment(
+ source: ModuleAndPackageDocumentationSource,
+ fragment: String
+): ModuleAndPackageDocumentationFragment {
+ val firstLineAndDocumentation = fragment.split("\r\n", "\n", "\r", limit = 2)
+ val firstLine = firstLineAndDocumentation[0]
+
+ val classifierAndName = firstLine.split(Regex("\\s+"), limit = 2)
+
+ val classifier = when (classifierAndName[0].trim()) {
+ "Module" -> Module
+ "Package" -> Package
+ else -> throw IllegalStateException(
+ """Unexpected classifier: "${classifierAndName[0]}", expected either "Module" or "Package".
+ |For more information consult the specification: https://kotlinlang.org/docs/dokka-module-and-package-docs.html""".trimMargin()
+ )
+ }
+
+ if (classifierAndName.size != 2 && classifier == Module) {
+ throw IllegalModuleAndPackageDocumentation(source, "Missing Module name")
+ }
+
+ val name = classifierAndName.getOrNull(1)?.trim().orEmpty()
+ if (classifier == Package && name.contains(Regex("\\s"))) {
+ throw IllegalModuleAndPackageDocumentation(
+ source, "Package name cannot contain whitespace in '$firstLine'"
+ )
+ }
+
+ return ModuleAndPackageDocumentationFragment(
+ name = name,
+ classifier = classifier,
+ documentation = firstLineAndDocumentation.getOrNull(1)?.trim().orEmpty(),
+ source = source
+ )
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorDocumentationContent.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorDocumentationContent.kt
new file mode 100644
index 00000000..5adf1194
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorDocumentationContent.kt
@@ -0,0 +1,16 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.java
+
+import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent
+import org.jetbrains.dokka.analysis.java.JavadocTag
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
+
+internal data class DescriptorDocumentationContent(
+ val descriptor: DeclarationDescriptor,
+ val element: KDocTag,
+ override val tag: JavadocTag,
+) : DocumentationContent {
+ override fun resolveSiblings(): List<DocumentationContent> {
+ return listOf(this)
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocComment.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocComment.kt
new file mode 100644
index 00000000..da7d5140
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocComment.kt
@@ -0,0 +1,79 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.java
+
+import org.jetbrains.dokka.analysis.java.*
+import org.jetbrains.dokka.analysis.java.doccomment.DocComment
+import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
+
+internal class DescriptorKotlinDocComment(
+ val comment: KDocTag,
+ val descriptor: DeclarationDescriptor
+) : DocComment {
+
+ private val tagsWithContent: List<KDocTag> = comment.children.mapNotNull { (it as? KDocTag) }
+
+ override fun hasTag(tag: JavadocTag): Boolean {
+ return when (tag) {
+ is DescriptionJavadocTag -> comment.getContent().isNotEmpty()
+ is ThrowingExceptionJavadocTag -> tagsWithContent.any { it.hasException(tag) }
+ else -> tagsWithContent.any { it.text.startsWith("@${tag.name}") }
+ }
+ }
+
+ private fun KDocTag.hasException(tag: ThrowingExceptionJavadocTag) =
+ text.startsWith("@${tag.name}") && getSubjectName() == tag.exceptionQualifiedName
+
+ override fun resolveTag(tag: JavadocTag): List<DocumentationContent> {
+ return when (tag) {
+ is DescriptionJavadocTag -> listOf(DescriptorDocumentationContent(descriptor, comment, tag))
+ is ParamJavadocTag -> {
+ val resolvedContent = resolveGeneric(tag)
+ listOf(resolvedContent[tag.paramIndex])
+ }
+
+ is ThrowsJavadocTag -> resolveThrowingException(tag)
+ is ExceptionJavadocTag -> resolveThrowingException(tag)
+ else -> resolveGeneric(tag)
+ }
+ }
+
+ private fun resolveThrowingException(tag: ThrowingExceptionJavadocTag): List<DescriptorDocumentationContent> {
+ val exceptionName = tag.exceptionQualifiedName ?: return resolveGeneric(tag)
+
+ return comment.children
+ .filterIsInstance<KDocTag>()
+ .filter { it.name == tag.name && it.getSubjectName() == exceptionName }
+ .map { DescriptorDocumentationContent(descriptor, it, tag) }
+ }
+
+ private fun resolveGeneric(tag: JavadocTag): List<DescriptorDocumentationContent> {
+ return comment.children.mapNotNull { element ->
+ if (element is KDocTag && element.name == tag.name) {
+ DescriptorDocumentationContent(descriptor, element, tag)
+ } else {
+ null
+ }
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as DescriptorKotlinDocComment
+
+ if (comment != other.comment) return false
+ if (descriptor.name != other.descriptor.name) return false
+ if (tagsWithContent != other.tagsWithContent) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = comment.hashCode()
+ result = 31 * result + descriptor.name.hashCode()
+ result = 31 * result + tagsWithContent.hashCode()
+ return result
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocCommentCreator.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocCommentCreator.kt
new file mode 100644
index 00000000..b191355d
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocCommentCreator.kt
@@ -0,0 +1,26 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.java
+
+import com.intellij.psi.PsiNamedElement
+import org.jetbrains.dokka.analysis.java.doccomment.DocComment
+import org.jetbrains.dokka.analysis.java.doccomment.DocCommentCreator
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KDocFinder
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.DescriptorFinder
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.KtElement
+
+internal class DescriptorKotlinDocCommentCreator(
+ private val kdocFinder: KDocFinder,
+ private val descriptorFinder: DescriptorFinder
+) : DocCommentCreator {
+ override fun create(element: PsiNamedElement): DocComment? {
+ val ktElement = element.navigationElement as? KtElement ?: return null
+ val kdoc = with (kdocFinder) {
+ ktElement.findKDoc()
+ } ?: return null
+ val descriptor = with (descriptorFinder) {
+ (element.navigationElement as? KtDeclaration)?.findDescriptor()
+ } ?: return null
+
+ return DescriptorKotlinDocComment(kdoc, descriptor)
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocCommentParser.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocCommentParser.kt
new file mode 100644
index 00000000..28565f2d
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocCommentParser.kt
@@ -0,0 +1,54 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.java
+
+import com.intellij.psi.PsiNamedElement
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.java.doccomment.DocComment
+import org.jetbrains.dokka.analysis.java.parsers.DocCommentParser
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.from
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator.parseFromKDocTag
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.utilities.DokkaLogger
+
+internal class DescriptorKotlinDocCommentParser(
+ private val context: DokkaContext,
+ private val logger: DokkaLogger
+) : DocCommentParser {
+
+ override fun canParse(docComment: DocComment): Boolean {
+ return docComment is DescriptorKotlinDocComment
+ }
+
+ override fun parse(docComment: DocComment, context: PsiNamedElement): DocumentationNode {
+ val kotlinDocComment = docComment as DescriptorKotlinDocComment
+ return parseDocumentation(kotlinDocComment)
+ }
+
+ fun parseDocumentation(element: DescriptorKotlinDocComment, parseWithChildren: Boolean = true): DocumentationNode {
+ val sourceSet = context.configuration.sourceSets.let { sourceSets ->
+ sourceSets.firstOrNull { it.sourceSetID.sourceSetName == "jvmMain" }
+ ?: sourceSets.first { it.analysisPlatform == Platform.jvm }
+ }
+ val kdocFinder = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kdocFinder }
+ return parseFromKDocTag(
+ kDocTag = element.comment,
+ externalDri = { link: String ->
+ try {
+ kdocFinder.resolveKDocLink(element.descriptor, link, sourceSet)
+ .firstOrNull()
+ ?.let { DRI.from(it) }
+ } catch (e1: IllegalArgumentException) {
+ logger.warn("Couldn't resolve link for $link")
+ null
+ }
+ },
+ kdocLocation = null,
+ parseWithChildren = parseWithChildren
+ )
+ }
+}
+
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinAnalysisProjectProvider.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinAnalysisProjectProvider.kt
new file mode 100644
index 00000000..72151a72
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinAnalysisProjectProvider.kt
@@ -0,0 +1,16 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.java
+
+import com.intellij.openapi.project.Project
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.java.ProjectProvider
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+
+internal class KotlinAnalysisProjectProvider : ProjectProvider {
+ override fun getProject(sourceSet: DokkaConfiguration.DokkaSourceSet, context: DokkaContext): Project {
+ val kotlinAnalysis = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kotlinAnalysis }
+ return kotlinAnalysis[sourceSet].project
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinAnalysisSourceRootsExtractor.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinAnalysisSourceRootsExtractor.kt
new file mode 100644
index 00000000..ed6b8c53
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinAnalysisSourceRootsExtractor.kt
@@ -0,0 +1,27 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.java
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.java.SourceRootsExtractor
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
+import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
+import java.io.File
+
+internal class KotlinAnalysisSourceRootsExtractor : SourceRootsExtractor {
+
+ override fun extract(sourceSet: DokkaConfiguration.DokkaSourceSet, context: DokkaContext): List<File> {
+ val kotlinAnalysis = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kotlinAnalysis }
+ val environment = kotlinAnalysis[sourceSet].environment
+ return environment.configuration.get(CLIConfigurationKeys.CONTENT_ROOTS)
+ ?.filterIsInstance<JavaSourceRoot>()
+ ?.mapNotNull { it.file.takeIf { isFileInSourceRoots(it, sourceSet) } }
+ ?: listOf()
+ }
+
+ private fun isFileInSourceRoots(file: File, sourceSet: DokkaConfiguration.DokkaSourceSet): Boolean =
+ sourceSet.sourceRoots.any { root -> file.startsWith(root) }
+
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinInheritDocTagContentProvider.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinInheritDocTagContentProvider.kt
new file mode 100644
index 00000000..3c92fc65
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinInheritDocTagContentProvider.kt
@@ -0,0 +1,31 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.java
+
+import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent
+import org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin
+import org.jetbrains.dokka.analysis.java.parsers.doctag.DocTagParserContext
+import org.jetbrains.dokka.analysis.java.parsers.doctag.InheritDocTagContentProvider
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.query
+
+internal class KotlinInheritDocTagContentProvider(
+ context: DokkaContext
+) : InheritDocTagContentProvider {
+
+ val parser: DescriptorKotlinDocCommentParser by lazy {
+ context.plugin<JavaAnalysisPlugin>().query { docCommentParsers }
+ .single { it is DescriptorKotlinDocCommentParser } as DescriptorKotlinDocCommentParser
+ }
+
+ override fun canConvert(content: DocumentationContent): Boolean = content is DescriptorDocumentationContent
+
+ override fun convertToHtml(content: DocumentationContent, docTagParserContext: DocTagParserContext): String {
+ val descriptorContent = content as DescriptorDocumentationContent
+ val inheritedDocNode = parser.parseDocumentation(
+ DescriptorKotlinDocComment(descriptorContent.element, descriptorContent.descriptor),
+ parseWithChildren = false
+ )
+ val id = docTagParserContext.store(inheritedDocNode)
+ return """<inheritdoc id="$id"/>"""
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/CollectionExtensions.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/CollectionExtensions.kt
new file mode 100644
index 00000000..e1dec28c
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/CollectionExtensions.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator
+
+// TODO [beresnev] remove this copy-paste and use the same method from stdlib instead after updating to 1.5
+internal inline fun <T, R : Any> Iterable<T>.firstNotNullOfOrNull(transform: (T) -> R?): R? {
+ for (element in this) {
+ val result = transform(element)
+ if (result != null) {
+ return result
+ }
+ }
+ return null
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultDescriptorToDocumentableTranslator.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultDescriptorToDocumentableTranslator.kt
new file mode 100644
index 00000000..7633a93f
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultDescriptorToDocumentableTranslator.kt
@@ -0,0 +1,1275 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator
+
+import com.intellij.psi.PsiElement
+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
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin
+import org.jetbrains.dokka.analysis.java.parsers.JavadocParser
+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.AnalysisContext
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.DescriptorDocumentableSource
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.KotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.from
+import org.jetbrains.dokka.links.*
+import org.jetbrains.dokka.links.Callable
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.AnnotationTarget
+import org.jetbrains.dokka.model.Nullable
+import org.jetbrains.dokka.model.Visibility
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.query
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.transformers.sources.AsyncSourceToDocumentableTranslator
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.dokka.utilities.parallelMap
+import org.jetbrains.dokka.utilities.parallelMapNotNull
+import org.jetbrains.kotlin.KtNodeTypes
+import org.jetbrains.kotlin.builtins.functions.FunctionClassDescriptor
+import org.jetbrains.kotlin.builtins.isBuiltinExtensionFunctionalType
+import org.jetbrains.kotlin.builtins.isExtensionFunctionType
+import org.jetbrains.kotlin.builtins.isSuspendFunctionTypeOrSubtype
+import org.jetbrains.kotlin.codegen.isJvmStaticInObjectOrClassOrInterface
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.descriptors.ClassKind
+import org.jetbrains.kotlin.descriptors.annotations.Annotated
+import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
+import org.jetbrains.kotlin.descriptors.java.JavaVisibilities
+import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
+import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
+import org.jetbrains.kotlin.load.kotlin.toSourceElement
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
+import org.jetbrains.kotlin.resolve.DescriptorUtils
+import org.jetbrains.kotlin.resolve.calls.components.isVararg
+import org.jetbrains.kotlin.resolve.calls.util.getValueArgumentsInParentheses
+import org.jetbrains.kotlin.resolve.constants.ConstantValue
+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.scopes.StaticScopeForKotlinEnum
+import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
+import org.jetbrains.kotlin.resolve.source.PsiSourceElement
+import org.jetbrains.kotlin.resolve.source.PsiSourceFile
+import org.jetbrains.kotlin.types.*
+import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes
+import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
+import java.nio.file.Paths
+import org.jetbrains.kotlin.resolve.constants.AnnotationValue as ConstantsAnnotationValue
+import org.jetbrains.kotlin.resolve.constants.ArrayValue as ConstantsArrayValue
+import org.jetbrains.kotlin.resolve.constants.BooleanValue as ConstantsBooleanValue
+import org.jetbrains.kotlin.resolve.constants.DoubleValue as ConstantsDoubleValue
+import org.jetbrains.kotlin.resolve.constants.EnumValue as ConstantsEnumValue
+import org.jetbrains.kotlin.resolve.constants.FloatValue as ConstantsFloatValue
+import org.jetbrains.kotlin.resolve.constants.IntValue as ConstantsIntValue
+import org.jetbrains.kotlin.resolve.constants.KClassValue as ConstantsKtClassValue
+import org.jetbrains.kotlin.resolve.constants.LongValue as ConstantsLongValue
+import org.jetbrains.kotlin.resolve.constants.NullValue as ConstantsNullValue
+import org.jetbrains.kotlin.resolve.constants.UIntValue as ConstantsUIntValue
+import org.jetbrains.kotlin.resolve.constants.ULongValue as ConstantsULongValue
+
+internal class DefaultDescriptorToDocumentableTranslator(
+ private val context: DokkaContext
+) : AsyncSourceToDocumentableTranslator, ExternalClasslikesTranslator {
+
+ private val kotlinAnalysis: KotlinAnalysis = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kotlinAnalysis }
+ private val kdocFinder: KDocFinder = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kdocFinder }
+
+ override suspend fun invokeSuspending(sourceSet: DokkaSourceSet, context: DokkaContext): DModule {
+ val analysisContext = kotlinAnalysis[sourceSet]
+ val environment = analysisContext.environment
+ val packageFragments = environment.getSourceFiles().asSequence()
+ .map { it.packageFqName }
+ .distinct()
+ .mapNotNull { analysisContext.resolveSession.getPackageFragment(it) }
+ .toList()
+
+ val javadocParser = JavadocParser(
+ docCommentParsers = context.plugin<JavaAnalysisPlugin>().query { docCommentParsers },
+ docCommentFinder = context.plugin<JavaAnalysisPlugin>().docCommentFinder
+ )
+
+
+ return DokkaDescriptorVisitor(sourceSet, kdocFinder, kotlinAnalysis[sourceSet], context.logger, javadocParser).run {
+ packageFragments.parallelMap {
+ visitPackageFragmentDescriptor(
+ it
+ )
+ }
+ }.let {
+ DModule(
+ name = context.configuration.moduleName,
+ packages = it,
+ documentation = emptyMap(),
+ expectPresentInSet = null,
+ sourceSets = setOf(sourceSet)
+ )
+ }
+ }
+
+ override fun translateClassDescriptor(descriptor: ClassDescriptor, sourceSet: DokkaSourceSet): DClasslike {
+ val driInfo = DRI.from(descriptor.parents.first()).withEmptyInfo()
+
+ val javadocParser = JavadocParser(
+ docCommentParsers = context.plugin<JavaAnalysisPlugin>().query { docCommentParsers },
+ docCommentFinder = context.plugin<JavaAnalysisPlugin>().docCommentFinder
+ )
+
+ return runBlocking(Dispatchers.Default) {
+ DokkaDescriptorVisitor(sourceSet, kdocFinder, kotlinAnalysis[sourceSet], context.logger, javadocParser)
+ .visitClassDescriptor(descriptor, driInfo)
+ }
+ }
+}
+
+internal data class DRIWithPlatformInfo(
+ val dri: DRI,
+ val actual: SourceSetDependent<DocumentableSource>
+)
+
+internal fun DRI.withEmptyInfo() = DRIWithPlatformInfo(this, emptyMap())
+
+private class DokkaDescriptorVisitor(
+ private val sourceSet: DokkaSourceSet,
+ private val kDocFinder: KDocFinder,
+ private val analysisContext: AnalysisContext,
+ private val logger: DokkaLogger,
+ private val javadocParser: JavadocParser
+) {
+ private val syntheticDocProvider = SyntheticDescriptorDocumentationProvider(kDocFinder, sourceSet)
+
+ private fun Collection<DeclarationDescriptor>.filterDescriptorsInSourceSet() = filter {
+ it.toSourceElement.containingFile.toString().let { path ->
+ path.isNotBlank() && sourceSet.sourceRoots.any { root ->
+ Paths.get(path).startsWith(root.toPath())
+ }
+ }
+ }
+
+ private fun <T> T.toSourceSetDependent() = if (this != null) mapOf(sourceSet to this) else emptyMap()
+
+ suspend fun visitPackageFragmentDescriptor(descriptor: PackageFragmentDescriptor): DPackage {
+ val name = descriptor.fqName.asString().takeUnless { it.isBlank() } ?: ""
+ val driWithPlatform = DRI(packageName = name).withEmptyInfo()
+ val scope = descriptor.getMemberScope()
+ return coroutineScope {
+ val descriptorsWithKind = scope.getDescriptorsWithKind(true)
+
+ val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) }
+ val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) }
+ val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) }
+ val typealiases = async { descriptorsWithKind.typealiases.visitTypealiases() }
+
+ DPackage(
+ dri = driWithPlatform.dri,
+ functions = functions.await(),
+ properties = properties.await(),
+ classlikes = classlikes.await(),
+ typealiases = typealiases.await(),
+ documentation = descriptor.resolveDescriptorData(),
+ sourceSets = setOf(sourceSet)
+ )
+ }
+ }
+
+ suspend fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DClasslike =
+ when (descriptor.kind) {
+ ClassKind.ENUM_CLASS -> enumDescriptor(descriptor, parent)
+ ClassKind.OBJECT -> objectDescriptor(descriptor, parent)
+ ClassKind.INTERFACE -> interfaceDescriptor(descriptor, parent)
+ ClassKind.ANNOTATION_CLASS -> annotationDescriptor(descriptor, parent)
+ else -> classDescriptor(descriptor, parent)
+ }
+
+ private suspend fun interfaceDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DInterface {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+ val isExpect = descriptor.isExpect
+ val isActual = descriptor.isActual
+ val info = descriptor.resolveClassDescriptionData()
+
+ return coroutineScope {
+ val descriptorsWithKind = scope.getDescriptorsWithKind()
+
+ val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) }
+ val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) }
+ val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) }
+ val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } }
+
+ DInterface(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ functions = functions.await(),
+ properties = properties.await(),
+ classlikes = classlikes.await(),
+ sources = descriptor.createSources(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ supertypes = info.supertypes.toSourceSetDependent(),
+ documentation = info.docs,
+ generics = generics.await(),
+ companion = descriptor.companion(driWithPlatform),
+ sourceSets = setOf(sourceSet),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()),
+ info.ancestry.exceptionInSupertypesOrNull()
+ )
+ )
+ }
+ }
+
+ private suspend fun objectDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DObject {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+ val isExpect = descriptor.isExpect
+ val isActual = descriptor.isActual
+ val info = descriptor.resolveClassDescriptionData()
+
+
+ return coroutineScope {
+ val descriptorsWithKind = scope.getDescriptorsWithKind()
+
+ val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) }
+ val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) }
+ val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) }
+
+ DObject(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ functions = functions.await(),
+ properties = properties.await(),
+ classlikes = classlikes.await(),
+ sources = descriptor.createSources(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ supertypes = info.supertypes.toSourceSetDependent(),
+ documentation = info.docs,
+ sourceSets = setOf(sourceSet),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()),
+ info.ancestry.exceptionInSupertypesOrNull()
+ )
+ )
+ }
+
+
+ }
+
+ private suspend fun enumDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DEnum {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val isExpect = descriptor.isExpect
+ val isActual = descriptor.isActual
+ val info = descriptor.resolveClassDescriptionData()
+
+ return coroutineScope {
+ val descriptorsWithKind = descriptor.getEnumDescriptorsWithKind()
+
+ val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) }
+ val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) }
+ val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) }
+ val constructors =
+ async { descriptor.constructors.parallelMap { visitConstructorDescriptor(it, driWithPlatform) } }
+ val entries = async { descriptorsWithKind.enumEntries.visitEnumEntries(driWithPlatform) }
+
+ DEnum(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ entries = entries.await(),
+ constructors = constructors.await(),
+ functions = functions.await(),
+ properties = properties.await(),
+ classlikes = classlikes.await(),
+ sources = descriptor.createSources(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ supertypes = info.supertypes.toSourceSetDependent(),
+ documentation = info.docs,
+ companion = descriptor.companion(driWithPlatform),
+ sourceSets = setOf(sourceSet),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent())
+ )
+ )
+ }
+ }
+
+ private fun ClassDescriptor.getEnumDescriptorsWithKind(): DescriptorsWithKind {
+ val descriptorsWithKind = this.unsubstitutedMemberScope.getDescriptorsWithKind()
+ val staticScopeForKotlinEnum = (this.staticScope as? StaticScopeForKotlinEnum) ?: return descriptorsWithKind
+
+ // synthetic values() and valueOf() functions are not present among average class functions
+ val enumSyntheticFunctions = staticScopeForKotlinEnum.getContributedDescriptors { true }
+ .filterIsInstance<FunctionDescriptor>()
+
+ return descriptorsWithKind.copy(functions = descriptorsWithKind.functions + enumSyntheticFunctions)
+ }
+
+ private suspend fun visitEnumEntryDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DEnumEntry {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+ val isExpect = descriptor.isExpect
+
+ return coroutineScope {
+ val descriptorsWithKind = scope.getDescriptorsWithKind()
+
+ val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) }
+ val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) }
+ val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) }
+
+ DEnumEntry(
+ dri = driWithPlatform.dri.withEnumEntryExtra(),
+ name = descriptor.name.asString(),
+ documentation = descriptor.resolveDescriptorData(),
+ functions = functions.await(),
+ properties = properties.await(),
+ classlikes = classlikes.await(),
+ sourceSets = setOf(sourceSet),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations()
+ )
+ )
+ }
+ }
+
+ private suspend fun annotationDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DAnnotation {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+ val isExpect = descriptor.isExpect
+ val isActual = descriptor.isActual
+
+ return coroutineScope {
+ val descriptorsWithKind = scope.getDescriptorsWithKind()
+
+ val functions = async { descriptorsWithKind.functions.visitFunctions(driWithPlatform) }
+ val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform) }
+ val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) }
+ val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } }
+ val constructors =
+ async { descriptor.constructors.parallelMap { visitConstructorDescriptor(it, driWithPlatform) } }
+
+ DAnnotation(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ documentation = descriptor.resolveDescriptorData(),
+ functions = functions.await(),
+ properties = properties.await(),
+ classlikes = classlikes.await(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ sourceSets = setOf(sourceSet),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations()
+ ),
+ companion = descriptor.companionObjectDescriptor?.let { objectDescriptor(it, driWithPlatform) },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ generics = generics.await(),
+ constructors = constructors.await(),
+ sources = descriptor.createSources()
+ )
+ }
+
+
+ }
+
+ private suspend fun classDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DClass {
+ val driWithPlatform = parent.dri.withClass(descriptor.name.asString()).withEmptyInfo()
+ val scope = descriptor.unsubstitutedMemberScope
+ val isExpect = descriptor.isExpect
+ val isActual = descriptor.isActual
+ val info = descriptor.resolveClassDescriptionData()
+ val actual = descriptor.createSources()
+
+ return coroutineScope {
+ val descriptorsWithKind = scope.getDescriptorsWithKind()
+
+ val (regularFunctions, accessors) = splitFunctionsAndInheritedAccessors(
+ properties = descriptorsWithKind.properties,
+ functions = descriptorsWithKind.functions
+ )
+
+ val functions = async { regularFunctions.visitFunctions(driWithPlatform) }
+ val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform, accessors) }
+ val classlikes = async { descriptorsWithKind.classlikes.visitClasslikes(driWithPlatform) }
+ val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } }
+ val constructors = async {
+ descriptor.constructors.parallelMap {
+ visitConstructorDescriptor(
+ it,
+ if (it.isPrimary) DRIWithPlatformInfo(driWithPlatform.dri, actual)
+ else DRIWithPlatformInfo(driWithPlatform.dri, emptyMap())
+ )
+ }
+ }
+
+ DClass(
+ dri = driWithPlatform.dri,
+ name = descriptor.name.asString(),
+ constructors = constructors.await(),
+ functions = functions.await(),
+ properties = properties.await(),
+ classlikes = classlikes.await(),
+ sources = actual,
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ supertypes = info.supertypes.toSourceSetDependent(),
+ generics = generics.await(),
+ documentation = info.docs,
+ modifier = descriptor.modifier().toSourceSetDependent(),
+ companion = descriptor.companion(driWithPlatform),
+ sourceSets = setOf(sourceSet),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ ImplementedInterfaces(info.ancestry.allImplementedInterfaces().toSourceSetDependent()),
+ info.ancestry.exceptionInSupertypesOrNull()
+ )
+ )
+ }
+ }
+
+ /**
+ * @param implicitAccessors getters/setters that are not part of the property descriptor, for instance
+ * average methods inherited from java sources that access the property
+ */
+ private suspend fun visitPropertyDescriptor(
+ originalDescriptor: PropertyDescriptor,
+ implicitAccessors: DescriptorAccessorHolder?,
+ parent: DRIWithPlatformInfo
+ ): DProperty {
+ val (dri, _) = originalDescriptor.createDRI()
+ val inheritedFrom = dri.getInheritedFromDRI(parent)
+ val descriptor = originalDescriptor.getConcreteDescriptor()
+ val isExpect = descriptor.isExpect
+ val isActual = descriptor.isActual
+
+ val actual = originalDescriptor.createSources()
+
+ // example - generated getter that comes with data classes
+ suspend fun getDescriptorGetter() =
+ descriptor.accessors
+ .firstIsInstanceOrNull<PropertyGetterDescriptor>()
+ ?.let {
+ visitPropertyAccessorDescriptor(it, descriptor, dri, inheritedFrom)
+ }
+
+ suspend fun getImplicitAccessorGetter() =
+ implicitAccessors?.getter?.let { visitFunctionDescriptor(it, parent) }
+
+ // example - generated setter that comes with data classes
+ suspend fun getDescriptorSetter() =
+ descriptor.accessors
+ .firstIsInstanceOrNull<PropertySetterDescriptor>()
+ ?.let {
+ visitPropertyAccessorDescriptor(it, descriptor, dri, inheritedFrom)
+ }
+
+ suspend fun getImplicitAccessorSetter() =
+ implicitAccessors?.setter?.let { visitFunctionDescriptor(it, parent) }
+
+ return coroutineScope {
+ val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } }
+ val getter = getDescriptorGetter() ?: getImplicitAccessorGetter()
+ val setter = getDescriptorSetter() ?: getImplicitAccessorSetter()
+
+ DProperty(
+ dri = dri,
+ name = descriptor.name.asString(),
+ receiver = descriptor.extensionReceiverParameter?.let {
+ visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual))
+ },
+ sources = actual,
+ getter = getter,
+ setter = setter,
+ visibility = descriptor.getVisibility(implicitAccessors).toSourceSetDependent(),
+ documentation = descriptor.resolveDescriptorData(),
+ modifier = descriptor.modifier().toSourceSetDependent(),
+ type = descriptor.returnType!!.toBound(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ sourceSets = setOf(sourceSet),
+ generics = generics.await(),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll(
+ listOfNotNull(
+ (descriptor.additionalExtras() + descriptor.getAnnotationsWithBackingField()
+ .toAdditionalExtras()).toSet().toSourceSetDependent().toAdditionalModifiers(),
+ (descriptor.getAnnotationsWithBackingField() + descriptor.fileLevelAnnotations()).toSourceSetDependent()
+ .toAnnotations(),
+ descriptor.getDefaultValue()?.let { DefaultValue(it.toSourceSetDependent()) },
+ inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) },
+ takeIf { descriptor.isVar(getter, setter) }?.let { IsVar },
+ takeIf { descriptor.findPsi() is KtParameter }?.let { IsAlsoParameter(listOf(sourceSet)) }
+ )
+ )
+ )
+ }
+ }
+
+ private fun PropertyDescriptor.isVar(getter: DFunction?, setter: DFunction?): Boolean {
+ return if (this is JavaPropertyDescriptor) {
+ // in Java, concepts of extensibility and mutability are mixed into a single `final` modifier
+ // in Kotlin, it's different - val/var controls mutability and open modifier controls extensibility
+ // so when inheriting Java properties, you can end up with a final var - non extensible mutable prop
+ val isMutable = this.isVar
+ // non-final java property should be var if it has no accessors at all or has a setter
+ (isMutable && getter == null && setter == null) || (getter != null && setter != null)
+ } else {
+ this.isVar
+ }
+ }
+
+ private fun PropertyDescriptor.getVisibility(implicitAccessors: DescriptorAccessorHolder?): Visibility {
+ val isNonPublicJavaProperty = this is JavaPropertyDescriptor && !this.visibility.isPublicAPI
+ val visibility =
+ if (isNonPublicJavaProperty) {
+ // only try to take implicit getter's visibility if it's a java property
+ // because it's not guaranteed that implicit accessor will be used
+ // for the kotlin property, as it may have an explicit accessor of its own,
+ // i.e in data classes or with get() and set() are overridden
+ (implicitAccessors?.getter?.visibility ?: this.visibility)
+ } else {
+ this.visibility
+ }
+
+ return visibility.toDokkaVisibility()
+ }
+
+ private fun CallableMemberDescriptor.createDRI(wasOverridenBy: DRI? = null): Pair<DRI, DRI?> =
+ if (kind == CallableMemberDescriptor.Kind.DECLARATION || overriddenDescriptors.isEmpty())
+ Pair(DRI.from(this), wasOverridenBy)
+ else
+ overriddenDescriptors.first().createDRI(DRI.from(this))
+
+ private suspend fun visitFunctionDescriptor(
+ originalDescriptor: FunctionDescriptor,
+ parent: DRIWithPlatformInfo
+ ): DFunction {
+ val (dri, _) = originalDescriptor.createDRI()
+ val inheritedFrom = dri.getInheritedFromDRI(parent)
+ val descriptor = originalDescriptor.getConcreteDescriptor()
+ val isExpect = descriptor.isExpect
+ val isActual = descriptor.isActual
+
+ val actual = originalDescriptor.createSources()
+ return coroutineScope {
+ val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } }
+
+ DFunction(
+ dri = dri,
+ name = descriptor.name.asString(),
+ isConstructor = false,
+ receiver = descriptor.extensionReceiverParameter?.let {
+ visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual))
+ },
+ parameters = descriptor.valueParameters.mapIndexed { index, desc ->
+ parameter(index, desc, DRIWithPlatformInfo(dri, actual))
+ },
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ sources = actual,
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ generics = generics.await(),
+ documentation = descriptor.getDocumentation(),
+ modifier = descriptor.modifier().toSourceSetDependent(),
+ type = descriptor.returnType!!.toBound(),
+ sourceSets = setOf(sourceSet),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll(
+ inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) },
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ (descriptor.getAnnotations() + descriptor.fileLevelAnnotations()).toSourceSetDependent()
+ .toAnnotations(),
+ ObviousMember.takeIf { descriptor.isObvious() },
+ )
+ )
+ }
+ }
+
+ private fun FunctionDescriptor.getDocumentation(): SourceSetDependent<DocumentationNode> {
+ val isSynthesized = this.kind == CallableMemberDescriptor.Kind.SYNTHESIZED
+ return if (isSynthesized) {
+ syntheticDocProvider.getDocumentation(this)?.toSourceSetDependent() ?: emptyMap()
+ } else {
+ this.resolveDescriptorData()
+ }
+ }
+
+ /**
+ * `createDRI` returns the DRI of the exact element and potential DRI of an element that is overriding it
+ * (It can be also FAKE_OVERRIDE which is in fact just inheritance of the symbol)
+ *
+ * Looking at what PSIs do, they give the DRI of the element within the classnames where it is actually
+ * declared and inheritedFrom as the same DRI but truncated callable part.
+ * Therefore, we set callable to null and take the DRI only if it is indeed coming from different class.
+ */
+ private fun DRI.getInheritedFromDRI(parent: DRIWithPlatformInfo): DRI? {
+ return this.copy(callable = null)
+ .takeIf { parent.dri.classNames != this.classNames || parent.dri.packageName != this.packageName }
+ }
+
+ private fun FunctionDescriptor.isObvious(): Boolean {
+ return kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE
+ || (kind == CallableMemberDescriptor.Kind.SYNTHESIZED && !syntheticDocProvider.isDocumented(this))
+ || containingDeclaration.fqNameOrNull()?.isObvious() == true
+ }
+
+ private fun FqName.isObvious(): Boolean = with(this.asString()) {
+ return this == "kotlin.Any" || this == "kotlin.Enum"
+ || this == "java.lang.Object" || this == "java.lang.Enum"
+ }
+
+ suspend fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, parent: DRIWithPlatformInfo): DFunction {
+ val name = descriptor.constructedClass.name.toString()
+ val dri = parent.dri.copy(callable = Callable.from(descriptor, name))
+ val actual = descriptor.createSources()
+ val isExpect = descriptor.isExpect
+ val isActual = descriptor.isActual
+
+ return coroutineScope {
+ val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } }
+
+ DFunction(
+ dri = dri,
+ name = name,
+ isConstructor = true,
+ receiver = descriptor.extensionReceiverParameter?.let {
+ visitReceiverParameterDescriptor(it, DRIWithPlatformInfo(dri, actual))
+ },
+ parameters = descriptor.valueParameters.mapIndexed { index, desc ->
+ parameter(index, desc, DRIWithPlatformInfo(dri, actual))
+ },
+ sources = actual,
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ documentation = descriptor.resolveDescriptorData().let { sourceSetDependent ->
+ if (descriptor.isPrimary) {
+ sourceSetDependent.map { entry ->
+ Pair(
+ entry.key,
+ entry.value.copy(children = (entry.value.children.find { it is Constructor }?.root?.let { constructor ->
+ listOf(Description(constructor))
+ } ?: emptyList<TagWrapper>()) + entry.value.children.filterIsInstance<Param>()))
+ }.toMap()
+ } else {
+ sourceSetDependent
+ }
+ },
+ type = descriptor.returnType.toBound(),
+ modifier = descriptor.modifier().toSourceSetDependent(),
+ generics = generics.await(),
+ sourceSets = setOf(sourceSet),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll<DFunction>(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations()
+ ).let {
+ if (descriptor.isPrimary) {
+ it + PrimaryConstructorExtra
+ } else it
+ }
+ )
+ }
+ }
+
+ private suspend fun visitReceiverParameterDescriptor(
+ descriptor: ReceiverParameterDescriptor,
+ parent: DRIWithPlatformInfo
+ ) = DParameter(
+ dri = parent.dri.copy(target = PointingToDeclaration),
+ name = null,
+ type = descriptor.type.toBound(),
+ expectPresentInSet = null,
+ documentation = descriptor.resolveDescriptorData(),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(descriptor.getAnnotations().toSourceSetDependent().toAnnotations())
+ )
+
+ private suspend fun visitPropertyAccessorDescriptor(
+ descriptor: PropertyAccessorDescriptor,
+ propertyDescriptor: PropertyDescriptor,
+ parent: DRI,
+ inheritedFrom: DRI? = null
+ ): DFunction {
+ val dri = parent.copy(callable = Callable.from(descriptor))
+ val isGetter = descriptor is PropertyGetterDescriptor
+ val isExpect = descriptor.isExpect
+ val isActual = descriptor.isActual
+
+ suspend fun PropertyDescriptor.asParameter(parent: DRI) =
+ DParameter(
+ parent.copy(target = PointingToCallableParameters(parameterIndex = 1)),
+ this.name.asString(),
+ type = this.type.toBound(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ documentation = descriptor.resolveDescriptorData(),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ getAnnotationsWithBackingField().toSourceSetDependent().toAnnotations()
+ )
+ )
+
+ val name = run {
+ val rawName = propertyDescriptor.name.asString()
+ /*
+ * Kotlin has special rules for conversion around properties that
+ * start with "is" For more info see:
+ * https://kotlinlang.org/docs/java-interop.html#getters-and-setters
+ * https://kotlinlang.org/docs/java-to-kotlin-interop.html#properties
+ *
+ * Based on our testing, this rule only applies when the letter after
+ * the "is" is *not* lowercase. This means that words like "issue" won't
+ * have the rule applied but "is_foobar" and "is1of" will have the rule applied.
+ */
+ val specialCaseIs = rawName.startsWith("is")
+ && rawName.getOrNull(2)?.isLowerCase() == false
+
+ if (specialCaseIs) {
+ if (isGetter) rawName else rawName.replaceFirst("is", "set")
+ } else {
+ if (isGetter) "get${rawName.capitalize()}" else "set${rawName.capitalize()}"
+ }
+ }
+
+ val parameters =
+ if (isGetter) {
+ emptyList()
+ } else {
+ listOf(propertyDescriptor.asParameter(dri))
+ }
+
+ return coroutineScope {
+ val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } }
+ DFunction(
+ dri,
+ name,
+ isConstructor = false,
+ parameters = parameters,
+ visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
+ documentation = descriptor.resolveDescriptorData().mapInheritedTagWrappers(),
+ type = descriptor.returnType!!.toBound(),
+ generics = generics.await(),
+ modifier = descriptor.modifier().toSourceSetDependent(),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ receiver = descriptor.extensionReceiverParameter?.let {
+ visitReceiverParameterDescriptor(
+ it,
+ DRIWithPlatformInfo(dri, descriptor.createSources())
+ )
+ },
+ sources = descriptor.createSources(),
+ sourceSets = setOf(sourceSet),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) }
+ )
+ )
+ }
+ }
+
+ /**
+ * Workaround for a problem with inheriting parent TagWrappers of the wrong type.
+ *
+ * For instance, if you annotate a class with `@property`, kotlin compiler will propagate
+ * this tag to the property and its getters and setters. In case of getters and setters,
+ * it's more correct to display propagated docs as description instead of property
+ */
+ private fun SourceSetDependent<DocumentationNode>.mapInheritedTagWrappers(): SourceSetDependent<DocumentationNode> {
+ return this.mapValues { (_, value) ->
+ val mappedChildren = value.children.map {
+ when (it) {
+ is Property -> Description(it.root)
+ else -> it
+ }
+ }
+ value.copy(children = mappedChildren)
+ }
+ }
+
+ private suspend fun visitTypeAliasDescriptor(descriptor: TypeAliasDescriptor) =
+ with(descriptor) {
+ coroutineScope {
+ val generics = async { descriptor.declaredTypeParameters.parallelMap { it.toVariantTypeParameter() } }
+ val info = buildAncestryInformation(defaultType).copy(
+ superclass = buildAncestryInformation(underlyingType),
+ interfaces = emptyList()
+ )
+ DTypeAlias(
+ dri = DRI.from(this@with),
+ name = name.asString(),
+ type = defaultType.toBound(),
+ expectPresentInSet = null,
+ underlyingType = underlyingType.toBound().toSourceSetDependent(),
+ visibility = visibility.toDokkaVisibility().toSourceSetDependent(),
+ documentation = resolveDescriptorData(),
+ sourceSets = setOf(sourceSet),
+ generics = generics.await(),
+ sources = descriptor.createSources(),
+ extra = PropertyContainer.withAll(
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ info.exceptionInSupertypesOrNull(),
+ )
+ )
+ }
+ }
+
+ private suspend fun parameter(index: Int, descriptor: ValueParameterDescriptor, parent: DRIWithPlatformInfo) =
+ DParameter(
+ dri = parent.dri.copy(target = PointingToCallableParameters(index)),
+ name = descriptor.name.asString(),
+ type = descriptor.varargElementType?.toBound() ?: descriptor.type.toBound(),
+ expectPresentInSet = null,
+ documentation = descriptor.resolveDescriptorData(),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(listOfNotNull(
+ descriptor.additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ descriptor.getAnnotations().toSourceSetDependent().toAnnotations(),
+ descriptor.getDefaultValue()?.let { DefaultValue(it.toSourceSetDependent()) }
+ ))
+ )
+
+ private data class DescriptorsWithKind(
+ val functions: List<FunctionDescriptor>,
+ val properties: List<PropertyDescriptor>,
+ val classlikes: List<ClassDescriptor>,
+ val typealiases: List<TypeAliasDescriptor>,
+ val enumEntries: List<ClassDescriptor>
+ )
+
+ private fun MemberScope.getDescriptorsWithKind(shouldFilter: Boolean = false): DescriptorsWithKind {
+ val descriptors = getContributedDescriptors { true }.let {
+ if (shouldFilter) it.filterDescriptorsInSourceSet() else it
+ }
+
+ class EnumEntryDescriptor
+
+ val groupedDescriptors = descriptors.groupBy {
+ when {
+ it is FunctionDescriptor -> FunctionDescriptor::class
+ it is PropertyDescriptor -> PropertyDescriptor::class
+ it is ClassDescriptor && it.kind != ClassKind.ENUM_ENTRY -> ClassDescriptor::class
+ it is TypeAliasDescriptor -> TypeAliasDescriptor::class
+ it is ClassDescriptor && it.kind == ClassKind.ENUM_ENTRY -> EnumEntryDescriptor::class
+ else -> IllegalStateException::class
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return DescriptorsWithKind(
+ (groupedDescriptors[FunctionDescriptor::class] ?: emptyList()) as List<FunctionDescriptor>,
+ (groupedDescriptors[PropertyDescriptor::class] ?: emptyList()) as List<PropertyDescriptor>,
+ (groupedDescriptors[ClassDescriptor::class] ?: emptyList()) as List<ClassDescriptor>,
+ (groupedDescriptors[TypeAliasDescriptor::class] ?: emptyList()) as List<TypeAliasDescriptor>,
+ (groupedDescriptors[EnumEntryDescriptor::class] ?: emptyList()) as List<ClassDescriptor>
+ )
+ }
+
+ private suspend fun List<FunctionDescriptor>.visitFunctions(parent: DRIWithPlatformInfo): List<DFunction> =
+ coroutineScope { parallelMap { visitFunctionDescriptor(it, parent) } }
+
+ private suspend fun List<PropertyDescriptor>.visitProperties(
+ parent: DRIWithPlatformInfo,
+ implicitAccessors: Map<PropertyDescriptor, DescriptorAccessorHolder> = emptyMap(),
+ ): List<DProperty> {
+ return coroutineScope {
+ parallelMap {
+ visitPropertyDescriptor(it, implicitAccessors[it], parent)
+ }
+ }
+ }
+
+ private suspend fun List<ClassDescriptor>.visitClasslikes(parent: DRIWithPlatformInfo): List<DClasslike> =
+ coroutineScope { parallelMap { visitClassDescriptor(it, parent) } }
+
+ private suspend fun List<TypeAliasDescriptor>.visitTypealiases(): List<DTypeAlias> =
+ coroutineScope { parallelMap { visitTypeAliasDescriptor(it) } }
+
+ private suspend fun List<ClassDescriptor>.visitEnumEntries(parent: DRIWithPlatformInfo): List<DEnumEntry> =
+ coroutineScope { parallelMap { visitEnumEntryDescriptor(it, parent) } }
+
+ private fun DeclarationDescriptor.resolveDescriptorData(): SourceSetDependent<DocumentationNode> =
+ getDocumentation()?.toSourceSetDependent() ?: emptyMap()
+
+
+ private suspend fun toTypeConstructor(kt: KotlinType) =
+ GenericTypeConstructor(
+ DRI.from(kt.constructor.declarationDescriptor as DeclarationDescriptor),
+ kt.arguments.map { it.toProjection() },
+ extra = PropertyContainer.withAll(kt.getAnnotations().toSourceSetDependent().toAnnotations())
+ )
+
+ private suspend fun buildAncestryInformation(
+ kotlinType: KotlinType
+ ): AncestryNode {
+ val (interfaces, superclass) = kotlinType.immediateSupertypes().filterNot { it.isAnyOrNullableAny() }
+ .partition {
+ val declaration = it.constructor.declarationDescriptor
+ val descriptor = declaration as? ClassDescriptor
+ ?: (declaration as? TypeAliasDescriptor)?.underlyingType?.constructor?.declarationDescriptor as? ClassDescriptor
+ descriptor?.kind == ClassKind.INTERFACE
+ }
+
+ return coroutineScope {
+ AncestryNode(
+ typeConstructor = toTypeConstructor(kotlinType),
+ superclass = superclass.parallelMap(::buildAncestryInformation).singleOrNull(),
+ interfaces = interfaces.parallelMap(::buildAncestryInformation)
+ )
+ }
+ }
+
+
+ private suspend fun ClassDescriptor.resolveClassDescriptionData(): ClassInfo {
+ return coroutineScope {
+ ClassInfo(
+ buildAncestryInformation(this@resolveClassDescriptionData.defaultType),
+ resolveDescriptorData()
+ )
+ }
+ }
+
+ private suspend fun TypeParameterDescriptor.toVariantTypeParameter() =
+ DTypeParameter(
+ variantTypeParameter(
+ TypeParameter(DRI.from(this), name.identifier, annotations.getPresentableName())
+ ),
+ resolveDescriptorData(),
+ null,
+ upperBounds.map { it.toBound() },
+ setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ additionalExtras().toSourceSetDependent().toAdditionalModifiers(),
+ getAnnotations().toSourceSetDependent().toAnnotations()
+ )
+ )
+
+ private fun org.jetbrains.kotlin.descriptors.annotations.Annotations.getPresentableName(): String? =
+ mapNotNull { it.toAnnotation() }.singleOrNull { it.dri.classNames == "ParameterName" }?.params?.get("name")
+ .let { it as? StringValue }?.value?.let { unquotedValue(it) }
+
+ private suspend fun KotlinType.toBound(): Bound {
+ suspend fun <T : AnnotationTarget> annotations(): PropertyContainer<T> =
+ getAnnotations().takeIf { it.isNotEmpty() }?.let { annotations ->
+ PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations())
+ } ?: PropertyContainer.empty()
+
+ return when (this) {
+ is DynamicType -> Dynamic
+ is AbbreviatedType -> TypeAliased(
+ abbreviation.toBound(),
+ expandedType.toBound(),
+ annotations()
+ )
+ is DefinitelyNotNullType -> DefinitelyNonNullable(
+ original.toBound()
+ )
+ else -> when (val ctor = constructor.declarationDescriptor) {
+ is TypeParameterDescriptor -> TypeParameter(
+ dri = DRI.from(ctor),
+ name = ctor.name.asString(),
+ presentableName = annotations.getPresentableName(),
+ extra = annotations()
+ )
+ is FunctionClassDescriptor -> FunctionalTypeConstructor(
+ DRI.from(ctor),
+ arguments.map { it.toProjection() },
+ isExtensionFunction = isExtensionFunctionType || isBuiltinExtensionFunctionalType,
+ isSuspendable = isSuspendFunctionTypeOrSubtype,
+ presentableName = annotations.getPresentableName(),
+ extra = annotations()
+ )
+ else -> GenericTypeConstructor(
+ DRI.from(ctor!!), // TODO: remove '!!'
+ arguments.map { it.toProjection() },
+ annotations.getPresentableName(),
+ extra = annotations()
+ )
+ }.let {
+ if (isMarkedNullable) Nullable(it) else it
+ }
+ }
+ }
+
+ private suspend fun TypeProjection.toProjection(): Projection =
+ if (isStarProjection) Star else formPossiblyVariant()
+
+ private suspend fun TypeProjection.formPossiblyVariant(): Projection =
+ type.toBound().wrapWithVariance(projectionKind)
+
+ private fun TypeParameterDescriptor.variantTypeParameter(type: TypeParameter) =
+ type.wrapWithVariance(variance)
+
+ private fun <T : Bound> T.wrapWithVariance(variance: org.jetbrains.kotlin.types.Variance) =
+ when (variance) {
+ org.jetbrains.kotlin.types.Variance.INVARIANT -> Invariance(this)
+ org.jetbrains.kotlin.types.Variance.IN_VARIANCE -> Contravariance(this)
+ org.jetbrains.kotlin.types.Variance.OUT_VARIANCE -> Covariance(this)
+ }
+
+ private fun descriptorToAnyDeclaration(descriptor: DeclarationDescriptor): PsiElement? {
+ val effectiveReferencedDescriptors = DescriptorToSourceUtils.getEffectiveReferencedDescriptors(descriptor)
+ //take any
+ return effectiveReferencedDescriptors.firstOrNull()?.let { DescriptorToSourceUtils.getSourceFromDescriptor(it) }
+ }
+
+ private fun DeclarationDescriptor.getDocumentation(): DocumentationNode? {
+ val find = with(kDocFinder) {
+ find(::descriptorToAnyDeclaration)
+ }
+
+ return (find?.let {
+ parseFromKDocTag(
+ kDocTag = it,
+ externalDri = { link: String ->
+ try {
+ val kdocLink = with(kDocFinder) {
+ resolveKDocLink(
+ fromDescriptor = this@getDocumentation,
+ qualifiedName = link,
+ sourceSet = sourceSet
+ )
+ }
+ kdocLink.firstOrNull()?.let {
+ DRI.from(
+ it
+ )
+ }
+ } catch (e1: IllegalArgumentException) {
+ logger.warn("Couldn't resolve link for $link")
+ null
+ }
+ },
+ kdocLocation = toSourceElement.containingFile.name?.let {
+ val fqName = fqNameOrNull()?.asString()
+ if (fqName != null) "$it/$fqName"
+ else it
+ }
+ )
+ } ?: getJavaDocs())?.takeIf { it.children.isNotEmpty() }
+ }
+
+ private fun DeclarationDescriptor.getJavaDocs(): DocumentationNode? {
+ val overriddenDescriptors = (this as? CallableDescriptor)?.overriddenDescriptors ?: emptyList()
+ val allDescriptors = overriddenDescriptors + listOf(this)
+ return allDescriptors
+ .mapNotNull { it.findPsi() as? PsiNamedElement }
+ .firstOrNull()
+ ?.let { javadocParser.parseDocumentation(it) }
+ }
+
+ private suspend fun ClassDescriptor.companion(dri: DRIWithPlatformInfo): DObject? = companionObjectDescriptor?.let {
+ objectDescriptor(it, dri)
+ }
+
+ private fun MemberDescriptor.modifier() = when (modality) {
+ Modality.FINAL -> KotlinModifier.Final
+ Modality.SEALED -> KotlinModifier.Sealed
+ Modality.OPEN -> KotlinModifier.Open
+ Modality.ABSTRACT -> KotlinModifier.Abstract
+ else -> KotlinModifier.Empty
+ }
+
+ private fun MemberDescriptor.createSources(): SourceSetDependent<DocumentableSource> =
+ DescriptorDocumentableSource(this).toSourceSetDependent()
+
+ private fun FunctionDescriptor.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.Infix.takeIf { isInfix },
+ ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline },
+ ExtraModifiers.KotlinOnlyModifiers.Suspend.takeIf { isSuspend },
+ ExtraModifiers.KotlinOnlyModifiers.Operator.takeIf { isOperator },
+ ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() },
+ ExtraModifiers.KotlinOnlyModifiers.TailRec.takeIf { isTailrec },
+ ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal },
+ ExtraModifiers.KotlinOnlyModifiers.Override.takeIf { DescriptorUtils.isOverride(this) }
+ ).toSet()
+
+ private fun ClassDescriptor.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline },
+ ExtraModifiers.KotlinOnlyModifiers.Value.takeIf { isValue },
+ ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal },
+ ExtraModifiers.KotlinOnlyModifiers.Inner.takeIf { isInner },
+ ExtraModifiers.KotlinOnlyModifiers.Data.takeIf { isData },
+ ExtraModifiers.KotlinOnlyModifiers.Fun.takeIf { isFun },
+ ).toSet()
+
+ private fun ValueParameterDescriptor.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.NoInline.takeIf { isNoinline },
+ ExtraModifiers.KotlinOnlyModifiers.CrossInline.takeIf { isCrossinline },
+ ExtraModifiers.KotlinOnlyModifiers.Const.takeIf { isConst },
+ ExtraModifiers.KotlinOnlyModifiers.LateInit.takeIf { isLateInit },
+ ExtraModifiers.KotlinOnlyModifiers.VarArg.takeIf { isVararg }
+ ).toSet()
+
+ private fun TypeParameterDescriptor.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.Reified.takeIf { isReified }
+ ).toSet()
+
+ private fun PropertyDescriptor.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.Const.takeIf { isConst },
+ ExtraModifiers.KotlinOnlyModifiers.LateInit.takeIf { isLateInit },
+ ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() },
+ ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal },
+ ExtraModifiers.KotlinOnlyModifiers.Override.takeIf { DescriptorUtils.isOverride(this) }
+ )
+
+ private suspend fun Annotated.getAnnotations() = annotations.parallelMapNotNull { it.toAnnotation() }
+
+ private fun ConstantValue<*>.toValue(): AnnotationParameterValue = when (this) {
+ is ConstantsAnnotationValue -> AnnotationValue(value.toAnnotation())
+ is ConstantsArrayValue -> ArrayValue(value.map { it.toValue() })
+ is ConstantsEnumValue -> EnumValue(
+ fullEnumEntryName(),
+ DRI(enumClassId.packageFqName.asString(), fullEnumEntryName())
+ )
+ is ConstantsKtClassValue -> when (value) {
+ is NormalClass -> (value as NormalClass).value.classId.let {
+ ClassValue(
+ it.relativeClassName.asString(),
+ DRI(it.packageFqName.asString(), it.relativeClassName.asString())
+ )
+ }
+ is LocalClass -> (value as LocalClass).type.let {
+ ClassValue(
+ it.toString(),
+ DRI.from(it.constructor.declarationDescriptor as DeclarationDescriptor)
+ )
+ }
+ }
+ is ConstantsFloatValue -> FloatValue(value)
+ is ConstantsDoubleValue -> DoubleValue(value)
+ is ConstantsUIntValue -> IntValue(value)
+ is ConstantsULongValue -> LongValue(value)
+ is ConstantsIntValue -> IntValue(value)
+ is ConstantsLongValue -> LongValue(value)
+ is ConstantsBooleanValue -> BooleanValue(value)
+ is ConstantsNullValue -> NullValue
+ else -> StringValue(unquotedValue(toString()))
+ }
+
+ private fun AnnotationDescriptor.toAnnotation(scope: Annotations.AnnotationScope = Annotations.AnnotationScope.DIRECT): Annotations.Annotation =
+ Annotations.Annotation(
+ DRI.from(annotationClass as DeclarationDescriptor),
+ allValueArguments.map { it.key.asString() to it.value.toValue() }.toMap(),
+ mustBeDocumented(),
+ scope
+ )
+
+ private fun AnnotationDescriptor.mustBeDocumented(): Boolean =
+ if (source.toString() == "NO_SOURCE") false
+ else annotationClass?.annotations?.hasAnnotation(FqName("kotlin.annotation.MustBeDocumented")) ?: false
+
+ private suspend fun PropertyDescriptor.getAnnotationsWithBackingField(): List<Annotations.Annotation> =
+ getAnnotations() + (backingField?.getAnnotations() ?: emptyList())
+
+ private fun List<Annotations.Annotation>.toAdditionalExtras() = mapNotNull {
+ try {
+ ExtraModifiers.valueOf(it.dri.classNames?.toLowerCase() ?: "")
+ } catch (e: IllegalArgumentException) {
+ null
+ }
+ }
+
+ private fun <T : CallableMemberDescriptor> T.getConcreteDescriptor(): T {
+ return if (kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE) {
+ this
+ } else {
+ @Suppress("UNCHECKED_CAST")
+ overriddenDescriptors.first().getConcreteDescriptor() as T
+ }
+ }
+
+ private fun ValueParameterDescriptor.getDefaultValue(): Expression? =
+ ((source as? KotlinSourceElement)?.psi as? KtParameter)?.defaultValue?.toDefaultValueExpression()
+
+ private fun PropertyDescriptor.getDefaultValue(): Expression? =
+ (source as? KotlinSourceElement)?.psi?.children?.firstIsInstanceOrNull<KtConstantExpression>()
+ ?.toDefaultValueExpression()
+
+ private fun ClassDescriptor.getAppliedConstructorParameters() =
+ (source as PsiSourceElement).psi?.children?.flatMap {
+ (it as? KtInitializerList)?.initializersAsExpression().orEmpty()
+ }.orEmpty()
+
+ private fun KtInitializerList.initializersAsExpression() =
+ initializers.firstIsInstanceOrNull<KtCallElement>()
+ ?.getValueArgumentsInParentheses()
+ ?.map { it.getArgumentExpression()?.toDefaultValueExpression() ?: ComplexExpression("") }
+ .orEmpty()
+
+ private fun KtExpression.toDefaultValueExpression(): Expression? = when (node?.elementType) {
+ KtNodeTypes.INTEGER_CONSTANT -> parseLong(node?.text)?.let { IntegerConstant(it) }
+ KtNodeTypes.FLOAT_CONSTANT -> if (node?.text?.toLowerCase()?.endsWith('f') == true)
+ parseFloat(node?.text)?.let { FloatConstant(it) }
+ else parseDouble(node?.text)?.let { DoubleConstant(it) }
+ KtNodeTypes.BOOLEAN_CONSTANT -> BooleanConstant(node?.text == "true")
+ KtNodeTypes.STRING_TEMPLATE -> StringConstant(node.findChildByType(KtNodeTypes.LITERAL_STRING_TEMPLATE_ENTRY)?.text.orEmpty())
+ else -> node?.text?.let { ComplexExpression(it) }
+ }
+
+ private data class ClassInfo(val ancestry: AncestryNode, val docs: SourceSetDependent<DocumentationNode>) {
+ val supertypes: List<TypeConstructorWithKind>
+ get() = listOfNotNull(ancestry.superclass?.let {
+ it.typeConstructor.let {
+ TypeConstructorWithKind(
+ it,
+ KotlinClassKindTypes.CLASS
+ )
+ }
+ }) + ancestry.interfaces.map { TypeConstructorWithKind(it.typeConstructor, KotlinClassKindTypes.INTERFACE) }
+ }
+
+ private fun DescriptorVisibility.toDokkaVisibility(): Visibility = when (this.delegate) {
+ Visibilities.Public -> KotlinVisibility.Public
+ Visibilities.Protected -> KotlinVisibility.Protected
+ Visibilities.Internal -> KotlinVisibility.Internal
+ Visibilities.Private, Visibilities.PrivateToThis -> KotlinVisibility.Private
+ JavaVisibilities.ProtectedAndPackage -> KotlinVisibility.Protected
+ JavaVisibilities.ProtectedStaticVisibility -> KotlinVisibility.Protected
+ JavaVisibilities.PackageVisibility -> JavaVisibility.Default
+ else -> KotlinVisibility.Public
+ }
+
+ private fun ConstantsEnumValue.fullEnumEntryName() =
+ "${this.enumClassId.relativeClassName.asString()}.${this.enumEntryName.identifier}"
+
+ private fun DeclarationDescriptorWithSource.ktFile(): KtFile? =
+ (source.containingFile as? PsiSourceFile)?.psiFile as? KtFile
+
+ private suspend fun DeclarationDescriptorWithSource.fileLevelAnnotations() = ktFile()
+ ?.let { file ->
+ analysisContext.resolveSession.getFileAnnotations(file)
+ }
+ ?.toList()
+ ?.parallelMap { it.toAnnotation(scope = Annotations.AnnotationScope.FILE) }
+ .orEmpty()
+
+ private fun AncestryNode.exceptionInSupertypesOrNull(): ExceptionInSupertypes? =
+ typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() }?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }
+}
+
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultExternalDocumentablesProvider.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultExternalDocumentablesProvider.kt
new file mode 100644
index 00000000..3c29b61d
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultExternalDocumentablesProvider.kt
@@ -0,0 +1,43 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator
+
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.DClasslike
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.kotlin.analysis.kotlin.internal.ExternalDocumentablesProvider
+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
+
+internal class DefaultExternalDocumentablesProvider(context: DokkaContext) : ExternalDocumentablesProvider {
+ private val analysis = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kotlinAnalysis }
+
+ private val translator: ExternalClasslikesTranslator = DefaultDescriptorToDocumentableTranslator(context)
+
+ 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].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}")
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DescriptorAccessorConventionUtil.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DescriptorAccessorConventionUtil.kt
new file mode 100644
index 00000000..fcb0b83d
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DescriptorAccessorConventionUtil.kt
@@ -0,0 +1,144 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator
+
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.descriptors.PropertyDescriptor
+import org.jetbrains.kotlin.load.java.JvmAbi
+import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
+import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName
+import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName
+
+internal data class DescriptorFunctionsHolder(
+ val regularFunctions: List<FunctionDescriptor>,
+ val accessors: Map<PropertyDescriptor, DescriptorAccessorHolder>
+)
+
+internal data class DescriptorAccessorHolder(
+ val getter: FunctionDescriptor? = null,
+ val setter: FunctionDescriptor? = null
+)
+
+/**
+ * Separate regular Kotlin/Java functions and inherited Java accessors
+ * to properly display properties inherited from Java.
+ *
+ * Take this example:
+ * ```
+ * // java
+ * public class JavaClass {
+ * private int a = 1;
+ * public int getA() { return a; }
+ * public void setA(int a) { this.a = a; }
+ * }
+ *
+ * // kotlin
+ * class Bar : JavaClass() {
+ * fun foo() {}
+ * }
+ * ```
+ *
+ * It should result in:
+ * - 1 regular function `foo`
+ * - Map a=[`getA`, `setA`]
+ */
+internal fun splitFunctionsAndInheritedAccessors(
+ properties: List<PropertyDescriptor>,
+ functions: List<FunctionDescriptor>
+): DescriptorFunctionsHolder {
+ val (javaMethods, kotlinFunctions) = functions.partition { it is JavaMethodDescriptor }
+ if (javaMethods.isEmpty()) {
+ return DescriptorFunctionsHolder(regularFunctions = kotlinFunctions, emptyMap())
+ }
+
+ val propertiesByName = properties.associateBy { it.name.asString() }
+ val regularFunctions = ArrayList<FunctionDescriptor>(kotlinFunctions)
+
+ val accessors = mutableMapOf<PropertyDescriptor, DescriptorAccessorHolder>()
+ javaMethods.forEach { function ->
+ val possiblePropertyNamesForFunction = function.toPossiblePropertyNames()
+ val property = possiblePropertyNamesForFunction.firstNotNullOfOrNull { propertiesByName[it] }
+ if (property != null && function.isAccessorFor(property)) {
+ accessors.compute(property) { prop, accessorHolder ->
+ if (function.isGetterFor(prop))
+ accessorHolder?.copy(getter = function) ?: DescriptorAccessorHolder(getter = function)
+ else
+ accessorHolder?.copy(setter = function) ?: DescriptorAccessorHolder(setter = function)
+ }
+ } else {
+ regularFunctions.add(function)
+ }
+ }
+
+ val accessorLookalikes = removeNonAccessorsReturning(accessors)
+ regularFunctions.addAll(accessorLookalikes)
+
+ return DescriptorFunctionsHolder(regularFunctions, accessors)
+}
+
+/**
+ * If a field has no getter, it's not accessible as a property from Kotlin's perspective,
+ * but it still might have a setter lookalike. In this case, this "setter" should be just a regular function
+ *
+ * @return removed elements
+ */
+private fun removeNonAccessorsReturning(
+ propertyAccessors: MutableMap<PropertyDescriptor, DescriptorAccessorHolder>
+): List<FunctionDescriptor> {
+ val nonAccessors = mutableListOf<FunctionDescriptor>()
+ propertyAccessors.entries.removeIf { (_, accessors) ->
+ if (accessors.getter == null && accessors.setter != null) {
+ nonAccessors.add(accessors.setter)
+ true
+ } else {
+ false
+ }
+ }
+ return nonAccessors
+}
+
+private fun FunctionDescriptor.toPossiblePropertyNames(): List<String> {
+ val stringName = this.name.asString()
+ return when {
+ JvmAbi.isSetterName(stringName) -> propertyNamesBySetMethodName(this.name).map { it.asString() }
+ JvmAbi.isGetterName(stringName) -> propertyNamesByGetMethod(this)
+ else -> listOf()
+ }
+}
+
+private fun propertyNamesByGetMethod(functionDescriptor: FunctionDescriptor): List<String> {
+ val stringName = functionDescriptor.name.asString()
+ // In java, the convention for boolean property accessors is as follows:
+ // - `private boolean active;`
+ // - `private boolean isActive();`
+ //
+ // Whereas in Kotlin, because there are no explicit accessors, the convention is
+ // - `val isActive: Boolean`
+ //
+ // This makes it difficult to guess the name of the accessor property in case of Java
+ val javaPropName = if (functionDescriptor is JavaMethodDescriptor && JvmAbi.startsWithIsPrefix(stringName)) {
+ val javaPropName = stringName.removePrefix("is").let { newName ->
+ newName.replaceFirst(newName[0], newName[0].toLowerCase())
+ }
+ javaPropName
+ } else {
+ null
+ }
+ val kotlinPropName = propertyNameByGetMethodName(functionDescriptor.name)?.asString()
+ return listOfNotNull(javaPropName, kotlinPropName)
+}
+
+private fun FunctionDescriptor.isAccessorFor(property: PropertyDescriptor): Boolean {
+ return (this.isGetterFor(property) || this.isSetterFor(property))
+ && !property.visibility.isPublicAPI
+ && this.visibility.isPublicAPI
+}
+
+private fun FunctionDescriptor.isGetterFor(property: PropertyDescriptor): Boolean {
+ return this.returnType == property.returnType
+ && this.valueParameters.isEmpty()
+}
+
+private fun FunctionDescriptor.isSetterFor(property: PropertyDescriptor): Boolean {
+ return this.valueParameters.size == 1
+ && this.valueParameters[0].type == property.returnType
+}
+
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/ExternalClasslikesTranslator.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/ExternalClasslikesTranslator.kt
new file mode 100644
index 00000000..0b4b4442
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/ExternalClasslikesTranslator.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator
+
+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.
+ */
+internal fun interface ExternalClasslikesTranslator {
+ fun translateClassDescriptor(descriptor: ClassDescriptor, sourceSet: DokkaSourceSet): DClasslike
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/KdocMarkdownParser.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/KdocMarkdownParser.kt
new file mode 100644
index 00000000..e47b9ba2
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/KdocMarkdownParser.kt
@@ -0,0 +1,101 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser
+import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser.Companion.fqDeclarationName
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.doc.Suppress
+import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
+
+internal fun parseFromKDocTag(
+ kDocTag: KDocTag?,
+ externalDri: (String) -> DRI?,
+ kdocLocation: String?,
+ parseWithChildren: Boolean = true
+): DocumentationNode {
+ return if (kDocTag == null) {
+ DocumentationNode(emptyList())
+ } else {
+ fun parseStringToDocNode(text: String) =
+ MarkdownParser(externalDri, kdocLocation).parseStringToDocNode(text)
+
+ fun pointedLink(tag: KDocTag): DRI? = (parseStringToDocNode("[${tag.getSubjectName()}]")).let {
+ val link = it.children[0].children[0]
+ if (link is DocumentationLink) link.dri else null
+ }
+
+ val allTags =
+ listOf(kDocTag) + if (kDocTag.canHaveParent() && parseWithChildren) getAllKDocTags(findParent(kDocTag)) else emptyList()
+ DocumentationNode(
+ allTags.map {
+ when (it.knownTag) {
+ null -> if (it.name == null) Description(parseStringToDocNode(it.getContent())) else CustomTagWrapper(
+ parseStringToDocNode(it.getContent()),
+ it.name!!
+ )
+ KDocKnownTag.AUTHOR -> Author(parseStringToDocNode(it.getContent()))
+ KDocKnownTag.THROWS -> {
+ val dri = pointedLink(it)
+ Throws(
+ parseStringToDocNode(it.getContent()),
+ dri?.fqDeclarationName() ?: it.getSubjectName().orEmpty(),
+ dri,
+ )
+ }
+ KDocKnownTag.EXCEPTION -> {
+ val dri = pointedLink(it)
+ Throws(
+ parseStringToDocNode(it.getContent()),
+ dri?.fqDeclarationName() ?: it.getSubjectName().orEmpty(),
+ dri
+ )
+ }
+ KDocKnownTag.PARAM -> Param(
+ parseStringToDocNode(it.getContent()),
+ it.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.RECEIVER -> Receiver(parseStringToDocNode(it.getContent()))
+ KDocKnownTag.RETURN -> Return(parseStringToDocNode(it.getContent()))
+ KDocKnownTag.SEE -> {
+ val dri = pointedLink(it)
+ See(
+ parseStringToDocNode(it.getContent()),
+ dri?.fqDeclarationName() ?: it.getSubjectName().orEmpty(),
+ dri,
+ )
+ }
+ KDocKnownTag.SINCE -> Since(parseStringToDocNode(it.getContent()))
+ KDocKnownTag.CONSTRUCTOR -> Constructor(parseStringToDocNode(it.getContent()))
+ KDocKnownTag.PROPERTY -> Property(
+ parseStringToDocNode(it.getContent()),
+ it.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.SAMPLE -> Sample(
+ parseStringToDocNode(it.getContent()),
+ it.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.SUPPRESS -> Suppress(parseStringToDocNode(it.getContent()))
+ }
+ }
+ )
+ }
+}
+
+//Horrible hack but since link resolution is passed as a function i am not able to resolve them otherwise
+@kotlin.Suppress("DeprecatedCallableAddReplaceWith")
+@Deprecated("This function makes wrong assumptions and is missing a lot of corner cases related to generics, " +
+ "parameters and static members. This is not supposed to be public API and will not be supported in the future")
+internal fun DRI.fqName(): String? = "$packageName.$classNames".takeIf { packageName != null && classNames != null }
+
+private fun findParent(kDoc: PsiElement): PsiElement =
+ if (kDoc.canHaveParent()) findParent(kDoc.parent) else kDoc
+
+private fun PsiElement.canHaveParent(): Boolean = this is KDocSection && knownTag != KDocKnownTag.PROPERTY
+
+private fun getAllKDocTags(kDocImpl: PsiElement): List<KDocTag> =
+ kDocImpl.children.filterIsInstance<KDocTag>().filterNot { it is KDocSection } + kDocImpl.children.flatMap {
+ getAllKDocTags(it)
+ }
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/SyntheticDescriptorDocumentationProvider.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/SyntheticDescriptorDocumentationProvider.kt
new file mode 100644
index 00000000..3b21f771
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/SyntheticDescriptorDocumentationProvider.kt
@@ -0,0 +1,46 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KDocFinder
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.from
+import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.resolve.DescriptorFactory
+
+private const val ENUM_VALUEOF_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumValueOf.kt.template"
+private const val ENUM_VALUES_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumValues.kt.template"
+
+internal class SyntheticDescriptorDocumentationProvider(
+ private val kDocFinder: KDocFinder,
+ private val sourceSet: DokkaConfiguration.DokkaSourceSet
+) {
+ fun isDocumented(descriptor: DeclarationDescriptor): Boolean = descriptor is FunctionDescriptor
+ && (DescriptorFactory.isEnumValuesMethod(descriptor) || DescriptorFactory.isEnumValueOfMethod(descriptor))
+
+ fun getDocumentation(descriptor: DeclarationDescriptor): DocumentationNode? {
+ val function = descriptor as? FunctionDescriptor ?: return null
+ return when {
+ DescriptorFactory.isEnumValuesMethod(function) -> loadTemplate(descriptor, ENUM_VALUES_TEMPLATE_PATH)
+ DescriptorFactory.isEnumValueOfMethod(function) -> loadTemplate(descriptor, ENUM_VALUEOF_TEMPLATE_PATH)
+ else -> null
+ }
+ }
+
+ private fun loadTemplate(descriptor: DeclarationDescriptor, filePath: String): DocumentationNode? {
+ val kdoc = loadContent(filePath) ?: return null
+ val parser = MarkdownParser({ link -> resolveLink(descriptor, link)}, filePath)
+ return parser.parse(kdoc)
+ }
+
+ private fun loadContent(filePath: String): String? = javaClass.getResource(filePath)?.readText()
+
+ private fun resolveLink(descriptor: DeclarationDescriptor, link: String): DRI? =
+ kDocFinder.resolveKDocLink(
+ fromDescriptor = descriptor,
+ qualifiedName = link,
+ sourceSet = sourceSet
+ ).firstOrNull()?.let { DRI.from(it) }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/annotationsValue.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/annotationsValue.kt
new file mode 100644
index 00000000..70254566
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/annotationsValue.kt
@@ -0,0 +1,3 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator
+
+internal fun unquotedValue(value: String): String = value.removeSurrounding("\"")
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/isException.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/isException.kt
new file mode 100644
index 00000000..710846b3
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/isException.kt
@@ -0,0 +1,18 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator
+
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.AncestryNode
+import org.jetbrains.dokka.model.TypeConstructor
+
+internal fun AncestryNode.typeConstructorsBeingExceptions(): List<TypeConstructor> {
+ fun traverseSupertypes(ancestry: AncestryNode): List<TypeConstructor> =
+ listOf(ancestry.typeConstructor) + (ancestry.superclass?.let(::traverseSupertypes) ?: emptyList())
+
+ return superclass?.let(::traverseSupertypes)?.filter { type -> type.dri.isDirectlyAnException() } ?: emptyList()
+}
+
+internal fun DRI.isDirectlyAnException(): Boolean =
+ toString().let { stringed ->
+ stringed == "kotlin/Exception///PointingToDeclaration/" ||
+ stringed == "java.lang/Exception///PointingToDeclaration/"
+ }
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/subprojects/analysis-kotlin-descriptors/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
new file mode 100644
index 00000000..c7a8a233
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
@@ -0,0 +1 @@
+org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/test/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ParseModuleAndPackageDocumentationFragmentsTest.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/test/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ParseModuleAndPackageDocumentationFragmentsTest.kt
new file mode 100644
index 00000000..321aba45
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/compiler/src/test/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ParseModuleAndPackageDocumentationFragmentsTest.kt
@@ -0,0 +1,282 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors
+
+
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs.*
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs.ModuleAndPackageDocumentation.Classifier.*
+import org.jetbrains.dokka.analysis.markdown.jb.MARKDOWN_ELEMENT_FILE_NAME
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
+import kotlin.test.assertEquals
+
+class ParseModuleAndPackageDocumentationFragmentsTest {
+
+ private fun testBasicExample(lineSeperator: String = "\n") {
+ val source = source(
+ """
+ # Module kotlin-demo
+ Module description
+
+ # Package org.jetbrains.kotlin.demo
+ Package demo description
+ ## Level 2 heading
+ Heading 2\r\n
+
+ # Package org.jetbrains.kotlin.demo2
+ Package demo2 description
+ """.trimIndent().replace("\n", lineSeperator)
+ )
+ val fragments = parseModuleAndPackageDocumentationFragments(source)
+
+ assertEquals(
+ listOf(
+ ModuleAndPackageDocumentationFragment(
+ classifier = Module,
+ name = "kotlin-demo",
+ documentation = "Module description",
+ source = source
+ ),
+ ModuleAndPackageDocumentationFragment(
+ classifier = Package,
+ name = "org.jetbrains.kotlin.demo",
+ documentation = "Package demo description${lineSeperator}## Level 2 heading${lineSeperator}Heading 2\\r\\n",
+ source = source
+ ),
+ ModuleAndPackageDocumentationFragment(
+ classifier = Package,
+ name = "org.jetbrains.kotlin.demo2",
+ documentation = "Package demo2 description",
+ source = source
+ )
+ ),
+ fragments
+ )
+ }
+
+ @Test
+ fun `basic example`() {
+ testBasicExample()
+ }
+
+ @Test
+ fun `CRLF line seperators`() {
+ testBasicExample("\r\n")
+ }
+
+ @Test
+ fun `no module name specified fails`() {
+ val exception = assertThrows<IllegalModuleAndPackageDocumentation> {
+ parseModuleAndPackageDocumentationFragments(
+ source(
+ """
+ # Module
+ No module name given
+ """.trimIndent()
+ )
+ )
+ }
+
+ assertTrue(
+ "Missing Module name" in exception.message.orEmpty(),
+ "Expected 'Missing Module name' in error message"
+ )
+ }
+
+ @Test
+ fun `no package name specified does not fail`() {
+ val source = source(
+ """
+ # Package
+ This is a root package
+ """.trimIndent()
+ )
+ val fragments = parseModuleAndPackageDocumentationFragments(source)
+ assertEquals(1, fragments.size, "Expected a single package fragment")
+
+ assertEquals(
+ ModuleAndPackageDocumentationFragment(
+ name = "",
+ classifier = Package,
+ documentation = "This is a root package",
+ source = source
+ ),
+ fragments.single()
+ )
+ }
+
+ @Test
+ fun `white space in module name is supported`() {
+ val fragment = parseModuleAndPackageDocumentationFragments(
+ source(
+ """
+ # Module My Module
+ Documentation for my module
+ """.trimIndent()
+ )
+ )
+
+ assertEquals(
+ Module, fragment.single().classifier,
+ "Expected module being parsec"
+ )
+
+ assertEquals(
+ "My Module", fragment.single().name,
+ "Expected module name with white spaces being parsed"
+ )
+
+ assertEquals(
+ "Documentation for my module", fragment.single().documentation,
+ "Expected documentation being available"
+ )
+ }
+
+ @Test
+ fun `white space in package name fails`() {
+ val exception = assertThrows<IllegalModuleAndPackageDocumentation> {
+ parseModuleAndPackageDocumentationFragments(
+ source(
+ """
+ # Package my package
+ """.trimIndent()
+ )
+ )
+ }
+
+ assertTrue(
+ "Package my package" in exception.message.orEmpty(),
+ "Expected problematic statement in error message"
+ )
+ }
+
+ @Test
+ fun `multiple whitespaces are supported in first line`() {
+ val source = source(
+ """
+ # Module my-module
+ My Module
+ # Package com.my.package
+ My Package
+ """.trimIndent()
+ )
+ val fragments = parseModuleAndPackageDocumentationFragments(source)
+
+ assertEquals(
+ listOf(
+ ModuleAndPackageDocumentationFragment(
+ classifier = Module,
+ name = "my-module",
+ documentation = "My Module",
+ source = source
+ ),
+ ModuleAndPackageDocumentationFragment(
+ classifier = Package,
+ name = "com.my.package",
+ documentation = "My Package",
+ source = source
+ )
+ ),
+ fragments
+ )
+ }
+
+ @Test
+ fun `parse from file`(@TempDir temporaryFolder: Path) {
+ val file = temporaryFolder.resolve("other.md").toFile()
+ file.writeText(
+ """
+ # Module MyModule
+ D1
+ # Package com.sample
+ D2
+ """.trimIndent()
+ )
+
+ assertEquals(
+ listOf(
+ ModuleAndPackageDocumentationFragment(
+ classifier = Module,
+ name = "MyModule",
+ documentation = "D1",
+ source = ModuleAndPackageDocumentationFile(file)
+ ),
+ ModuleAndPackageDocumentationFragment(
+ classifier = Package,
+ name = "com.sample",
+ documentation = "D2",
+ source = ModuleAndPackageDocumentationFile(file)
+ )
+ ),
+ parseModuleAndPackageDocumentationFragments(file)
+ )
+ }
+
+ @Test
+ fun `at in code block is supported`() {
+ val fragment = parseModuleAndPackageDocumentationFragments(
+ source(
+ """
+ # Module My Module
+ ```
+ @Smth
+ ```
+ @author Smb
+ """.trimIndent()
+ )
+ )
+
+ assertEquals(
+ "```\n" +
+ "@Smth\n" +
+ "```\n" +
+ "@author Smb", fragment.single().documentation,
+ "Expected documentation being available"
+ )
+
+ val parsingContext = ModuleAndPackageDocumentationParsingContext(object : DokkaLogger {
+ override var warningsCount: Int = 0
+ override var errorsCount: Int = 0
+ override fun debug(message: String) {}
+ override fun info(message: String) {}
+ override fun progress(message: String) {}
+ override fun warn(message: String) {}
+ override fun error(message: String) {}
+ })
+ val parsedFragment = parseModuleAndPackageDocumentation(parsingContext, fragment.single())
+ val expectedDocumentationNode = DocumentationNode(
+ listOf(
+ Description(
+ CustomDocTag(
+ listOf(
+ CodeBlock(
+ listOf(
+ Text("@Smth")
+ )
+ )
+ ), name = MARKDOWN_ELEMENT_FILE_NAME
+ )
+ ),
+ Author(
+ CustomDocTag(
+ listOf(
+ P(listOf(Text("Smb")))
+ ), name = MARKDOWN_ELEMENT_FILE_NAME
+ )
+ )
+ )
+ )
+ assertEquals(
+ expectedDocumentationNode, parsedFragment.documentation
+ )
+ }
+
+ private fun source(documentation: String): ModuleAndPackageDocumentationSource =
+ object : ModuleAndPackageDocumentationSource() {
+ override val sourceDescription: String = "inline test"
+ override val documentation: String = documentation
+ }
+}