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-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 + 79 files changed, 5825 insertions(+) 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 (limited to 'subprojects/analysis-kotlin-descriptors') 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