From 0e00edc6fcd406fcf38673ef6a2f8f59e8374de2 Mon Sep 17 00:00:00 2001 From: Vadim Mishenev Date: Mon, 28 Aug 2023 19:42:21 +0300 Subject: Support Dokka K2 analysis (#3094) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dokka has its own documentable model to represent analyzed code. The analysis is performed by a compiler frontend. In K1 the compiler frontend has descriptors that use the underlying Binding Context (global shared stateful structure). Dokka just maps descriptors to Documentable by DefaultDescriptorToDocumentableTranslator. K2 compiler has FIR tree, which means “Frontend Intermediate Representation”, instead of Binding Context. But we do not use FIR in Dokka directly, since it is too low-level for analysis. The Kotlin compiler provides high-level Analysis API for this case. The API is used by KSP too. Analysis API represent elements of FIR (declarations, parameters and so on) as Symbols. For more details see KtSymbolByFirBuilder, KtSymbol. For Dokka symbol is the replacement of descriptor in K2. Also, to set up the environment of project analysis in K1 we use idea dependencies (or copy-past from there). In K2 for these aims, there is a Standalone mode for Analysis API. --- .../analysis-java-psi/api/analysis-java-psi.api | 4 + .../jetbrains/dokka/analysis/java/util/PsiUtil.kt | 3 +- .../api/analysis-kotlin-symbols.api | 19 + .../analysis-kotlin-symbols/build.gradle.kts | 71 +- .../compiler/api/compiler.api | 4 - .../compiler/build.gradle.kts | 13 - .../compiler/CompilerSymbolsAnalysisPlugin.kt | 11 - .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 - .../analysis-kotlin-symbols/ide/api/ide.api | 4 - .../analysis-kotlin-symbols/ide/build.gradle.kts | 13 - .../kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt | 11 - .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 - .../analysis/kotlin/symbols/kdoc/KDocProvider.kt | 170 ++++ .../kotlin/symbols/kdoc/KdocMarkdownParser.kt | 97 +++ .../kotlin/symbols/kdoc/ResolveKDocLink.kt | 42 + .../kotlin/symbols/kdoc/SyntheticKDocProvider.kt | 61 ++ .../kdoc/java/DescriptorDocumentationContent.kt | 15 + .../kdoc/java/DescriptorKotlinDocCommentCreator.kt | 16 + .../kotlin/symbols/kdoc/java/KotlinDocComment.kt | 81 ++ .../symbols/kdoc/java/KotlinDocCommentParser.kt | 46 + .../java/KotlinInheritDocTagContentProvider.kt | 31 + .../IllegalModuleAndPackageDocumentation.kt | 7 + .../moduledocs/ModuleAndPackageDocumentation.kt | 11 + .../ModuleAndPackageDocumentationFragment.kt | 9 + .../ModuleAndPackageDocumentationParsingContext.kt | 47 + .../ModuleAndPackageDocumentationReader.kt | 110 +++ .../ModuleAndPackageDocumentationSource.kt | 14 + .../parseModuleAndPackageDocumentation.kt | 12 + .../parseModuleAndPackageDocumentationFragments.kt | 55 ++ .../kotlin/symbols/plugin/AnalysisContext.kt | 139 +++ .../kotlin/symbols/plugin/KotlinAnalysis.kt | 245 ++++++ .../kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt | 121 +++ .../services/KotlinAnalysisProjectProvider.kt | 16 + .../services/KotlinAnalysisSourceRootsExtractor.kt | 12 + .../KotlinDocumentableSourceLanguageParser.kt | 26 + .../symbols/services/KotlinSampleProvider.kt | 85 ++ .../symbols/services/KtPsiDocumentableSource.kt | 20 + .../SymbolExternalDocumentablesProvider.kt | 34 + .../services/SymbolFullClassHierarchyBuilder.kt | 85 ++ .../SymbolSyntheticDocumentableDetector.kt | 41 + .../symbols/translators/AnnotationTranslator.kt | 134 +++ .../kotlin/symbols/translators/DRIFactory.kt | 140 +++ .../DefaultSymbolToDocumentableTranslator.kt | 949 +++++++++++++++++++++ .../kotlin/symbols/translators/TranslatorError.kt | 29 + .../symbols/translators/TypeReferenceFactory.kt | 78 ++ .../kotlin/symbols/translators/TypeTranslator.kt | 186 ++++ .../analysis/kotlin/symbols/utils/isException.kt | 18 + .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 + 48 files changed, 3276 insertions(+), 62 deletions(-) delete mode 100644 subprojects/analysis-kotlin-symbols/compiler/api/compiler.api delete mode 100644 subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts delete mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin.kt delete mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin delete mode 100644 subprojects/analysis-kotlin-symbols/ide/api/ide.api delete mode 100644 subprojects/analysis-kotlin-symbols/ide/build.gradle.kts delete mode 100644 subprojects/analysis-kotlin-symbols/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt delete mode 100644 subprojects/analysis-kotlin-symbols/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/AnalysisContext.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/KotlinAnalysis.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisProjectProvider.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisSourceRootsExtractor.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinDocumentableSourceLanguageParser.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KtPsiDocumentableSource.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolFullClassHierarchyBuilder.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSyntheticDocumentableDetector.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DRIFactory.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TranslatorError.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeReferenceFactory.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt create mode 100644 subprojects/analysis-kotlin-symbols/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin (limited to 'subprojects') diff --git a/subprojects/analysis-java-psi/api/analysis-java-psi.api b/subprojects/analysis-java-psi/api/analysis-java-psi.api index 404249f8..6b4d444f 100644 --- a/subprojects/analysis-java-psi/api/analysis-java-psi.api +++ b/subprojects/analysis-java-psi/api/analysis-java-psi.api @@ -146,3 +146,7 @@ public final class org/jetbrains/dokka/analysis/java/util/PsiDocumentableSource public final fun getPsi ()Lcom/intellij/psi/PsiNamedElement; } +public final class org/jetbrains/dokka/analysis/java/util/PsiUtilKt { + public static final fun from (Lorg/jetbrains/dokka/links/DRI$Companion;Lcom/intellij/psi/PsiElement;)Lorg/jetbrains/dokka/links/DRI; +} + diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiUtil.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiUtil.kt index ed58eb56..eb2058d8 100644 --- a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiUtil.kt +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiUtil.kt @@ -12,7 +12,8 @@ import org.jetbrains.dokka.utilities.firstIsInstanceOrNull internal val PsiElement.parentsWithSelf: Sequence get() = generateSequence(this) { if (it is PsiFile) null else it.parent } -internal fun DRI.Companion.from(psi: PsiElement) = psi.parentsWithSelf.run { +@InternalDokkaApi +fun DRI.Companion.from(psi: PsiElement) = psi.parentsWithSelf.run { val psiMethod = firstIsInstanceOrNull() val psiField = firstIsInstanceOrNull() val classes = filterIsInstance().filterNot { it is PsiTypeParameter } diff --git a/subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api b/subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api index e69de29b..4bddfcf1 100644 --- a/subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api +++ b/subprojects/analysis-kotlin-symbols/api/analysis-kotlin-symbols.api @@ -0,0 +1,19 @@ +public final class org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin { + public fun ()V +} + +public class org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider : org/jetbrains/dokka/analysis/kotlin/internal/SampleProvider { + public fun (Lorg/jetbrains/dokka/plugability/DokkaContext;)V + public fun close ()V + public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext; + public fun getSample (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Ljava/lang/String;)Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider$SampleSnippet; + protected fun processBody (Lcom/intellij/psi/PsiElement;)Ljava/lang/String; + protected fun processImports (Lcom/intellij/psi/PsiElement;)Ljava/lang/String; +} + +public final class org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProviderFactory : org/jetbrains/dokka/analysis/kotlin/internal/SampleProviderFactory { + public fun (Lorg/jetbrains/dokka/plugability/DokkaContext;)V + public fun build ()Lorg/jetbrains/dokka/analysis/kotlin/internal/SampleProvider; + public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext; +} + diff --git a/subprojects/analysis-kotlin-symbols/build.gradle.kts b/subprojects/analysis-kotlin-symbols/build.gradle.kts index c000df58..9fddcde1 100644 --- a/subprojects/analysis-kotlin-symbols/build.gradle.kts +++ b/subprojects/analysis-kotlin-symbols/build.gradle.kts @@ -8,9 +8,74 @@ plugins { } dependencies { - implementation(projects.subprojects.analysisKotlinApi) - implementation(projects.subprojects.analysisKotlinSymbols.compiler) - implementation(projects.subprojects.analysisKotlinSymbols.ide) + compileOnly(projects.core) + compileOnly(projects.subprojects.analysisKotlinApi) + + implementation(projects.subprojects.analysisMarkdownJb) + implementation(projects.subprojects.analysisJavaPsi) + + + // ----------- IDE dependencies ---------------------------------------------------------------------------- + + listOf( + libs.intellij.platform.util.rt, + libs.intellij.platform.util.api, + libs.intellij.java.psi.api, + libs.intellij.java.psi.impl + ).forEach { + runtimeOnly(it) { isTransitive = false } + } + + implementation(libs.intellij.java.psi.api) { isTransitive = false } + + + // TODO move to toml + listOf( + "com.jetbrains.intellij.platform:util-class-loader", + "com.jetbrains.intellij.platform:util-text-matching", + "com.jetbrains.intellij.platform:util-base", + "com.jetbrains.intellij.platform:util-xml-dom", + "com.jetbrains.intellij.platform:core-impl", + "com.jetbrains.intellij.platform:extensions", + ).forEach { + runtimeOnly("$it:213.7172.25") { isTransitive = false } + } + + implementation("com.jetbrains.intellij.platform:core:213.7172.25") { + isTransitive = false + } // for Standalone prototype + + // ----------- Analysis dependencies ---------------------------------------------------------------------------- + + listOf( + libs.kotlin.high.level.api.api, + libs.kotlin.analysis.api.standalone, + libs.kotlin.high.level.api.impl // for Standalone prototype + ).forEach { + implementation(it) { + isTransitive = false // see KTIJ-19820 + } + } + listOf( + libs.kotlin.high.level.api.impl, + libs.kotlin.high.level.api.fir, + libs.kotlin.high.level.api.fe10, + libs.kotlin.low.level.api.fir, + libs.kotlin.analysis.project.structure, + libs.kotlin.analysis.api.providers, + libs.kotlin.symbol.light.classes + ).forEach { + runtimeOnly(it) { + isTransitive = false // see KTIJ-19820 + } + } + runtimeOnly(libs.kotlinx.collections.immutable) + implementation(libs.kotlin.compiler.k2) { + isTransitive = false + } + + // TODO [beresnev] get rid of it + compileOnly(libs.kotlinx.coroutines.core) } tasks { diff --git a/subprojects/analysis-kotlin-symbols/compiler/api/compiler.api b/subprojects/analysis-kotlin-symbols/compiler/api/compiler.api deleted file mode 100644 index 39870f57..00000000 --- a/subprojects/analysis-kotlin-symbols/compiler/api/compiler.api +++ /dev/null @@ -1,4 +0,0 @@ -public final class org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin { - public fun ()V -} - diff --git a/subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts b/subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts deleted file mode 100644 index 876d87ca..00000000 --- a/subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id("org.jetbrains.conventions.kotlin-jvm") -} - -dependencies { - compileOnly(projects.core) - compileOnly(projects.subprojects.analysisKotlinApi) - - // TODO - - // TODO [beresnev] get rid of it - compileOnly(libs.kotlinx.coroutines.core) -} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin.kt deleted file mode 100644 index 4a39e0d8..00000000 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.jetbrains.dokka.analysis.kotlin.symbols.compiler - -import org.jetbrains.dokka.plugability.DokkaPlugin -import org.jetbrains.dokka.plugability.DokkaPluginApiPreview -import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement - -class CompilerSymbolsAnalysisPlugin : DokkaPlugin() { - - @OptIn(DokkaPluginApiPreview::class) - override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement -} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/subprojects/analysis-kotlin-symbols/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin deleted file mode 100644 index 47163f6e..00000000 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin +++ /dev/null @@ -1 +0,0 @@ -org.jetbrains.dokka.analysis.kotlin.symbols.compiler.CompilerSymbolsAnalysisPlugin diff --git a/subprojects/analysis-kotlin-symbols/ide/api/ide.api b/subprojects/analysis-kotlin-symbols/ide/api/ide.api deleted file mode 100644 index 9be46c0b..00000000 --- a/subprojects/analysis-kotlin-symbols/ide/api/ide.api +++ /dev/null @@ -1,4 +0,0 @@ -public final class org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin { - public fun ()V -} - diff --git a/subprojects/analysis-kotlin-symbols/ide/build.gradle.kts b/subprojects/analysis-kotlin-symbols/ide/build.gradle.kts deleted file mode 100644 index 876d87ca..00000000 --- a/subprojects/analysis-kotlin-symbols/ide/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id("org.jetbrains.conventions.kotlin-jvm") -} - -dependencies { - compileOnly(projects.core) - compileOnly(projects.subprojects.analysisKotlinApi) - - // TODO - - // TODO [beresnev] get rid of it - compileOnly(libs.kotlinx.coroutines.core) -} diff --git a/subprojects/analysis-kotlin-symbols/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt b/subprojects/analysis-kotlin-symbols/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt deleted file mode 100644 index 33f555cb..00000000 --- a/subprojects/analysis-kotlin-symbols/ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/ide/IdeSymbolsAnalysisPlugin.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.jetbrains.dokka.analysis.kotlin.symbols.ide - -import org.jetbrains.dokka.plugability.DokkaPlugin -import org.jetbrains.dokka.plugability.DokkaPluginApiPreview -import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement - -class IdeSymbolsAnalysisPlugin : DokkaPlugin() { - - @OptIn(DokkaPluginApiPreview::class) - override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement -} diff --git a/subprojects/analysis-kotlin-symbols/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/subprojects/analysis-kotlin-symbols/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin deleted file mode 100644 index 59245578..00000000 --- a/subprojects/analysis-kotlin-symbols/ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin +++ /dev/null @@ -1 +0,0 @@ -org.jetbrains.dokka.analysis.kotlin.symbols.ide.IdeSymbolsAnalysisPlugin diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt new file mode 100644 index 00000000..c1b8651a --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt @@ -0,0 +1,170 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc + +import com.intellij.psi.PsiNamedElement +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.dokka.analysis.java.parsers.JavadocParser +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.utilities.DokkaLogger +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolOrigin +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol +import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag +import org.jetbrains.kotlin.kdoc.psi.api.KDoc +import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType +import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter +import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly + +internal fun KtAnalysisSession.getJavaDocDocumentationFrom( + symbol: KtSymbol, + javadocParser: JavadocParser +): DocumentationNode? { + if (symbol.origin == KtSymbolOrigin.JAVA) { + return (symbol.psi as? PsiNamedElement)?.let { + javadocParser.parseDocumentation(it) + } + } else if (symbol.origin == KtSymbolOrigin.SOURCE && symbol is KtCallableSymbol) { + // Note: javadocParser searches in overridden JAVA declarations for JAVA method, not Kotlin + symbol.getAllOverriddenSymbols().forEach { overrider -> + if (overrider.origin == KtSymbolOrigin.JAVA) + return@getJavaDocDocumentationFrom (overrider.psi as? PsiNamedElement)?.let { + javadocParser.parseDocumentation(it) + } + } + } + return null +} + +internal fun KtAnalysisSession.getKDocDocumentationFrom(symbol: KtSymbol, logger: DokkaLogger) = findKDoc(symbol)?.let { kDocContent -> + + val ktElement = symbol.psi + val kdocLocation = ktElement?.containingFile?.name?.let { + val name = when(symbol) { + is KtCallableSymbol -> symbol.callableIdIfNonLocal?.toString() + is KtClassOrObjectSymbol -> symbol.classIdIfNonLocal?.toString() + is KtNamedSymbol -> symbol.name.asString() + else -> null + }?.replace('/', '.') // replace to be compatible with K1 + + if (name != null) "$it/$name" + else it + } + + + parseFromKDocTag( + kDocTag = kDocContent.contentTag, + externalDri = { link -> resolveKDocLink(link).logIfNotResolved(link.getLinkText(), logger) }, + kdocLocation = kdocLocation + ) +} + + + + +// ----------- copy-paste from IDE ---------------------------------------------------------------------------- + +internal data class KDocContent( + val contentTag: KDocTag, + val sections: List +) + +internal fun KtAnalysisSession.findKDoc(symbol: KtSymbol): KDocContent? { + // for generated function (e.g. `copy`) psi returns class, see test `data class kdocs over generated methods` + if (symbol.origin != KtSymbolOrigin.SOURCE) return null + val ktElement = symbol.psi as? KtElement + ktElement?.findKDoc()?.let { + return it + } + + if (symbol is KtCallableSymbol) { + symbol.getAllOverriddenSymbols().forEach { overrider -> + findKDoc(overrider)?.let { + return it + } + } + } + return null +} + + +internal fun KtElement.findKDoc(): KDocContent? = this.lookupOwnedKDoc() + ?: this.lookupKDocInContainer() + + + +private fun KtElement.lookupOwnedKDoc(): KDocContent? { + // KDoc for primary constructor is located inside of its class KDoc + val psiDeclaration = when (this) { + is KtPrimaryConstructor -> getContainingClassOrObject() + else -> this + } + + if (psiDeclaration is KtDeclaration) { + val kdoc = psiDeclaration.docComment + if (kdoc != null) { + if (this is KtConstructor<*>) { + // ConstructorDescriptor resolves to the same JetDeclaration + val constructorSection = kdoc.findSectionByTag(KDocKnownTag.CONSTRUCTOR) + if (constructorSection != null) { + // if annotated with @constructor tag and the caret is on constructor definition, + // then show @constructor description as the main content, and additional sections + // that contain @param tags (if any), as the most relatable ones + // practical example: val foo = Foo("argument") -- show @constructor and @param content + val paramSections = kdoc.findSectionsContainingTag(KDocKnownTag.PARAM) + return KDocContent(constructorSection, paramSections) + } + } + return KDocContent(kdoc.getDefaultSection(), kdoc.getAllSections()) + } + } + + return null +} + +/** + * Looks for sections that have a deeply nested [tag], + * as opposed to [KDoc.findSectionByTag], which only looks among the top level + */ +private fun KDoc.findSectionsContainingTag(tag: KDocKnownTag): List { + return getChildrenOfType() + .filter { it.findTagByName(tag.name.toLowerCaseAsciiOnly()) != null } +} + +private fun KtElement.lookupKDocInContainer(): KDocContent? { + val subjectName = name + val containingDeclaration = + PsiTreeUtil.findFirstParent(this, true) { + it is KtDeclarationWithBody && it !is KtPrimaryConstructor + || it is KtClassOrObject + } + + val containerKDoc = containingDeclaration?.getChildOfType() + if (containerKDoc == null || subjectName == null) return null + val propertySection = containerKDoc.findSectionByTag(KDocKnownTag.PROPERTY, subjectName) + val paramTag = + containerKDoc.findDescendantOfType { it.knownTag == KDocKnownTag.PARAM && it.getSubjectName() == subjectName } + + val primaryContent = when { + // class Foo(val s: String) + this is KtParameter && this.isPropertyParameter() -> propertySection ?: paramTag + // fun some(f: String) || class Some<T: Base> || Foo(s = "argument") + this is KtParameter || this is KtTypeParameter -> paramTag + // if this property is declared separately (outside primary constructor), but it's for some reason + // annotated as @property in class's description, instead of having its own KDoc + this is KtProperty && containingDeclaration is KtClassOrObject -> propertySection + else -> null + } + return primaryContent?.let { + // makes little sense to include any other sections, since we found + // documentation for a very specific element, like a property/param + KDocContent(it, sections = emptyList()) + } +} + diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt new file mode 100644 index 00000000..ec23b0c8 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt @@ -0,0 +1,97 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc + +import com.intellij.psi.PsiElement +import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser +import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser.Companion.fqDeclarationName +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.doc.* +import org.jetbrains.dokka.model.doc.Suppress +import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag +import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink +import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag +import org.jetbrains.kotlin.psi.psiUtil.allChildren + +internal fun parseFromKDocTag( + kDocTag: KDocTag?, + externalDri: (KDocLink) -> DRI?, + kdocLocation: String?, + parseWithChildren: Boolean = true +): DocumentationNode { + return if (kDocTag == null) { + DocumentationNode(emptyList()) + } else { + fun parseStringToDocNode(text: String, externalDRIProvider: (String) -> DRI?) = + MarkdownParser(externalDRIProvider, kdocLocation).parseStringToDocNode(text) + + fun pointedLink(tag: KDocTag): DRI? = tag.getSubjectLink()?.let(externalDri) + + val allTags = + listOf(kDocTag) + if (kDocTag.canHaveParent() && parseWithChildren) getAllKDocTags(findParent(kDocTag)) else emptyList() + DocumentationNode( + allTags.map { tag -> + val links = tag.allChildren.filterIsInstance().associate { it.getLinkText() to externalDri(it) } + val externalDRIProvider = { linkText: String -> links[linkText] } + + when (tag.knownTag) { + null -> if (tag.name == null) Description(parseStringToDocNode(tag.getContent(), externalDRIProvider)) else CustomTagWrapper( + parseStringToDocNode(tag.getContent(), externalDRIProvider), + tag.name!! + ) + KDocKnownTag.AUTHOR -> Author(parseStringToDocNode(tag.getContent(), externalDRIProvider)) + KDocKnownTag.THROWS -> { + val dri = pointedLink(tag) + Throws( + parseStringToDocNode(tag.getContent(), externalDRIProvider), + dri?.fqDeclarationName() ?: tag.getSubjectName().orEmpty(), + dri, + ) + } + KDocKnownTag.EXCEPTION -> { + val dri = pointedLink(tag) + Throws( + parseStringToDocNode(tag.getContent(), externalDRIProvider), + dri?.fqDeclarationName() ?: tag.getSubjectName().orEmpty(), + dri + ) + } + KDocKnownTag.PARAM -> Param( + parseStringToDocNode(tag.getContent(), externalDRIProvider), + tag.getSubjectName().orEmpty() + ) + KDocKnownTag.RECEIVER -> Receiver(parseStringToDocNode(tag.getContent(), externalDRIProvider)) + KDocKnownTag.RETURN -> Return(parseStringToDocNode(tag.getContent(), externalDRIProvider)) + KDocKnownTag.SEE -> { + val dri = pointedLink(tag) + See( + parseStringToDocNode(tag.getContent(), externalDRIProvider), + dri?.fqDeclarationName() ?: tag.getSubjectName().orEmpty(), + dri, + ) + } + KDocKnownTag.SINCE -> Since(parseStringToDocNode(tag.getContent(), externalDRIProvider)) + KDocKnownTag.CONSTRUCTOR -> Constructor(parseStringToDocNode(tag.getContent(), externalDRIProvider)) + KDocKnownTag.PROPERTY -> Property( + parseStringToDocNode(tag.getContent(), externalDRIProvider), + tag.getSubjectName().orEmpty() + ) + KDocKnownTag.SAMPLE -> Sample( + parseStringToDocNode(tag.getContent(), externalDRIProvider), + tag.getSubjectName().orEmpty() + ) + KDocKnownTag.SUPPRESS -> Suppress(parseStringToDocNode(tag.getContent(), externalDRIProvider)) + } + } + ) + } +} + +private fun findParent(kDoc: PsiElement): PsiElement = + if (kDoc.canHaveParent()) findParent(kDoc.parent) else kDoc + +private fun PsiElement.canHaveParent(): Boolean = this is KDocSection && knownTag != KDocKnownTag.PROPERTY + +private fun getAllKDocTags(kDocImpl: PsiElement): List = + kDocImpl.children.filterIsInstance().filterNot { it is KDocSection } + kDocImpl.children.flatMap { + getAllKDocTags(it) + } diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt new file mode 100644 index 00000000..9d26d917 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt @@ -0,0 +1,42 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc + +import com.intellij.psi.PsiElement +import org.jetbrains.dokka.analysis.kotlin.symbols.translators.getDRIFromSymbol +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.utilities.DokkaLogger +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.kdoc.psi.api.KDoc +import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink +import org.jetbrains.kotlin.kdoc.psi.impl.KDocName +import org.jetbrains.kotlin.psi.KtPsiFactory + +internal fun DRI?.logIfNotResolved(link: String, logger: DokkaLogger): DRI? { + if(this == null) + logger.warn("Couldn't resolve link for $link") + return this +} + +/** + * It resolves KDoc link via creating PSI. + * + */ +internal fun KtAnalysisSession.resolveKDocTextLink(link: String, context: PsiElement? = null): DRI? { + val psiFactory = context?.let { KtPsiFactory.contextual(it) } ?: KtPsiFactory(this.useSiteModule.project) + val kDoc = psiFactory.createComment( + """ + /** + * [$link] + */ + """.trimIndent() + ) as? KDoc + val kDocLink = kDoc?.getDefaultSection()?.children?.filterIsInstance()?.singleOrNull() + return kDocLink?.let { resolveKDocLink(it) } +} + +internal fun KtAnalysisSession.resolveKDocLink(link: KDocLink): DRI? { + val lastNameSegment = link.children.filterIsInstance().lastOrNull() + val linkedSymbol = lastNameSegment?.mainReference?.resolveToSymbols()?.firstOrNull() + return if (linkedSymbol == null) null // logger.warn("Couldn't resolve link for $link") + else getDRIFromSymbol(linkedSymbol) +} \ No newline at end of file diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt new file mode 100644 index 00000000..9b108f80 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt @@ -0,0 +1,61 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc + +import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin +import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.* +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtPossibleMemberSymbol +import org.jetbrains.kotlin.builtins.StandardNames + +private const val ENUM_ENTRIES_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumEntries.kt.template" +private const val ENUM_VALUEOF_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumValueOf.kt.template" +private const val ENUM_VALUES_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumValues.kt.template" + +internal fun KtAnalysisSession.hasGeneratedKDocDocumentation(symbol: KtSymbol): Boolean = + getDocumentationTemplatePath(symbol) != null + +private fun KtAnalysisSession.getDocumentationTemplatePath(symbol: KtSymbol): String? = + when (symbol) { + is KtPropertySymbol -> if (isEnumEntriesProperty(symbol)) ENUM_ENTRIES_TEMPLATE_PATH else null + is KtFunctionSymbol -> { + when { + isEnumValuesMethod(symbol) -> ENUM_VALUES_TEMPLATE_PATH + isEnumValueOfMethod(symbol) -> ENUM_VALUEOF_TEMPLATE_PATH + else -> null + } + } + + else -> null + } + +private fun KtAnalysisSession.isEnumSpecialMember(symbol: KtPossibleMemberSymbol): Boolean = + symbol.origin == KtSymbolOrigin.SOURCE_MEMBER_GENERATED + && (symbol.getContainingSymbol() as? KtClassOrObjectSymbol)?.classKind == KtClassKind.ENUM_CLASS + +private fun KtAnalysisSession.isEnumEntriesProperty(symbol: KtPropertySymbol): Boolean = + symbol.name == StandardNames.ENUM_ENTRIES && isEnumSpecialMember(symbol) + +private fun KtAnalysisSession.isEnumValuesMethod(symbol: KtFunctionSymbol): Boolean = + symbol.name == StandardNames.ENUM_VALUES && isEnumSpecialMember(symbol) + +private fun KtAnalysisSession.isEnumValueOfMethod(symbol: KtFunctionSymbol): Boolean = + symbol.name == StandardNames.ENUM_VALUE_OF && isEnumSpecialMember(symbol) + +internal fun KtAnalysisSession.getGeneratedKDocDocumentationFrom(symbol: KtSymbol): DocumentationNode? { + val templatePath = getDocumentationTemplatePath(symbol) ?: return null + return loadTemplate(templatePath) +} + +private fun KtAnalysisSession.loadTemplate(filePath: String): DocumentationNode? { + val kdoc = loadContent(filePath) ?: return null + val externalDriProvider = { link: String -> + resolveKDocTextLink(link) + } + + val parser = MarkdownParser(externalDriProvider, filePath) + return parser.parse(kdoc) +} + +private fun loadContent(filePath: String): String? = + SymbolsAnalysisPlugin::class.java.getResource(filePath)?.readText() diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt new file mode 100644 index 00000000..a3651603 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt @@ -0,0 +1,15 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java + +import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent +import org.jetbrains.dokka.analysis.java.JavadocTag +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag + +internal data class DescriptorDocumentationContent( + val resolveDocContext: ResolveDocContext, + val element: KDocTag, + override val tag: JavadocTag, +) : DocumentationContent { + override fun resolveSiblings(): List { + return listOf(this) + } +} diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt new file mode 100644 index 00000000..399d005a --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt @@ -0,0 +1,16 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java + +import com.intellij.psi.PsiNamedElement +import org.jetbrains.dokka.analysis.java.doccomment.DocComment +import org.jetbrains.dokka.analysis.java.doccomment.DocCommentCreator +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.findKDoc +import org.jetbrains.kotlin.psi.KtElement + +internal class DescriptorKotlinDocCommentCreator : DocCommentCreator { + override fun create(element: PsiNamedElement): DocComment? { + val ktElement = element.navigationElement as? KtElement ?: return null + val kdoc = ktElement.findKDoc() ?: return null + + return KotlinDocComment(kdoc.contentTag, ResolveDocContext(ktElement)) + } +} diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt new file mode 100644 index 00000000..385b5328 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt @@ -0,0 +1,81 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java + +import org.jetbrains.dokka.analysis.java.* +import org.jetbrains.dokka.analysis.java.doccomment.DocComment +import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag +import org.jetbrains.kotlin.psi.KtElement + +internal class ResolveDocContext(val ktElement: KtElement) + +internal class KotlinDocComment( + val comment: KDocTag, + val resolveDocContext: ResolveDocContext +) : DocComment { + + private val tagsWithContent: List = comment.children.mapNotNull { (it as? KDocTag) } + + override fun hasTag(tag: JavadocTag): Boolean { + return when (tag) { + is DescriptionJavadocTag -> comment.getContent().isNotEmpty() + is ThrowingExceptionJavadocTag -> tagsWithContent.any { it.hasException(tag) } + else -> tagsWithContent.any { it.text.startsWith("@${tag.name}") } + } + } + + private fun KDocTag.hasException(tag: ThrowingExceptionJavadocTag) = + text.startsWith("@${tag.name}") && getSubjectName() == tag.exceptionQualifiedName + + override fun resolveTag(tag: JavadocTag): List { + return when (tag) { + is DescriptionJavadocTag -> listOf(DescriptorDocumentationContent(resolveDocContext, comment, tag)) + is ParamJavadocTag -> { + val resolvedContent = resolveGeneric(tag) + listOf(resolvedContent[tag.paramIndex]) + } + + is ThrowsJavadocTag -> resolveThrowingException(tag) + is ExceptionJavadocTag -> resolveThrowingException(tag) + else -> resolveGeneric(tag) + } + } + + private fun resolveThrowingException(tag: ThrowingExceptionJavadocTag): List { + val exceptionName = tag.exceptionQualifiedName ?: return resolveGeneric(tag) + + return comment.children + .filterIsInstance() + .filter { it.name == tag.name && it.getSubjectName() == exceptionName } + .map { DescriptorDocumentationContent(resolveDocContext, it, tag) } + } + + private fun resolveGeneric(tag: JavadocTag): List { + return comment.children.mapNotNull { element -> + if (element is KDocTag && element.name == tag.name) { + DescriptorDocumentationContent(resolveDocContext, element, tag) + } else { + null + } + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as KotlinDocComment + + if (comment != other.comment) return false + //if (resolveDocContext.name != other.resolveDocContext.name) return false + if (tagsWithContent != other.tagsWithContent) return false + + return true + } + + override fun hashCode(): Int { + var result = comment.hashCode() + // result = 31 * result + resolveDocContext.name.hashCode() + result = 31 * result + tagsWithContent.hashCode() + return result + } +} diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt new file mode 100644 index 00000000..c8ecc786 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt @@ -0,0 +1,46 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java + +import com.intellij.psi.PsiNamedElement +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.analysis.java.doccomment.DocComment +import org.jetbrains.dokka.analysis.java.parsers.DocCommentParser +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.logIfNotResolved +import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.parseFromKDocTag +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocLink +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.kotlin.analysis.api.analyze + +internal class KotlinDocCommentParser( + private val context: DokkaContext +) : DocCommentParser { + + override fun canParse(docComment: DocComment): Boolean { + return docComment is KotlinDocComment + } + + override fun parse(docComment: DocComment, context: PsiNamedElement): DocumentationNode { + val kotlinDocComment = docComment as KotlinDocComment + return parseDocumentation(kotlinDocComment) + } + + fun parseDocumentation(element: KotlinDocComment, parseWithChildren: Boolean = true): DocumentationNode { + val sourceSet = context.configuration.sourceSets.let { sourceSets -> + sourceSets.firstOrNull { it.sourceSetID.sourceSetName == "jvmMain" } + ?: sourceSets.first { it.analysisPlatform == Platform.jvm } + } + val kotlinAnalysis = context.plugin().querySingle { kotlinAnalysis } + return analyze(kotlinAnalysis[sourceSet].mainModule) { + parseFromKDocTag( + kDocTag = element.comment, + externalDri = { link -> resolveKDocLink(link).logIfNotResolved(link.getLinkText(), context.logger) }, + kdocLocation = null, + parseWithChildren = parseWithChildren + ) + } + } +} + diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt new file mode 100644 index 00000000..6d2bfff4 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt @@ -0,0 +1,31 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java + +import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent +import org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin +import org.jetbrains.dokka.analysis.java.parsers.doctag.DocTagParserContext +import org.jetbrains.dokka.analysis.java.parsers.doctag.InheritDocTagContentProvider +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.query + +internal class KotlinInheritDocTagContentProvider( + context: DokkaContext +) : InheritDocTagContentProvider { + + val parser: KotlinDocCommentParser by lazy { + context.plugin().query { docCommentParsers } + .single { it is KotlinDocCommentParser } as KotlinDocCommentParser + } + + override fun canConvert(content: DocumentationContent): Boolean = content is DescriptorDocumentationContent + + override fun convertToHtml(content: DocumentationContent, docTagParserContext: DocTagParserContext): String { + val descriptorContent = content as DescriptorDocumentationContent + val inheritedDocNode = parser.parseDocumentation( + KotlinDocComment(descriptorContent.element, descriptorContent.resolveDocContext), + parseWithChildren = false + ) + val id = docTagParserContext.store(inheritedDocNode) + return """""" + } +} diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt new file mode 100644 index 00000000..6e3f215a --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt @@ -0,0 +1,7 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs + +import org.jetbrains.dokka.DokkaException + +internal class IllegalModuleAndPackageDocumentation( + source: ModuleAndPackageDocumentationSource, message: String +) : DokkaException("[$source] $message") diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt new file mode 100644 index 00000000..46f714cb --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt @@ -0,0 +1,11 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs + +import org.jetbrains.dokka.model.doc.DocumentationNode + +internal data class ModuleAndPackageDocumentation( + val name: String, + val classifier: Classifier, + val documentation: DocumentationNode +) { + enum class Classifier { Module, Package } +} diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt new file mode 100644 index 00000000..1eafc688 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt @@ -0,0 +1,9 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs + + +internal data class ModuleAndPackageDocumentationFragment( + val name: String, + val classifier: ModuleAndPackageDocumentation.Classifier, + val documentation: String, + val source: ModuleAndPackageDocumentationSource +) diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt new file mode 100644 index 00000000..cf627e77 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt @@ -0,0 +1,47 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.logIfNotResolved +import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.KotlinAnalysis +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs.ModuleAndPackageDocumentation.Classifier.Module +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs.ModuleAndPackageDocumentation.Classifier.Package +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocTextLink +import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.utilities.DokkaLogger +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.name.FqName + +internal fun interface ModuleAndPackageDocumentationParsingContext { + fun markdownParserFor(fragment: ModuleAndPackageDocumentationFragment, location: String): MarkdownParser +} + +internal fun ModuleAndPackageDocumentationParsingContext.parse( + fragment: ModuleAndPackageDocumentationFragment +): DocumentationNode { + return markdownParserFor(fragment, fragment.source.sourceDescription).parse(fragment.documentation) +} + +internal fun ModuleAndPackageDocumentationParsingContext( + logger: DokkaLogger, + kotlinAnalysis: KotlinAnalysis? = null, + sourceSet: DokkaConfiguration.DokkaSourceSet? = null +) = ModuleAndPackageDocumentationParsingContext { fragment, sourceLocation -> + + if(kotlinAnalysis == null || sourceSet == null) { + MarkdownParser(externalDri = { null }, sourceLocation) + } else { + val analysisContext = kotlinAnalysis[sourceSet] + analyze(analysisContext.mainModule) { + val contextSymbol = when (fragment.classifier) { + Module -> ROOT_PACKAGE_SYMBOL + Package -> getPackageSymbolIfPackageExists(FqName(fragment.name)) + } + + MarkdownParser( + externalDri = { resolveKDocTextLink(it, contextSymbol?.psi).logIfNotResolved(it, logger) }, + sourceLocation + ) + } + } +} diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt new file mode 100644 index 00000000..82259b89 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt @@ -0,0 +1,110 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs.ModuleAndPackageDocumentation.Classifier +import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.model.DPackage +import org.jetbrains.dokka.model.SourceSetDependent +import org.jetbrains.dokka.model.doc.* +import org.jetbrains.dokka.model.doc.Deprecated +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.dokka.utilities.associateWithNotNull +import org.jetbrains.dokka.analysis.kotlin.internal.ModuleAndPackageDocumentationReader + +internal fun ModuleAndPackageDocumentationReader(context: DokkaContext): ModuleAndPackageDocumentationReader = + ContextModuleAndPackageDocumentationReader(context) + +private class ContextModuleAndPackageDocumentationReader( + private val context: DokkaContext +) : ModuleAndPackageDocumentationReader { + + private val kotlinAnalysis = context.plugin().querySingle { kotlinAnalysis } + + private val documentationFragments: SourceSetDependent> = + context.configuration.sourceSets.associateWith { sourceSet -> + sourceSet.includes.flatMap { include -> parseModuleAndPackageDocumentationFragments(include) } + } + + private fun findDocumentationNodes( + sourceSets: Set, + predicate: (ModuleAndPackageDocumentationFragment) -> Boolean + ): SourceSetDependent { + return sourceSets.associateWithNotNull { sourceSet -> + val fragments = documentationFragments[sourceSet].orEmpty().filter(predicate) + kotlinAnalysis[sourceSet] // test: to throw exception for unknown sourceSet + val documentations = fragments.map { fragment -> + parseModuleAndPackageDocumentation( + context = ModuleAndPackageDocumentationParsingContext(context.logger, kotlinAnalysis, sourceSet), + fragment = fragment + ) + } + when (documentations.size) { + 0 -> null + 1 -> documentations.single().documentation + else -> DocumentationNode(documentations.flatMap { it.documentation.children } + .mergeDocumentationNodes()) + } + } + } + + private val ModuleAndPackageDocumentationFragment.canonicalPackageName: String + get() { + check(classifier == Classifier.Package) + if (name == "[root]") return "" + return name + } + + override fun read(module: DModule): SourceSetDependent { + return findDocumentationNodes(module.sourceSets) { fragment -> + fragment.classifier == Classifier.Module && (fragment.name == module.name) + } + } + + override fun read(pkg: DPackage): SourceSetDependent { + return findDocumentationNodes(pkg.sourceSets) { fragment -> + fragment.classifier == Classifier.Package && fragment.canonicalPackageName == pkg.dri.packageName + } + } + + override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode? { + val parsingContext = ModuleAndPackageDocumentationParsingContext(context.logger) + + val documentationFragment = module.includes + .flatMap { include -> parseModuleAndPackageDocumentationFragments(include) } + .firstOrNull { fragment -> fragment.classifier == Classifier.Module && fragment.name == module.name } + ?: return null + + val moduleDocumentation = parseModuleAndPackageDocumentation(parsingContext, documentationFragment) + return moduleDocumentation.documentation + } + + private fun List.mergeDocumentationNodes(): List = + groupBy { it::class }.values.map { + it.reduce { acc, tagWrapper -> + val newRoot = CustomDocTag( + acc.children + tagWrapper.children, + name = (tagWrapper as? NamedTagWrapper)?.name.orEmpty() + ) + when (acc) { + is See -> acc.copy(newRoot) + is Param -> acc.copy(newRoot) + is Throws -> acc.copy(newRoot) + is Sample -> acc.copy(newRoot) + is Property -> acc.copy(newRoot) + is CustomTagWrapper -> acc.copy(newRoot) + is Description -> acc.copy(newRoot) + is Author -> acc.copy(newRoot) + is Version -> acc.copy(newRoot) + is Since -> acc.copy(newRoot) + is Return -> acc.copy(newRoot) + is Receiver -> acc.copy(newRoot) + is Constructor -> acc.copy(newRoot) + is Deprecated -> acc.copy(newRoot) + is org.jetbrains.dokka.model.doc.Suppress -> acc.copy(newRoot) + } + } + } +} diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt new file mode 100644 index 00000000..d7877559 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt @@ -0,0 +1,14 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs + +import java.io.File + +internal abstract class ModuleAndPackageDocumentationSource { + abstract val sourceDescription: String + abstract val documentation: String + override fun toString(): String = sourceDescription +} + +internal data class ModuleAndPackageDocumentationFile(private val file: File) : ModuleAndPackageDocumentationSource() { + override val sourceDescription: String = file.path + override val documentation: String by lazy(LazyThreadSafetyMode.PUBLICATION) { file.readText() } +} diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt new file mode 100644 index 00000000..b92adf4a --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt @@ -0,0 +1,12 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs + +internal fun parseModuleAndPackageDocumentation( + context: ModuleAndPackageDocumentationParsingContext, + fragment: ModuleAndPackageDocumentationFragment +): ModuleAndPackageDocumentation { + return ModuleAndPackageDocumentation( + name = fragment.name, + classifier = fragment.classifier, + documentation = context.parse(fragment) + ) +} diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt new file mode 100644 index 00000000..ae728a28 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageD