From 9559158bfeeb274e9ccf1b4563f1b23b42afc493 Mon Sep 17 00:00:00 2001 From: Ignat Beresnev Date: Wed, 5 Jul 2023 10:04:55 +0200 Subject: Decompose Kotlin/Java analysis (#3034) * Extract analysis into separate modules --- subprojects/analysis-java-psi/README.md | 5 + .../analysis-java-psi/api/analysis-java-psi.api | 148 +++ subprojects/analysis-java-psi/build.gradle.kts | 18 + .../java/DefaultPsiToDocumentableTranslator.kt | 83 ++ .../dokka/analysis/java/JavaAnalysisPlugin.kt | 106 ++ .../jetbrains/dokka/analysis/java/JavadocTag.kt | 48 + .../java/SynheticElementDocumentationProvider.kt | 42 + .../dokka/analysis/java/doccomment/DocComment.kt | 14 + .../analysis/java/doccomment/DocCommentCreator.kt | 9 + .../analysis/java/doccomment/DocCommentFactory.kt | 20 + .../analysis/java/doccomment/DocCommentFinder.kt | 64 + .../java/doccomment/DocumentationContent.kt | 11 + .../analysis/java/doccomment/JavaDocComment.kt | 84 ++ .../java/doccomment/JavaDocCommentCreator.kt | 11 + .../java/doccomment/PsiDocumentationContent.kt | 22 + .../java/parsers/CommentResolutionContext.kt | 9 + .../analysis/java/parsers/DocCommentParser.kt | 12 + .../dokka/analysis/java/parsers/DokkaPsiParser.kt | 797 ++++++++++++ .../analysis/java/parsers/JavaDocCommentParser.kt | 228 ++++ .../dokka/analysis/java/parsers/JavadocParser.kt | 24 + .../java/parsers/doctag/DocTagParserContext.kt | 47 + .../java/parsers/doctag/HtmlToDocTagConverter.kt | 114 ++ .../parsers/doctag/InheritDocTagContentProvider.kt | 10 + .../java/parsers/doctag/InheritDocTagResolver.kt | 114 ++ .../java/parsers/doctag/PsiDocTagParser.kt | 39 + .../parsers/doctag/PsiElementToHtmlConverter.kt | 214 ++++ .../dokka/analysis/java/util/CoreCopyPaste.kt | 20 + .../dokka/analysis/java/util/NoopIntellijLogger.kt | 43 + .../analysis/java/util/PropertiesConventionUtil.kt | 101 ++ .../java/util/PsiAccessorConventionUtil.kt | 98 ++ .../dokka/analysis/java/util/PsiCommentsUtils.kt | 49 + .../jetbrains/dokka/analysis/java/util/PsiUtil.kt | 119 ++ .../dokka/analysis/java/util/StdlibUtil.kt | 33 + .../dokka/analysis/java/util/resolveToGetDri.kt | 7 + .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 + subprojects/analysis-kotlin-api/README.md | 10 + .../api/analysis-kotlin-api.api | 70 ++ subprojects/analysis-kotlin-api/build.gradle.kts | 14 + .../kotlin/analysis/kotlin/KotlinAnalysisPlugin.kt | 17 + .../internal/DocumentableSourceLanguageParser.kt | 16 + .../internal/ExternalDocumentablesProvider.kt | 24 + .../kotlin/internal/FullClassHierarchyBuilder.kt | 17 + .../analysis/kotlin/internal/InheritanceBuilder.kt | 21 + .../internal/InternalKotlinAnalysisPlugin.kt | 31 + .../kotlin/internal/KotlinToJavaService.kt | 9 + .../ModuleAndPackageDocumentationReader.kt | 15 + .../internal/SyntheticDocumentableDetector.kt | 11 + subprojects/analysis-kotlin-descriptors/README.md | 8 + .../api/analysis-kotlin-descriptors.api | 0 .../analysis-kotlin-descriptors/build.gradle.kts | 43 + .../analysis-kotlin-descriptors/compiler/README.md | 9 + .../compiler/api/compiler.api | 68 ++ .../compiler/build.gradle.kts | 21 + .../descriptors/compiler/AnalysisContextCreator.kt | 20 + .../compiler/CompilerDescriptorAnalysisPlugin.kt | 135 +++ .../CompilerDocumentableSourceLanguageParser.kt | 23 + .../compiler/CompilerExtensionPointProvider.kt | 14 + .../descriptors/compiler/DescriptorFinder.kt | 10 + .../kotlin/descriptors/compiler/KDocFinder.kt | 30 + .../kotlin/descriptors/compiler/KLibService.kt | 23 + .../descriptors/compiler/MockApplicationHack.kt | 9 + .../compiler/configuration/AbsolutePathString.kt | 3 + .../compiler/configuration/AnalysisContext.kt | 94 ++ .../compiler/configuration/AnalysisEnvironment.kt | 599 +++++++++ .../compiler/configuration/CallableFactory.kt | 31 + .../compiler/configuration/DRIFactory.kt | 49 + .../compiler/configuration/DRITargetFactory.kt | 42 + .../compiler/configuration/Documentable.kt | 24 + .../configuration/JvmDependenciesIndexImpl.kt | 264 ++++ .../compiler/configuration/KotlinAnalysis.kt | 94 ++ .../configuration/KotlinCliJavaFileManagerImpl.kt | 304 +++++ .../compiler/configuration/TypeReferenceFactory.kt | 68 ++ .../configuration/resolve/CommonKlibModuleInfo.kt | 25 + .../resolve/DokkaJsKlibLibraryInfo.kt | 30 + .../resolve/DokkaJsResolverForModuleFactory.kt | 125 ++ .../resolve/DokkaKlibLibraryDependencyResolver.kt | 17 + .../configuration/resolve/DokkaKlibLibraryInfo.kt | 10 + .../DokkaKlibMetadataCommonDependencyContainer.kt | 139 +++ .../resolve/DokkaNativeKlibLibraryInfo.kt | 50 + .../resolve/DokkaNativeResolverForModuleFactory.kt | 79 ++ .../compiler/impl/DefaultSamplesTransformer.kt | 35 + .../impl/DescriptorFullClassHierarchyBuilder.kt | 85 ++ .../compiler/impl/DescriptorInheritanceBuilder.kt | 91 ++ .../compiler/impl/DescriptorKotlinToJavaMapper.kt | 31 + .../DescriptorSyntheticDocumentableDetector.kt | 33 + .../compiler/impl/SamplesTransformerImpl.kt | 153 +++ .../IllegalModuleAndPackageDocumentation.kt | 7 + .../moduledocs/ModuleAndPackageDocumentation.kt | 11 + .../ModuleAndPackageDocumentationFragment.kt | 9 + .../ModuleAndPackageDocumentationParsingContext.kt | 71 ++ .../ModuleAndPackageDocumentationReader.kt | 113 ++ .../ModuleAndPackageDocumentationSource.kt | 14 + .../parseModuleAndPackageDocumentation.kt | 12 + .../parseModuleAndPackageDocumentationFragments.kt | 55 + .../java/DescriptorDocumentationContent.kt | 16 + .../compiler/java/DescriptorKotlinDocComment.kt | 79 ++ .../java/DescriptorKotlinDocCommentCreator.kt | 26 + .../java/DescriptorKotlinDocCommentParser.kt | 54 + .../compiler/java/KotlinAnalysisProjectProvider.kt | 16 + .../java/KotlinAnalysisSourceRootsExtractor.kt | 27 + .../java/KotlinInheritDocTagContentProvider.kt | 31 + .../compiler/translator/CollectionExtensions.kt | 12 + .../DefaultDescriptorToDocumentableTranslator.kt | 1275 ++++++++++++++++++++ .../DefaultExternalDocumentablesProvider.kt | 43 + .../translator/DescriptorAccessorConventionUtil.kt | 144 +++ .../translator/ExternalClasslikesTranslator.kt | 12 + .../compiler/translator/KdocMarkdownParser.kt | 101 ++ .../SyntheticDescriptorDocumentationProvider.kt | 46 + .../compiler/translator/annotationsValue.kt | 3 + .../descriptors/compiler/translator/isException.kt | 18 + .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 + ...seModuleAndPackageDocumentationFragmentsTest.kt | 282 +++++ .../analysis-kotlin-descriptors/ide/README.md | 11 + .../analysis-kotlin-descriptors/ide/api/ide.api | 4 + .../ide/build.gradle.kts | 21 + .../descriptors/ide/CoreKotlinCacheService.kt | 54 + .../descriptors/ide/DokkaResolutionFacade.kt | 123 ++ .../descriptors/ide/IdeAnalysisContextCreator.kt | 29 + .../ide/IdeCompilerExtensionPointProvider.kt | 46 + .../descriptors/ide/IdeDescriptorAnalysisPlugin.kt | 38 + .../kotlin/descriptors/ide/IdeDescriptorFinder.kt | 12 + .../kotlin/descriptors/ide/IdeKLibService.kt | 30 + .../descriptors/ide/IdeMockApplicationHack.kt | 12 + .../kotlin/descriptors/ide/IdePluginKDocFinder.kt | 48 + .../ide/ResolutionFacadeAnalysisContext.kt | 30 + .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 + subprojects/analysis-kotlin-symbols/README.md | 8 + .../api/analysis-kotlin-symbols.api | 0 .../analysis-kotlin-symbols/build.gradle.kts | 34 + .../compiler/api/compiler.api | 4 + .../compiler/build.gradle.kts | 13 + .../compiler/CompilerSymbolsAnalysisPlugin.kt | 11 + .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 + .../analysis-kotlin-symbols/ide/api/ide.api | 4 + .../analysis-kotlin-symbols/ide/build.gradle.kts | 13 + .../kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt | 11 + .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 + subprojects/analysis-markdown-jb/README.md | 7 + .../api/analysis-markdown-jb.api | 28 + subprojects/analysis-markdown-jb/build.gradle.kts | 17 + .../dokka/analysis/markdown/jb/MarkdownApi.kt | 8 + .../dokka/analysis/markdown/jb/MarkdownParser.kt | 511 ++++++++ .../dokka/analysis/markdown/jb/ParseUtils.kt | 39 + .../jetbrains/dokka/analysis/markdown/jb/Parser.kt | 131 ++ .../jb/factories/DocTagsFromIElementFactory.kt | 86 ++ 145 files changed, 9771 insertions(+) create mode 100644 subprojects/analysis-java-psi/README.md create mode 100644 subprojects/analysis-java-psi/api/analysis-java-psi.api create mode 100644 subprojects/analysis-java-psi/build.gradle.kts create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavadocTag.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/SynheticElementDocumentationProvider.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocComment.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocComment.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocCommentCreator.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/PsiDocumentationContent.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/CommentResolutionContext.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DocCommentParser.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavaDocCommentParser.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavadocParser.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/DocTagParserContext.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/HtmlToDocTagConverter.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagContentProvider.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagResolver.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiDocTagParser.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiElementToHtmlConverter.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/CoreCopyPaste.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/NoopIntellijLogger.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PropertiesConventionUtil.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiAccessorConventionUtil.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiCommentsUtils.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiUtil.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/StdlibUtil.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/resolveToGetDri.kt create mode 100644 subprojects/analysis-java-psi/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin create mode 100644 subprojects/analysis-kotlin-api/README.md create mode 100644 subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api create mode 100644 subprojects/analysis-kotlin-api/build.gradle.kts create mode 100644 subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/KotlinAnalysisPlugin.kt create mode 100644 subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/DocumentableSourceLanguageParser.kt create mode 100644 subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/ExternalDocumentablesProvider.kt create mode 100644 subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/FullClassHierarchyBuilder.kt create mode 100644 subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/InheritanceBuilder.kt create mode 100644 subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt create mode 100644 subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/KotlinToJavaService.kt create mode 100644 subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/ModuleAndPackageDocumentationReader.kt create mode 100644 subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/kotlin/analysis/kotlin/internal/SyntheticDocumentableDetector.kt create mode 100644 subprojects/analysis-kotlin-descriptors/README.md create mode 100644 subprojects/analysis-kotlin-descriptors/api/analysis-kotlin-descriptors.api create mode 100644 subprojects/analysis-kotlin-descriptors/build.gradle.kts create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/README.md create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/api/compiler.api create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/build.gradle.kts create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/AnalysisContextCreator.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDocumentableSourceLanguageParser.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerExtensionPointProvider.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/DescriptorFinder.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KDocFinder.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/KLibService.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/MockApplicationHack.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AbsolutePathString.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisContext.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/AnalysisEnvironment.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/CallableFactory.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/DRIFactory.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/DRITargetFactory.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/Documentable.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/JvmDependenciesIndexImpl.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinAnalysis.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinCliJavaFileManagerImpl.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/TypeReferenceFactory.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/CommonKlibModuleInfo.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaJsKlibLibraryInfo.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaJsResolverForModuleFactory.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibLibraryDependencyResolver.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibLibraryInfo.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaKlibMetadataCommonDependencyContainer.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaNativeKlibLibraryInfo.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/resolve/DokkaNativeResolverForModuleFactory.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DefaultSamplesTransformer.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorFullClassHierarchyBuilder.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorInheritanceBuilder.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorKotlinToJavaMapper.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/DescriptorSyntheticDocumentableDetector.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/SamplesTransformerImpl.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/IllegalModuleAndPackageDocumentation.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentation.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationFragment.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationParsingContext.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationReader.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/ModuleAndPackageDocumentationSource.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/parseModuleAndPackageDocumentation.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/impl/moduledocs/parseModuleAndPackageDocumentationFragments.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorDocumentationContent.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocComment.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocCommentCreator.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/DescriptorKotlinDocCommentParser.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinAnalysisProjectProvider.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinAnalysisSourceRootsExtractor.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/java/KotlinInheritDocTagContentProvider.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/CollectionExtensions.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultDescriptorToDocumentableTranslator.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultExternalDocumentablesProvider.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DescriptorAccessorConventionUtil.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/ExternalClasslikesTranslator.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/KdocMarkdownParser.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/SyntheticDescriptorDocumentationProvider.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/annotationsValue.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/isException.kt create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin create mode 100644 subprojects/analysis-kotlin-descriptors/compiler/src/test/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ParseModuleAndPackageDocumentationFragmentsTest.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/README.md create mode 100644 subprojects/analysis-kotlin-descriptors/ide/api/ide.api create mode 100644 subprojects/analysis-kotlin-descriptors/ide/build.gradle.kts create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/CoreKotlinCacheService.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/DokkaResolutionFacade.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeAnalysisContextCreator.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeCompilerExtensionPointProvider.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorAnalysisPlugin.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorFinder.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeKLibService.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeMockApplicationHack.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdePluginKDocFinder.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/ResolutionFacadeAnalysisContext.kt create mode 100644 subprojects/analysis-kotlin-descriptors/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin create mode 100644 subprojects/analysis-kotlin-symbols/README.md create mode 100644 subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api create mode 100644 subprojects/analysis-kotlin-symbols/build.gradle.kts create mode 100644 subprojects/analysis-kotlin-symbols/compiler/api/compiler.api create mode 100644 subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin create mode 100644 subprojects/analysis-kotlin-symbols/ide/api/ide.api create mode 100644 subprojects/analysis-kotlin-symbols/ide/build.gradle.kts create mode 100644 subprojects/analysis-kotlin-symbols/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt create mode 100644 subprojects/analysis-kotlin-symbols/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin create mode 100644 subprojects/analysis-markdown-jb/README.md create mode 100644 subprojects/analysis-markdown-jb/api/analysis-markdown-jb.api create mode 100644 subprojects/analysis-markdown-jb/build.gradle.kts create mode 100644 subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/MarkdownApi.kt create mode 100644 subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/MarkdownParser.kt create mode 100644 subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/ParseUtils.kt create mode 100644 subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/Parser.kt create mode 100644 subprojects/analysis-markdown-jb/src/main/kotlin/org/jetbrains/dokka/analysis/markdown/jb/factories/DocTagsFromIElementFactory.kt (limited to 'subprojects') 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 (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 ()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 (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 (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 (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 (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 (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 (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 (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 (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 ()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 (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().querySingle { projectProvider } + val project = projectProvider.getProject(sourceSet, context) + + val sourceRootsExtractor = context.plugin().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) -> + docParser.parsePackage(packageName, psiFiles) + }, + documentation = emptyMap(), + expectPresentInSet = null, + sourceSets = setOf(sourceSet) + ) + } + } + + private fun createPsiParser(sourceSet: DokkaSourceSet, context: DokkaContext): DokkaPsiParser { + val projectProvider = context.plugin().querySingle { projectProvider } + val docCommentParsers = context.plugin().query { docCommentParsers } + return DokkaPsiParser( + sourceSetData = sourceSet, + project = projectProvider.getProject(sourceSet, context), + logger = context.logger, + javadocParser = JavadocParser( + docCommentParsers = docCommentParsers, + docCommentFinder = context.plugin().docCommentFinder + ), + javaPsiDocCommentParser = docCommentParsers.single { it is JavaPsiDocCommentParser } as JavaPsiDocCommentParser, + lightMethodChecker = context.plugin().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 +} + +@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() + + // single + val sourceRootsExtractor by extensionPoint() + + // multiple + val docCommentCreators by extensionPoint() + + // multiple + val docCommentParsers by extensionPoint() + + // none or more + val inheritDocTagContentProviders by extensionPoint() + + // TODO [beresnev] figure out a better way depending on what it's used for + val kotlinLightMethodChecker by extensionPoint() + + 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 +} 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 +) { + 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() + .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 +} 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() + ?.resolveToElement() + ?.getKotlinFqName() == tag.exceptionQualifiedName + + override fun resolveTag(tag: JavadocTag): List { + 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 { + val resolvedParamElements = comment.resolveTag(tag) + .filterIsInstance() + .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 { + 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 { + return when (tag) { + DescriptionJavadocTag -> this.descriptionElements.toList() + else -> this.findTagsByName(tag.name).toList() + } + } + + private fun List.withoutReferenceLink(): List = 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 { + 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() + + 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.toSourceSetDependent() = mapOf(sourceSetData to this) + + suspend fun parsePackage(packageName: String, psiFiles: List): 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() + val superMethods = mutableListOf>() + val superFieldsKeys = hashSetOf() + val superFields = mutableListOf>() + 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.getSuperTypesPsiClasses(): List> { + 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().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 { + 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 { + 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 { + 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.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.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 PsiTypeParameter.annotations(): PropertyContainer = this.annotations.toList().toListOfAnnotations().annotations() + private fun PsiType.annotations(): PropertyContainer = this.annotations.toList().toListOfAnnotations().annotations() + + private fun List.annotations(): PropertyContainer = + 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) -> 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 { + fun mapBounds(bounds: Array): List = + 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>, + 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, 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.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::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? { + 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()?.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): 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, + 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() + + /** + * 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() + + /** + * @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 { + return Jsoup.parseBodyFragment(html) + .body() + .childNodes() + .flatMap { convertHtmlNode(it) } + } + + private fun convertHtmlNode(node: Node, keepFormatting: Boolean = false): List = 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 { + val tagName = element.tagName() + val children = element.childNodes() + .flatMap { convertHtmlNode(it, keepFormatting = keepFormatting || tagName == "pre" || tagName == "code") } + + fun ifChildrenPresent(operation: () -> DocTag): List { + 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 { + 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().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 +) { + internal fun convertToHtml(content: DocumentationContent, docTagParserContext: DocTagParserContext): String? { + return contentProviders + .firstOrNull { it.canConvert(content) } + ?.convertToHtml(content, docTagParserContext) + } + + internal fun resolveContent(context: CommentResolutionContext): List? { + 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 { + 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 { + 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 { + 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 = + 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, + commentResolutionContext: CommentResolutionContext + ): List = parse(asParagraph = false, psiElements, commentResolutionContext) + + fun parseAsParagraph( + psiElements: Iterable, + commentResolutionContext: CommentResolutionContext + ): List = parse(asParagraph = true, psiElements, commentResolutionContext) + + private fun parse( + asParagraph: Boolean, + psiElements: Iterable, + commentResolutionContext: CommentResolutionContext + ): List { + val docTagParserContext = DocTagParserContext() + + val psiToHtmlConverter = PsiElementToHtmlConverter(inheritDocTagResolver) + val elementsHtml = psiToHtmlConverter.convert(psiElements, docTagParserContext, commentResolutionContext) + ?: return emptyList() + + val htmlToDocTagConverter = HtmlToDocTagConverter(docTagParserContext) + val html = if (asParagraph) "

$elementsHtml

" 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 = "".toRegex() + private val preClosingTagRegex = "".toRegex() + + fun convert( + psiElements: Iterable, + 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): 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().joinToString(" ") { + it.stringifyElementAsText(keepFormatting = false).orEmpty() + }) + + "code" -> "${dataElementsAsText(this)}" + "literal" -> "${dataElementsAsText(this)}" + "index" -> "${this.children.filterIsInstance().joinToString { it.text }}" + "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 """${label.ifBlank { defaultLabel().text }}""" + } + } +} + +private fun PsiElement.stringifyElementAsText(keepFormatting: Boolean, previousElement: PsiElement? = null) = + if (keepFormatting) { + /* + For values in the
 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 
 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: Something since then the space will be displayed in the following text
+ *  - next line starts with a 

or

 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(" {
+    fun traverseSupertypes(ancestry: AncestryNode): List =
+        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 =
+    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,
+    val accessors: Map>
+)
+
+internal fun splitFunctionsAndAccessors(fields: Array, methods: Array): PsiFunctionsHolder {
+    val fieldsByName = fields.associateBy { it.name }
+    val regularFunctions = mutableListOf()
+    val accessors = mutableMapOf>()
+    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 {
+    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>
+): List {
+    val nonAccessors = mutableListOf()
+    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 = 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
+    get() = generateSequence(this) { if (it is PsiFile) null else it.parent }
+
+internal fun DRI.Companion.from(psi: PsiElement) = psi.parentsWithSelf.run {
+    val psiMethod = firstIsInstanceOrNull()
+    val psiField = firstIsInstanceOrNull()
+    val classes = filterIsInstance().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()?.let {
+            val callable = firstIsInstanceOrNull()
+            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 {
+    return object : Sequence {
+        override fun iterator(): Iterator {
+            var next: PsiElement? = this@siblings
+            return object : Iterator {
+                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()?.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  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  Iterable.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  ()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  (Lorg/jetbrains/dokka/links/DRI;Ljava/util/List;Ljava/util/List;Z)V
+	public synthetic fun  (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  ()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
+
+@InternalDokkaApi
+typealias ClassHierarchy = SourceSetDependent>
+
+@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): List
+}
+
+@InternalDokkaApi
+data class InheritanceNode(
+    val dri: DRI,
+    val children: List = emptyList(),
+    val interfaces: List = 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()
+
+    val syntheticDocumentableDetector by extensionPoint()
+
+    val moduleAndPackageDocumentationReader by extensionPoint()
+
+    val kotlinToJavaService by extensionPoint()
+
+    val inheritanceBuilder by extensionPoint()
+
+    val externalDocumentablesProvider by extensionPoint()
+
+    val documentableSourceLanguageParser by extensionPoint()
+
+    @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
+    fun read(pkg: DPackage): SourceSetDependent
+    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
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  ()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  (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  (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  ()V
+	public fun  (Lorg/jetbrains/dokka/analysis/kotlin/descriptors/compiler/configuration/KotlinAnalysis;)V
+	public synthetic fun  (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()
+
+    val descriptorFinder by extensionPoint()
+
+    val klibService by extensionPoint()
+
+    val compilerExtensionPointProvider by extensionPoint()
+
+    val mockApplicationHack by extensionPoint()
+
+    val analysisContextCreator by extensionPoint()
+
+    val kotlinAnalysis by extensionPoint()
+
+    internal val documentableAnalyzerImpl by extending {
+        plugin().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().fullClassHierarchyBuilder providing { DescriptorFullClassHierarchyBuilder() }
+    }
+
+    internal val descriptorSyntheticDocumentableDetector by extending {
+        plugin().syntheticDocumentableDetector providing { DescriptorSyntheticDocumentableDetector() }
+    }
+
+    internal val moduleAndPackageDocumentationReader by extending {
+        plugin().moduleAndPackageDocumentationReader providing ::ModuleAndPackageDocumentationReader
+    }
+
+    internal val kotlinToJavaMapper by extending {
+        plugin().kotlinToJavaService providing { DescriptorKotlinToJavaMapper() }
+    }
+
+    internal val descriptorInheritanceBuilder by extending {
+        plugin().inheritanceBuilder providing { DescriptorInheritanceBuilder() }
+    }
+
+    internal val defaultExternalDocumentablesProvider by extending {
+        plugin().externalDocumentablesProvider providing ::DefaultExternalDocumentablesProvider
+    }
+
+    private val javaAnalysisPlugin by lazy { plugin() }
+
+    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
+
+    class CompilerExtensionPoint(
+        val extensionDescriptor: ApplicationExtensionDescriptor,
+        val extensions: List
+    )
+}
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
+}
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,
+    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,
+    sourceRoots: Set,
+    sourceSet: DokkaConfiguration.DokkaSourceSet,
+    analysisConfiguration: DokkaAnalysisConfiguration
+): AnalysisContext {
+    val analysisEnvironment = AnalysisEnvironment(
+        DokkaMessageCollector(context.logger),
+        sourceSet.analysisPlatform,
+        context.plugin().querySingle { compilerExtensionPointProvider },
+        context.plugin().querySingle { mockApplicationHack },
+        context.plugin().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().querySingle { 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): 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 = 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("")
+            override val platform: TargetPlatform = targetPlatform
+            override fun dependencies(): List = listOf(this)
+            override fun getLibraryRoots(): Collection = 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("")
+            override val platform: TargetPlatform = targetPlatform
+            override fun dependencies(): List =
+                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 = {
+            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.registerLibraries(): List {
+        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 {
+        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,
+        environment: KotlinCoreEnvironment,
+        dependencyContainer: CommonDependenciesContainer?
+    ): ResolverForProject {
+        return object : AbstractResolverForProject(
+            "Dokka",
+            projectContext,
+            modules = module.dependencies()
+        ) {
+            override fun modulesContent(module: ModuleInfo): ModuleContent = 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
+    ): ResolverForProject {
+        return object : AbstractResolverForProject(
+            "Dokka",
+            projectContext,
+            modules = module.dependencies()
+        ) {
+            override fun modulesContent(module: ModuleInfo): ModuleContent = 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
+    ): ResolverForProject {
+        return object : AbstractResolverForProject(
+            "Dokka",
+            projectContext,
+            modules = module.dependencies()
+        ) {
+            override fun modulesContent(module: ModuleInfo): ModuleContent = 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,
+        sourcesScope: GlobalSearchScope,
+        builtIns: KotlinBuiltIns
+    ): ResolverForProject {
+        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(
+            "Dokka",
+            projectContext,
+            modules = listOf(module, library)
+        ) {
+            override fun modulesContent(module: ModuleInfo): ModuleContent =
+                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
+        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) {
+        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
+        get() = configuration.get(CLIConfigurationKeys.CONTENT_ROOTS)
+            ?.filterIsInstance()
+            ?.map { it.path } ?: emptyList()
+
+    /**
+     * Adds list of paths to source roots.
+     * $list: collection of files to add
+     */
+    internal fun addSources(sourceDirectories: Iterable) {
+        sourceDirectories.forEach { directory ->
+            configuration.addKotlinSourceRoot(directory.path)
+            if (directory.isDirectory || directory.extension == "java") {
+                configuration.addJavaSourceRoot(directory)
+            }
+        }
+    }
+
+    internal fun addRoots(list: List) {
+        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  registerExtensionPoint(
+            appExtension: ApplicationExtensionDescriptor,
+            instances: List,
+            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()
+    val callable = parameter?.containingDeclaration ?: firstIsInstanceOrNull()
+
+    DRI(
+        packageName = firstIsInstanceOrNull()?.fqName?.asString() ?: "",
+        classNames = (filterIsInstance() + filterIsInstance()).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()
+    val psiField = firstIsInstanceOrNull()
+    val classes = filterIsInstance().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()
+            val params =
+                callable?.let { listOfNotNull(it.extensionReceiverParameter) + it.valueParameters }.orEmpty()
+            val parameterDescriptor = firstIsInstanceOrNull()
+
+            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()?.let {
+            val callable = firstIsInstanceOrNull()
+            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) : JvmDependenciesIndex {
+    //these fields are computed based on _roots passed to constructor which are filled in later
+    private val roots: List 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()
+
+        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? = null
+
+    override val indexedRoots by lazy { roots.asSequence() }
+
+    private val packageCache: Array> by lazy {
+        Array(roots.size) { THashMap() }
+    }
+
+    override fun traverseDirectoriesInPackage(
+        packageFqName: FqName,
+        acceptedRootTypes: Set,
+        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  findClass(
+        classId: ClassId,
+        acceptedRootTypes: Set,
+        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  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,
+        fillCachesAfter: Int,
+        cachesPath: List
+    ): 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, fillCachesAfter: Int, cachesPath: List): 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): List {
+        val caches = ArrayList(path.size + 1)
+        caches.add(rootCache)
+        var currentCache = rootCache
+        for (subPackageName in path) {
+            currentCache = currentCache[subPackageName]
+            caches.add(currentCache)
+        }
+        return caches
+    }
+
+    private fun  MutableList.trimToSize(newSize: Int) {
+        subList(newSize, size).clear()
+    }
+
+    private data class FindClassRequest(val classId: ClassId, override val acceptedRootTypes: Set) :
+        SearchRequest {
+        override val packageFqName: FqName
+            get() = classId.packageFqName
+    }
+
+    private data class TraverseRequest(
+        override val packageFqName: FqName,
+        override val acceptedRootTypes: Set
+    ) : SearchRequest
+
+    private interface SearchRequest {
+        val packageFqName: FqName
+        val acceptedRootTypes: Set
+    }
+
+    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,
+    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,
+    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,
+    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
+    private val topLevelClassesCache: MutableMap = THashMap()
+    private val allScope = GlobalSearchScope.allScope(myPsiManager.project)
+    private var usePsiClassFilesReading = false
+
+    fun initialize(
+        index: JvmDependenciesIndex,
+        packagePartProviders: List,
+        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 = 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 = perfCounter.time {
+        val result = ArrayList(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 {
+        val result = THashSet()
+        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 {
+        // TODO
+        return emptySet()
+    }
+
+    override fun getNonTrivialPackagePrefixes(): Collection = 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  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): TypeReference =
+    fromPossiblyRecursive(t, paramTrace).let { if (t.isMarkedNullable) Nullable(it) else it }
+
+private fun TypeReference.Companion.fromPossiblyRecursive(t: KotlinType, paramTrace: List): 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): 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): 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 {
+    override fun dependencies(): List = 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 = listOf(this) + dependencyResolver.resolveDependencies(this)
+    override fun getLibraryRoots(): Collection = 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  createResolverForModule(
+        moduleDescriptor: ModuleDescriptorImpl,
+        moduleContext: ModuleContext,
+        moduleContent: ModuleContent,
+        resolverForProject: ResolverForProject,
+        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().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  createPackageFragmentProvider(
+        moduleInfo: M,
+        container: StorageComponentContainer,
+        moduleContext: ModuleContext,
+        moduleDescriptor: ModuleDescriptorImpl,
+        languageVersionSettings: LanguageVersionSettings
+    ): List = 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()
+
+    fun registerLibrary(libraryInfo: DokkaKlibLibraryInfo) {
+        cachedDependencies[libraryInfo.kotlinLibrary.uniqueName] = libraryInfo
+    }
+
+    fun resolveDependencies(libraryInfo: DokkaKlibLibraryInfo): List {
+        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,
+    private val configuration: CompilerConfiguration,
+    private val storageManager: StorageManager
+) : CommonDependenciesContainer {
+
+    private val builtIns
+        get() = DefaultBuiltIns.Instance
+
+    private val mutableDependenciesForAllModuleDescriptors = mutableListOf().apply {
+        add(builtIns.builtInsModule)
+    }
+
+    private val mutableDependenciesForAllModules = mutableListOf()
+
+    private val moduleDescriptorsForKotlinLibraries: Map =
+        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 = mutableListOf().apply {
+        addAll(
+            moduleDescriptorsForKotlinLibraries.map { (kotlinLibrary, moduleDescriptor) ->
+                CommonKlibModuleInfo(moduleDescriptor.name, kotlinLibrary, mutableDependenciesForAllModules)
+            }
+        )
+        mutableDependenciesForAllModules.addAll(this@apply)
+    }
+
+    override val moduleInfos: List get() = moduleInfosImpl
+
+    /* not used in Dokka */
+    override val friendModuleInfos: List = emptyList()
+
+    /* not used in Dokka */
+    override val refinesModuleInfos: List = 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 = listOf(this) + dependencyResolver.resolveDependencies(this)
+    override fun getLibraryRoots(): Collection = listOf(libraryRoot)
+
+    override val capabilities: Map, Any?>
+        get() {
+            val capabilities = super.capabilities.toMutableMap()
+            capabilities[KlibModuleOrigin.CAPABILITY] = DeserializedKlibModuleOrigin(kotlinLibrary)
+            capabilities[ImplicitIntegerCoercion.MODULE_CAPABILITY] = kotlinLibrary.safeRead(false) { isInterop }
+            return capabilities
+        }
+
+    private fun  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  createResolverForModule(
+        moduleDescriptor: ModuleDescriptorImpl,
+        moduleContext: ModuleContext,
+        moduleContent: ModuleContent,
+        resolverForProject: ResolverForProject,
+        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().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>() }
+        module.packages.parallelForEach { visitDocumentable(it, map) }
+        map
+    }
+
+    private suspend fun collectSupertypesFromKotlinType(
+        driWithKType: Pair,
+        supersMap: MutableMap
+    ): 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,
+        supersMap: MutableMap
+    ): 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>>
+    ): 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): List {
+        val descriptorMap = getDescriptorMap(documentables)
+
+        val psiInheritanceTree =
+            documentables.flatMap { (_, v) -> (v as? WithSources)?.sources?.values.orEmpty() }
+                .filterIsInstance().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>> = psi.supers.toList().let { l ->
+        listOf(psi to l) + l.flatMap { gatherPsiClasses(it) }
+    }
+
+    private fun getDescriptorMap(documentables: Map): Map {
+        val map: MutableMap = 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) {
+        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 = ""
+
+internal abstract class SamplesTransformerImpl(val context: DokkaContext) : PageTransformer {
+
+    private val kDocFinder: KDocFinder = context.plugin().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().querySingle { kotlinAnalysis }
+            )
+            analysis.use {
+                input.transformContentPagesTree { page ->
+                    val samples = (page as? WithDocumentables)?.documentables?.flatMap {
+                        it.documentation.entries.flatMap { entry ->
+                            entry.value.children.filterIsInstance().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)
+            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,
+        dri: Set,
+        content: String,
+        language: String,
+        styles: Set