aboutsummaryrefslogtreecommitdiff
path: root/subprojects
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
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')
-rw-r--r--subprojects/analysis-java-psi/README.md5
-rw-r--r--subprojects/analysis-java-psi/api/analysis-java-psi.api148
-rw-r--r--subprojects/analysis-java-psi/build.gradle.kts18
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt83
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin.kt106
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavadocTag.kt48
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/SynheticElementDocumentationProvider.kt42
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocComment.kt14
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator.kt9
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory.kt20
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder.kt64
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent.kt11
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocComment.kt84
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocCommentCreator.kt11
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/PsiDocumentationContent.kt22
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/CommentResolutionContext.kt9
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DocCommentParser.kt12
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt797
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavaDocCommentParser.kt228
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavadocParser.kt24
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/DocTagParserContext.kt47
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/HtmlToDocTagConverter.kt114
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagContentProvider.kt10
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagResolver.kt114
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiDocTagParser.kt39
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiElementToHtmlConverter.kt214
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/CoreCopyPaste.kt20
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/NoopIntellijLogger.kt43
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PropertiesConventionUtil.kt101
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiAccessorConventionUtil.kt98
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiCommentsUtils.kt49
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiUtil.kt119
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/StdlibUtil.kt33
-rw-r--r--subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/resolveToGetDri.kt7
-rw-r--r--subprojects/analysis-java-psi/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin1
-rw-r--r--subprojects/analysis-kotlin-api/README.md10
-rw-r--r--subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api70
-rw-r--r--subprojects/analysis-kotlin-api/build.gradle.kts14
-rw-r--r--subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/KotlinAnalysisPlugin.kt17
-rw-r--r--subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/DocumentableSourceLanguageParser.kt16
-rw-r--r--subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/ExternalDocumentablesProvider.kt24
-rw-r--r--subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/FullClassHierarchyBuilder.kt17
-rw-r--r--subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/InheritanceBuilder.kt21
-rw-r--r--subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt31
-rw-r--r--subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/KotlinToJavaService.kt9
-rw-r--r--subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/ModuleAndPackageDocumentationReader.kt15
-rw-r--r--subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/SyntheticDocumentableDetector.kt11
-rw-r--r--subprojects/analysis-kotlin-descriptors/README.md8
-rw-r--r--subprojects/analysis-kotlin-descriptors/api/analysis-kotlin-descriptors.api0
-rw-r--r--subprojects/analysis-kotlin-descriptors/build.gradle.kts43
-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
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/README.md11
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/api/ide.api4
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/build.gradle.kts21
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/CoreKotlinCacheService.kt54
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/DokkaResolutionFacade.kt123
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeAnalysisContextCreator.kt29
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeCompilerExtensionPointProvider.kt46
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorAnalysisPlugin.kt38
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorFinder.kt12
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeKLibService.kt30
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeMockApplicationHack.kt12
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdePluginKDocFinder.kt48
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/ResolutionFacadeAnalysisContext.kt30
-rw-r--r--subprojects/analysis-kotlin-descriptors/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin1
-rw-r--r--subprojects/analysis-kotlin-symbols/README.md8
-rw-r--r--subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api0
-rw-r--r--subprojects/analysis-kotlin-symbols/build.gradle.kts34
-rw-r--r--subprojects/analysis-kotlin-symbols/compiler/api/compiler.api4
-rw-r--r--subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts13
-rw-r--r--subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin.kt11
-rw-r--r--subprojects/analysis-kotlin-symbols/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin1
-rw-r--r--subprojects/analysis-kotlin-symbols/ide/api/ide.api4
-rw-r--r--subprojects/analysis-kotlin-symbols/ide/build.gradle.kts13
-rw-r--r--subprojects/analysis-kotlin-symbols/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt11
-rw-r--r--subprojects/analysis-kotlin-symbols/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin1
-rw-r--r--subprojects/analysis-markdown-jb/README.md7
-rw-r--r--subprojects/analysis-markdown-jb/api/analysis-markdown-jb.api28
-rw-r--r--subprojects/analysis-markdown-jb/build.gradle.kts17
-rw-r--r--subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/MarkdownApi.kt8
-rw-r--r--subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/MarkdownParser.kt511
-rw-r--r--subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/ParseUtils.kt39
-rw-r--r--subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/Parser.kt131
-rw-r--r--subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/factories/DocTagsFromIElementFactory.kt86
145 files changed, 9771 insertions, 0 deletions
diff --git a/subprojects/analysis-java-psi/README.md b/subprojects/analysis-java-psi/README.md
new file mode 100644
index 00000000..d2bbd080
--- /dev/null
+++ b/subprojects/analysis-java-psi/README.md
@@ -0,0 +1,5 @@
+# Analysis: Java PSI
+
+An internal module for parsing Java sources. Defines no stable public API and is not published anywhere.
+
+Used by the Kotlin analysis artifacts to provide support for mixed-language (Kotlin+Java) projects.
diff --git a/subprojects/analysis-java-psi/api/analysis-java-psi.api b/subprojects/analysis-java-psi/api/analysis-java-psi.api
new file mode 100644
index 00000000..404249f8
--- /dev/null
+++ b/subprojects/analysis-java-psi/api/analysis-java-psi.api
@@ -0,0 +1,148 @@
+public final class org/jetbrains/dokka/analysis/java/AuthorJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag {
+ public static final field INSTANCE Lorg/jetbrains/dokka/analysis/java/AuthorJavadocTag;
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/java/BreakingAbstractionKotlinLightMethodChecker {
+ public abstract fun isLightAnnotation (Lcom/intellij/psi/PsiAnnotation;)Z
+ public abstract fun isLightAnnotationAttribute (Lcom/intellij/lang/jvm/annotation/JvmAnnotationAttribute;)Z
+}
+
+public final class org/jetbrains/dokka/analysis/java/DeprecatedJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag {
+ public static final field INSTANCE Lorg/jetbrains/dokka/analysis/java/DeprecatedJavadocTag;
+}
+
+public final class org/jetbrains/dokka/analysis/java/DescriptionJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag {
+ public static final field INSTANCE Lorg/jetbrains/dokka/analysis/java/DescriptionJavadocTag;
+}
+
+public final class org/jetbrains/dokka/analysis/java/ExceptionJavadocTag : org/jetbrains/dokka/analysis/java/ThrowingExceptionJavadocTag {
+ public static final field Companion Lorg/jetbrains/dokka/analysis/java/ExceptionJavadocTag$Companion;
+ public static final field name Ljava/lang/String;
+ public fun <init> (Ljava/lang/String;)V
+}
+
+public final class org/jetbrains/dokka/analysis/java/ExceptionJavadocTag$Companion {
+}
+
+public final class org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin {
+ public fun <init> ()V
+ public final fun getDocCommentCreators ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getDocCommentFinder ()Lorg/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder;
+ public final fun getDocCommentParsers ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getInheritDocTagContentProviders ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getKotlinLightMethodChecker ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getProjectProvider ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getSourceRootsExtractor ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+}
+
+public abstract class org/jetbrains/dokka/analysis/java/JavadocTag {
+ public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun getName ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/analysis/java/ParamJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag {
+ public static final field Companion Lorg/jetbrains/dokka/analysis/java/ParamJavadocTag$Companion;
+ public static final field name Ljava/lang/String;
+ public fun <init> (Lcom/intellij/psi/PsiMethod;Ljava/lang/String;I)V
+ public final fun getMethod ()Lcom/intellij/psi/PsiMethod;
+ public final fun getParamIndex ()I
+ public final fun getParamName ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/analysis/java/ParamJavadocTag$Companion {
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/java/ProjectProvider {
+ public abstract fun getProject (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Lorg/jetbrains/dokka/plugability/DokkaContext;)Lcom/intellij/openapi/project/Project;
+}
+
+public final class org/jetbrains/dokka/analysis/java/ReturnJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag {
+ public static final field INSTANCE Lorg/jetbrains/dokka/analysis/java/ReturnJavadocTag;
+}
+
+public final class org/jetbrains/dokka/analysis/java/SeeJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag {
+ public static final field Companion Lorg/jetbrains/dokka/analysis/java/SeeJavadocTag$Companion;
+ public static final field name Ljava/lang/String;
+ public fun <init> (Ljava/lang/String;)V
+ public final fun getQualifiedReference ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/analysis/java/SeeJavadocTag$Companion {
+}
+
+public final class org/jetbrains/dokka/analysis/java/SinceJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag {
+ public static final field INSTANCE Lorg/jetbrains/dokka/analysis/java/SinceJavadocTag;
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/java/SourceRootsExtractor {
+ public abstract fun extract (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Lorg/jetbrains/dokka/plugability/DokkaContext;)Ljava/util/List;
+}
+
+public abstract class org/jetbrains/dokka/analysis/java/ThrowingExceptionJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag {
+ public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun getExceptionQualifiedName ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/analysis/java/ThrowsJavadocTag : org/jetbrains/dokka/analysis/java/ThrowingExceptionJavadocTag {
+ public static final field Companion Lorg/jetbrains/dokka/analysis/java/ThrowsJavadocTag$Companion;
+ public static final field name Ljava/lang/String;
+ public fun <init> (Ljava/lang/String;)V
+}
+
+public final class org/jetbrains/dokka/analysis/java/ThrowsJavadocTag$Companion {
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/java/doccomment/DocComment {
+ public abstract fun hasTag (Lorg/jetbrains/dokka/analysis/java/JavadocTag;)Z
+ public abstract fun resolveTag (Lorg/jetbrains/dokka/analysis/java/JavadocTag;)Ljava/util/List;
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator {
+ public abstract fun create (Lcom/intellij/psi/PsiNamedElement;)Lorg/jetbrains/dokka/analysis/java/doccomment/DocComment;
+}
+
+public final class org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory {
+ public fun <init> (Ljava/util/List;)V
+ public final fun fromElement (Lcom/intellij/psi/PsiNamedElement;)Lorg/jetbrains/dokka/analysis/java/doccomment/DocComment;
+}
+
+public final class org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder {
+ public fun <init> (Lorg/jetbrains/dokka/utilities/DokkaLogger;Lorg/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory;)V
+ public final fun findClosestToElement (Lcom/intellij/psi/PsiNamedElement;)Lorg/jetbrains/dokka/analysis/java/doccomment/DocComment;
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent {
+ public abstract fun getTag ()Lorg/jetbrains/dokka/analysis/java/JavadocTag;
+ public abstract fun resolveSiblings ()Ljava/util/List;
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/java/parsers/DocCommentParser {
+ public abstract fun canParse (Lorg/jetbrains/dokka/analysis/java/doccomment/DocComment;)Z
+ public abstract fun parse (Lorg/jetbrains/dokka/analysis/java/doccomment/DocComment;Lcom/intellij/psi/PsiNamedElement;)Lorg/jetbrains/dokka/model/doc/DocumentationNode;
+}
+
+public final class org/jetbrains/dokka/analysis/java/parsers/JavadocParser : org/jetbrains/dokka/analysis/java/parsers/JavaDocumentationParser {
+ public fun <init> (Ljava/util/List;Lorg/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder;)V
+ public fun parseDocumentation (Lcom/intellij/psi/PsiNamedElement;)Lorg/jetbrains/dokka/model/doc/DocumentationNode;
+}
+
+public final class org/jetbrains/dokka/analysis/java/parsers/doctag/DocTagParserContext {
+ public fun <init> ()V
+ public final fun getDocumentationNode (Ljava/lang/String;)Lorg/jetbrains/dokka/model/doc/DocumentationNode;
+ public final fun getDri (Ljava/lang/String;)Lorg/jetbrains/dokka/links/DRI;
+ public final fun store (Lorg/jetbrains/dokka/links/DRI;)Ljava/lang/String;
+ public final fun store (Lorg/jetbrains/dokka/model/doc/DocumentationNode;)Ljava/lang/String;
+}
+
+public abstract interface class org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagContentProvider {
+ public abstract fun canConvert (Lorg/jetbrains/dokka/analysis/java/doccomment/DocumentationContent;)Z
+ public abstract fun convertToHtml (Lorg/jetbrains/dokka/analysis/java/doccomment/DocumentationContent;Lorg/jetbrains/dokka/analysis/java/parsers/doctag/DocTagParserContext;)Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/analysis/java/util/PsiDocumentableSource : org/jetbrains/dokka/model/DocumentableSource {
+ public fun <init> (Lcom/intellij/psi/PsiNamedElement;)V
+ public fun computeLineNumber ()Ljava/lang/Integer;
+ public fun getPath ()Ljava/lang/String;
+ public final fun getPsi ()Lcom/intellij/psi/PsiNamedElement;
+}
+
diff --git a/subprojects/analysis-java-psi/build.gradle.kts b/subprojects/analysis-java-psi/build.gradle.kts
new file mode 100644
index 00000000..88d043ee
--- /dev/null
+++ b/subprojects/analysis-java-psi/build.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ id("org.jetbrains.conventions.kotlin-jvm")
+}
+
+dependencies {
+ compileOnly(projects.core)
+
+ api(libs.intellij.java.psi.api)
+
+ implementation(projects.subprojects.analysisMarkdownJb)
+
+ implementation(libs.intellij.java.psi.impl)
+ implementation(libs.intellij.platform.util.api)
+ implementation(libs.intellij.platform.util.rt)
+
+ implementation(libs.kotlinx.coroutines.core)
+ implementation(libs.jsoup)
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt
new file mode 100644
index 00000000..72cf45d9
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt
@@ -0,0 +1,83 @@
+package org.jetbrains.dokka.analysis.java
+
+import com.intellij.openapi.vfs.VirtualFileManager
+import com.intellij.psi.PsiJavaFile
+import com.intellij.psi.PsiKeyword
+import com.intellij.psi.PsiManager
+import com.intellij.psi.PsiModifierListOwner
+import kotlinx.coroutines.coroutineScope
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.analysis.java.parsers.DokkaPsiParser
+import org.jetbrains.dokka.analysis.java.parsers.JavaPsiDocCommentParser
+import org.jetbrains.dokka.analysis.java.parsers.JavadocParser
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.model.JavaVisibility
+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.parallelMap
+import org.jetbrains.dokka.utilities.parallelMapNotNull
+
+internal class DefaultPsiToDocumentableTranslator : AsyncSourceToDocumentableTranslator {
+
+ override suspend fun invokeSuspending(sourceSet: DokkaSourceSet, context: DokkaContext): DModule {
+ return coroutineScope {
+ val projectProvider = context.plugin<JavaAnalysisPlugin>().querySingle { projectProvider }
+ val project = projectProvider.getProject(sourceSet, context)
+
+ val sourceRootsExtractor = context.plugin<JavaAnalysisPlugin>().querySingle { sourceRootsExtractor }
+ val sourceRoots = sourceRootsExtractor.extract(sourceSet, context)
+
+ val localFileSystem = VirtualFileManager.getInstance().getFileSystem("file")
+
+ val psiFiles = sourceRoots.parallelMap { sourceRoot ->
+ sourceRoot.absoluteFile.walkTopDown().mapNotNull {
+ localFileSystem.findFileByPath(it.path)?.let { vFile ->
+ PsiManager.getInstance(project).findFile(vFile) as? PsiJavaFile
+ }
+ }.toList()
+ }.flatten()
+
+ val docParser = createPsiParser(sourceSet, context)
+
+ DModule(
+ name = context.configuration.moduleName,
+ packages = psiFiles.parallelMapNotNull { it }.groupBy { it.packageName }.toList()
+ .parallelMap { (packageName: String, psiFiles: List<PsiJavaFile>) ->
+ docParser.parsePackage(packageName, psiFiles)
+ },
+ documentation = emptyMap(),
+ expectPresentInSet = null,
+ sourceSets = setOf(sourceSet)
+ )
+ }
+ }
+
+ private fun createPsiParser(sourceSet: DokkaSourceSet, context: DokkaContext): DokkaPsiParser {
+ val projectProvider = context.plugin<JavaAnalysisPlugin>().querySingle { projectProvider }
+ val docCommentParsers = context.plugin<JavaAnalysisPlugin>().query { docCommentParsers }
+ return DokkaPsiParser(
+ sourceSetData = sourceSet,
+ project = projectProvider.getProject(sourceSet, context),
+ logger = context.logger,
+ javadocParser = JavadocParser(
+ docCommentParsers = docCommentParsers,
+ docCommentFinder = context.plugin<JavaAnalysisPlugin>().docCommentFinder
+ ),
+ javaPsiDocCommentParser = docCommentParsers.single { it is JavaPsiDocCommentParser } as JavaPsiDocCommentParser,
+ lightMethodChecker = context.plugin<JavaAnalysisPlugin>().querySingle { kotlinLightMethodChecker }
+ )
+ }
+}
+
+internal fun PsiModifierListOwner.getVisibility() = modifierList?.let {
+ val ml = it.children.toList()
+ when {
+ ml.any { it.text == PsiKeyword.PUBLIC } || it.hasModifierProperty("public") -> JavaVisibility.Public
+ ml.any { it.text == PsiKeyword.PROTECTED } || it.hasModifierProperty("protected") -> JavaVisibility.Protected
+ ml.any { it.text == PsiKeyword.PRIVATE } || it.hasModifierProperty("private") -> JavaVisibility.Private
+ else -> JavaVisibility.Default
+ }
+} ?: JavaVisibility.Default
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin.kt
new file mode 100644
index 00000000..8884d444
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin.kt
@@ -0,0 +1,106 @@
+package org.jetbrains.dokka.analysis.java
+
+import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute
+import com.intellij.openapi.diagnostic.Logger
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiAnnotation
+import org.jetbrains.dokka.CoreExtensions
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.analysis.java.doccomment.DocCommentCreator
+import org.jetbrains.dokka.analysis.java.doccomment.DocCommentFactory
+import org.jetbrains.dokka.analysis.java.doccomment.DocCommentFinder
+import org.jetbrains.dokka.analysis.java.doccomment.JavaDocCommentCreator
+import org.jetbrains.dokka.analysis.java.parsers.DocCommentParser
+import org.jetbrains.dokka.analysis.java.parsers.doctag.InheritDocTagContentProvider
+import org.jetbrains.dokka.analysis.java.parsers.JavaPsiDocCommentParser
+import org.jetbrains.dokka.analysis.java.parsers.doctag.InheritDocTagResolver
+import org.jetbrains.dokka.analysis.java.parsers.doctag.PsiDocTagParser
+import org.jetbrains.dokka.analysis.java.util.NoopIntellijLoggerFactory
+import org.jetbrains.dokka.plugability.*
+import java.io.File
+
+
+@InternalDokkaApi
+interface ProjectProvider {
+ fun getProject(sourceSet: DokkaSourceSet, context: DokkaContext): Project
+}
+
+@InternalDokkaApi
+interface SourceRootsExtractor {
+ fun extract(sourceSet: DokkaSourceSet, context: DokkaContext): List<File>
+}
+
+@InternalDokkaApi
+interface BreakingAbstractionKotlinLightMethodChecker {
+ // TODO [beresnev] not even sure it's needed, but left for compatibility and to preserve behaviour
+ fun isLightAnnotation(annotation: PsiAnnotation): Boolean
+ fun isLightAnnotationAttribute(attribute: JvmAnnotationAttribute): Boolean
+}
+
+@InternalDokkaApi
+class JavaAnalysisPlugin : DokkaPlugin() {
+
+ // single
+ val projectProvider by extensionPoint<ProjectProvider>()
+
+ // single
+ val sourceRootsExtractor by extensionPoint<SourceRootsExtractor>()
+
+ // multiple
+ val docCommentCreators by extensionPoint<DocCommentCreator>()
+
+ // multiple
+ val docCommentParsers by extensionPoint<DocCommentParser>()
+
+ // none or more
+ val inheritDocTagContentProviders by extensionPoint<InheritDocTagContentProvider>()
+
+ // TODO [beresnev] figure out a better way depending on what it's used for
+ val kotlinLightMethodChecker by extensionPoint<BreakingAbstractionKotlinLightMethodChecker>()
+
+ private val docCommentFactory by lazy {
+ DocCommentFactory(query { docCommentCreators }.reversed())
+ }
+
+ val docCommentFinder by lazy {
+ DocCommentFinder(logger, docCommentFactory)
+ }
+
+ internal val javaDocCommentCreator by extending {
+ docCommentCreators providing { JavaDocCommentCreator() }
+ }
+
+ private val psiDocTagParser by lazy {
+ PsiDocTagParser(
+ inheritDocTagResolver = InheritDocTagResolver(
+ docCommentFactory = docCommentFactory,
+ docCommentFinder = docCommentFinder,
+ contentProviders = query { inheritDocTagContentProviders }
+ )
+ )
+ }
+
+ internal val javaDocCommentParser by extending {
+ docCommentParsers providing {
+ JavaPsiDocCommentParser(
+ psiDocTagParser
+ )
+ }
+ }
+
+ internal val psiToDocumentableTranslator by extending {
+ CoreExtensions.sourceToDocumentableTranslator providing { DefaultPsiToDocumentableTranslator() }
+ }
+
+ @OptIn(DokkaPluginApiPreview::class)
+ override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement
+
+ private companion object {
+ init {
+ // Suppress messages emitted by the IntelliJ logger since
+ // there's not much the end user can do about it
+ Logger.setFactory(NoopIntellijLoggerFactory())
+ }
+ }
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavadocTag.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavadocTag.kt
new file mode 100644
index 00000000..f5cd550f
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavadocTag.kt
@@ -0,0 +1,48 @@
+package org.jetbrains.dokka.analysis.java
+
+import com.intellij.psi.PsiMethod
+import org.jetbrains.dokka.InternalDokkaApi
+
+@InternalDokkaApi
+sealed class JavadocTag(val name: String)
+
+object AuthorJavadocTag : JavadocTag("author")
+object DeprecatedJavadocTag : JavadocTag("deprecated")
+object DescriptionJavadocTag : JavadocTag("description")
+object ReturnJavadocTag : JavadocTag("return")
+object SinceJavadocTag : JavadocTag("since")
+
+class ParamJavadocTag(
+ val method: PsiMethod,
+ val paramName: String,
+ val paramIndex: Int
+) : JavadocTag(name) {
+ companion object {
+ const val name: String = "param"
+ }
+}
+
+class SeeJavadocTag(
+ val qualifiedReference: String
+) : JavadocTag(name) {
+ companion object {
+ const val name: String = "see"
+ }
+}
+
+sealed class ThrowingExceptionJavadocTag(
+ name: String,
+ val exceptionQualifiedName: String?
+) : JavadocTag(name)
+
+class ThrowsJavadocTag(exceptionQualifiedName: String?) : ThrowingExceptionJavadocTag(name, exceptionQualifiedName) {
+ companion object {
+ const val name: String = "throws"
+ }
+}
+
+class ExceptionJavadocTag(exceptionQualifiedName: String?) : ThrowingExceptionJavadocTag(name, exceptionQualifiedName) {
+ companion object {
+ const val name: String = "exception"
+ }
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/SynheticElementDocumentationProvider.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/SynheticElementDocumentationProvider.kt
new file mode 100644
index 00000000..d780bb40
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/SynheticElementDocumentationProvider.kt
@@ -0,0 +1,42 @@
+package org.jetbrains.dokka.analysis.java
+
+import com.intellij.openapi.project.Project
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.SyntheticElement
+import com.intellij.psi.javadoc.PsiDocComment
+import org.jetbrains.dokka.analysis.java.parsers.JavaPsiDocCommentParser
+import org.jetbrains.dokka.model.doc.DocumentationNode
+
+private const val ENUM_VALUEOF_TEMPLATE_PATH = "/dokka/docs/javadoc/EnumValueOf.java.template"
+private const val ENUM_VALUES_TEMPLATE_PATH = "/dokka/docs/javadoc/EnumValues.java.template"
+
+internal class SyntheticElementDocumentationProvider(
+ private val javadocParser: JavaPsiDocCommentParser,
+ private val project: Project
+) {
+ fun isDocumented(psiElement: PsiElement): Boolean = psiElement is PsiMethod
+ && (psiElement.isSyntheticEnumValuesMethod() || psiElement.isSyntheticEnumValueOfMethod())
+
+ fun getDocumentation(psiElement: PsiElement): DocumentationNode? {
+ val psiMethod = psiElement as? PsiMethod ?: return null
+ val templatePath = when {
+ psiMethod.isSyntheticEnumValuesMethod() -> ENUM_VALUES_TEMPLATE_PATH
+ psiMethod.isSyntheticEnumValueOfMethod() -> ENUM_VALUEOF_TEMPLATE_PATH
+ else -> return null
+ }
+ val docComment = loadSyntheticDoc(templatePath) ?: return null
+ return javadocParser.parsePsiDocComment(docComment, psiElement)
+ }
+
+ private fun loadSyntheticDoc(path: String): PsiDocComment? {
+ val text = javaClass.getResource(path)?.readText() ?: return null
+ return JavaPsiFacade.getElementFactory(project).createDocCommentFromText(text)
+ }
+}
+
+private fun PsiMethod.isSyntheticEnumValuesMethod() = this.isSyntheticEnumFunction() && this.name == "values"
+private fun PsiMethod.isSyntheticEnumValueOfMethod() = this.isSyntheticEnumFunction() && this.name == "valueOf"
+private fun PsiMethod.isSyntheticEnumFunction() = this is SyntheticElement && this.containingClass?.isEnum == true
+
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocComment.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocComment.kt
new file mode 100644
index 00000000..6cc32233
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocComment.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.dokka.analysis.java.doccomment
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.analysis.java.JavadocTag
+
+/**
+ * MUST override equals and hashcode
+ */
+@InternalDokkaApi
+interface DocComment {
+ fun hasTag(tag: JavadocTag): Boolean
+
+ fun resolveTag(tag: JavadocTag): List<DocumentationContent>
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator.kt
new file mode 100644
index 00000000..3d7d4247
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator.kt
@@ -0,0 +1,9 @@
+package org.jetbrains.dokka.analysis.java.doccomment
+
+import com.intellij.psi.PsiNamedElement
+import org.jetbrains.dokka.InternalDokkaApi
+
+@InternalDokkaApi
+interface DocCommentCreator {
+ fun create(element: PsiNamedElement): DocComment?
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory.kt
new file mode 100644
index 00000000..96245ac2
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory.kt
@@ -0,0 +1,20 @@
+package org.jetbrains.dokka.analysis.java.doccomment
+
+import com.intellij.psi.PsiNamedElement
+import org.jetbrains.dokka.InternalDokkaApi
+
+@InternalDokkaApi
+class DocCommentFactory(
+ private val docCommentCreators: List<DocCommentCreator>
+) {
+ fun fromElement(element: PsiNamedElement): DocComment? {
+ docCommentCreators.forEach { creator ->
+ val comment = creator.create(element)
+ if (comment != null) {
+ return comment
+ }
+ }
+ return null
+ }
+}
+
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder.kt
new file mode 100644
index 00000000..32c8dc65
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder.kt
@@ -0,0 +1,64 @@
+package org.jetbrains.dokka.analysis.java.doccomment
+
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiNamedElement
+import com.intellij.psi.javadoc.PsiDocComment
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.analysis.java.util.from
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.utilities.DokkaLogger
+
+@InternalDokkaApi
+class DocCommentFinder(
+ private val logger: DokkaLogger,
+ private val docCommentFactory: DocCommentFactory,
+) {
+ fun findClosestToElement(element: PsiNamedElement): DocComment? {
+ val docComment = docCommentFactory.fromElement(element)
+ if (docComment != null) {
+ return docComment
+ }
+
+ return if (element is PsiMethod) {
+ findClosestToMethod(element)
+ } else {
+ element.children
+ .filterIsInstance<PsiDocComment>()
+ .firstOrNull()
+ ?.let { JavaDocComment(it) }
+ }
+ }
+
+ private fun findClosestToMethod(method: PsiMethod): DocComment? {
+ val superMethods = method.findSuperMethods()
+ if (superMethods.isEmpty()) return null
+
+ if (superMethods.size == 1) {
+ return findClosestToElement(superMethods.single())
+ }
+
+ val superMethodDocumentation = superMethods.map { superMethod -> findClosestToElement(superMethod) }.distinct()
+ if (superMethodDocumentation.size == 1) {
+ return superMethodDocumentation.single()
+ }
+
+ logger.debug(
+ "Conflicting documentation for ${DRI.from(method)}" +
+ "${superMethods.map { DRI.from(it) }}"
+ )
+
+ /* Prioritize super class over interface */
+ val indexOfSuperClass = superMethods.indexOfFirst { superMethod ->
+ val parent = superMethod.parent
+ if (parent is PsiClass) !parent.isInterface
+ else false
+ }
+
+ return if (indexOfSuperClass >= 0) {
+ superMethodDocumentation[indexOfSuperClass]
+ } else {
+ superMethodDocumentation.first()
+ }
+ }
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent.kt
new file mode 100644
index 00000000..c06ed496
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.dokka.analysis.java.doccomment
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.analysis.java.JavadocTag
+
+@InternalDokkaApi
+interface DocumentationContent {
+ val tag: JavadocTag
+
+ fun resolveSiblings(): List<DocumentationContent>
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocComment.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocComment.kt
new file mode 100644
index 00000000..5c9be887
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocComment.kt
@@ -0,0 +1,84 @@
+package org.jetbrains.dokka.analysis.java.doccomment
+
+import com.intellij.psi.PsiElement
+import com.intellij.psi.javadoc.PsiDocComment
+import com.intellij.psi.javadoc.PsiDocTag
+import org.jetbrains.dokka.analysis.java.*
+import org.jetbrains.dokka.analysis.java.util.contentElementsWithSiblingIfNeeded
+import org.jetbrains.dokka.analysis.java.util.getKotlinFqName
+import org.jetbrains.dokka.analysis.java.util.hasTag
+import org.jetbrains.dokka.analysis.java.util.resolveToElement
+import org.jetbrains.dokka.utilities.firstIsInstanceOrNull
+
+internal class JavaDocComment(val comment: PsiDocComment) : DocComment {
+ override fun hasTag(tag: JavadocTag): Boolean {
+ return when (tag) {
+ is ThrowingExceptionJavadocTag -> hasTag(tag)
+ else -> comment.hasTag(tag)
+ }
+ }
+
+ private fun hasTag(tag: ThrowingExceptionJavadocTag): Boolean =
+ comment.hasTag(tag) && comment.resolveTag(tag).firstIsInstanceOrNull<PsiDocTag>()
+ ?.resolveToElement()
+ ?.getKotlinFqName() == tag.exceptionQualifiedName
+
+ override fun resolveTag(tag: JavadocTag): List<DocumentationContent> {
+ return when (tag) {
+ is ParamJavadocTag -> resolveParamTag(tag)
+ is ThrowingExceptionJavadocTag -> resolveThrowingTag(tag)
+ else -> comment.resolveTag(tag).map { PsiDocumentationContent(it, tag) }
+ }
+ }
+
+ private fun resolveParamTag(tag: ParamJavadocTag): List<DocumentationContent> {
+ val resolvedParamElements = comment.resolveTag(tag)
+ .filterIsInstance<PsiDocTag>()
+ .map { it.contentElementsWithSiblingIfNeeded() }
+ .firstOrNull {
+ it.firstOrNull()?.text == tag.method.parameterList.parameters[tag.paramIndex].name
+ }.orEmpty()
+
+ return resolvedParamElements
+ .withoutReferenceLink()
+ .map { PsiDocumentationContent(it, tag) }
+ }
+
+ private fun resolveThrowingTag(tag: ThrowingExceptionJavadocTag): List<DocumentationContent> {
+ val resolvedElements = comment.resolveTag(tag)
+ .flatMap {
+ when (it) {
+ is PsiDocTag -> it.contentElementsWithSiblingIfNeeded()
+ else -> listOf(it)
+ }
+ }
+
+ return resolvedElements
+ .withoutReferenceLink()
+ .map { PsiDocumentationContent(it, tag) }
+ }
+
+ private fun PsiDocComment.resolveTag(tag: JavadocTag): List<PsiElement> {
+ return when (tag) {
+ DescriptionJavadocTag -> this.descriptionElements.toList()
+ else -> this.findTagsByName(tag.name).toList()
+ }
+ }
+
+ private fun List<PsiElement>.withoutReferenceLink(): List<PsiElement> = drop(1)
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as JavaDocComment
+
+ if (comment != other.comment) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return comment.hashCode()
+ }
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocCommentCreator.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocCommentCreator.kt
new file mode 100644
index 00000000..00efeb0a
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocCommentCreator.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.dokka.analysis.java.doccomment
+
+import com.intellij.psi.PsiDocCommentOwner
+import com.intellij.psi.PsiNamedElement
+
+internal class JavaDocCommentCreator : DocCommentCreator {
+ override fun create(element: PsiNamedElement): DocComment? {
+ val psiDocComment = (element as? PsiDocCommentOwner)?.docComment ?: return null
+ return JavaDocComment(psiDocComment)
+ }
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/PsiDocumentationContent.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/PsiDocumentationContent.kt
new file mode 100644
index 00000000..c36ce50d
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/PsiDocumentationContent.kt
@@ -0,0 +1,22 @@
+package org.jetbrains.dokka.analysis.java.doccomment
+
+import com.intellij.psi.PsiElement
+import com.intellij.psi.javadoc.PsiDocTag
+import org.jetbrains.dokka.analysis.java.JavadocTag
+import org.jetbrains.dokka.analysis.java.util.contentElementsWithSiblingIfNeeded
+
+internal data class PsiDocumentationContent(
+ val psiElement: PsiElement,
+ override val tag: JavadocTag
+) : DocumentationContent {
+
+ override fun resolveSiblings(): List<DocumentationContent> {
+ return if (psiElement is PsiDocTag) {
+ psiElement.contentElementsWithSiblingIfNeeded()
+ .map { content -> PsiDocumentationContent(content, tag) }
+ } else {
+ listOf(this)
+ }
+ }
+
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/CommentResolutionContext.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/CommentResolutionContext.kt
new file mode 100644
index 00000000..1e193648
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/CommentResolutionContext.kt
@@ -0,0 +1,9 @@
+package org.jetbrains.dokka.analysis.java.parsers
+
+import com.intellij.psi.javadoc.PsiDocComment
+import org.jetbrains.dokka.analysis.java.JavadocTag
+
+internal data class CommentResolutionContext(
+ val comment: PsiDocComment,
+ val tag: JavadocTag?
+)
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DocCommentParser.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DocCommentParser.kt
new file mode 100644
index 00000000..3f691799
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DocCommentParser.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.analysis.java.parsers
+
+import com.intellij.psi.PsiNamedElement
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.analysis.java.doccomment.DocComment
+import org.jetbrains.dokka.model.doc.DocumentationNode
+
+@InternalDokkaApi
+interface DocCommentParser {
+ fun canParse(docComment: DocComment): Boolean
+ fun parse(docComment: DocComment, context: PsiNamedElement): DocumentationNode
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt
new file mode 100644
index 00000000..45f44338
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt
@@ -0,0 +1,797 @@
+package org.jetbrains.dokka.analysis.java.parsers
+
+import com.intellij.lang.jvm.JvmModifier
+import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute
+import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue
+import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue
+import com.intellij.lang.jvm.annotation.JvmAnnotationEnumFieldValue
+import com.intellij.lang.jvm.types.JvmReferenceType
+import com.intellij.openapi.project.Project
+import com.intellij.psi.*
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.java.BreakingAbstractionKotlinLightMethodChecker
+import org.jetbrains.dokka.analysis.java.SyntheticElementDocumentationProvider
+import org.jetbrains.dokka.analysis.java.getVisibility
+import org.jetbrains.dokka.analysis.java.util.*
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.nextTarget
+import org.jetbrains.dokka.links.withClass
+import org.jetbrains.dokka.links.withEnumEntryExtra
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.AnnotationTarget
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.model.doc.Param
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.dokka.utilities.parallelForEach
+import org.jetbrains.dokka.utilities.parallelMap
+import org.jetbrains.dokka.utilities.parallelMapNotNull
+
+internal class DokkaPsiParser(
+ private val sourceSetData: DokkaConfiguration.DokkaSourceSet,
+ private val project: Project,
+ private val logger: DokkaLogger,
+ private val javadocParser: JavadocParser,
+ private val javaPsiDocCommentParser: JavaPsiDocCommentParser,
+ private val lightMethodChecker: BreakingAbstractionKotlinLightMethodChecker,
+) {
+ private val syntheticDocProvider = SyntheticElementDocumentationProvider(javaPsiDocCommentParser, project)
+
+ private val cachedBounds = hashMapOf<String, Bound>()
+
+ private val PsiMethod.hash: Int
+ get() = "$returnType $name$parameterList".hashCode()
+
+ private val PsiField.hash: Int
+ get() = "$type $name".hashCode()
+
+ private val PsiClassType.shouldBeIgnored: Boolean
+ get() = isClass("java.lang.Enum") || isClass("java.lang.Object")
+
+ private fun PsiClassType.isClass(qName: String): Boolean {
+ val shortName = qName.substringAfterLast('.')
+ if (className == shortName) {
+ val psiClass = resolve()
+ return psiClass?.qualifiedName == qName
+ }
+ return false
+ }
+
+ private fun <T> T.toSourceSetDependent() = mapOf(sourceSetData to this)
+
+ suspend fun parsePackage(packageName: String, psiFiles: List<PsiJavaFile>): DPackage = coroutineScope {
+ val dri = DRI(packageName = packageName)
+ val packageInfo = psiFiles.singleOrNull { it.name == "package-info.java" }
+ val documentation = packageInfo?.let {
+ javadocParser.parseDocumentation(it).toSourceSetDependent()
+ }.orEmpty()
+ val annotations = packageInfo?.packageStatement?.annotationList?.annotations
+
+ DPackage(
+ dri = dri,
+ functions = emptyList(),
+ properties = emptyList(),
+ classlikes = psiFiles.parallelMap { psiFile ->
+ coroutineScope {
+ psiFile.classes.asIterable().parallelMap { parseClasslike(it, dri) }
+ }
+ }.flatten(),
+ typealiases = emptyList(),
+ documentation = documentation,
+ expectPresentInSet = null,
+ sourceSets = setOf(sourceSetData),
+ extra = PropertyContainer.withAll(
+ annotations?.toList().orEmpty().toListOfAnnotations().toSourceSetDependent().toAnnotations()
+ )
+ )
+ }
+
+ private suspend fun parseClasslike(psi: PsiClass, parent: DRI): DClasslike = coroutineScope {
+ with(psi) {
+ val dri = parent.withClass(name.toString())
+ val superMethodsKeys = hashSetOf<Int>()
+ val superMethods = mutableListOf<Pair<PsiMethod, DRI>>()
+ val superFieldsKeys = hashSetOf<Int>()
+ val superFields = mutableListOf<Pair<PsiField, DRI>>()
+ methods.asIterable().parallelForEach { superMethodsKeys.add(it.hash) }
+
+ /**
+ * Caution! This method mutates
+ * - superMethodsKeys
+ * - superMethods
+ * - superFieldsKeys
+ * - superKeys
+ */
+ /**
+ * Caution! This method mutates
+ * - superMethodsKeys
+ * - superMethods
+ * - superFieldsKeys
+ * - superKeys
+ */
+ fun Array<PsiClassType>.getSuperTypesPsiClasses(): List<Pair<PsiClass, JavaClassKindTypes>> {
+ forEach { type ->
+ (type as? PsiClassType)?.resolve()?.let {
+ val definedAt = DRI.from(it)
+ it.methods.forEach { method ->
+ val hash = method.hash
+ if (!method.isConstructor && !superMethodsKeys.contains(hash) &&
+ method.getVisibility() != JavaVisibility.Private
+ ) {
+ superMethodsKeys.add(hash)
+ superMethods.add(Pair(method, definedAt))
+ }
+ }
+ it.fields.forEach { field ->
+ val hash = field.hash
+ if (!superFieldsKeys.contains(hash)) {
+ superFieldsKeys.add(hash)
+ superFields.add(Pair(field, definedAt))
+ }
+ }
+ }
+ }
+ return filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi ->
+ supertypePsi.resolve()?.let { supertypePsiClass ->
+ val javaClassKind = when {
+ supertypePsiClass.isInterface -> JavaClassKindTypes.INTERFACE
+ else -> JavaClassKindTypes.CLASS
+ }
+ supertypePsiClass to javaClassKind
+ }
+ }
+ }
+
+ fun traversePsiClassForAncestorsAndInheritedMembers(psiClass: PsiClass): AncestryNode {
+ val (classes, interfaces) = psiClass.superTypes.getSuperTypesPsiClasses()
+ .partition { it.second == JavaClassKindTypes.CLASS }
+
+ return AncestryNode(
+ typeConstructor = GenericTypeConstructor(
+ DRI.from(psiClass),
+ psiClass.typeParameters.map { typeParameter ->
+ TypeParameter(
+ dri = DRI.from(typeParameter),
+ name = typeParameter.name.orEmpty(),
+ extra = typeParameter.annotations()
+ )
+ }
+ ),
+ superclass = classes.singleOrNull()?.first?.let(::traversePsiClassForAncestorsAndInheritedMembers),
+ interfaces = interfaces.map { traversePsiClassForAncestorsAndInheritedMembers(it.first) }
+ )
+ }
+
+ val ancestry: AncestryNode = traversePsiClassForAncestorsAndInheritedMembers(this)
+
+ val (regularFunctions, accessors) = splitFunctionsAndAccessors(psi.fields, psi.methods)
+ val (regularSuperFunctions, superAccessors) = splitFunctionsAndAccessors(
+ fields = superFields.map { it.first }.toTypedArray(),
+ methods = superMethods.map { it.first }.toTypedArray()
+ )
+
+ val regularSuperFunctionsKeys = regularSuperFunctions.map { it.hash }.toSet()
+ val regularSuperFunctionsWithDRI = superMethods.filter { it.first.hash in regularSuperFunctionsKeys }
+
+ val superAccessorsWithDRI = superAccessors.mapValues { (field, methods) ->
+ val containsJvmField = field.annotations.mapNotNull { it.toAnnotation() }.any { it.isJvmField() }
+ if (containsJvmField) {
+ emptyList()
+ } else {
+ methods.mapNotNull { method -> superMethods.find { it.first.hash == method.hash } }
+ }
+ }
+
+ val overridden = regularFunctions.flatMap { it.findSuperMethods().toList() }
+ val documentation = javadocParser.parseDocumentation(this).toSourceSetDependent()
+ val allFunctions = async {
+ val parsedRegularFunctions = regularFunctions.parallelMapNotNull {
+ if (!it.isConstructor) parseFunction(
+ it,
+ parentDRI = dri
+ ) else null
+ }
+ val parsedSuperFunctions = regularSuperFunctionsWithDRI
+ .filter { it.first !in overridden }
+ .parallelMap { parseFunction(it.first, inheritedFrom = it.second) }
+
+ parsedRegularFunctions + parsedSuperFunctions
+ }
+ val allFields = async {
+ val parsedFields = fields.toList().parallelMapNotNull {
+ parseField(it, accessors[it].orEmpty())
+ }
+ val parsedSuperFields = superFields.parallelMapNotNull { (field, dri) ->
+ parseFieldWithInheritingAccessors(
+ field,
+ superAccessorsWithDRI[field].orEmpty(),
+ inheritedFrom = dri
+ )
+ }
+ parsedFields + parsedSuperFields
+ }
+ val source = parseSources()
+ val classlikes = async { innerClasses.asIterable().parallelMap { parseClasslike(it, dri) } }
+ val visibility = getVisibility().toSourceSetDependent()
+ val ancestors = (listOfNotNull(ancestry.superclass?.let {
+ it.typeConstructor.let { typeConstructor ->
+ TypeConstructorWithKind(
+ typeConstructor,
+ JavaClassKindTypes.CLASS
+ )
+ }
+ }) + ancestry.interfaces.map {
+ TypeConstructorWithKind(
+ it.typeConstructor,
+ JavaClassKindTypes.INTERFACE
+ )
+ }).toSourceSetDependent()
+ val modifiers = getModifier().toSourceSetDependent()
+ val implementedInterfacesExtra =
+ ImplementedInterfaces(ancestry.allImplementedInterfaces().toSourceSetDependent())
+
+ when {
+ isAnnotationType ->
+ DAnnotation(
+ name = name.orEmpty(),
+ dri = dri,
+ documentation = documentation,
+ expectPresentInSet = null,
+ sources = source,
+ functions = allFunctions.await(),
+ properties = allFields.await(),
+ classlikes = classlikes.await(),
+ visibility = visibility,
+ companion = null,
+ constructors = parseConstructors(dri),
+ generics = mapTypeParameters(dri),
+ sourceSets = setOf(sourceSetData),
+ isExpectActual = false,
+ extra = PropertyContainer.withAll(
+ implementedInterfacesExtra,
+ annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations()
+ )
+ )
+
+ isEnum -> DEnum(
+ dri = dri,
+ name = name.orEmpty(),
+ entries = fields.filterIsInstance<PsiEnumConstant>().map { entry ->
+ DEnumEntry(
+ dri = dri.withClass(entry.name).withEnumEntryExtra(),
+ name = entry.name,
+ documentation = javadocParser.parseDocumentation(entry).toSourceSetDependent(),
+ expectPresentInSet = null,
+ functions = emptyList(),
+ properties = emptyList(),
+ classlikes = emptyList(),
+ sourceSets = setOf(sourceSetData),
+ extra = PropertyContainer.withAll(
+ implementedInterfacesExtra,
+ annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations()
+ )
+ )
+ },
+ documentation = documentation,
+ expectPresentInSet = null,
+ sources = source,
+ functions = allFunctions.await(),
+ properties = fields.filter { it !is PsiEnumConstant }
+ .map { parseField(it, accessors[it].orEmpty()) },
+ classlikes = classlikes.await(),
+ visibility = visibility,
+ companion = null,
+ constructors = parseConstructors(dri),
+ supertypes = ancestors,
+ sourceSets = setOf(sourceSetData),
+ isExpectActual = false,
+ extra = PropertyContainer.withAll(
+ implementedInterfacesExtra,
+ annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations()
+ )
+ )
+
+ isInterface -> DInterface(
+ dri = dri,
+ name = name.orEmpty(),
+ documentation = documentation,
+ expectPresentInSet = null,
+ sources = source,
+ functions = allFunctions.await(),
+ properties = allFields.await(),
+ classlikes = classlikes.await(),
+ visibility = visibility,
+ companion = null,
+ generics = mapTypeParameters(dri),
+ supertypes = ancestors,
+ sourceSets = setOf(sourceSetData),
+ isExpectActual = false,
+ extra = PropertyContainer.withAll(
+ implementedInterfacesExtra,
+ annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations()
+ )
+ )
+
+ else -> DClass(
+ dri = dri,
+ name = name.orEmpty(),
+ constructors = parseConstructors(dri),
+ functions = allFunctions.await(),
+ properties = allFields.await(),
+ classlikes = classlikes.await(),
+ sources = source,
+ visibility = visibility,
+ companion = null,
+ generics = mapTypeParameters(dri),
+ supertypes = ancestors,
+ documentation = documentation,
+ expectPresentInSet = null,
+ modifier = modifiers,
+ sourceSets = setOf(sourceSetData),
+ isExpectActual = false,
+ extra = PropertyContainer.withAll(
+ implementedInterfacesExtra,
+ annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations(),
+ ancestry.exceptionInSupertypesOrNull()
+ )
+ )
+ }
+ }
+ }
+
+ /*
+ * Parameter `parentDRI` required for substitute package name:
+ * in the case of synthetic constructor, it will return empty from [DRI.Companion.from].
+ */
+ private fun PsiClass.parseConstructors(parentDRI: DRI): List<DFunction> {
+ val constructors = when {
+ isAnnotationType || isInterface -> emptyArray()
+ isEnum -> this.constructors
+ else -> this.constructors.takeIf { it.isNotEmpty() } ?: arrayOf(createDefaultConstructor())
+ }
+ return constructors.map { parseFunction(psi = it, isConstructor = true, parentDRI = parentDRI) }
+ }
+
+ /**
+ * PSI doesn't return a default constructor if class doesn't contain an explicit one.
+ * This method create synthetic constructor
+ * Visibility modifier is preserved from the class.
+ */
+ private fun PsiClass.createDefaultConstructor(): PsiMethod {
+ val psiElementFactory = JavaPsiFacade.getElementFactory(project)
+ val signature = when (val classVisibility = getVisibility()) {
+ JavaVisibility.Default -> name.orEmpty()
+ else -> "${classVisibility.name} $name"
+ }
+ return psiElementFactory.createConstructor(signature, this)
+ }
+
+ private fun AncestryNode.exceptionInSupertypesOrNull(): ExceptionInSupertypes? =
+ typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() }
+ ?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }
+
+ private fun parseFunction(
+ psi: PsiMethod,
+ isConstructor: Boolean = false,
+ inheritedFrom: DRI? = null,
+ parentDRI: DRI? = null,
+ ): DFunction {
+ val dri = parentDRI?.let { dri ->
+ DRI.from(psi).copy(packageName = dri.packageName, classNames = dri.classNames)
+ } ?: DRI.from(psi)
+ val docs = psi.getDocumentation()
+ return DFunction(
+ dri = dri,
+ name = psi.name,
+ isConstructor = isConstructor,
+ parameters = psi.parameterList.parameters.map { psiParameter ->
+ DParameter(
+ dri = dri.copy(target = dri.target.nextTarget()),
+ name = psiParameter.name,
+ documentation = DocumentationNode(
+ listOfNotNull(docs.firstChildOfTypeOrNull<Param> {
+ it.name == psiParameter.name
+ })
+ ).toSourceSetDependent(),
+ expectPresentInSet = null,
+ type = getBound(psiParameter.type),
+ sourceSets = setOf(sourceSetData),
+ extra = PropertyContainer.withAll(
+ psiParameter.annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations()
+ )
+ )
+ },
+ documentation = docs.toSourceSetDependent(),
+ expectPresentInSet = null,
+ sources = psi.parseSources(),
+ visibility = psi.getVisibility().toSourceSetDependent(),
+ type = psi.returnType?.let { getBound(type = it) } ?: Void,
+ generics = psi.mapTypeParameters(dri),
+ receiver = null,
+ modifier = psi.getModifier().toSourceSetDependent(),
+ sourceSets = setOf(sourceSetData),
+ isExpectActual = false,
+ extra = psi.additionalExtras().let {
+ PropertyContainer.withAll(
+ inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) },
+ it.toSourceSetDependent().toAdditionalModifiers(),
+ (psi.annotations.toList()
+ .toListOfAnnotations() + it.toListOfAnnotations()).toSourceSetDependent()
+ .toAnnotations(),
+ ObviousMember.takeIf { psi.isObvious(inheritedFrom) },
+ psi.throwsList.toDriList().takeIf { it.isNotEmpty() }
+ ?.let { CheckedExceptions(it.toSourceSetDependent()) }
+ )
+ }
+ )
+ }
+
+ private fun PsiNamedElement.parseSources(): SourceSetDependent<DocumentableSource> {
+ return when {
+ // `isPhysical` detects the virtual declarations without real sources.
+ // Otherwise, `PsiDocumentableSource` initialization will fail: non-physical declarations doesn't have `virtualFile`.
+ // This check protects from accidentally requesting sources for synthetic / virtual declarations.
+ isPhysical -> PsiDocumentableSource(this).toSourceSetDependent()
+ else -> emptyMap()
+ }
+ }
+
+ private fun PsiMethod.getDocumentation(): DocumentationNode =
+ this.takeIf { it is SyntheticElement }?.let { syntheticDocProvider.getDocumentation(it) }
+ ?: javadocParser.parseDocumentation(this)
+
+ private fun PsiMethod.isObvious(inheritedFrom: DRI? = null): Boolean {
+ return (this is SyntheticElement && !syntheticDocProvider.isDocumented(this))
+ || inheritedFrom?.isObvious() == true
+ }
+
+ private fun DRI.isObvious(): Boolean {
+ return packageName == "java.lang" && (classNames == "Object" || classNames == "Enum")
+ }
+
+ private fun PsiReferenceList.toDriList() = referenceElements.mapNotNull { it?.resolve()?.let { DRI.from(it) } }
+
+ private fun PsiModifierListOwner.additionalExtras() = listOfNotNull(
+ ExtraModifiers.JavaOnlyModifiers.Static.takeIf { hasModifier(JvmModifier.STATIC) },
+ ExtraModifiers.JavaOnlyModifiers.Native.takeIf { hasModifier(JvmModifier.NATIVE) },
+ ExtraModifiers.JavaOnlyModifiers.Synchronized.takeIf { hasModifier(JvmModifier.SYNCHRONIZED) },
+ ExtraModifiers.JavaOnlyModifiers.StrictFP.takeIf { hasModifier(JvmModifier.STRICTFP) },
+ ExtraModifiers.JavaOnlyModifiers.Transient.takeIf { hasModifier(JvmModifier.TRANSIENT) },
+ ExtraModifiers.JavaOnlyModifiers.Volatile.takeIf { hasModifier(JvmModifier.VOLATILE) },
+ ExtraModifiers.JavaOnlyModifiers.Transitive.takeIf { hasModifier(JvmModifier.TRANSITIVE) }
+ ).toSet()
+
+ private fun Set<ExtraModifiers>.toListOfAnnotations() = map {
+ if (it !is ExtraModifiers.JavaOnlyModifiers.Static)
+ Annotations.Annotation(DRI("kotlin.jvm", it.name.toLowerCase().capitalize()), emptyMap())
+ else
+ Annotations.Annotation(DRI("kotlin.jvm", "JvmStatic"), emptyMap())
+ }
+
+ /**
+ * Workaround for getting JvmField Kotlin annotation in PSIs
+ */
+ private fun Collection<PsiAnnotation>.findJvmFieldAnnotation(): Annotations.Annotation? {
+ val anyJvmFieldAnnotation = this.any {
+ it.qualifiedName == "$JVM_FIELD_PACKAGE_NAME.$JVM_FIELD_CLASS_NAMES"
+ }
+ return if (anyJvmFieldAnnotation) {
+ Annotations.Annotation(DRI(JVM_FIELD_PACKAGE_NAME, JVM_FIELD_CLASS_NAMES), emptyMap())
+ } else {
+ null
+ }
+ }
+
+ private fun <T : AnnotationTarget> PsiTypeParameter.annotations(): PropertyContainer<T> = this.annotations.toList().toListOfAnnotations().annotations()
+ private fun <T : AnnotationTarget> PsiType.annotations(): PropertyContainer<T> = this.annotations.toList().toListOfAnnotations().annotations()
+
+ private fun <T : AnnotationTarget> List<Annotations.Annotation>.annotations(): PropertyContainer<T> =
+ this.takeIf { it.isNotEmpty() }?.let { annotations ->
+ PropertyContainer.withAll(annotations.toSourceSetDependent().toAnnotations())
+ } ?: PropertyContainer.empty()
+
+ private fun getBound(type: PsiType): Bound {
+ //We would like to cache most of the bounds since it is not common to annotate them,
+ //but if this is the case, we treat them as 'one of'
+ fun PsiType.cacheBoundIfHasNoAnnotation(f: (List<Annotations.Annotation>) -> Bound): Bound {
+ val annotations = this.annotations.toList().toListOfAnnotations()
+ return if (annotations.isNotEmpty()) f(annotations)
+ else cachedBounds.getOrPut(canonicalText) {
+ f(annotations)
+ }
+ }
+
+ return when (type) {
+ is PsiClassType ->
+ type.resolve()?.let { resolved ->
+ when {
+ resolved.qualifiedName == "java.lang.Object" -> type.cacheBoundIfHasNoAnnotation { annotations -> JavaObject(annotations.annotations()) }
+ resolved is PsiTypeParameter -> {
+ TypeParameter(
+ dri = DRI.from(resolved),
+ name = resolved.name.orEmpty(),
+ extra = type.annotations()
+ )
+ }
+
+ Regex("kotlin\\.jvm\\.functions\\.Function.*").matches(resolved.qualifiedName ?: "") ||
+ Regex("java\\.util\\.function\\.Function.*").matches(
+ resolved.qualifiedName ?: ""
+ ) -> FunctionalTypeConstructor(
+ DRI.from(resolved),
+ type.parameters.map { getProjection(it) },
+ extra = type.annotations()
+ )
+
+ else -> {
+ // cache types that have no annotation and no type parameter
+ // since we cache only by name and type parameters depend on context
+ val typeParameters = type.parameters.map { getProjection(it) }
+ if (typeParameters.isEmpty())
+ type.cacheBoundIfHasNoAnnotation { annotations ->
+ GenericTypeConstructor(
+ DRI.from(resolved),
+ typeParameters,
+ extra = annotations.annotations()
+ )
+ }
+ else
+ GenericTypeConstructor(
+ DRI.from(resolved),
+ typeParameters,
+ extra = type.annotations()
+ )
+ }
+ }
+ } ?: UnresolvedBound(type.presentableText, type.annotations())
+
+ is PsiArrayType -> GenericTypeConstructor(
+ DRI("kotlin", "Array"),
+ listOf(getProjection(type.componentType)),
+ extra = type.annotations()
+ )
+
+ is PsiPrimitiveType -> if (type.name == "void") Void
+ else type.cacheBoundIfHasNoAnnotation { annotations -> PrimitiveJavaType(type.name, annotations.annotations()) }
+ else -> throw IllegalStateException("${type.presentableText} is not supported by PSI parser")
+ }
+ }
+
+
+ private fun getVariance(type: PsiWildcardType): Projection = when {
+ type.extendsBound != PsiType.NULL -> Covariance(getBound(type.extendsBound))
+ type.superBound != PsiType.NULL -> Contravariance(getBound(type.superBound))
+ else -> throw IllegalStateException("${type.presentableText} has incorrect bounds")
+ }
+
+ private fun getProjection(type: PsiType): Projection = when (type) {
+ is PsiEllipsisType -> Star
+ is PsiWildcardType -> getVariance(type)
+ else -> getBound(type)
+ }
+
+ private fun PsiModifierListOwner.getModifier() = when {
+ hasModifier(JvmModifier.ABSTRACT) -> JavaModifier.Abstract
+ hasModifier(JvmModifier.FINAL) -> JavaModifier.Final
+ else -> JavaModifier.Empty
+ }
+
+ private fun PsiTypeParameterListOwner.mapTypeParameters(dri: DRI): List<DTypeParameter> {
+ fun mapBounds(bounds: Array<JvmReferenceType>): List<Bound> =
+ if (bounds.isEmpty()) emptyList() else bounds.mapNotNull {
+ (it as? PsiClassType)?.let { classType -> Nullable(getBound(classType)) }
+ }
+ return typeParameters.map { type ->
+ DTypeParameter(
+ dri = dri.copy(target = dri.target.nextTarget()),
+ name = type.name.orEmpty(),
+ presentableName = null,
+ documentation = javadocParser.parseDocumentation(type).toSourceSetDependent(),
+ expectPresentInSet = null,
+ bounds = mapBounds(type.bounds),
+ sourceSets = setOf(sourceSetData),
+ extra = PropertyContainer.withAll(
+ type.annotations.toList().toListOfAnnotations().toSourceSetDependent()
+ .toAnnotations()
+ )
+ )
+ }
+ }
+
+ private fun parseFieldWithInheritingAccessors(
+ psi: PsiField,
+ accessors: List<Pair<PsiMethod, DRI>>,
+ inheritedFrom: DRI
+ ): DProperty {
+ val getter = accessors
+ .firstOrNull { (method, _) -> method.isGetterFor(psi) }
+ ?.let { (method, dri) -> parseFunction(method, inheritedFrom = dri) }
+
+ val setter = accessors
+ .firstOrNull { (method, _) -> method.isSetterFor(psi) }
+ ?.let { (method, dri) -> parseFunction(method, inheritedFrom = dri) }
+
+ return parseField(
+ psi = psi,
+ getter = getter,
+ setter = setter,
+ inheritedFrom = inheritedFrom
+ )
+ }
+
+ private fun parseField(psi: PsiField, accessors: List<PsiMethod>, inheritedFrom: DRI? = null): DProperty {
+ val getter = accessors.firstOrNull { it.isGetterFor(psi) }?.let { parseFunction(it) }
+ val setter = accessors.firstOrNull { it.isSetterFor(psi) }?.let { parseFunction(it) }
+ return parseField(
+ psi = psi,
+ getter = getter,
+ setter = setter,
+ inheritedFrom = inheritedFrom
+ )
+ }
+
+ private fun parseField(psi: PsiField, getter: DFunction?, setter: DFunction?, inheritedFrom: DRI? = null): DProperty {
+ val dri = DRI.from(psi)
+
+ // non-final java field without accessors should be a var
+ // setter should be not null when inheriting kotlin vars
+ val isMutable = !psi.hasModifierProperty("final")
+ val isVar = (isMutable && getter == null && setter == null) || (getter != null && setter != null)
+
+ return DProperty(
+ dri = dri,
+ name = psi.name,
+ documentation = javadocParser.parseDocumentation(psi).toSourceSetDependent(),
+ expectPresentInSet = null,
+ sources = psi.parseSources(),
+ visibility = psi.getVisibility(getter).toSourceSetDependent(),
+ type = getBound(psi.type),
+ receiver = null,
+ setter = setter,
+ getter = getter,
+ modifier = psi.getModifier().toSourceSetDependent(),
+ sourceSets = setOf(sourceSetData),
+ generics = emptyList(),
+ isExpectActual = false,
+ extra = psi.additionalExtras().let {
+ val psiAnnotations = psi.annotations.toList()
+ val parsedAnnotations = psiAnnotations.toListOfAnnotations()
+ val extraModifierAnnotations = it.toListOfAnnotations()
+ val jvmFieldAnnotation = psiAnnotations.findJvmFieldAnnotation()
+ val annotations = parsedAnnotations + extraModifierAnnotations + listOfNotNull(jvmFieldAnnotation)
+
+ PropertyContainer.withAll(
+ inheritedFrom?.let { inheritedFrom -> InheritedMember(inheritedFrom.toSourceSetDependent()) },
+ it.toSourceSetDependent().toAdditionalModifiers(),
+ annotations.toSourceSetDependent().toAnnotations(),
+ psi.getConstantExpression()?.let { DefaultValue(it.toSourceSetDependent()) },
+ takeIf { isVar }?.let { IsVar }
+ )
+ }
+ )
+ }
+
+ private fun PsiField.getVisibility(getter: DFunction?): Visibility {
+ return getter?.visibility?.get(sourceSetData) ?: this.getVisibility()
+ }
+
+ private fun Collection<PsiAnnotation>.toListOfAnnotations() =
+ filter { !lightMethodChecker.isLightAnnotation(it) }.mapNotNull { it.toAnnotation() }
+
+ private fun PsiField.getConstantExpression(): Expression? {
+ val constantValue = this.computeConstantValue() ?: return null
+ return when (constantValue) {
+ is Byte -> IntegerConstant(constantValue.toLong())
+ is Short -> IntegerConstant(constantValue.toLong())
+ is Int -> IntegerConstant(constantValue.toLong())
+ is Long -> IntegerConstant(constantValue)
+ is Char -> StringConstant(constantValue.toString())
+ is String -> StringConstant(constantValue)
+ is Double -> DoubleConstant(constantValue)
+ is Float -> FloatConstant(constantValue)
+ is Boolean -> BooleanConstant(constantValue)
+ else -> ComplexExpression(constantValue.toString())
+ }
+ }
+
+ private fun JvmAnnotationAttribute.toValue(): AnnotationParameterValue = when (this) {
+ is PsiNameValuePair -> value?.toValue() ?: attributeValue?.toValue() ?: StringValue("")
+ else -> StringValue(this.attributeName)
+ }.let { annotationValue ->
+ if (annotationValue is StringValue) annotationValue.copy(annotationValue.value.removeSurrounding("\""))
+ else annotationValue
+ }
+
+ /**
+ * This is a workaround for static imports from JDK like RetentionPolicy
+ * For some reason they are not represented in the same way than using normal import
+ */
+ private fun JvmAnnotationAttributeValue.toValue(): AnnotationParameterValue? {
+ return when (this) {
+ is JvmAnnotationEnumFieldValue -> (field as? PsiElement)?.let { EnumValue(fieldName ?: "", DRI.from(it)) }
+ // static import of a constant is resolved to constant value instead of a field/link
+ is JvmAnnotationConstantValue -> this.constantValue?.toAnnotationLiteralValue()
+ else -> null
+ }
+ }
+
+ private fun Any.toAnnotationLiteralValue() = when (this) {
+ is Byte -> IntValue(this.toInt())
+ is Short -> IntValue(this.toInt())
+ is Char -> StringValue(this.toString())
+ is Int -> IntValue(this)
+ is Long -> LongValue(this)
+ is Boolean -> BooleanValue(this)
+ is Float -> FloatValue(this)
+ is Double -> DoubleValue(this)
+ else -> StringValue(this.toString())
+ }
+
+ private fun PsiAnnotationMemberValue.toValue(): AnnotationParameterValue? = when (this) {
+ is PsiAnnotation -> toAnnotation()?.let { AnnotationValue(it) }
+ is PsiArrayInitializerMemberValue -> ArrayValue(initializers.mapNotNull { it.toValue() })
+ is PsiReferenceExpression -> psiReference?.let { EnumValue(text ?: "", DRI.from(it)) }
+ is PsiClassObjectAccessExpression -> {
+ val parameterType = (type as? PsiClassType)?.parameters?.firstOrNull()
+ val classType = when (parameterType) {
+ is PsiClassType -> parameterType.resolve()
+ // Notice: Array<String>::class will be passed down as String::class
+ // should probably be Array::class instead but this reflects behaviour for Kotlin sources
+ is PsiArrayType -> (parameterType.componentType as? PsiClassType)?.resolve()
+ else -> null
+ }
+ classType?.let { ClassValue(it.name ?: "", DRI.from(it)) }
+ }
+ is PsiLiteralExpression -> toValue()
+ else -> StringValue(text ?: "")
+ }
+
+ private fun PsiLiteralExpression.toValue(): AnnotationParameterValue? = when (type) {
+ PsiType.INT -> (value as? Int)?.let { IntValue(it) }
+ PsiType.LONG -> (value as? Long)?.let { LongValue(it) }
+ PsiType.FLOAT -> (value as? Float)?.let { FloatValue(it) }
+ PsiType.DOUBLE -> (value as? Double)?.let { DoubleValue(it) }
+ PsiType.BOOLEAN -> (value as? Boolean)?.let { BooleanValue(it) }
+ PsiType.NULL -> NullValue
+ else -> StringValue(text ?: "")
+ }
+
+ private fun PsiAnnotation.toAnnotation(): Annotations.Annotation? {
+ // TODO Mitigating workaround for issue https://github.com/Kotlin/dokka/issues/1341
+ // Tracking https://youtrack.jetbrains.com/issue/KT-41234
+ // Needs to be removed once this issue is fixed in light classes
+ fun PsiElement.getAnnotationsOrNull(): Array<PsiAnnotation>? {
+ this as PsiClass
+ return try {
+ this.annotations
+ } catch (e: Exception) {
+ logger.warn("Failed to get annotations from ${this.qualifiedName}")
+ null
+ }
+ }
+
+ return psiReference?.let { psiElement ->
+ Annotations.Annotation(
+ dri = DRI.from(psiElement),
+ params = attributes
+ .filter { !lightMethodChecker.isLightAnnotationAttribute(it) }
+ .mapNotNull { it.attributeName to it.toValue() }
+ .toMap(),
+ mustBeDocumented = psiElement.getAnnotationsOrNull().orEmpty().any { annotation ->
+ annotation.hasQualifiedName("java.lang.annotation.Documented")
+ }
+ )
+ }
+ }
+
+ private val PsiElement.psiReference
+ get() = getChildOfType<PsiJavaCodeReferenceElement>()?.resolve()
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavaDocCommentParser.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavaDocCommentParser.kt
new file mode 100644
index 00000000..9c475054
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavaDocCommentParser.kt
@@ -0,0 +1,228 @@
+package org.jetbrains.dokka.analysis.java.parsers
+
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiNamedElement
+import com.intellij.psi.PsiWhiteSpace
+import com.intellij.psi.impl.source.tree.JavaDocElementType
+import com.intellij.psi.impl.source.tree.LazyParseablePsiElement
+import com.intellij.psi.javadoc.PsiDocComment
+import com.intellij.psi.javadoc.PsiDocTag
+
+import org.jetbrains.dokka.analysis.java.*
+import org.jetbrains.dokka.analysis.java.doccomment.DocComment
+import org.jetbrains.dokka.analysis.java.doccomment.JavaDocComment
+import org.jetbrains.dokka.analysis.java.parsers.doctag.PsiDocTagParser
+import org.jetbrains.dokka.analysis.java.util.*
+import org.jetbrains.dokka.analysis.markdown.jb.MARKDOWN_ELEMENT_FILE_NAME
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.doc.Deprecated
+
+internal class JavaPsiDocCommentParser(
+ private val psiDocTagParser: PsiDocTagParser,
+) : DocCommentParser {
+
+ override fun canParse(docComment: DocComment): Boolean {
+ return docComment is JavaDocComment
+ }
+
+ override fun parse(docComment: DocComment, context: PsiNamedElement): DocumentationNode {
+ val javaDocComment = docComment as JavaDocComment
+ return parsePsiDocComment(javaDocComment.comment, context)
+ }
+
+ internal fun parsePsiDocComment(docComment: PsiDocComment, context: PsiNamedElement): DocumentationNode {
+ val description = listOfNotNull(docComment.getDescription())
+ val tags = docComment.tags.mapNotNull { tag ->
+ parseDocTag(tag, docComment, context)
+ }
+ return DocumentationNode(description + tags)
+ }
+
+ private fun PsiDocComment.getDescription(): Description? {
+ val docTags = psiDocTagParser.parseAsParagraph(
+ psiElements = descriptionElements.asIterable(),
+ commentResolutionContext = CommentResolutionContext(this, DescriptionJavadocTag),
+ )
+ return docTags.takeIf { it.isNotEmpty() }?.let {
+ Description(wrapTagIfNecessary(it))
+ }
+ }
+
+ private fun parseDocTag(tag: PsiDocTag, docComment: PsiDocComment, analysedElement: PsiNamedElement): TagWrapper? {
+ return when (tag.name) {
+ ParamJavadocTag.name -> parseParamTag(tag, docComment, analysedElement)
+ ThrowsJavadocTag.name, ExceptionJavadocTag.name -> parseThrowsTag(tag, docComment)
+ ReturnJavadocTag.name -> parseReturnTag(tag, docComment)
+ SinceJavadocTag.name -> parseSinceTag(tag, docComment)
+ AuthorJavadocTag.name -> parseAuthorTag(tag, docComment)
+ SeeJavadocTag.name -> parseSeeTag(tag, docComment)
+ DeprecatedJavadocTag.name -> parseDeprecatedTag(tag, docComment)
+ else -> emptyTagWrapper(tag, docComment)
+ }
+ }
+
+ private fun parseParamTag(
+ tag: PsiDocTag,
+ docComment: PsiDocComment,
+ analysedElement: PsiNamedElement
+ ): TagWrapper? {
+ val paramName = tag.dataElements.firstOrNull()?.text.orEmpty()
+
+ // can be a PsiClass if @param is referencing class generics, like here:
+ // https://github.com/biojava/biojava/blob/2417c230be36e4ba73c62bb3631b60f876265623/biojava-core/src/main/java/org/biojava/nbio/core/alignment/SimpleProfilePair.java#L43
+ // not supported at the moment
+ val method = analysedElement as? PsiMethod ?: return null
+ val paramIndex = method.parameterList.parameters.map { it.name }.indexOf(paramName)
+
+ val docTags = psiDocTagParser.parseAsParagraph(
+ psiElements = tag.contentElementsWithSiblingIfNeeded().drop(1),
+ commentResolutionContext = CommentResolutionContext(
+ comment = docComment,
+ tag = ParamJavadocTag(method, paramName, paramIndex)
+ )
+ )
+ return Param(root = wrapTagIfNecessary(docTags), name = paramName)
+ }
+
+ private fun parseThrowsTag(
+ tag: PsiDocTag,
+ docComment: PsiDocComment
+ ): Throws {
+ val resolvedElement = tag.resolveToElement()
+ val exceptionAddress = resolvedElement?.let { DRI.from(it) }
+
+ /* we always would like to have a fully qualified name as name,
+ * because it will be used as a display name later and we would like to have those unified
+ * even if documentation states shortened version
+ * Only if dri search fails we should use the provided phrase (since then we are not able to get a fq name)
+ */
+ val fullyQualifiedExceptionName =
+ resolvedElement?.getKotlinFqName() ?: tag.dataElements.firstOrNull()?.text.orEmpty()
+
+ val javadocTag = when (tag.name) {
+ ThrowsJavadocTag.name -> ThrowsJavadocTag(fullyQualifiedExceptionName)
+ ExceptionJavadocTag.name -> ExceptionJavadocTag(fullyQualifiedExceptionName)
+ else -> throw IllegalArgumentException("Expected @throws or @exception")
+ }
+
+ val docTags = psiDocTagParser.parseAsParagraph(
+ psiElements = tag.dataElements.drop(1),
+ commentResolutionContext = CommentResolutionContext(
+ comment = docComment,
+ tag = javadocTag
+ ),
+ )
+ return Throws(
+ root = wrapTagIfNecessary(docTags),
+ name = fullyQualifiedExceptionName,
+ exceptionAddress = exceptionAddress
+ )
+ }
+
+ private fun parseReturnTag(
+ tag: PsiDocTag,
+ docComment: PsiDocComment
+ ): Return {
+ val docTags = psiDocTagParser.parseAsParagraph(
+ psiElements = tag.contentElementsWithSiblingIfNeeded(),
+ commentResolutionContext = CommentResolutionContext(comment = docComment, tag = ReturnJavadocTag),
+ )
+ return Return(root = wrapTagIfNecessary(docTags))
+ }
+
+ private fun parseSinceTag(
+ tag: PsiDocTag,
+ docComment: PsiDocComment
+ ): Since {
+ val docTags = psiDocTagParser.parseAsParagraph(
+ psiElements = tag.contentElementsWithSiblingIfNeeded(),
+ commentResolutionContext = CommentResolutionContext(comment = docComment, tag = ReturnJavadocTag),
+ )
+ return Since(root = wrapTagIfNecessary(docTags))
+ }
+
+ private fun parseAuthorTag(
+ tag: PsiDocTag,
+ docComment: PsiDocComment
+ ): Author {
+ // TODO [beresnev] see what the hell this is
+ // Workaround: PSI returns first word after @author tag as a `DOC_TAG_VALUE_ELEMENT`,
+ // then the rest as a `DOC_COMMENT_DATA`, so for `Name Surname` we get them parted
+ val docTags = psiDocTagParser.parseAsParagraph(
+ psiElements = tag.contentElementsWithSiblingIfNeeded(),
+ commentResolutionContext = CommentResolutionContext(comment = docComment, tag = AuthorJavadocTag),
+ )
+ return Author(root = wrapTagIfNecessary(docTags))
+ }
+
+ private fun parseSeeTag(
+ tag: PsiDocTag,
+ docComment: PsiDocComment
+ ): See {
+ val referenceElement = tag.referenceElement()
+ val fullyQualifiedSeeReference = tag.resolveToElement()?.getKotlinFqName()
+ ?: referenceElement?.text.orEmpty().removePrefix("#")
+
+ val context = CommentResolutionContext(comment = docComment, tag = SeeJavadocTag(fullyQualifiedSeeReference))
+ val docTags = psiDocTagParser.parseAsParagraph(
+ psiElements = tag.dataElements.dropWhile {
+ it is PsiWhiteSpace || it.isDocReferenceHolder() || it == referenceElement
+ },
+ commentResolutionContext = context,
+ )
+
+ return See(
+ root = wrapTagIfNecessary(docTags),
+ name = fullyQualifiedSeeReference,
+ address = referenceElement?.toDocumentationLink(context = context)?.dri
+ )
+ }
+
+ private fun PsiElement.isDocReferenceHolder(): Boolean {
+ return (this as? LazyParseablePsiElement)?.elementType == JavaDocElementType.DOC_REFERENCE_HOLDER
+ }
+
+ private fun parseDeprecatedTag(
+ tag: PsiDocTag,
+ docComment: PsiDocComment
+ ): Deprecated {
+ val docTags = psiDocTagParser.parseAsParagraph(
+ tag.contentElementsWithSiblingIfNeeded(),
+ CommentResolutionContext(comment = docComment, tag = DeprecatedJavadocTag),
+ )
+ return Deprecated(root = wrapTagIfNecessary(docTags))
+ }
+
+ private fun wrapTagIfNecessary(tags: List<DocTag>): CustomDocTag {
+ val isFile = (tags.singleOrNull() as? CustomDocTag)?.name == MARKDOWN_ELEMENT_FILE_NAME
+ return if (isFile) {
+ tags.first() as CustomDocTag
+ } else {
+ CustomDocTag(tags, name = MARKDOWN_ELEMENT_FILE_NAME)
+ }
+ }
+
+ // Wrapper for unsupported tags https://github.com/Kotlin/dokka/issues/1618
+ private fun emptyTagWrapper(
+ tag: PsiDocTag,
+ docComment: PsiDocComment,
+ ): CustomTagWrapper {
+ val docTags = psiDocTagParser.parseAsParagraph(
+ psiElements = tag.contentElementsWithSiblingIfNeeded(),
+ commentResolutionContext = CommentResolutionContext(docComment, null),
+ )
+ return CustomTagWrapper(
+ root = wrapTagIfNecessary(docTags),
+ name = tag.name
+ )
+ }
+
+ private fun PsiElement.toDocumentationLink(labelElement: PsiElement? = null, context: CommentResolutionContext): DocumentationLink? {
+ val resolvedElement = this.resolveToGetDri() ?: return null
+ val label = labelElement ?: defaultLabel()
+ val docTags = psiDocTagParser.parse(listOfNotNull(label), context)
+ return DocumentationLink(dri = DRI.from(resolvedElement), children = docTags)
+ }
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavadocParser.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavadocParser.kt
new file mode 100644
index 00000000..b8eba878
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavadocParser.kt
@@ -0,0 +1,24 @@
+package org.jetbrains.dokka.analysis.java.parsers
+
+import com.intellij.psi.PsiNamedElement
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.analysis.java.doccomment.DocCommentFinder
+import org.jetbrains.dokka.model.doc.DocumentationNode
+
+internal fun interface JavaDocumentationParser {
+ fun parseDocumentation(element: PsiNamedElement): DocumentationNode
+}
+
+@InternalDokkaApi
+class JavadocParser(
+ private val docCommentParsers: List<DocCommentParser>,
+ private val docCommentFinder: DocCommentFinder
+) : JavaDocumentationParser {
+
+ override fun parseDocumentation(element: PsiNamedElement): DocumentationNode {
+ val comment = docCommentFinder.findClosestToElement(element) ?: return DocumentationNode(emptyList())
+ return docCommentParsers
+ .first { it.canParse(comment) }
+ .parse(comment, element)
+ }
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/DocTagParserContext.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/DocTagParserContext.kt
new file mode 100644
index 00000000..050736f0
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/DocTagParserContext.kt
@@ -0,0 +1,47 @@
+package org.jetbrains.dokka.analysis.java.parsers.doctag
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import java.util.*
+
+@InternalDokkaApi
+class DocTagParserContext {
+ /**
+ * exists for resolving `@link element` links, where the referenced
+ * PSI element is mapped as DRI
+ *
+ * only used in the context of parsing to html and then from html to doctag
+ */
+ private val driMap = mutableMapOf<String, DRI>()
+
+ /**
+ * Cache created to make storing entries from kotlin easier.
+ *
+ * It has to be mutable to allow for adding entries when @inheritDoc resolves to kotlin code,
+ * from which we get a DocTags not descriptors.
+ */
+ private val inheritDocSections = mutableMapOf<String, DocumentationNode>()
+
+ /**
+ * @return key of the stored DRI
+ */
+ fun store(dri: DRI): String {
+ val id = dri.toString()
+ driMap[id] = dri
+ return id
+ }
+
+ /**
+ * @return key of the stored documentation node
+ */
+ fun store(documentationNode: DocumentationNode): String {
+ val id = UUID.randomUUID().toString()
+ inheritDocSections[id] = documentationNode
+ return id
+ }
+
+ fun getDri(id: String): DRI? = driMap[id]
+
+ fun getDocumentationNode(id: String): DocumentationNode? = inheritDocSections[id]
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/HtmlToDocTagConverter.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/HtmlToDocTagConverter.kt
new file mode 100644
index 00000000..ea1997b8
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/HtmlToDocTagConverter.kt
@@ -0,0 +1,114 @@
+package org.jetbrains.dokka.analysis.java.parsers.doctag
+
+import org.jetbrains.dokka.analysis.markdown.jb.parseHtmlEncodedWithNormalisedSpaces
+import org.jetbrains.dokka.model.doc.*
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Comment
+import org.jsoup.nodes.Element
+import org.jsoup.nodes.Node
+import org.jsoup.nodes.TextNode
+
+internal class HtmlToDocTagConverter(
+ private val docTagParserContext: DocTagParserContext
+) {
+ fun convertToDocTag(html: String): List<DocTag> {
+ return Jsoup.parseBodyFragment(html)
+ .body()
+ .childNodes()
+ .flatMap { convertHtmlNode(it) }
+ }
+
+ private fun convertHtmlNode(node: Node, keepFormatting: Boolean = false): List<DocTag> = when (node) {
+ is TextNode -> (if (keepFormatting) {
+ node.wholeText.takeIf { it.isNotBlank() }?.let { listOf(Text(body = it)) }
+ } else {
+ node.wholeText.parseHtmlEncodedWithNormalisedSpaces(renderWhiteCharactersAsSpaces = true)
+ }).orEmpty()
+ is Comment -> listOf(Text(body = node.outerHtml(), params = DocTag.contentTypeParam("html")))
+ is Element -> createBlock(node, keepFormatting)
+ else -> emptyList()
+ }
+
+ private fun createBlock(element: Element, keepFormatting: Boolean = false): List<DocTag> {
+ val tagName = element.tagName()
+ val children = element.childNodes()
+ .flatMap { convertHtmlNode(it, keepFormatting = keepFormatting || tagName == "pre" || tagName == "code") }
+
+ fun ifChildrenPresent(operation: () -> DocTag): List<DocTag> {
+ return if (children.isNotEmpty()) listOf(operation()) else emptyList()
+ }
+ return when (tagName) {
+ "blockquote" -> ifChildrenPresent { BlockQuote(children) }
+ "p" -> ifChildrenPresent { P(children) }
+ "b" -> ifChildrenPresent { B(children) }
+ "strong" -> ifChildrenPresent { Strong(children) }
+ "index" -> listOf(Index(children))
+ "i" -> ifChildrenPresent { I(children) }
+ "img" -> listOf(
+ Img(
+ children,
+ element.attributes().associate { (if (it.key == "src") "href" else it.key) to it.value })
+ )
+ "em" -> listOf(Em(children))
+ "code" -> ifChildrenPresent { if(keepFormatting) CodeBlock(children) else CodeInline(children) }
+ "pre" -> if(children.size == 1) {
+ when(children.first()) {
+ is CodeInline -> listOf(CodeBlock(children.first().children))
+ is CodeBlock -> listOf(children.first())
+ else -> listOf(Pre(children))
+ }
+ } else {
+ listOf(Pre(children))
+ }
+ "ul" -> ifChildrenPresent { Ul(children) }
+ "ol" -> ifChildrenPresent { Ol(children) }
+ "li" -> listOf(Li(children))
+ "dl" -> ifChildrenPresent { Dl(children) }
+ "dt" -> listOf(Dt(children))
+ "dd" -> listOf(Dd(children))
+ "a" -> listOf(createLink(element, children))
+ "table" -> ifChildrenPresent { Table(children) }
+ "tr" -> ifChildrenPresent { Tr(children) }
+ "td" -> listOf(Td(children))
+ "thead" -> listOf(THead(children))
+ "tbody" -> listOf(TBody(children))
+ "tfoot" -> listOf(TFoot(children))
+ "caption" -> ifChildrenPresent { Caption(children) }
+ "inheritdoc" -> {
+ // TODO [beresnev] describe how it works
+ val id = element.attr("id")
+ val section = docTagParserContext.getDocumentationNode(id)
+ val parsed = section?.children?.flatMap { it.root.children }.orEmpty()
+ if(parsed.size == 1 && parsed.first() is P){
+ parsed.first().children
+ } else {
+ parsed
+ }
+ }
+ "h1" -> ifChildrenPresent { H1(children) }
+ "h2" -> ifChildrenPresent { H2(children) }
+ "h3" -> ifChildrenPresent { H3(children) }
+ "var" -> ifChildrenPresent { Var(children) }
+ "u" -> ifChildrenPresent { U(children) }
+ else -> listOf(Text(body = element.ownText()))
+ }
+ }
+
+ private fun createLink(element: Element, children: List<DocTag>): DocTag {
+ return when {
+ element.hasAttr("docref") ->
+ A(children, params = mapOf("docref" to element.attr("docref")))
+ element.hasAttr("href") ->
+ A(children, params = mapOf("href" to element.attr("href")))
+ element.hasAttr("data-dri") && docTagParserContext.getDri(element.attr("data-dri")) != null -> {
+ val referencedDriId = element.attr("data-dri")
+ DocumentationLink(
+ dri = docTagParserContext.getDri(referencedDriId)
+ ?: error("docTagParserContext.getDri is null, TODO"), // TODO [beresnev] handle
+ children = children
+ )
+ }
+ else -> Text(body = children.filterIsInstance<Text>().joinToString { it.body })
+ }
+ }
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagContentProvider.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagContentProvider.kt
new file mode 100644
index 00000000..31149898
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagContentProvider.kt
@@ -0,0 +1,10 @@
+package org.jetbrains.dokka.analysis.java.parsers.doctag
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent
+
+@InternalDokkaApi
+interface InheritDocTagContentProvider {
+ fun canConvert(content: DocumentationContent): Boolean
+ fun convertToHtml(content: DocumentationContent, docTagParserContext: DocTagParserContext): String
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagResolver.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagResolver.kt
new file mode 100644
index 00000000..031a7b32
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagResolver.kt
@@ -0,0 +1,114 @@
+package org.jetbrains.dokka.analysis.java.parsers.doctag
+
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.javadoc.PsiDocComment
+import org.jetbrains.dokka.analysis.java.*
+import org.jetbrains.dokka.analysis.java.doccomment.*
+import org.jetbrains.dokka.analysis.java.doccomment.JavaDocComment
+import org.jetbrains.dokka.analysis.java.parsers.CommentResolutionContext
+
+internal class InheritDocTagResolver(
+ private val docCommentFactory: DocCommentFactory,
+ private val docCommentFinder: DocCommentFinder,
+ private val contentProviders: List<InheritDocTagContentProvider>
+) {
+ internal fun convertToHtml(content: DocumentationContent, docTagParserContext: DocTagParserContext): String? {
+ return contentProviders
+ .firstOrNull { it.canConvert(content) }
+ ?.convertToHtml(content, docTagParserContext)
+ }
+
+ internal fun resolveContent(context: CommentResolutionContext): List<DocumentationContent>? {
+ val javadocTag = context.tag ?: return null
+
+ return when (javadocTag) {
+ is ThrowingExceptionJavadocTag -> {
+ javadocTag.exceptionQualifiedName?.let { _ ->
+ resolveThrowsTag(
+ javadocTag,
+ context.comment,
+ )
+ } ?: return null
+ }
+ is ParamJavadocTag -> resolveParamTag(context.comment, javadocTag)
+ is DeprecatedJavadocTag -> resolveGenericTag(context.comment, DescriptionJavadocTag)
+ is SeeJavadocTag -> emptyList()
+ else -> resolveGenericTag(context.comment, javadocTag)
+ }
+ }
+
+ private fun resolveGenericTag(currentElement: PsiDocComment, tag: JavadocTag): List<DocumentationContent> {
+ val docComment = when (val owner = currentElement.owner) {
+ is PsiClass -> lowestClassWithTag(owner, tag)
+ is PsiMethod -> lowestMethodWithTag(owner, tag)
+ else -> null
+ }
+ return docComment?.resolveTag(tag)?.flatMap {
+ it.resolveSiblings()
+ }.orEmpty()
+ }
+
+ /**
+ * Main resolution point for exception like tags
+ *
+ * This should be used only with [ThrowsJavadocTag] or [ExceptionJavadocTag] as their resolution path should be the same
+ */
+ private fun resolveThrowsTag(
+ tag: ThrowingExceptionJavadocTag,
+ currentElement: PsiDocComment,
+ ): List<DocumentationContent> {
+ val closestDocsWithThrows =
+ (currentElement.owner as? PsiMethod)?.let { method -> lowestMethodsWithTag(method, tag) }
+ .orEmpty().firstOrNull {
+ docCommentFinder.findClosestToElement(it)?.hasTag(tag) == true
+ } ?: return emptyList()
+
+ return docCommentFactory.fromElement(closestDocsWithThrows)
+ ?.resolveTag(tag)
+ ?: emptyList()
+ }
+
+ private fun resolveParamTag(
+ currentElement: PsiDocComment,
+ paramTag: ParamJavadocTag,
+ ): List<DocumentationContent> {
+ val parameterIndex = paramTag.paramIndex
+
+ val methods = (currentElement.owner as? PsiMethod)
+ ?.let { method -> lowestMethodsWithTag(method, paramTag) }
+ .orEmpty()
+
+ return methods.flatMap {
+ if (parameterIndex >= it.parameterList.parametersCount || parameterIndex < 0) {
+ return@flatMap emptyList()
+ }
+
+ val closestTag = docCommentFinder.findClosestToElement(it)
+ val hasTag = closestTag?.hasTag(paramTag) ?: false
+ closestTag?.takeIf { hasTag }?.resolveTag(ParamJavadocTag(it, "", parameterIndex)) ?: emptyList()
+ }
+ }
+
+ //if we are in psi class javadoc only inherits docs from classes and not from interfaces
+ private fun lowestClassWithTag(baseClass: PsiClass, javadocTag: JavadocTag): DocComment? =
+ baseClass.superClass?.let {
+ docCommentFinder.findClosestToElement(it)?.takeIf { tag -> tag.hasTag(javadocTag) } ?: lowestClassWithTag(
+ it,
+ javadocTag
+ )
+ }
+
+ private fun lowestMethodWithTag(
+ baseMethod: PsiMethod,
+ javadocTag: JavadocTag,
+ ): DocComment? {
+ val methodsWithTag = lowestMethodsWithTag(baseMethod, javadocTag).firstOrNull()
+ return methodsWithTag?.let {
+ it.docComment?.let { JavaDocComment(it) } ?: docCommentFinder.findClosestToElement(it)
+ }
+ }
+
+ private fun lowestMethodsWithTag(baseMethod: PsiMethod, javadocTag: JavadocTag): List<PsiMethod> =
+ baseMethod.findSuperMethods().filter { docCommentFinder.findClosestToElement(it)?.hasTag(javadocTag) == true }
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiDocTagParser.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiDocTagParser.kt
new file mode 100644
index 00000000..803eabcb
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiDocTagParser.kt
@@ -0,0 +1,39 @@
+package org.jetbrains.dokka.analysis.java.parsers.doctag
+
+import com.intellij.psi.PsiElement
+import com.intellij.psi.javadoc.PsiDocTag
+import org.jetbrains.dokka.analysis.java.parsers.CommentResolutionContext
+import org.jetbrains.dokka.model.doc.*
+
+/**
+ * Parses [PsiElement] of [PsiDocTag] into Dokka's [DocTag]
+ */
+internal class PsiDocTagParser(
+ private val inheritDocTagResolver: InheritDocTagResolver
+) {
+ fun parse(
+ psiElements: Iterable<PsiElement>,
+ commentResolutionContext: CommentResolutionContext
+ ): List<DocTag> = parse(asParagraph = false, psiElements, commentResolutionContext)
+
+ fun parseAsParagraph(
+ psiElements: Iterable<PsiElement>,
+ commentResolutionContext: CommentResolutionContext
+ ): List<DocTag> = parse(asParagraph = true, psiElements, commentResolutionContext)
+
+ private fun parse(
+ asParagraph: Boolean,
+ psiElements: Iterable<PsiElement>,
+ commentResolutionContext: CommentResolutionContext
+ ): List<DocTag> {
+ val docTagParserContext = DocTagParserContext()
+
+ val psiToHtmlConverter = PsiElementToHtmlConverter(inheritDocTagResolver)
+ val elementsHtml = psiToHtmlConverter.convert(psiElements, docTagParserContext, commentResolutionContext)
+ ?: return emptyList()
+
+ val htmlToDocTagConverter = HtmlToDocTagConverter(docTagParserContext)
+ val html = if (asParagraph) "<p>$elementsHtml</p>" else elementsHtml
+ return htmlToDocTagConverter.convertToDocTag(html)
+ }
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiElementToHtmlConverter.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiElementToHtmlConverter.kt
new file mode 100644
index 00000000..0c20a9b7
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiElementToHtmlConverter.kt
@@ -0,0 +1,214 @@
+package org.jetbrains.dokka.analysis.java.parsers.doctag
+
+import com.intellij.lexer.JavaDocTokenTypes
+import com.intellij.psi.*
+import com.intellij.psi.impl.source.javadoc.PsiDocParamRef
+import com.intellij.psi.impl.source.tree.LeafPsiElement
+import com.intellij.psi.javadoc.PsiDocTagValue
+import com.intellij.psi.javadoc.PsiDocToken
+import com.intellij.psi.javadoc.PsiInlineDocTag
+import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent
+import org.jetbrains.dokka.analysis.java.JavadocTag
+import org.jetbrains.dokka.analysis.java.doccomment.PsiDocumentationContent
+import org.jetbrains.dokka.analysis.java.parsers.CommentResolutionContext
+import org.jetbrains.dokka.analysis.java.util.*
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.utilities.htmlEscape
+
+private const val UNRESOLVED_PSI_ELEMENT = "UNRESOLVED_PSI_ELEMENT"
+
+private data class HtmlParserState(
+ val currentJavadocTag: JavadocTag?,
+ val previousElement: PsiElement? = null,
+ val openPreTags: Int = 0,
+ val closedPreTags: Int = 0
+)
+
+private data class HtmlParsingResult(val newState: HtmlParserState, val parsedLine: String? = null) {
+ constructor(tag: JavadocTag?) : this(HtmlParserState(tag))
+
+ operator fun plus(other: HtmlParsingResult): HtmlParsingResult {
+ return HtmlParsingResult(
+ newState = other.newState,
+ parsedLine = listOfNotNull(parsedLine, other.parsedLine).joinToString(separator = "")
+ )
+ }
+}
+
+internal class PsiElementToHtmlConverter(
+ private val inheritDocTagResolver: InheritDocTagResolver
+) {
+ private val preOpeningTagRegex = "<pre(\\s+.*)?>".toRegex()
+ private val preClosingTagRegex = "</pre>".toRegex()
+
+ fun convert(
+ psiElements: Iterable<PsiElement>,
+ docTagParserContext: DocTagParserContext,
+ commentResolutionContext: CommentResolutionContext
+ ): String? {
+ return WithContext(docTagParserContext, commentResolutionContext)
+ .convert(psiElements)
+ }
+
+ private inner class WithContext(
+ private val docTagParserContext: DocTagParserContext,
+ private val commentResolutionContext: CommentResolutionContext
+ ) {
+ fun convert(psiElements: Iterable<PsiElement>): String? {
+ val parsingResult =
+ psiElements.fold(HtmlParsingResult(commentResolutionContext.tag)) { resultAccumulator, psiElement ->
+ resultAccumulator + parseHtml(psiElement, resultAccumulator.newState)
+ }
+ return parsingResult.parsedLine?.trim()
+ }
+
+ private fun parseHtml(psiElement: PsiElement, state: HtmlParserState): HtmlParsingResult =
+ when (psiElement) {
+ is PsiReference -> psiElement.children.fold(HtmlParsingResult(state)) { acc, e ->
+ acc + parseHtml(e, acc.newState)
+ }
+ else -> parseHtmlOfSimpleElement(psiElement, state)
+ }
+
+ private fun parseHtmlOfSimpleElement(psiElement: PsiElement, state: HtmlParserState): HtmlParsingResult {
+ val text = psiElement.text
+
+ val openPre = state.openPreTags + preOpeningTagRegex.findAll(text).count()
+ val closedPre = state.closedPreTags + preClosingTagRegex.findAll(text).count()
+ val isInsidePre = openPre > closedPre
+
+ val parsed = when (psiElement) {
+ is PsiInlineDocTag -> psiElement.toHtml(state.currentJavadocTag)
+ is PsiDocParamRef -> psiElement.toDocumentationLinkString()
+ is PsiDocTagValue, is LeafPsiElement -> {
+ psiElement.stringifyElementAsText(isInsidePre, state.previousElement)
+ }
+ else -> null
+ }
+ val previousElement = if (text.trim() == "") state.previousElement else psiElement
+ return HtmlParsingResult(
+ state.copy(
+ previousElement = previousElement,
+ closedPreTags = closedPre,
+ openPreTags = openPre
+ ), parsed
+ )
+ }
+
+ /**
+ * Inline tags can be met in the middle of some text. Example of an inline tag usage:
+ *
+ * ```java
+ * Use the {@link #getComponentAt(int, int) getComponentAt} method.
+ * ```
+ */
+ private fun PsiInlineDocTag.toHtml(javadocTag: JavadocTag?): String? =
+ when (this.name) {
+ "link", "linkplain" -> this.referenceElement()
+ ?.toDocumentationLinkString(this.dataElements.filterIsInstance<PsiDocToken>().joinToString(" ") {
+ it.stringifyElementAsText(keepFormatting = false).orEmpty()
+ })
+
+ "code" -> "<code data-inline>${dataElementsAsText(this)}</code>"
+ "literal" -> "<literal>${dataElementsAsText(this)}</literal>"
+ "index" -> "<index>${this.children.filterIsInstance<PsiDocTagValue>().joinToString { it.text }}</index>"
+ "inheritDoc" -> {
+ val inheritDocContent = inheritDocTagResolver.resolveContent(commentResolutionContext)
+ val html = inheritDocContent?.fold(HtmlParsingResult(javadocTag)) { result, content ->
+ result + content.toInheritDocHtml(result.newState, docTagParserContext)
+ }?.parsedLine.orEmpty()
+ html
+ }
+
+ else -> this.text
+ }
+
+ private fun DocumentationContent.toInheritDocHtml(
+ parserState: HtmlParserState,
+ docTagParserContext: DocTagParserContext
+ ): HtmlParsingResult {
+ // TODO [beresnev] comment
+ return if (this is PsiDocumentationContent) {
+ parseHtml(this.psiElement, parserState)
+ } else {
+ HtmlParsingResult(parserState, inheritDocTagResolver.convertToHtml(this, docTagParserContext))
+ }
+ }
+
+ private fun dataElementsAsText(tag: PsiInlineDocTag): String {
+ return tag.dataElements.joinToString("") {
+ it.stringifyElementAsText(keepFormatting = true).orEmpty()
+ }.htmlEscape()
+ }
+
+ private fun PsiElement.toDocumentationLinkString(label: String = ""): String {
+ val driId = reference?.resolve()?.takeIf { it !is PsiParameter }?.let {
+ val dri = DRI.from(it)
+ val id = docTagParserContext.store(dri)
+ id
+ } ?: UNRESOLVED_PSI_ELEMENT // TODO [beresnev] log this somewhere maybe?
+
+ // TODO [beresnev] data-dri into a constant
+ return """<a data-dri="${driId.htmlEscape()}">${label.ifBlank { defaultLabel().text }}</a>"""
+ }
+ }
+}
+
+private fun PsiElement.stringifyElementAsText(keepFormatting: Boolean, previousElement: PsiElement? = null) =
+ if (keepFormatting) {
+ /*
+ For values in the <pre> tag we try to keep formatting, so only the leading space is trimmed,
+ since it is there because it separates this line from the leading asterisk
+ */
+ text.let {
+ if (((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true || (prevSibling as? PsiDocToken)?.isTagName() == true) && it.firstOrNull() == ' ')
+ it.drop(1) else it
+ }.let {
+ if ((nextSibling as? PsiDocToken)?.isLeadingAsterisk() == true) it.dropLastWhile { it == ' ' } else it
+ }
+ } else {
+ /*
+ Outside of the <pre> we would like to trim everything from the start and end of a line since
+ javadoc doesn't care about it.
+ */
+ text.let {
+ if ((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true && text.isNotBlank() && previousElement !is PsiInlineDocTag) it?.trimStart() else it
+ }?.let {
+ if ((nextSibling as? PsiDocToken)?.isLeadingAsterisk() == true && text.isNotBlank()) it.trimEnd() else it
+ }?.let {
+ if (shouldHaveSpaceAtTheEnd()) "$it " else it
+ }
+ }
+
+private fun PsiDocToken.isLeadingAsterisk() = tokenType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS
+
+private fun PsiDocToken.isTagName() = tokenType == JavaDocTokenType.DOC_TAG_NAME
+
+/**
+ * We would like to know if we need to have a space after a this tag
+ *
+ * The space is required when:
+ * - tag spans multiple lines, between every line we would need a space
+ *
+ * We wouldn't like to render a space if:
+ * - tag is followed by an end of comment
+ * - after a tag there is another tag (eg. multiple @author tags)
+ * - they end with an html tag like: <a href="...">Something</a> since then the space will be displayed in the following text
+ * - next line starts with a <p> or <pre> token
+ */
+private fun PsiElement.shouldHaveSpaceAtTheEnd(): Boolean {
+ val siblings = siblings(withItself = false).toList().filterNot { it.text.trim() == "" }
+ val nextNotEmptySibling = (siblings.firstOrNull() as? PsiDocToken)
+ val furtherNotEmptySibling =
+ (siblings.drop(1).firstOrNull { it is PsiDocToken && !it.isLeadingAsterisk() } as? PsiDocToken)
+ val lastHtmlTag = text.trim().substringAfterLast("<")
+ val endsWithAnUnclosedTag = lastHtmlTag.endsWith(">") && !lastHtmlTag.startsWith("</")
+
+ return (nextSibling as? PsiWhiteSpace)?.text?.startsWith("\n ") == true &&
+ (getNextSiblingIgnoringWhitespace() as? PsiDocToken)?.tokenType != JavaDocTokenTypes.INSTANCE.commentEnd() &&
+ nextNotEmptySibling?.isLeadingAsterisk() == true &&
+ furtherNotEmptySibling?.tokenType == JavaDocTokenTypes.INSTANCE.commentData() &&
+ !endsWithAnUnclosedTag
+}
+
+
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/CoreCopyPaste.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/CoreCopyPaste.kt
new file mode 100644
index 00000000..d8702336
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/CoreCopyPaste.kt
@@ -0,0 +1,20 @@
+package org.jetbrains.dokka.analysis.java.util
+
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.AncestryNode
+import org.jetbrains.dokka.model.TypeConstructor
+
+// TODO [beresnev] copy-pasted
+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()
+}
+
+// TODO [beresnev] copy-pasted
+internal fun DRI.isDirectlyAnException(): Boolean =
+ toString().let { stringed ->
+ stringed == "kotlin/Exception///PointingToDeclaration/" ||
+ stringed == "java.lang/Exception///PointingToDeclaration/"
+ }
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/NoopIntellijLogger.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/NoopIntellijLogger.kt
new file mode 100644
index 00000000..82482e35
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/NoopIntellijLogger.kt
@@ -0,0 +1,43 @@
+package org.jetbrains.dokka.analysis.java.util
+
+import com.intellij.openapi.diagnostic.Attachment
+import com.intellij.openapi.diagnostic.DefaultLogger
+import com.intellij.openapi.diagnostic.Logger
+
+internal class NoopIntellijLoggerFactory : Logger.Factory {
+ override fun getLoggerInstance(p0: String): Logger = NoopIntellijLogger
+}
+
+/**
+ * Ignores all messages passed to it
+ */
+internal object NoopIntellijLogger : DefaultLogger(null) {
+ override fun isDebugEnabled(): Boolean = false
+ override fun isTraceEnabled(): Boolean = false
+
+ override fun debug(message: String?) {}
+ override fun debug(t: Throwable?) {}
+ override fun debug(message: String?, t: Throwable?) {}
+ override fun debug(message: String, vararg details: Any?) {}
+ override fun debugValues(header: String, values: MutableCollection<*>) {}
+
+ override fun trace(message: String?) {}
+ override fun trace(t: Throwable?) {}
+
+ override fun info(message: String?) {}
+ override fun info(message: String?, t: Throwable?) {}
+ override fun info(t: Throwable) {}
+
+ override fun warn(message: String?, t: Throwable?) {}
+ override fun warn(message: String?) {}
+ override fun warn(t: Throwable) {}
+
+ override fun error(message: String?, t: Throwable?, vararg details: String?) {}
+ override fun error(message: String?) {}
+ override fun error(message: Any?) {}
+ override fun error(message: String?, vararg attachments: Attachment?) {}
+ override fun error(message: String?, t: Throwable?, vararg attachments: Attachment?) {}
+ override fun error(message: String?, vararg details: String?) {}
+ override fun error(message: String?, t: Throwable?) {}
+ override fun error(t: Throwable) {}
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PropertiesConventionUtil.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PropertiesConventionUtil.kt
new file mode 100644
index 00000000..137f0792
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PropertiesConventionUtil.kt
@@ -0,0 +1,101 @@
+package org.jetbrains.dokka.analysis.java.util
+
+// TODO [beresnev] copy-paste
+
+internal fun propertyNamesBySetMethodName(methodName: String): List<String> =
+ listOfNotNull(propertyNameBySetMethodName(methodName, false), propertyNameBySetMethodName(methodName, true))
+
+internal fun propertyNameByGetMethodName(methodName: String): String? =
+ propertyNameFromAccessorMethodName(methodName, "get") ?: propertyNameFromAccessorMethodName(methodName, "is", removePrefix = false)
+
+private fun propertyNameBySetMethodName(methodName: String, withIsPrefix: Boolean): String? =
+ propertyNameFromAccessorMethodName(methodName, "set", addPrefix = if (withIsPrefix) "is" else null)
+
+private fun propertyNameFromAccessorMethodName(
+ methodName: String,
+ prefix: String,
+ removePrefix: Boolean = true,
+ addPrefix: String? = null
+): String? {
+ val isSpecial = methodName.startsWith("<") // see special in org.jetbrains.kotlin.Name
+ if (isSpecial) return null
+ if (!methodName.startsWith(prefix)) return null
+ if (methodName.length == prefix.length) return null
+ if (methodName[prefix.length] in 'a'..'z') return null
+
+ if (addPrefix != null) {
+ assert(removePrefix)
+ return addPrefix + methodName.removePrefix(prefix)
+ }
+
+ if (!removePrefix) return methodName
+ val name = methodName.removePrefix(prefix).decapitalizeSmartForCompiler(asciiOnly = true)
+ if (!isValidIdentifier(name)) return null
+ return name
+}
+
+/**
+ * "FooBar" -> "fooBar"
+ * "FOOBar" -> "fooBar"
+ * "FOO" -> "foo"
+ * "FOO_BAR" -> "foO_BAR"
+ */
+private fun String.decapitalizeSmartForCompiler(asciiOnly: Boolean = false): String {
+ if (isEmpty() || !isUpperCaseCharAt(0, asciiOnly)) return this
+
+ if (length == 1 || !isUpperCaseCharAt(1, asciiOnly)) {
+ return if (asciiOnly) decapitalizeAsciiOnly() else replaceFirstChar(Char::lowercaseChar)
+ }
+
+ val secondWordStart = (indices.firstOrNull { !isUpperCaseCharAt(it, asciiOnly) } ?: return toLowerCase(this, asciiOnly)) - 1
+
+ return toLowerCase(substring(0, secondWordStart), asciiOnly) + substring(secondWordStart)
+}
+
+private fun String.isUpperCaseCharAt(index: Int, asciiOnly: Boolean): Boolean {
+ val c = this[index]
+ return if (asciiOnly) c in 'A'..'Z' else c.isUpperCase()
+}
+
+private fun toLowerCase(string: String, asciiOnly: Boolean): String {
+ return if (asciiOnly) string.toLowerCaseAsciiOnly() else string.lowercase()
+}
+
+private fun toUpperCase(string: String, asciiOnly: Boolean): String {
+ return if (asciiOnly) string.toUpperCaseAsciiOnly() else string.uppercase()
+}
+
+private fun String.decapitalizeAsciiOnly(): String {
+ if (isEmpty()) return this
+ val c = this[0]
+ return if (c in 'A'..'Z')
+ c.lowercaseChar() + substring(1)
+ else
+ this
+}
+
+private fun String.toLowerCaseAsciiOnly(): String {
+ val builder = StringBuilder(length)
+ for (c in this) {
+ builder.append(if (c in 'A'..'Z') c.lowercaseChar() else c)
+ }
+ return builder.toString()
+}
+
+private fun String.toUpperCaseAsciiOnly(): String {
+ val builder = StringBuilder(length)
+ for (c in this) {
+ builder.append(if (c in 'a'..'z') c.uppercaseChar() else c)
+ }
+ return builder.toString()
+}
+
+private fun isValidIdentifier(name: String): Boolean {
+ if (name.isEmpty() || name.startsWith("<")) return false
+ for (element in name) {
+ if (element == '.' || element == '/' || element == '\\') {
+ return false
+ }
+ }
+ return true
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiAccessorConventionUtil.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiAccessorConventionUtil.kt
new file mode 100644
index 00000000..1424244d
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiAccessorConventionUtil.kt
@@ -0,0 +1,98 @@
+package org.jetbrains.dokka.analysis.java.util
+
+import com.intellij.psi.PsiField
+import com.intellij.psi.PsiMethod
+import org.jetbrains.dokka.analysis.java.getVisibility
+import org.jetbrains.dokka.model.JavaVisibility
+import org.jetbrains.dokka.model.KotlinVisibility
+import org.jetbrains.dokka.model.Visibility
+
+
+internal data class PsiFunctionsHolder(
+ val regularFunctions: List<PsiMethod>,
+ val accessors: Map<PsiField, List<PsiMethod>>
+)
+
+internal fun splitFunctionsAndAccessors(fields: Array<PsiField>, methods: Array<PsiMethod>): PsiFunctionsHolder {
+ val fieldsByName = fields.associateBy { it.name }
+ val regularFunctions = mutableListOf<PsiMethod>()
+ val accessors = mutableMapOf<PsiField, MutableList<PsiMethod>>()
+ methods.forEach { method ->
+ val possiblePropertyNamesForFunction = method.getPossiblePropertyNamesForFunction()
+ val field = possiblePropertyNamesForFunction.firstNotNullOfOrNull { fieldsByName[it] }
+ if (field != null && method.isAccessorFor(field)) {
+ accessors.getOrPut(field, ::mutableListOf).add(method)
+ } else {
+ regularFunctions.add(method)
+ }
+ }
+
+ val accessorLookalikes = removeNonAccessorsReturning(accessors)
+ regularFunctions.addAll(accessorLookalikes)
+
+ return PsiFunctionsHolder(regularFunctions, accessors)
+}
+
+private fun PsiMethod.getPossiblePropertyNamesForFunction(): List<String> {
+ val jvmName = getAnnotation("kotlin.jvm.JvmName")?.findAttributeValue("name")?.text
+ if (jvmName != null) return listOf(jvmName)
+
+ return when {
+ isGetterName(name) -> listOfNotNull(
+ propertyNameByGetMethodName(name)
+ )
+ isSetterName(name) -> {
+ propertyNamesBySetMethodName(name)
+ }
+ else -> listOf()
+ }
+}
+
+private fun isGetterName(name: String): Boolean {
+ return name.startsWith("get") || name.startsWith("is")
+}
+
+private fun isSetterName(name: String): Boolean {
+ return name.startsWith("set")
+}
+
+/**
+ * If a field has no getter, it's not accessible as a property from Kotlin's perspective,
+ * but it still might have a setter. In this case, this "setter" should be just a regular function
+ */
+private fun removeNonAccessorsReturning(
+ fieldAccessors: MutableMap<PsiField, MutableList<PsiMethod>>
+): List<PsiMethod> {
+ val nonAccessors = mutableListOf<PsiMethod>()
+ fieldAccessors.entries.removeIf { (field, methods) ->
+ if (methods.size == 1 && methods[0].isSetterFor(field)) {
+ nonAccessors.add(methods[0])
+ true
+ } else {
+ false
+ }
+ }
+ return nonAccessors
+}
+
+internal fun PsiMethod.isAccessorFor(field: PsiField): Boolean {
+ return (this.isGetterFor(field) || this.isSetterFor(field))
+ && !field.getVisibility().isPublicAPI()
+ && this.getVisibility().isPublicAPI()
+}
+
+internal fun PsiMethod.isGetterFor(field: PsiField): Boolean {
+ return this.returnType == field.type && !this.hasParameters()
+}
+
+internal fun PsiMethod.isSetterFor(field: PsiField): Boolean {
+ return parameterList.getParameter(0)?.type == field.type && parameterList.getParametersCount() == 1
+}
+
+private fun Visibility.isPublicAPI() = when(this) {
+ KotlinVisibility.Public,
+ KotlinVisibility.Protected,
+ JavaVisibility.Public,
+ JavaVisibility.Protected -> true
+ else -> false
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiCommentsUtils.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiCommentsUtils.kt
new file mode 100644
index 00000000..10bb79c7
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiCommentsUtils.kt
@@ -0,0 +1,49 @@
+package org.jetbrains.dokka.analysis.java.util
+
+import com.intellij.psi.JavaDocTokenType
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiJavaCodeReferenceElement
+import com.intellij.psi.PsiWhiteSpace
+import com.intellij.psi.impl.source.tree.JavaDocElementType
+import com.intellij.psi.javadoc.PsiDocComment
+import com.intellij.psi.javadoc.PsiDocTag
+import com.intellij.psi.javadoc.PsiDocToken
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.dokka.analysis.java.DescriptionJavadocTag
+import org.jetbrains.dokka.analysis.java.JavadocTag
+
+internal fun PsiDocComment.hasTag(tag: JavadocTag): Boolean =
+ when (tag) {
+ DescriptionJavadocTag -> descriptionElements.isNotEmpty()
+ else -> findTagByName(tag.name) != null
+ }
+
+internal fun PsiDocTag.contentElementsWithSiblingIfNeeded(): List<PsiElement> = if (dataElements.isNotEmpty()) {
+ listOfNotNull(
+ dataElements[0],
+ dataElements[0].nextSibling?.takeIf { it.text != dataElements.drop(1).firstOrNull()?.text },
+ *dataElements.drop(1).toTypedArray()
+ )
+} else {
+ emptyList()
+}
+
+internal fun PsiDocTag.resolveToElement(): PsiElement? =
+ dataElements.firstOrNull()?.firstChild?.referenceElementOrSelf()?.resolveToGetDri()
+
+internal fun PsiDocTag.referenceElement(): PsiElement? =
+ linkElement()?.referenceElementOrSelf()
+
+internal fun PsiElement.referenceElementOrSelf(): PsiElement? =
+ if (node.elementType == JavaDocElementType.DOC_REFERENCE_HOLDER) {
+ PsiTreeUtil.findChildOfType(this, PsiJavaCodeReferenceElement::class.java)
+ } else this
+
+internal fun PsiDocTag.linkElement(): PsiElement? =
+ valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace }
+
+internal fun PsiElement.defaultLabel() = children.firstOrNull {
+ it is PsiDocToken && it.text.isNotBlank() && !it.isSharpToken()
+} ?: this
+
+internal fun PsiDocToken.isSharpToken() = tokenType == JavaDocTokenType.DOC_TAG_VALUE_SHARP_TOKEN
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiUtil.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiUtil.kt
new file mode 100644
index 00000000..ed58eb56
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiUtil.kt
@@ -0,0 +1,119 @@
+package org.jetbrains.dokka.analysis.java.util
+
+import com.intellij.psi.*
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.links.*
+import org.jetbrains.dokka.model.DocumentableSource
+import org.jetbrains.dokka.utilities.firstIsInstanceOrNull
+
+// TODO [beresnev] copy-paste
+
+internal val PsiElement.parentsWithSelf: Sequence<PsiElement>
+ get() = generateSequence(this) { if (it is PsiFile) null else it.parent }
+
+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
+ )
+}
+
+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()
+ )
+}
+
+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
+ }
+}
+
+// TODO [beresnev] copy-paste
+internal fun PsiElement.siblings(forward: Boolean = true, withItself: Boolean = true): Sequence<PsiElement> {
+ return object : Sequence<PsiElement> {
+ override fun iterator(): Iterator<PsiElement> {
+ var next: PsiElement? = this@siblings
+ return object : Iterator<PsiElement> {
+ init {
+ if (!withItself) next()
+ }
+
+ override fun hasNext(): Boolean = next != null
+ override fun next(): PsiElement {
+ val result = next ?: throw NoSuchElementException()
+ next = if (forward) result.nextSibling else result.prevSibling
+ return result
+ }
+ }
+ }
+ }
+}
+
+// TODO [beresnev] copy-paste
+internal fun PsiElement.getNextSiblingIgnoringWhitespace(withItself: Boolean = false): PsiElement? {
+ return siblings(withItself = withItself).filter { it !is PsiWhiteSpace }.firstOrNull()
+}
+
+@InternalDokkaApi
+class PsiDocumentableSource(val psi: PsiNamedElement) : DocumentableSource {
+ override val path = psi.containingFile.virtualFile.path
+
+ override fun computeLineNumber(): Int? {
+ val range = psi.getChildOfType<PsiIdentifier>()?.textRange ?: psi.textRange
+ val doc = PsiDocumentManager.getInstance(psi.project).getDocument(psi.containingFile)
+ // IJ uses 0-based line-numbers; external source browsers use 1-based
+ return doc?.getLineNumber(range.startOffset)?.plus(1)
+ }
+}
+
+inline fun <reified T : PsiElement> PsiElement.getChildOfType(): T? {
+ return PsiTreeUtil.getChildOfType(this, T::class.java)
+}
+
+internal fun PsiElement.getKotlinFqName(): String? = this.kotlinFqNameProp
+
+//// from import org.jetbrains.kotlin.idea.base.psi.kotlinFqName
+internal val PsiElement.kotlinFqNameProp: String?
+ get() = when (val element = this) {
+ is PsiPackage -> element.qualifiedName
+ is PsiClass -> element.qualifiedName
+ is PsiMember -> element.name?.let { name ->
+ val prefix = element.containingClass?.qualifiedName
+ if (prefix != null) "$prefix.$name" else name
+ }
+// is KtNamedDeclaration -> element.fqName TODO [beresnev] decide what to do with it
+ is PsiQualifiedNamedElement -> element.qualifiedName
+ else -> null
+ }
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/StdlibUtil.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/StdlibUtil.kt
new file mode 100644
index 00000000..cce76ce6
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/StdlibUtil.kt
@@ -0,0 +1,33 @@
+package org.jetbrains.dokka.analysis.java.util
+
+import java.util.*
+
+// TODO [beresnev] copy-paste
+
+// TODO [beresnev] remove this copy-paste and use the same method from stdlib instead after updating to 1.5
+internal 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
+}
+
+// TODO [beresnev] remove this copy-paste and use the same method from stdlib instead after updating to 1.5
+internal fun Char.uppercaseChar(): Char = Character.toUpperCase(this)
+
+// TODO [beresnev] remove this copy-paste and use the same method from stdlib instead after updating to 1.5
+internal fun Char.lowercaseChar(): Char = Character.toLowerCase(this)
+
+// TODO [beresnev] remove this copy-paste and use the same method from stdlib instead after updating to 1.5
+internal fun String.lowercase(): String = this.toLowerCase(Locale.ROOT)
+
+// TODO [beresnev] remove this copy-paste and use the same method from stdlib instead after updating to 1.5
+internal fun String.uppercase(): String = this.toUpperCase(Locale.ROOT)
+
+// TODO [beresnev] remove this copy-paste and use the same method from stdlib instead after updating to 1.5
+internal fun String.replaceFirstChar(transform: (Char) -> Char): String {
+ return if (isNotEmpty()) transform(this[0]) + substring(1) else this
+}
diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/resolveToGetDri.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/resolveToGetDri.kt
new file mode 100644
index 00000000..2972c009
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/resolveToGetDri.kt
@@ -0,0 +1,7 @@
+package org.jetbrains.dokka.analysis.java.util
+
+import com.intellij.psi.PsiElement
+
+// TODO [beresnev] get rid of
+internal fun PsiElement.resolveToGetDri(): PsiElement? =
+ reference?.resolve()
diff --git a/subprojects/analysis-java-psi/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/subprojects/analysis-java-psi/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
new file mode 100644
index 00000000..51d36899
--- /dev/null
+++ b/subprojects/analysis-java-psi/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
@@ -0,0 +1 @@
+org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin
diff --git a/subprojects/analysis-kotlin-api/README.md b/subprojects/analysis-kotlin-api/README.md
new file mode 100644
index 00000000..5b03b297
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/README.md
@@ -0,0 +1,10 @@
+# Analysis: Kotlin API
+
+Public API for interacting with Kotlin analysis, regardless of implementation. Contains no business logic.
+
+Can be used to request additional information about Kotlin declarations.
+
+Has to be used as a `compileOnly` dependency as Dokka bundles it by default in all runners.
+
+The actual implementation (K1/K2/etc) will be resolved and bootstrapped during runtime, so the
+user must not think about it.
diff --git a/subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api b/subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api
new file mode 100644
index 00000000..74bd7da9
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api
@@ -0,0 +1,70 @@
+public final class org/jetbrains/kotlin/analysis/kotlin/KotlinAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin {
+ public fun <init> ()V
+}
+
+public final class org/jetbrains/kotlin/analysis/kotlin/internal/DocumentableLanguage : java/lang/Enum {
+ public static final field JAVA Lorg/jetbrains/kotlin/analysis/kotlin/internal/DocumentableLanguage;
+ public static final field KOTLIN Lorg/jetbrains/kotlin/analysis/kotlin/internal/DocumentableLanguage;
+ public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/kotlin/analysis/kotlin/internal/DocumentableLanguage;
+ public static fun values ()[Lorg/jetbrains/kotlin/analysis/kotlin/internal/DocumentableLanguage;
+}
+
+public abstract interface class org/jetbrains/kotlin/analysis/kotlin/internal/DocumentableSourceLanguageParser {
+ public abstract fun getLanguage (Lorg/jetbrains/dokka/model/Documentable;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/kotlin/analysis/kotlin/internal/DocumentableLanguage;
+}
+
+public abstract interface class org/jetbrains/kotlin/analysis/kotlin/internal/ExternalDocumentablesProvider {
+ public abstract fun findClasslike (Lorg/jetbrains/dokka/links/DRI;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/model/DClasslike;
+}
+
+public abstract interface class org/jetbrains/kotlin/analysis/kotlin/internal/FullClassHierarchyBuilder {
+ public abstract fun build (Lorg/jetbrains/dokka/model/DModule;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public abstract interface class org/jetbrains/kotlin/analysis/kotlin/internal/InheritanceBuilder {
+ public abstract fun build (Ljava/util/Map;)Ljava/util/List;
+}
+
+public final class org/jetbrains/kotlin/analysis/kotlin/internal/InheritanceNode {
+ public fun <init> (Lorg/jetbrains/dokka/links/DRI;Ljava/util/List;Ljava/util/List;Z)V
+ public synthetic fun <init> (Lorg/jetbrains/dokka/links/DRI;Ljava/util/List;Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lorg/jetbrains/dokka/links/DRI;
+ public final fun component2 ()Ljava/util/List;
+ public final fun component3 ()Ljava/util/List;
+ public final fun component4 ()Z
+ public final fun copy (Lorg/jetbrains/dokka/links/DRI;Ljava/util/List;Ljava/util/List;Z)Lorg/jetbrains/kotlin/analysis/kotlin/internal/InheritanceNode;
+ public static synthetic fun copy$default (Lorg/jetbrains/kotlin/analysis/kotlin/internal/InheritanceNode;Lorg/jetbrains/dokka/links/DRI;Ljava/util/List;Ljava/util/List;ZILjava/lang/Object;)Lorg/jetbrains/kotlin/analysis/kotlin/internal/InheritanceNode;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getChildren ()Ljava/util/List;
+ public final fun getDri ()Lorg/jetbrains/dokka/links/DRI;
+ public final fun getInterfaces ()Ljava/util/List;
+ public fun hashCode ()I
+ public final fun isInterface ()Z
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class org/jetbrains/kotlin/analysis/kotlin/internal/InternalKotlinAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin {
+ public fun <init> ()V
+ public final fun getDocumentableSourceLanguageParser ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getExternalDocumentablesProvider ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getFullClassHierarchyBuilder ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getInheritanceBuilder ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getKotlinToJavaService ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getModuleAndPackageDocumentationReader ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+ public final fun getSyntheticDocumentableDetector ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
+}
+
+public abstract interface class org/jetbrains/kotlin/analysis/kotlin/internal/KotlinToJavaService {
+ public abstract fun findAsJava (Lorg/jetbrains/dokka/links/DRI;)Lorg/jetbrains/dokka/links/DRI;
+}
+
+public abstract interface class org/jetbrains/kotlin/analysis/kotlin/internal/ModuleAndPackageDocumentationReader {
+ public abstract fun read (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaModuleDescription;)Lorg/jetbrains/dokka/model/doc/DocumentationNode;
+ public abstract fun read (Lorg/jetbrains/dokka/model/DModule;)Ljava/util/Map;
+ public abstract fun read (Lorg/jetbrains/dokka/model/DPackage;)Ljava/util/Map;
+}
+
+public abstract interface class org/jetbrains/kotlin/analysis/kotlin/internal/SyntheticDocumentableDetector {
+ public abstract fun isSynthetic (Lorg/jetbrains/dokka/model/Documentable;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Z
+}
+
diff --git a/subprojects/analysis-kotlin-api/build.gradle.kts b/subprojects/analysis-kotlin-api/build.gradle.kts
new file mode 100644
index 00000000..3a10ff55
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/build.gradle.kts
@@ -0,0 +1,14 @@
+import org.jetbrains.registerDokkaArtifactPublication
+
+plugins {
+ id("org.jetbrains.conventions.kotlin-jvm")
+ id("org.jetbrains.conventions.maven-publish")
+}
+
+dependencies {
+ compileOnly(projects.core)
+}
+
+registerDokkaArtifactPublication("analysisKotlinApi") {
+ artifactId = "analysis-kotlin-api"
+}
diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/KotlinAnalysisPlugin.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/KotlinAnalysisPlugin.kt
new file mode 100644
index 00000000..3138e17b
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/KotlinAnalysisPlugin.kt
@@ -0,0 +1,17 @@
+package org.jetbrains.kotlin.analysis.kotlin
+
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import org.jetbrains.dokka.plugability.DokkaPluginApiPreview
+import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement
+
+class KotlinAnalysisPlugin : DokkaPlugin() {
+
+ /*
+ * This is where stable public API will go.
+ *
+ * No stable public API for now.
+ */
+
+ @OptIn(DokkaPluginApiPreview::class)
+ override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement
+}
diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/DocumentableSourceLanguageParser.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/DocumentableSourceLanguageParser.kt
new file mode 100644
index 00000000..08a465c0
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/DocumentableSourceLanguageParser.kt
@@ -0,0 +1,16 @@
+package org.jetbrains.kotlin.analysis.kotlin.internal
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.WithSources
+
+@InternalDokkaApi
+enum class DocumentableLanguage {
+ JAVA, KOTLIN
+}
+
+@InternalDokkaApi
+interface DocumentableSourceLanguageParser {
+ fun getLanguage(documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet): DocumentableLanguage?
+}
diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/ExternalDocumentablesProvider.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/ExternalDocumentablesProvider.kt
new file mode 100644
index 00000000..ea418fba
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/ExternalDocumentablesProvider.kt
@@ -0,0 +1,24 @@
+package org.jetbrains.kotlin.analysis.kotlin.internal
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.DClasslike
+
+/**
+ * Service that can be queried with [DRI] and source set to obtain a documentable for classlike.
+ *
+ * There are some cases when there is a need to process documentables of classlikes that were not defined
+ * in the project itself but are somehow related to the symbols defined in the documented project (e.g. are supertypes
+ * of classes defined in project).
+ */
+@InternalDokkaApi
+fun interface ExternalDocumentablesProvider {
+
+ /**
+ * Returns [DClasslike] matching provided [DRI] in specified source set.
+ *
+ * Result is null if compiler haven't generated matching class descriptor.
+ */
+ fun findClasslike(dri: DRI, sourceSet: DokkaConfiguration.DokkaSourceSet): DClasslike?
+}
diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/FullClassHierarchyBuilder.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/FullClassHierarchyBuilder.kt
new file mode 100644
index 00000000..91460002
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/FullClassHierarchyBuilder.kt
@@ -0,0 +1,17 @@
+package org.jetbrains.kotlin.analysis.kotlin.internal
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.model.SourceSetDependent
+
+@InternalDokkaApi
+typealias Supertypes = List<DRI>
+
+@InternalDokkaApi
+typealias ClassHierarchy = SourceSetDependent<Map<DRI, Supertypes>>
+
+@InternalDokkaApi
+interface FullClassHierarchyBuilder {
+ suspend fun build(module: DModule): ClassHierarchy
+}
diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/InheritanceBuilder.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/InheritanceBuilder.kt
new file mode 100644
index 00000000..7f0313ea
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/InheritanceBuilder.kt
@@ -0,0 +1,21 @@
+package org.jetbrains.kotlin.analysis.kotlin.internal
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.Documentable
+
+@InternalDokkaApi
+interface InheritanceBuilder {
+ fun build(documentables: Map<DRI, Documentable>): List<InheritanceNode>
+}
+
+@InternalDokkaApi
+data class InheritanceNode(
+ val dri: DRI,
+ val children: List<InheritanceNode> = emptyList(),
+ val interfaces: List<DRI> = emptyList(),
+ val isInterface: Boolean = false
+) {
+ override fun equals(other: Any?): Boolean = other is InheritanceNode && other.dri == dri
+ override fun hashCode(): Int = dri.hashCode()
+}
diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt
new file mode 100644
index 00000000..e2335474
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt
@@ -0,0 +1,31 @@
+package org.jetbrains.kotlin.analysis.kotlin.internal
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import org.jetbrains.dokka.plugability.DokkaPluginApiPreview
+import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement
+
+/**
+ * A plugin for internal use, has no stable public API and thus must not be used by third party,
+ * external plugins. If you need any of the given API stabilized, please create an issue describing your use case.
+ */
+@InternalDokkaApi
+class InternalKotlinAnalysisPlugin : DokkaPlugin() {
+
+ val fullClassHierarchyBuilder by extensionPoint<FullClassHierarchyBuilder>()
+
+ val syntheticDocumentableDetector by extensionPoint<SyntheticDocumentableDetector>()
+
+ val moduleAndPackageDocumentationReader by extensionPoint<ModuleAndPackageDocumentationReader>()
+
+ val kotlinToJavaService by extensionPoint<KotlinToJavaService>()
+
+ val inheritanceBuilder by extensionPoint<InheritanceBuilder>()
+
+ val externalDocumentablesProvider by extensionPoint<ExternalDocumentablesProvider>()
+
+ val documentableSourceLanguageParser by extensionPoint<DocumentableSourceLanguageParser>()
+
+ @OptIn(DokkaPluginApiPreview::class)
+ override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement
+}
diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/KotlinToJavaService.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/KotlinToJavaService.kt
new file mode 100644
index 00000000..3631fac2
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/KotlinToJavaService.kt
@@ -0,0 +1,9 @@
+package org.jetbrains.kotlin.analysis.kotlin.internal
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.links.DRI
+
+@InternalDokkaApi
+interface KotlinToJavaService {
+ fun findAsJava(kotlinDri: DRI): DRI?
+}
diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/ModuleAndPackageDocumentationReader.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/ModuleAndPackageDocumentationReader.kt
new file mode 100644
index 00000000..6e641c3a
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/ModuleAndPackageDocumentationReader.kt
@@ -0,0 +1,15 @@
+package org.jetbrains.kotlin.analysis.kotlin.internal
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.model.DPackage
+import org.jetbrains.dokka.model.SourceSetDependent
+import org.jetbrains.dokka.model.doc.DocumentationNode
+
+@InternalDokkaApi
+interface ModuleAndPackageDocumentationReader {
+ fun read(module: DModule): SourceSetDependent<DocumentationNode>
+ fun read(pkg: DPackage): SourceSetDependent<DocumentationNode>
+ fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode?
+}
diff --git a/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/SyntheticDocumentableDetector.kt b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/SyntheticDocumentableDetector.kt
new file mode 100644
index 00000000..3dc8afa5
--- /dev/null
+++ b/subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/SyntheticDocumentableDetector.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.kotlin.analysis.kotlin.internal
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.model.Documentable
+
+// TODO [beresnev] isSynthetic could be a property of Documentable
+@InternalDokkaApi
+interface SyntheticDocumentableDetector {
+ fun isSynthetic(documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet): Boolean
+}
diff --git a/subprojects/analysis-kotlin-descriptors/README.md b/subprojects/analysis-kotlin-descriptors/README.md
new file mode 100644
index 00000000..fbfd1c8b
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/README.md
@@ -0,0 +1,8 @@
+# Analysis: Kotlin descriptors
+
+An internal descriptor-based implementation for [analysis-kotlin-api](../analysis-kotlin-api), also known as K1 or
+"the old compiler".
+
+Contains no stable public API and must not be used by anyone directly, only via [analysis-kotlin-api](../analysis-kotlin-api).
+
+Can be added as a runtime dependency by the runner.
diff --git a/subprojects/analysis-kotlin-descriptors/api/analysis-kotlin-descriptors.api b/subprojects/analysis-kotlin-descriptors/api/analysis-kotlin-descriptors.api
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/api/analysis-kotlin-descriptors.api
diff --git a/subprojects/analysis-kotlin-descriptors/build.gradle.kts b/subprojects/analysis-kotlin-descriptors/build.gradle.kts
new file mode 100644
index 00000000..3f028090
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/build.gradle.kts
@@ -0,0 +1,43 @@
+import org.jetbrains.DokkaPublicationBuilder
+import org.jetbrains.registerDokkaArtifactPublication
+
+plugins {
+ id("org.jetbrains.conventions.kotlin-jvm")
+ id("org.jetbrains.conventions.maven-publish")
+ id("com.github.johnrengelman.shadow")
+}
+
+dependencies {
+ implementation(projects.subprojects.analysisKotlinApi)
+ implementation(projects.subprojects.analysisKotlinDescriptors.compiler)
+ implementation(projects.subprojects.analysisKotlinDescriptors.ide)
+}
+
+tasks {
+ // There are several reasons for shadowing all dependencies in one place:
+ // 1. Some of the artifacts Dokka depends on, like com.jetbrains.intellij.java:java-psi, are not
+ // published to Maven Central, so the users would need to add custom repositories to their build scripts.
+ // 2. There are many intertwining transitive dependencies of different versions, as well as direct copy-paste,
+ // that can lead to runtime errors due to classpath conflicts, so it's best to let Gradle take care of
+ // dependency resolution, and then pack everything into a single jar in a single place that can be tuned.
+ // 3. The compiler and ide modules are internal details that are likely to change, so packing everything into
+ // a single jar provides some stability for the CLI users, while not exposing too many internals. Publishing
+ // the compiler, ide and other subprojects separately would make it difficult to refactor the project structure.
+ shadowJar {
+ val dokka_version: String by project
+
+ // cannot be named exactly like the artifact (i.e analysis-kotlin-descriptors-VER.jar),
+ // otherwise leads to obscure test failures when run via CLI, but not via IJ
+ archiveFileName.set("analysis-kotlin-descriptors-all-$dokka_version.jar")
+ archiveClassifier.set("")
+
+ // service files are merged to make sure all Dokka plugins
+ // from the dependencies are loaded, and not just a single one.
+ mergeServiceFiles()
+ }
+}
+
+registerDokkaArtifactPublication("analysisKotlinDescriptors") {
+ artifactId = "analysis-kotlin-descriptors"
+ component = DokkaPublicationBuilder.Component.Shadow
+}
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
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/README.md b/subprojects/analysis-kotlin-descriptors/ide/README.md
new file mode 100644
index 00000000..14ed5baa
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/README.md
@@ -0,0 +1,11 @@
+# Descriptors: IDE
+
+An internal module that encapsulates external IDE (`org.jetbrains.kotlin:idea`) dependencies.
+
+IDE artifacts are reused for things that are not possible to do with the Kotlin compiler API, such
+as KDoc or KLib parsing/processing, because Dokka is very similar to an IDE when it comes to analyzing
+source code and docs.
+
+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/ide/api/ide.api b/subprojects/analysis-kotlin-descriptors/ide/api/ide.api
new file mode 100644
index 00000000..a59658a3
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/api/ide.api
@@ -0,0 +1,4 @@
+public final class org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin {
+ public fun <init> ()V
+}
+
diff --git a/subprojects/analysis-kotlin-descriptors/ide/build.gradle.kts b/subprojects/analysis-kotlin-descriptors/ide/build.gradle.kts
new file mode 100644
index 00000000..79777256
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ id("org.jetbrains.conventions.kotlin-jvm")
+}
+
+dependencies {
+ compileOnly(projects.core)
+ compileOnly(projects.subprojects.analysisKotlinApi)
+
+ implementation(projects.subprojects.analysisKotlinDescriptors.compiler)
+
+ api(libs.kotlin.idePlugin.common)
+ api(libs.kotlin.idePlugin.idea)
+ api(libs.kotlin.idePlugin.core)
+ api(libs.kotlin.idePlugin.native)
+
+ // TODO [beresnev] needed for CommonIdePlatformKind, describe
+ implementation(libs.kotlin.jps.common)
+
+ // TODO [beresnev] get rid of it
+ compileOnly(libs.kotlinx.coroutines.core)
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/CoreKotlinCacheService.kt b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/CoreKotlinCacheService.kt
new file mode 100644
index 00000000..d7069656
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/CoreKotlinCacheService.kt
@@ -0,0 +1,54 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.ide
+
+import com.intellij.psi.PsiFile
+import org.jetbrains.kotlin.analyzer.ModuleInfo
+import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
+import org.jetbrains.kotlin.caches.resolve.PlatformAnalysisSettings
+import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
+import org.jetbrains.kotlin.platform.TargetPlatform
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.resolve.diagnostics.KotlinSuppressCache
+
+
+internal class CoreKotlinCacheService(private val resolutionFacade: DokkaResolutionFacade) : KotlinCacheService {
+ override fun getResolutionFacade(elements: List<KtElement>): ResolutionFacade {
+ return resolutionFacade
+ }
+
+ override fun getResolutionFacade(element: KtElement): ResolutionFacade {
+ return resolutionFacade
+ }
+
+ override fun getResolutionFacadeByFile(
+ file: PsiFile,
+ platform: org.jetbrains.kotlin.platform.TargetPlatform
+ ): ResolutionFacade {
+ return resolutionFacade
+ }
+
+ override fun getResolutionFacadeByModuleInfo(
+ moduleInfo: ModuleInfo,
+ settings: PlatformAnalysisSettings
+ ): ResolutionFacade {
+ return resolutionFacade
+ }
+
+ override fun getResolutionFacadeByModuleInfo(
+ moduleInfo: ModuleInfo,
+ platform: org.jetbrains.kotlin.platform.TargetPlatform
+ ): ResolutionFacade {
+ return resolutionFacade
+ }
+
+ override fun getResolutionFacadeWithForcedPlatform(
+ elements: List<KtElement>,
+ platform: TargetPlatform
+ ): ResolutionFacade {
+ return resolutionFacade
+ }
+
+ override fun getSuppressionCache(): KotlinSuppressCache {
+ throw UnsupportedOperationException()
+ }
+
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/DokkaResolutionFacade.kt b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/DokkaResolutionFacade.kt
new file mode 100644
index 00000000..e2253b99
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/DokkaResolutionFacade.kt
@@ -0,0 +1,123 @@
+@file:OptIn(FrontendInternals::class)
+
+package org.jetbrains.dokka.analysis.kotlin.descriptors.ide
+
+import com.google.common.collect.ImmutableMap
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.analyzer.AnalysisResult
+import org.jetbrains.kotlin.analyzer.ModuleInfo
+import org.jetbrains.kotlin.analyzer.ResolverForModule
+import org.jetbrains.kotlin.analyzer.ResolverForProject
+import org.jetbrains.kotlin.container.getService
+import org.jetbrains.kotlin.container.tryGetService
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.diagnostics.DiagnosticSink
+import org.jetbrains.kotlin.idea.FrontendInternals
+import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.kotlin.psi.KtParameter
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.BindingTrace
+import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
+import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
+import org.jetbrains.kotlin.resolve.lazy.ResolveSession
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
+import org.jetbrains.kotlin.util.slicedMap.WritableSlice
+
+internal class DokkaResolutionFacade(
+ override val project: Project,
+ override val moduleDescriptor: ModuleDescriptor,
+ val resolverForModule: ResolverForModule
+) : ResolutionFacade {
+ override fun analyzeWithAllCompilerChecks(
+ elements: Collection<KtElement>,
+ callback: DiagnosticSink.DiagnosticsCallback?
+ ): AnalysisResult {
+ throw UnsupportedOperationException()
+ }
+
+ @OptIn(FrontendInternals::class)
+ override fun <T : Any> tryGetFrontendService(element: PsiElement, serviceClass: Class<T>): T? {
+ return resolverForModule.componentProvider.tryGetService(serviceClass)
+ }
+
+ override fun resolveToDescriptor(
+ declaration: KtDeclaration,
+ bodyResolveMode: BodyResolveMode
+ ): DeclarationDescriptor {
+ return resolveSession.resolveToDescriptor(declaration)
+ }
+
+ override fun analyze(elements: Collection<KtElement>, bodyResolveMode: BodyResolveMode): BindingContext {
+ throw UnsupportedOperationException()
+ }
+
+ val resolveSession: ResolveSession get() = getFrontendService(ResolveSession::class.java)
+
+ override fun analyze(element: KtElement, bodyResolveMode: BodyResolveMode): BindingContext {
+ if (element is KtDeclaration) {
+ val descriptor = resolveToDescriptor(element)
+ return object : BindingContext {
+ override fun <K : Any?, V : Any?> getKeys(p0: WritableSlice<K, V>?): Collection<K> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getType(p0: KtExpression): KotlinType? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <K : Any?, V : Any?> get(slice: ReadOnlySlice<K, V>?, key: K): V? {
+ if (key != element) {
+ throw UnsupportedOperationException()
+ }
+ @Suppress("UNCHECKED_CAST")
+ return when {
+ slice == BindingContext.DECLARATION_TO_DESCRIPTOR -> descriptor as V
+ slice == BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER && (element as KtParameter).hasValOrVar() -> descriptor as V
+ else -> null
+ }
+ }
+
+ override fun getDiagnostics(): Diagnostics {
+ throw UnsupportedOperationException()
+ }
+
+ override fun addOwnDataTo(p0: BindingTrace, p1: Boolean) {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <K : Any?, V : Any?> getSliceContents(p0: ReadOnlySlice<K, V>): ImmutableMap<K, V> {
+ throw UnsupportedOperationException()
+ }
+
+ }
+ }
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any> getFrontendService(element: PsiElement, serviceClass: Class<T>): T {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any> getFrontendService(serviceClass: Class<T>): T {
+ return resolverForModule.componentProvider.getService(serviceClass)
+ }
+
+ override fun <T : Any> getFrontendService(moduleDescriptor: ModuleDescriptor, serviceClass: Class<T>): T {
+ return resolverForModule.componentProvider.getService(serviceClass)
+ }
+
+ override fun <T : Any> getIdeService(serviceClass: Class<T>): T {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getResolverForProject(): ResolverForProject<out ModuleInfo> {
+ throw UnsupportedOperationException()
+ }
+
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeAnalysisContextCreator.kt b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeAnalysisContextCreator.kt
new file mode 100644
index 00000000..166e25fa
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeAnalysisContextCreator.kt
@@ -0,0 +1,29 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.ide
+
+import com.intellij.mock.MockComponentManager
+import com.intellij.mock.MockProject
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.AnalysisContextCreator
+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.caches.resolve.KotlinCacheService
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+
+internal class IdeAnalysisContextCreator : AnalysisContextCreator {
+ override fun create(
+ project: MockProject,
+ moduleDescriptor: ModuleDescriptor,
+ moduleResolver: ResolverForModule,
+ kotlinEnvironment: KotlinCoreEnvironment,
+ analysisEnvironment: AnalysisEnvironment,
+ ): AnalysisContext {
+ val facade = DokkaResolutionFacade(project, moduleDescriptor, moduleResolver)
+ val projectComponentManager = project as MockComponentManager
+ projectComponentManager.registerService(
+ KotlinCacheService::class.java,
+ CoreKotlinCacheService(facade)
+ )
+ return ResolutionFacadeAnalysisContext(facade, kotlinEnvironment, analysisEnvironment)
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeCompilerExtensionPointProvider.kt b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeCompilerExtensionPointProvider.kt
new file mode 100644
index 00000000..73d908e9
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeCompilerExtensionPointProvider.kt
@@ -0,0 +1,46 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.ide
+
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerExtensionPointProvider
+import org.jetbrains.kotlin.caches.resolve.CommonPlatformKindResolution
+import org.jetbrains.kotlin.caches.resolve.IdePlatformKindResolution
+import org.jetbrains.kotlin.caches.resolve.JsPlatformKindResolution
+import org.jetbrains.kotlin.caches.resolve.JvmPlatformKindResolution
+import org.jetbrains.kotlin.extensions.ApplicationExtensionDescriptor
+import org.jetbrains.kotlin.ide.konan.NativePlatformKindResolution
+import org.jetbrains.kotlin.platform.IdePlatformKind
+import org.jetbrains.kotlin.platform.impl.CommonIdePlatformKind
+import org.jetbrains.kotlin.platform.impl.JsIdePlatformKind
+import org.jetbrains.kotlin.platform.impl.JvmIdePlatformKind
+import org.jetbrains.kotlin.platform.impl.NativeIdePlatformKind
+
+internal class IdeCompilerExtensionPointProvider : CompilerExtensionPointProvider {
+ override fun get(): List<CompilerExtensionPointProvider.CompilerExtensionPoint> {
+
+ @Suppress("UNCHECKED_CAST")
+ val idePlatformKind = CompilerExtensionPointProvider.CompilerExtensionPoint(
+ ApplicationExtensionDescriptor(
+ "org.jetbrains.kotlin.idePlatformKind",
+ IdePlatformKind::class.java
+ ) as ApplicationExtensionDescriptor<Any>,
+ listOf(
+ CommonIdePlatformKind,
+ JvmIdePlatformKind,
+ JsIdePlatformKind,
+ NativeIdePlatformKind
+ )
+ )
+
+ @Suppress("UNCHECKED_CAST")
+ val resolution = CompilerExtensionPointProvider.CompilerExtensionPoint(
+ IdePlatformKindResolution as ApplicationExtensionDescriptor<Any>,
+ listOf(
+ CommonPlatformKindResolution(),
+ JvmPlatformKindResolution(),
+ JsPlatformKindResolution(),
+ NativePlatformKindResolution()
+ )
+ )
+
+ return listOf(idePlatformKind, resolution)
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorAnalysisPlugin.kt b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorAnalysisPlugin.kt
new file mode 100644
index 00000000..930e4a3f
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorAnalysisPlugin.kt
@@ -0,0 +1,38 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.ide
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import org.jetbrains.dokka.plugability.DokkaPluginApiPreview
+import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement
+
+@InternalDokkaApi
+class IdeDescriptorAnalysisPlugin : DokkaPlugin() {
+
+ internal val ideKdocFinder by extending {
+ plugin<CompilerDescriptorAnalysisPlugin>().kdocFinder providing ::IdePluginKDocFinder
+ }
+
+ internal val ideDescriptorFinder by extending {
+ plugin<CompilerDescriptorAnalysisPlugin>().descriptorFinder providing { IdeDescriptorFinder() }
+ }
+
+ internal val ideKlibService by extending {
+ plugin<CompilerDescriptorAnalysisPlugin>().klibService providing { IdeKLibService() }
+ }
+
+ internal val ideCompilerExtensionPointProvider by extending {
+ plugin<CompilerDescriptorAnalysisPlugin>().compilerExtensionPointProvider providing { IdeCompilerExtensionPointProvider() }
+ }
+
+ internal val ideApplicationHack by extending {
+ plugin<CompilerDescriptorAnalysisPlugin>().mockApplicationHack providing { IdeMockApplicationHack() }
+ }
+
+ internal val ideAnalysisContextCreator by extending {
+ plugin<CompilerDescriptorAnalysisPlugin>().analysisContextCreator providing { IdeAnalysisContextCreator() }
+ }
+
+ @OptIn(DokkaPluginApiPreview::class)
+ override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorFinder.kt b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorFinder.kt
new file mode 100644
index 00000000..bc591151
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorFinder.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.ide
+
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.DescriptorFinder
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
+import org.jetbrains.kotlin.psi.KtDeclaration
+
+internal class IdeDescriptorFinder : DescriptorFinder {
+ override fun KtDeclaration.findDescriptor(): DeclarationDescriptor? {
+ return this.descriptor
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeKLibService.kt b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeKLibService.kt
new file mode 100644
index 00000000..c3422f56
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeKLibService.kt
@@ -0,0 +1,30 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.ide
+
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KLibService
+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.idea.klib.createKlibPackageFragmentProvider
+import org.jetbrains.kotlin.idea.klib.getCompatibilityInfo
+import org.jetbrains.kotlin.incremental.components.LookupTracker
+import org.jetbrains.kotlin.library.KotlinLibrary
+import org.jetbrains.kotlin.storage.StorageManager
+
+internal class IdeKLibService : KLibService {
+ override fun KotlinLibrary.createPackageFragmentProvider(
+ storageManager: StorageManager,
+ metadataModuleDescriptorFactory: KlibMetadataModuleDescriptorFactory,
+ languageVersionSettings: LanguageVersionSettings,
+ moduleDescriptor: ModuleDescriptor,
+ lookupTracker: LookupTracker,
+ ): PackageFragmentProvider? {
+ return this.createKlibPackageFragmentProvider(
+ storageManager, metadataModuleDescriptorFactory, languageVersionSettings, moduleDescriptor, lookupTracker
+ )
+ }
+
+ override fun isAnalysisCompatible(kotlinLibrary: KotlinLibrary): Boolean {
+ return kotlinLibrary.getCompatibilityInfo().isCompatible
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeMockApplicationHack.kt b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeMockApplicationHack.kt
new file mode 100644
index 00000000..a582572d
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeMockApplicationHack.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.ide
+
+import com.intellij.mock.MockApplication
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.MockApplicationHack
+import org.jetbrains.kotlin.idea.klib.KlibLoadingMetadataCache
+
+internal class IdeMockApplicationHack : MockApplicationHack {
+ override fun hack(mockApplication: MockApplication) {
+ if (mockApplication.getService(KlibLoadingMetadataCache::class.java) == null)
+ mockApplication.registerService(KlibLoadingMetadataCache::class.java, KlibLoadingMetadataCache())
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdePluginKDocFinder.kt b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdePluginKDocFinder.kt
new file mode 100644
index 00000000..d0d217f6
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdePluginKDocFinder.kt
@@ -0,0 +1,48 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.ide
+
+import com.intellij.psi.PsiElement
+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.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
+import org.jetbrains.kotlin.idea.kdoc.findKDoc
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
+
+internal class IdePluginKDocFinder(
+ private val context: DokkaContext
+) : KDocFinder {
+
+ override fun KtElement.findKDoc(): KDocTag? {
+ return this.findKDoc { DescriptorToSourceUtils.descriptorToDeclaration(it) }
+ }
+
+ override fun DeclarationDescriptor.find(descriptorToPsi: (DeclarationDescriptorWithSource) -> PsiElement?): KDocTag? {
+ return this.findKDoc(descriptorToPsi)
+ }
+
+ override fun resolveKDocLink(
+ fromDescriptor: DeclarationDescriptor,
+ qualifiedName: String,
+ sourceSet: DokkaConfiguration.DokkaSourceSet,
+ emptyBindingContext: Boolean
+ ): Collection<DeclarationDescriptor> {
+ val facadeAnalysisContext = context
+ .plugin<CompilerDescriptorAnalysisPlugin>()
+ .querySingle { kotlinAnalysis }[sourceSet] as ResolutionFacadeAnalysisContext
+
+ return org.jetbrains.kotlin.idea.kdoc.resolveKDocLink(
+ context = if (emptyBindingContext) BindingContext.EMPTY else facadeAnalysisContext.resolveSession.bindingContext,
+ resolutionFacade = facadeAnalysisContext.facade,
+ fromDescriptor = fromDescriptor,
+ fromSubjectOfTag = null,
+ qualifiedName = qualifiedName.split('.')
+ )
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/ResolutionFacadeAnalysisContext.kt b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/ResolutionFacadeAnalysisContext.kt
new file mode 100644
index 00000000..d16ee7d2
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/ResolutionFacadeAnalysisContext.kt
@@ -0,0 +1,30 @@
+package org.jetbrains.dokka.analysis.kotlin.descriptors.ide
+
+import com.intellij.openapi.project.Project
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.AnalysisContext
+import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.AnalysisEnvironment
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.resolve.lazy.ResolveSession
+
+internal class ResolutionFacadeAnalysisContext(
+ val facade: DokkaResolutionFacade,
+
+ private val kotlinEnvironment: KotlinCoreEnvironment,
+ private val analysisEnvironment: AnalysisEnvironment
+) : AnalysisContext {
+ private var isClosed: Boolean = false
+
+ override val environment: KotlinCoreEnvironment
+ get() = kotlinEnvironment.takeUnless { isClosed }
+ ?: throw IllegalStateException("AnalysisEnvironment is already closed")
+
+ override val resolveSession: ResolveSession = facade.resolveSession
+ override val moduleDescriptor: ModuleDescriptor = facade.moduleDescriptor
+ override val project: Project = facade.project
+
+ override fun close() {
+ isClosed = true
+ analysisEnvironment.dispose()
+ }
+}
diff --git a/subprojects/analysis-kotlin-descriptors/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/subprojects/analysis-kotlin-descriptors/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
new file mode 100644
index 00000000..f993dcb1
--- /dev/null
+++ b/subprojects/analysis-kotlin-descriptors/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
@@ -0,0 +1 @@
+org.jetbrains.dokka.analysis.kotlin.descriptors.ide.IdeDescriptorAnalysisPlugin
diff --git a/subprojects/analysis-kotlin-symbols/README.md b/subprojects/analysis-kotlin-symbols/README.md
new file mode 100644
index 00000000..12e3041c
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/README.md
@@ -0,0 +1,8 @@
+# Analysis: Kotlin symbols
+
+An internal symbols-based implementation for [analysis-kotlin-api](../analysis-kotlin-api), also known as K2 or
+"the new compiler".
+
+Contains no stable public API and must not be used by anyone directly, only via [analysis-kotlin-api](../analysis-kotlin-api).
+
+Can be added as a runtime dependency by the runner.
diff --git a/subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api b/subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api
diff --git a/subprojects/analysis-kotlin-symbols/build.gradle.kts b/subprojects/analysis-kotlin-symbols/build.gradle.kts
new file mode 100644
index 00000000..c000df58
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/build.gradle.kts
@@ -0,0 +1,34 @@
+import org.jetbrains.DokkaPublicationBuilder
+import org.jetbrains.registerDokkaArtifactPublication
+
+plugins {
+ id("org.jetbrains.conventions.kotlin-jvm")
+ id("org.jetbrains.conventions.maven-publish")
+ id("com.github.johnrengelman.shadow")
+}
+
+dependencies {
+ implementation(projects.subprojects.analysisKotlinApi)
+ implementation(projects.subprojects.analysisKotlinSymbols.compiler)
+ implementation(projects.subprojects.analysisKotlinSymbols.ide)
+}
+
+tasks {
+ shadowJar {
+ val dokka_version: String by project
+
+ // cannot be named exactly like the artifact (i.e analysis-kotlin-symbols-VER.jar),
+ // otherwise leads to obscure test failures when run via CLI, but not via IJ
+ archiveFileName.set("analysis-kotlin-symbols-all-$dokka_version.jar")
+ archiveClassifier.set("")
+
+ // service files are merged to make sure all Dokka plugins
+ // from the dependencies are loaded, and not just a single one.
+ mergeServiceFiles()
+ }
+}
+
+registerDokkaArtifactPublication("analysisKotlinSymbols") {
+ artifactId = "analysis-kotlin-symbols"
+ component = DokkaPublicationBuilder.Component.Shadow
+}
diff --git a/subprojects/analysis-kotlin-symbols/compiler/api/compiler.api b/subprojects/analysis-kotlin-symbols/compiler/api/compiler.api
new file mode 100644
index 00000000..39870f57
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/compiler/api/compiler.api
@@ -0,0 +1,4 @@
+public final class org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin {
+ public fun <init> ()V
+}
+
diff --git a/subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts b/subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts
new file mode 100644
index 00000000..876d87ca
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts
@@ -0,0 +1,13 @@
+plugins {
+ id("org.jetbrains.conventions.kotlin-jvm")
+}
+
+dependencies {
+ compileOnly(projects.core)
+ compileOnly(projects.subprojects.analysisKotlinApi)
+
+ // TODO
+
+ // TODO [beresnev] get rid of it
+ compileOnly(libs.kotlinx.coroutines.core)
+}
diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin.kt
new file mode 100644
index 00000000..4a39e0d8
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.compiler
+
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import org.jetbrains.dokka.plugability.DokkaPluginApiPreview
+import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement
+
+class CompilerSymbolsAnalysisPlugin : DokkaPlugin() {
+
+ @OptIn(DokkaPluginApiPreview::class)
+ override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement
+}
diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/subprojects/analysis-kotlin-symbols/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
new file mode 100644
index 00000000..47163f6e
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
@@ -0,0 +1 @@
+org.jetbrains.dokka.analysis.kotlin.symbols.compiler.CompilerSymbolsAnalysisPlugin
diff --git a/subprojects/analysis-kotlin-symbols/ide/api/ide.api b/subprojects/analysis-kotlin-symbols/ide/api/ide.api
new file mode 100644
index 00000000..9be46c0b
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/ide/api/ide.api
@@ -0,0 +1,4 @@
+public final class org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin {
+ public fun <init> ()V
+}
+
diff --git a/subprojects/analysis-kotlin-symbols/ide/build.gradle.kts b/subprojects/analysis-kotlin-symbols/ide/build.gradle.kts
new file mode 100644
index 00000000..876d87ca
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/ide/build.gradle.kts
@@ -0,0 +1,13 @@
+plugins {
+ id("org.jetbrains.conventions.kotlin-jvm")
+}
+
+dependencies {
+ compileOnly(projects.core)
+ compileOnly(projects.subprojects.analysisKotlinApi)
+
+ // TODO
+
+ // TODO [beresnev] get rid of it
+ compileOnly(libs.kotlinx.coroutines.core)
+}
diff --git a/subprojects/analysis-kotlin-symbols/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt b/subprojects/analysis-kotlin-symbols/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt
new file mode 100644
index 00000000..33f555cb
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.ide
+
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import org.jetbrains.dokka.plugability.DokkaPluginApiPreview
+import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement
+
+class IdeSymbolsAnalysisPlugin : DokkaPlugin() {
+
+ @OptIn(DokkaPluginApiPreview::class)
+ override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement
+}
diff --git a/subprojects/analysis-kotlin-symbols/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/subprojects/analysis-kotlin-symbols/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
new file mode 100644
index 00000000..59245578
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
@@ -0,0 +1 @@
+org.jetbrains.dokka.analysis.kotlin.symbols.ide.IdeSymbolsAnalysisPlugin
diff --git a/subprojects/analysis-markdown-jb/README.md b/subprojects/analysis-markdown-jb/README.md
new file mode 100644
index 00000000..2922abc8
--- /dev/null
+++ b/subprojects/analysis-markdown-jb/README.md
@@ -0,0 +1,7 @@
+# Ananlysis: Markdown (JetBrains)
+
+An internal module that encapsulates Markdown file and format parsing by using `org.jetbrains:markdown`
+as the primary implementation dependency.
+
+Used by other Dokka modules, but it must not be used by external users directly until stable public API
+is provided.
diff --git a/subprojects/analysis-markdown-jb/api/analysis-markdown-jb.api b/subprojects/analysis-markdown-jb/api/analysis-markdown-jb.api
new file mode 100644
index 00000000..3a8c37c5
--- /dev/null
+++ b/subprojects/analysis-markdown-jb/api/analysis-markdown-jb.api
@@ -0,0 +1,28 @@
+public final class org/jetbrains/dokka/analysis/markdown/jb/MarkdownApiKt {
+ public static final fun getMARKDOWN_ELEMENT_FILE_NAME ()Ljava/lang/String;
+}
+
+public class org/jetbrains/dokka/analysis/markdown/jb/MarkdownParser : org/jetbrains/dokka/analysis/markdown/jb/Parser {
+ public static final field Companion Lorg/jetbrains/dokka/analysis/markdown/jb/MarkdownParser$Companion;
+ public fun <init> (Lkotlin/jvm/functions/Function1;Ljava/lang/String;)V
+ public fun parseStringToDocNode (Ljava/lang/String;)Lorg/jetbrains/dokka/model/doc/DocTag;
+ protected fun parseTagWithBody (Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/dokka/model/doc/TagWrapper;
+ protected fun preparse (Ljava/lang/String;)Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/analysis/markdown/jb/MarkdownParser$Companion {
+ public final fun fqDeclarationName (Lorg/jetbrains/dokka/links/DRI;)Ljava/lang/String;
+}
+
+public final class org/jetbrains/dokka/analysis/markdown/jb/ParseUtilsKt {
+ public static final fun parseHtmlEncodedWithNormalisedSpaces (Ljava/lang/String;Z)Ljava/util/List;
+}
+
+public abstract class org/jetbrains/dokka/analysis/markdown/jb/Parser {
+ public fun <init> ()V
+ public fun parse (Ljava/lang/String;)Lorg/jetbrains/dokka/model/doc/DocumentationNode;
+ public abstract fun parseStringToDocNode (Ljava/lang/String;)Lorg/jetbrains/dokka/model/doc/DocTag;
+ protected fun parseTagWithBody (Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/dokka/model/doc/TagWrapper;
+ protected abstract fun preparse (Ljava/lang/String;)Ljava/lang/String;
+}
+
diff --git a/subprojects/analysis-markdown-jb/build.gradle.kts b/subprojects/analysis-markdown-jb/build.gradle.kts
new file mode 100644
index 00000000..c67ee53c
--- /dev/null
+++ b/subprojects/analysis-markdown-jb/build.gradle.kts
@@ -0,0 +1,17 @@
+import org.jetbrains.registerDokkaArtifactPublication
+
+plugins {
+ id("org.jetbrains.conventions.kotlin-jvm")
+ id("org.jetbrains.conventions.maven-publish")
+}
+
+dependencies {
+ compileOnly(projects.core)
+
+ implementation(libs.jsoup)
+ implementation(libs.jetbrains.markdown)
+}
+
+registerDokkaArtifactPublication("analysisMarkdown") {
+ artifactId = "analysis-markdown"
+}
diff --git a/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/MarkdownApi.kt b/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/MarkdownApi.kt
new file mode 100644
index 00000000..e8738190
--- /dev/null
+++ b/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/MarkdownApi.kt
@@ -0,0 +1,8 @@
+package org.jetbrains.dokka.analysis.markdown.jb
+
+import org.intellij.markdown.MarkdownElementTypes
+import org.jetbrains.dokka.InternalDokkaApi
+
+// TODO [beresnev] move/rename if it's only used for CustomDocTag. for now left as is for compatibility
+@InternalDokkaApi
+val MARKDOWN_ELEMENT_FILE_NAME = MarkdownElementTypes.MARKDOWN_FILE.name
diff --git a/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/MarkdownParser.kt b/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/MarkdownParser.kt
new file mode 100644
index 00000000..165ca4c6
--- /dev/null
+++ b/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/MarkdownParser.kt
@@ -0,0 +1,511 @@
+package org.jetbrains.dokka.analysis.markdown.jb
+
+import org.intellij.markdown.MarkdownElementTypes
+import org.intellij.markdown.MarkdownTokenTypes
+import org.intellij.markdown.ast.ASTNode
+import org.intellij.markdown.ast.CompositeASTNode
+import org.intellij.markdown.ast.LeafASTNode
+import org.intellij.markdown.ast.impl.ListItemCompositeNode
+import org.intellij.markdown.flavours.gfm.GFMElementTypes
+import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
+import org.intellij.markdown.flavours.gfm.GFMTokenTypes
+import org.intellij.markdown.html.HtmlGenerator
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.analysis.markdown.jb.factories.DocTagsFromIElementFactory
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.PointingToDeclaration
+import org.jetbrains.dokka.model.doc.*
+import java.net.MalformedURLException
+import java.net.URL
+import org.intellij.markdown.parser.MarkdownParser as IntellijMarkdownParser
+
+@InternalDokkaApi
+open class MarkdownParser(
+ private val externalDri: (String) -> DRI?,
+ private val kdocLocation: String?,
+) : Parser() {
+
+ private lateinit var destinationLinksMap: Map<String, String>
+ private lateinit var text: String
+
+ override fun parseStringToDocNode(extractedString: String): DocTag {
+ val gfmFlavourDescriptor = GFMFlavourDescriptor()
+ val markdownAstRoot = IntellijMarkdownParser(gfmFlavourDescriptor).buildMarkdownTreeFromString(extractedString)
+ destinationLinksMap = getAllDestinationLinks(extractedString, markdownAstRoot).toMap()
+ text = extractedString
+
+ val parsed = visitNode(markdownAstRoot)
+ if (parsed.size == 1) {
+ return parsed.first()
+ }
+ return CustomDocTag(children = parsed, params = emptyMap(), name = "")
+ }
+
+ override fun preparse(text: String) = text.replace("\r\n", "\n").replace("\r", "\n")
+
+ override fun parseTagWithBody(tagName: String, content: String): TagWrapper =
+ when (tagName) {
+ "see" -> {
+ val referencedName = content.substringBefore(' ')
+ val dri = externalDri(referencedName)
+ See(
+ parseStringToDocNode(content.substringAfter(' ')),
+ dri?.fqDeclarationName() ?: referencedName,
+ dri
+ )
+ }
+ "throws", "exception" -> {
+ val dri = externalDri(content.substringBefore(' '))
+ Throws(
+ parseStringToDocNode(content.substringAfter(' ')),
+ dri?.fqDeclarationName() ?: content.substringBefore(' '),
+ dri
+ )
+ }
+ else -> super.parseTagWithBody(tagName, content)
+ }
+
+ private fun headersHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ visitNode(node.children.find { it.type == MarkdownTokenTypes.ATX_CONTENT }
+ ?: throw detailedException("Wrong AST Tree. Header does not contain expected content", node)
+ ).flatMap { it.children }
+ )
+
+ private fun horizontalRulesHandler() =
+ DocTagsFromIElementFactory.getInstance(MarkdownTokenTypes.HORIZONTAL_RULE)
+
+ private fun emphasisHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = node.children.evaluateChildrenWithDroppedEnclosingTokens(1)
+ )
+
+ private fun strongHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = node.children.evaluateChildrenWithDroppedEnclosingTokens(2)
+ )
+
+ private fun List<ASTNode>.evaluateChildrenWithDroppedEnclosingTokens(count: Int) =
+ drop(count).dropLast(count).evaluateChildren()
+
+ private fun blockquotesHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type, children = node.children
+ .filterIsInstance<CompositeASTNode>()
+ .evaluateChildren()
+ )
+
+ private fun listsHandler(node: ASTNode): List<DocTag> {
+
+ val children = node.children.filterIsInstance<ListItemCompositeNode>().flatMap {
+ if (it.children.last().type in listOf(
+ MarkdownElementTypes.ORDERED_LIST,
+ MarkdownElementTypes.UNORDERED_LIST
+ )
+ ) {
+ val nestedList = it.children.last()
+ (it.children as MutableList).removeAt(it.children.lastIndex)
+ listOf(it, nestedList)
+ } else
+ listOf(it)
+ }
+
+ return DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children =
+ children
+ .flatMap {
+ if (it.type == MarkdownElementTypes.LIST_ITEM)
+ DocTagsFromIElementFactory.getInstance(
+ it.type,
+ children = it
+ .children
+ .filterIsInstance<CompositeASTNode>()
+ .evaluateChildren()
+ )
+ else
+ visitNode(it)
+ },
+ params =
+ if (node.type == MarkdownElementTypes.ORDERED_LIST) {
+ val listNumberNode = node.children.first().children.first()
+ mapOf(
+ "start" to text.substring(
+ listNumberNode.startOffset,
+ listNumberNode.endOffset
+ ).trim().dropLast(1)
+ )
+ } else
+ emptyMap()
+ )
+ }
+
+ private fun resolveDRI(mdLink: String): DRI? =
+ mdLink
+ .removePrefix("[")
+ .removeSuffix("]")
+ .let { link ->
+ try {
+ URL(link)
+ null
+ } catch (e: MalformedURLException) {
+ externalDri(link)
+ }
+ }
+
+ private fun getAllDestinationLinks(text: String, node: ASTNode): List<Pair<String, String>> =
+ node.children
+ .filter { it.type == MarkdownElementTypes.LINK_DEFINITION }
+ .map {
+ text.substring(it.children[0].startOffset, it.children[0].endOffset).toLowerCase() to
+ text.substring(it.children[2].startOffset, it.children[2].endOffset)
+ } +
+ node.children.filterIsInstance<CompositeASTNode>().flatMap { getAllDestinationLinks(text, it) }
+
+
+ private fun referenceLinksHandler(node: ASTNode): List<DocTag> {
+ val linkLabel = node.children.find { it.type == MarkdownElementTypes.LINK_LABEL }
+ ?: throw detailedException("Wrong AST Tree. Reference link does not contain link label", node)
+ val linkText = node.children.findLast { it.type == MarkdownElementTypes.LINK_TEXT } ?: linkLabel
+
+ val linkKey = text.substring(linkLabel.startOffset, linkLabel.endOffset)
+
+ val link = destinationLinksMap[linkKey.toLowerCase()] ?: linkKey
+
+ return linksHandler(linkText, link)
+ }
+
+ private fun inlineLinksHandler(node: ASTNode): List<DocTag> {
+ val linkText = node.children.find { it.type == MarkdownElementTypes.LINK_TEXT }
+ ?: throw detailedException("Wrong AST Tree. Inline link does not contain link text", node)
+ val linkDestination = node.children.find { it.type == MarkdownElementTypes.LINK_DESTINATION }
+ val linkTitle = node.children.find { it.type == MarkdownElementTypes.LINK_TITLE }
+
+ // Link destination may be ommited: https://github.github.com/gfm/#example-495
+ val link = linkDestination?.let { text.substring(it.startOffset, it.endOffset) }
+
+ return linksHandler(linkText, link, linkTitle)
+ }
+
+ private fun markdownFileHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = node.children
+ .filterSpacesAndEOL()
+ .evaluateChildren()
+ )
+
+ private fun autoLinksHandler(node: ASTNode): List<DocTag> {
+ val link = text.substring(node.startOffset + 1, node.endOffset - 1)
+
+ return linksHandler(node, link)
+ }
+
+ private fun linksHandler(linkText: ASTNode, link: String?, linkTitle: ASTNode? = null): List<DocTag> {
+ val dri: DRI? = link?.let { resolveDRI(it) }
+ val linkOrEmpty = link ?: ""
+ val linkTextString =
+ if (linkTitle == null) linkOrEmpty else text.substring(linkTitle.startOffset + 1, linkTitle.endOffset - 1)
+
+ val params = if (linkTitle == null)
+ mapOf("href" to linkOrEmpty)
+ else
+ mapOf("href" to linkOrEmpty, "title" to linkTextString)
+
+ return if (link != null && dri == null && !linkOrEmpty.isRemoteLink()) {
+ DocTagsFromIElementFactory.getInstance(
+ MarkdownTokenTypes.TEXT,
+ params = params,
+ children = linkText.children.drop(1).dropLast(1).evaluateChildren(),
+ body = linkTextString.removeSurrounding("[", "]")
+ )
+ } else {
+ DocTagsFromIElementFactory.getInstance(
+ MarkdownElementTypes.INLINE_LINK,
+ params = params,
+ children = linkText.children.drop(1).dropLast(1).evaluateChildren(),
+ dri = dri
+ )
+ }
+ }
+
+ private fun codeLineHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ MarkdownElementTypes.CODE_BLOCK,
+ body = text.substring(node.startOffset, node.endOffset)
+ )
+
+ private fun textHandler(node: ASTNode, keepAllFormatting: Boolean) = DocTagsFromIElementFactory.getInstance(
+ MarkdownTokenTypes.TEXT,
+ body = text.substring(node.startOffset, node.endOffset).transform(),
+ keepFormatting = keepAllFormatting
+ )
+
+ private fun strikeThroughHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = node.children.evaluateChildrenWithDroppedEnclosingTokens(2)
+ )
+
+ private fun tableHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ GFMElementTypes.TABLE,
+ children = node.children
+ .filter { it.type == GFMElementTypes.ROW || it.type == GFMElementTypes.HEADER }
+ .evaluateChildren()
+ )
+
+ private fun headerHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ GFMElementTypes.HEADER,
+ children = node.children
+ .filter { it.type == GFMTokenTypes.CELL }
+ .evaluateChildren()
+ )
+
+ private fun rowHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ GFMElementTypes.ROW,
+ children = node.children
+ .filter { it.type == GFMTokenTypes.CELL }
+ .evaluateChildren()
+ )
+
+ private fun cellHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ GFMTokenTypes.CELL,
+ children = node.children.filterTabSeparators().evaluateChildren().trimSurroundingTokensIfText()
+ )
+
+ private fun String.isRemoteLink() = try {
+ URL(this)
+ true
+ } catch (e: MalformedURLException) {
+ false
+ }
+
+ private fun imagesHandler(node: ASTNode): List<DocTag> =
+ with(node.children.last().children) {
+ val destination = find { it.type == MarkdownElementTypes.LINK_DESTINATION }
+ val description = find { it.type == MarkdownElementTypes.LINK_TEXT }
+
+ val src = destination?.let {
+ mapOf("href" to text.substring(it.startOffset, it.endOffset))
+ } ?: emptyMap()
+
+ val alt = description?.let {
+ mapOf("alt" to text.substring(it.startOffset + 1, it.endOffset - 1))
+ } ?: emptyMap()
+
+ return DocTagsFromIElementFactory.getInstance(
+ node.type,
+ params = src + alt
+ )
+ }
+
+
+ private fun rawHtmlHandler(node: ASTNode): List<DocTag> =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ body = text.substring(node.startOffset, node.endOffset)
+ )
+
+ private fun codeSpansHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = DocTagsFromIElementFactory.getInstance(
+ MarkdownTokenTypes.TEXT,
+ body = text.substring(node.startOffset + 1, node.endOffset - 1).replace('\n', ' ').trimIndent(),
+ keepFormatting = true
+ )
+ )
+
+ private fun codeFencesHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = node
+ .children
+ .dropWhile { it.type != MarkdownTokenTypes.CODE_FENCE_CONTENT }
+ .dropLastWhile { it.type != MarkdownTokenTypes.CODE_FENCE_CONTENT }
+ .filter { it.type != MarkdownTokenTypes.WHITE_SPACE }
+ .map {
+ if (it.type == MarkdownTokenTypes.EOL)
+ LeafASTNode(MarkdownTokenTypes.HARD_LINE_BREAK, 0, 0)
+ else
+ it
+ }.evaluateChildren(keepAllFormatting = true),
+ params = node
+ .children
+ .find { it.type == MarkdownTokenTypes.FENCE_LANG }
+ ?.let { mapOf("lang" to text.substring(it.startOffset, it.endOffset)) }
+ ?: emptyMap()
+ )
+
+ private fun codeBlocksHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(node.type, children = node.children.mergeLeafASTNodes().flatMap {
+ DocTagsFromIElementFactory.getInstance(
+ MarkdownTokenTypes.TEXT,
+ body = HtmlGenerator.trimIndents(text.substring(it.startOffset, it.endOffset), 4).toString()
+ )
+ })
+
+ private fun defaultHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ MarkdownElementTypes.PARAGRAPH,
+ children = node.children.evaluateChildren()
+ )
+
+ private fun visitNode(node: ASTNode, keepAllFormatting: Boolean = false): List<DocTag> =
+ when (node.type) {
+ MarkdownElementTypes.ATX_1,
+ MarkdownElementTypes.ATX_2,
+ MarkdownElementTypes.ATX_3,
+ MarkdownElementTypes.ATX_4,
+ MarkdownElementTypes.ATX_5,
+ MarkdownElementTypes.ATX_6,
+ -> headersHandler(node)
+ MarkdownTokenTypes.HORIZONTAL_RULE -> horizontalRulesHandler()
+ MarkdownElementTypes.STRONG -> strongHandler(node)
+ MarkdownElementTypes.EMPH -> emphasisHandler(node)
+ MarkdownElementTypes.FULL_REFERENCE_LINK,
+ MarkdownElementTypes.SHORT_REFERENCE_LINK,
+ -> referenceLinksHandler(node)
+ MarkdownElementTypes.INLINE_LINK -> inlineLinksHandler(node)
+ MarkdownElementTypes.AUTOLINK -> autoLinksHandler(node)
+ MarkdownElementTypes.BLOCK_QUOTE -> blockquotesHandler(node)
+ MarkdownElementTypes.UNORDERED_LIST,
+ MarkdownElementTypes.ORDERED_LIST,
+ -> listsHandler(node)
+ MarkdownElementTypes.CODE_BLOCK -> codeBlocksHandler(node)
+ MarkdownElementTypes.CODE_FENCE -> codeFencesHandler(node)
+ MarkdownElementTypes.CODE_SPAN -> codeSpansHandler(node)
+ MarkdownElementTypes.IMAGE -> imagesHandler(node)
+ MarkdownElementTypes.HTML_BLOCK,
+ MarkdownTokenTypes.HTML_TAG,
+ MarkdownTokenTypes.HTML_BLOCK_CONTENT,
+ -> rawHtmlHandler(node)
+ MarkdownTokenTypes.HARD_LINE_BREAK -> DocTagsFromIElementFactory.getInstance(node.type)
+ MarkdownTokenTypes.CODE_FENCE_CONTENT,
+ MarkdownTokenTypes.CODE_LINE,
+ -> codeLineHandler(node)
+ MarkdownTokenTypes.TEXT -> textHandler(node, keepAllFormatting)
+ MarkdownElementTypes.MARKDOWN_FILE -> markdownFileHandler(node)
+ GFMElementTypes.STRIKETHROUGH -> strikeThroughHandler(node)
+ GFMElementTypes.TABLE -> tableHandler(node)
+ GFMElementTypes.HEADER -> headerHandler(node)
+ GFMElementTypes.ROW -> rowHandler(node)
+ GFMTokenTypes.CELL -> cellHandler(node)
+ else -> defaultHandler(node)
+ }
+
+ private fun List<ASTNode>.filterTabSeparators() =
+ this.filterNot { it.type == GFMTokenTypes.TABLE_SEPARATOR }
+
+ private fun List<ASTNode>.filterSpacesAndEOL() =
+ this.filterNot { it.type == MarkdownTokenTypes.WHITE_SPACE || it.type == MarkdownTokenTypes.EOL }
+
+ private fun List<ASTNode>.evaluateChildren(keepAllFormatting: Boolean = false): List<DocTag> =
+ this.removeUselessTokens().swapImagesThatShouldBeLinks(keepAllFormatting).mergeLeafASTNodes().flatMap { visitNode(it, keepAllFormatting) }
+
+ private fun List<ASTNode>.swapImagesThatShouldBeLinks(keepAllFormatting: Boolean): List<ASTNode> =
+ if (keepAllFormatting) {
+ this
+ } else {
+ flatMap { node ->
+ if (node.type == MarkdownElementTypes.IMAGE
+ && node.children.firstOrNull()?.let { it is LeafASTNode && it.type.name == "!" } == true
+ && node.children.lastOrNull()?.type == MarkdownElementTypes.SHORT_REFERENCE_LINK
+ ) {
+ node.children
+ } else {
+ listOf(node)
+ }
+ }
+ }
+
+ private fun List<ASTNode>.removeUselessTokens(): List<ASTNode> =
+ this.filterIndexed { index, node ->
+ !(node.type == MarkdownElementTypes.LINK_DEFINITION || (
+ node.type == MarkdownTokenTypes.EOL &&
+ this.getOrNull(index - 1)?.type == MarkdownTokenTypes.HARD_LINE_BREAK
+ ))
+ }
+
+ private fun List<DocTag>.trimSurroundingTokensIfText() = mapIndexed { index, elem ->
+ val elemTransformed = if (index == 0 && elem is Text) elem.copy(elem.body.trimStart()) else elem
+ if (index == lastIndex && elemTransformed is Text) elemTransformed.copy(elemTransformed.body.trimEnd()) else elemTransformed
+ }
+
+ private val notLeafNodes = listOf(
+ MarkdownTokenTypes.HORIZONTAL_RULE,
+ MarkdownTokenTypes.HARD_LINE_BREAK,
+ MarkdownTokenTypes.HTML_TAG,
+ MarkdownTokenTypes.HTML_BLOCK_CONTENT
+ )
+
+ private fun ASTNode.isNotLeaf() = this is CompositeASTNode || this.type in notLeafNodes
+
+ private fun List<ASTNode>.isNotLeaf(index: Int): Boolean =
+ if (index in 0..this.lastIndex)
+ this[index].isNotLeaf()
+ else
+ false
+
+ private fun List<ASTNode>.mergeLeafASTNodes(): List<ASTNode> {
+ val children: MutableList<ASTNode> = mutableListOf()
+ var index = 0
+ while (index <= this.lastIndex) {
+ if (this.isNotLeaf(index)) {
+ children += this[index]
+ } else {
+ val startOffset = this[index].startOffset
+ val sIndex = index
+ while (index < this.lastIndex) {
+ if (this.isNotLeaf(index + 1) || this[index + 1].startOffset != this[index].endOffset) {
+ children += mergedLeafNode(this, index, startOffset, sIndex)
+ break
+ }
+ index++
+ }
+ if (index == this.lastIndex) {
+ children += mergedLeafNode(this, index, startOffset, sIndex)
+ }
+ }
+ index++
+ }
+ return children
+ }
+
+ private fun mergedLeafNode(nodes: List<ASTNode>, index: Int, startOffset: Int, sIndex: Int): LeafASTNode {
+ val endOffset = nodes[index].endOffset
+ val type = if (nodes.subList(sIndex, index)
+ .any { it.type == MarkdownTokenTypes.CODE_LINE }
+ ) MarkdownTokenTypes.CODE_LINE else MarkdownTokenTypes.TEXT
+ return LeafASTNode(type, startOffset, endOffset)
+ }
+
+ private fun String.transform() = this
+ .replace(Regex("\n\n+"), "") // Squashing new lines between paragraphs
+ .replace(Regex("\n"), " ")
+ .replace(Regex(" >+ +"), " ") // Replacement used in blockquotes, get rid of garbage
+
+ private fun detailedException(baseMessage: String, node: ASTNode) =
+ IllegalStateException(
+ baseMessage + " in ${kdocLocation ?: "unspecified location"}, element starts from offset ${node.startOffset} and ends ${node.endOffset}: ${
+ text.substring(
+ node.startOffset,
+ node.endOffset
+ )
+ }"
+ )
+
+
+ companion object {
+ fun DRI.fqDeclarationName(): String? {
+ if (this.target !is PointingToDeclaration) {
+ return null
+ }
+ return listOfNotNull(this.packageName, this.classNames, this.callable?.name)
+ .joinToString(separator = ".")
+ .takeIf { it.isNotBlank() }
+ }
+ }
+}
+
diff --git a/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/ParseUtils.kt b/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/ParseUtils.kt
new file mode 100644
index 00000000..7f520591
--- /dev/null
+++ b/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/ParseUtils.kt
@@ -0,0 +1,39 @@
+package org.jetbrains.dokka.analysis.markdown.jb
+
+import org.intellij.markdown.lexer.Compat
+import org.intellij.markdown.lexer.Compat.forEachCodePoint
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.model.doc.DocTag
+import org.jetbrains.dokka.model.doc.Text
+import org.jsoup.internal.StringUtil
+import org.jsoup.nodes.Entities
+
+@InternalDokkaApi
+fun String.parseHtmlEncodedWithNormalisedSpaces(
+ renderWhiteCharactersAsSpaces: Boolean
+): List<DocTag> {
+ val accum = StringBuilder()
+ val tags = mutableListOf<DocTag>()
+ var lastWasWhite = false
+
+ forEachCodePoint { c ->
+ if (renderWhiteCharactersAsSpaces && StringUtil.isWhitespace(c)) {
+ if (!lastWasWhite) {
+ accum.append(' ')
+ lastWasWhite = true
+ }
+ } else if (Compat.codePointToString(c).let { it != Entities.escape(it) }) {
+ accum.toString().takeIf { it.isNotBlank() }?.let { tags.add(Text(it)) }
+ accum.delete(0, accum.length)
+
+ accum.appendCodePoint(c)
+ tags.add(Text(accum.toString(), params = DocTag.contentTypeParam("html")))
+ accum.delete(0, accum.length)
+ } else if (!StringUtil.isInvisibleChar(c)) {
+ accum.appendCodePoint(c)
+ lastWasWhite = false
+ }
+ }
+ accum.toString().takeIf { it.isNotBlank() }?.let { tags.add(Text(it)) }
+ return tags
+}
diff --git a/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/Parser.kt b/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/Parser.kt
new file mode 100644
index 00000000..09650b32
--- /dev/null
+++ b/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/Parser.kt
@@ -0,0 +1,131 @@
+package org.jetbrains.dokka.analysis.markdown.jb
+
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.model.doc.*
+
+@InternalDokkaApi
+abstract class Parser {
+
+ abstract fun parseStringToDocNode(extractedString: String): DocTag
+
+ protected abstract fun preparse(text: String): String
+
+ open fun parse(text: String): DocumentationNode =
+ DocumentationNode(extractTagsToListOfPairs(preparse(text)).map { (tag, content) -> parseTagWithBody(tag, content) })
+
+ protected open fun parseTagWithBody(tagName: String, content: String): TagWrapper =
+ when (tagName) {
+ "description" -> Description(parseStringToDocNode(content))
+ "author" -> Author(parseStringToDocNode(content))
+ "version" -> Version(parseStringToDocNode(content))
+ "since" -> Since(parseStringToDocNode(content))
+ "see" -> See(
+ parseStringToDocNode(content.substringAfter(' ')),
+ content.substringBefore(' '),
+ null
+ )
+ "param" -> Param(
+ parseStringToDocNode(content.substringAfter(' ')),
+ content.substringBefore(' ')
+ )
+ "property" -> Property(
+ parseStringToDocNode(content.substringAfter(' ')),
+ content.substringBefore(' ')
+ )
+ "return" -> Return(parseStringToDocNode(content))
+ "constructor" -> Constructor(parseStringToDocNode(content))
+ "receiver" -> Receiver(parseStringToDocNode(content))
+ "throws", "exception" -> Throws(
+ parseStringToDocNode(content.substringAfter(' ')),
+ content.substringBefore(' '),
+ null
+ )
+ "deprecated" -> Deprecated(parseStringToDocNode(content))
+ "sample" -> Sample(
+ parseStringToDocNode(content.substringAfter(' ')),
+ content.substringBefore(' ')
+ )
+ "suppress" -> Suppress(parseStringToDocNode(content))
+ else -> CustomTagWrapper(parseStringToDocNode(content), tagName)
+ }
+
+ /**
+ * KDoc parser from Kotlin compiler relies on a comment asterisk
+ * So there is a mini parser here
+ * TODO: at least to adapt [org.jetbrains.kotlin.kdoc.lexer.KDocLexer] to analyze KDoc without the asterisks and use it here
+ */
+ private fun extractTagsToListOfPairs(text: String): List<Pair<String, String>> =
+ "description $text"
+ .extractKDocSections()
+ .map { content ->
+ val contentWithEscapedAts = content.replace("\\@", "@")
+ val (tag, body) = contentWithEscapedAts.split(" ", limit = 2)
+ tag to body
+ }
+
+ /**
+ * Ignore a doc tag inside code spans and blocks
+ * @see org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
+ */
+ private fun CharSequence.extractKDocSections(delimiter: String = "\n@"): List<String> {
+ var countOfBackticks = 0
+ var countOfTildes = 0
+ var countOfBackticksInOpeningFence = 0
+ var countOfTildesInOpeningFence = 0
+
+ var isInCode = false
+ val result = mutableListOf<String>()
+ var rangeStart = 0
+ var rangeEnd = 0
+ var currentOffset = 0
+ while (currentOffset < length) {
+
+ when (get(currentOffset)) {
+ '`' -> {
+ countOfBackticks++
+ countOfTildes = 0
+ }
+ '~' -> {
+ countOfTildes++
+ countOfBackticks = 0
+ }
+ else -> {
+ if (isInCode) {
+ // The closing code fence must be at least as long as the opening fence
+ if(countOfBackticks >= countOfBackticksInOpeningFence
+ || countOfTildes >= countOfTildesInOpeningFence)
+ isInCode = false
+ } else {
+ // as per CommonMark spec, there can be any number of backticks for a code span, not only one or three
+ if (countOfBackticks > 0) {
+ isInCode = true
+ countOfBackticksInOpeningFence = countOfBackticks
+ countOfTildesInOpeningFence = Int.MAX_VALUE
+ }
+ // tildes are only for a code block, not code span
+ if (countOfTildes >= 3) {
+ isInCode = true
+ countOfTildesInOpeningFence = countOfTildes
+ countOfBackticksInOpeningFence = Int.MAX_VALUE
+ }
+ }
+ countOfTildes = 0
+ countOfBackticks = 0
+ }
+ }
+ if (!isInCode && startsWith(delimiter, currentOffset)) {
+ result.add(substring(rangeStart, rangeEnd))
+ currentOffset += delimiter.length
+ rangeStart = currentOffset
+ rangeEnd = currentOffset
+ continue
+ }
+
+ ++rangeEnd
+ ++currentOffset
+ }
+ result.add(substring(rangeStart, rangeEnd))
+ return result
+ }
+
+}
diff --git a/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/factories/DocTagsFromIElementFactory.kt b/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/factories/DocTagsFromIElementFactory.kt
new file mode 100644
index 00000000..86de8000
--- /dev/null
+++ b/subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/factories/DocTagsFromIElementFactory.kt
@@ -0,0 +1,86 @@
+package org.jetbrains.dokka.analysis.markdown.jb.factories
+
+import org.intellij.markdown.IElementType
+import org.intellij.markdown.MarkdownElementTypes
+import org.intellij.markdown.MarkdownTokenTypes
+import org.intellij.markdown.flavours.gfm.GFMElementTypes
+import org.intellij.markdown.flavours.gfm.GFMTokenTypes
+import org.jetbrains.dokka.analysis.markdown.jb.MARKDOWN_ELEMENT_FILE_NAME
+import org.jetbrains.dokka.analysis.markdown.jb.parseHtmlEncodedWithNormalisedSpaces
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.doc.DocTag.Companion.contentTypeParam
+import org.jsoup.Jsoup
+
+internal object DocTagsFromIElementFactory {
+
+ @Suppress("IMPLICIT_CAST_TO_ANY")
+ fun getInstance(type: IElementType, children: List<DocTag> = emptyList(), params: Map<String, String> = emptyMap(), body: String? = null, dri: DRI? = null, keepFormatting: Boolean = false) =
+ when(type) {
+ MarkdownElementTypes.SHORT_REFERENCE_LINK,
+ MarkdownElementTypes.FULL_REFERENCE_LINK,
+ MarkdownElementTypes.INLINE_LINK -> if(dri == null) A(children, params) else DocumentationLink(dri, children, params)
+ MarkdownElementTypes.STRONG -> B(children, params)
+ MarkdownElementTypes.BLOCK_QUOTE -> BlockQuote(children, params)
+ MarkdownElementTypes.CODE_SPAN -> CodeInline(children, params)
+ MarkdownElementTypes.CODE_BLOCK,
+ MarkdownElementTypes.CODE_FENCE -> CodeBlock(children, params)
+ MarkdownElementTypes.ATX_1 -> H1(children, params)
+ MarkdownElementTypes.ATX_2 -> H2(children, params)
+ MarkdownElementTypes.ATX_3 -> H3(children, params)
+ MarkdownElementTypes.ATX_4 -> H4(children, params)
+ MarkdownElementTypes.ATX_5 -> H5(children, params)
+ MarkdownElementTypes.ATX_6 -> H6(children, params)
+ MarkdownElementTypes.EMPH -> I(children, params)
+ MarkdownElementTypes.IMAGE -> Img(children, params)
+ MarkdownElementTypes.LIST_ITEM -> Li(children, params)
+ MarkdownElementTypes.ORDERED_LIST -> Ol(children, params)
+ MarkdownElementTypes.UNORDERED_LIST -> Ul(children, params)
+ MarkdownElementTypes.PARAGRAPH -> P(children, params)
+ MarkdownTokenTypes.TEXT -> if (keepFormatting) Text(
+ body.orEmpty(),
+ children,
+ params
+ ) else {
+ // corner case: there are only spaces between two Markdown nodes
+ val containsOnlySpaces = body?.isNotEmpty() == true && body.all { it.isWhitespace() }
+ if (containsOnlySpaces) Text(" ", children, params)
+ else body?.parseWithNormalisedSpaces(renderWhiteCharactersAsSpaces = false).orEmpty()
+ }
+ MarkdownTokenTypes.HORIZONTAL_RULE -> HorizontalRule
+ MarkdownTokenTypes.HARD_LINE_BREAK -> Br
+ GFMElementTypes.STRIKETHROUGH -> Strikethrough(children, params)
+ GFMElementTypes.TABLE -> Table(children, params)
+ GFMElementTypes.HEADER -> Th(children, params)
+ GFMElementTypes.ROW -> Tr(children, params)
+ GFMTokenTypes.CELL -> Td(children, params)
+ MarkdownElementTypes.MARKDOWN_FILE -> CustomDocTag(children, params, MARKDOWN_ELEMENT_FILE_NAME)
+ MarkdownElementTypes.HTML_BLOCK,
+ MarkdownTokenTypes.HTML_TAG,
+ MarkdownTokenTypes.HTML_BLOCK_CONTENT -> Text(body.orEmpty(), params = params + contentTypeParam("html"))
+ else -> CustomDocTag(children, params, type.name)
+ }.let {
+ @Suppress("UNCHECKED_CAST")
+ when (it) {
+ is List<*> -> it as List<DocTag>
+ else -> listOf(it as DocTag)
+ }
+ }
+
+ /**
+ * Parses string into [Text] doc tags that can have either value of the string or html-encoded value with content-type=html parameter.
+ * Content type is added when dealing with html entries like `&nbsp;`
+ */
+ private fun String.parseWithNormalisedSpaces(
+ renderWhiteCharactersAsSpaces: Boolean
+ ): List<DocTag> {
+ if (!requiresHtmlEncoding()) {
+ return parseHtmlEncodedWithNormalisedSpaces(renderWhiteCharactersAsSpaces)
+ }
+ // parsing it using jsoup is required to get codePoints, otherwise they are interpreted separately, as chars
+ // But we dont need to do it for java as it is already parsed with jsoup
+ return Jsoup.parseBodyFragment(this).body().wholeText().parseHtmlEncodedWithNormalisedSpaces(renderWhiteCharactersAsSpaces)
+ }
+
+ private fun String.requiresHtmlEncoding(): Boolean = indexOf('&') != -1
+}