From 1f4db2b52eb1204ac688b5be7068012d1a8955ce Mon Sep 17 00:00:00 2001 From: vmishenev Date: Mon, 17 Jul 2023 16:36:56 +0300 Subject: [PATCH 01/30] Support K2 Analysis Api --- .../conventions/kotlin-jvm.gradle.kts | 1 + plugins/base/base-test-utils/build.gradle.kts | 2 +- settings.gradle.kts | 1 - .../DefaultPsiToDocumentableTranslator.kt | 3 +- .../analysis-kotlin-symbols/build.gradle.kts | 20 +- .../compiler/build.gradle.kts | 62 +- .../kotlin/symbols/AnalysisContext.kt | 88 ++ .../kotlin/symbols/KtPsiDocumentableSource.kt | 20 + ...ompilerDocumentableSourceLanguageParser.kt | 23 + .../compiler/CompilerSymbolsAnalysisPlugin.kt | 11 - .../kotlin/symbols/compiler/DRIFactory.kt | 104 ++ .../kotlin/symbols/compiler/KDocProvider.kt | 158 +++ .../kotlin/symbols/compiler/KotlinAnalysis.kt | 214 ++++ .../compiler/KotlinAnalysisProjectProvider.kt | 15 + .../ModuleAndPackageDocumentationReader.kt | 28 + .../SymbolFullClassHierarchyBuilder.kt | 12 + .../SymbolSyntheticDocumentableDetector.kt | 12 + .../symbols/compiler/SymbolsAnalysisPlugin.kt | 110 ++ .../symbols/compiler/TypeReferenceFactory.kt | 68 ++ .../kotlin/symbols/kdoc/KdocMarkdownParser.kt | 101 ++ .../translators/AnnotationTranslator.kt | 101 ++ .../DefaultSymbolToDocumentableTranslator.kt | 1066 +++++++++++++++++ ...rg.jetbrains.dokka.plugability.DokkaPlugin | 2 +- 23 files changed, 2204 insertions(+), 18 deletions(-) create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/AnalysisContext.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/KtPsiDocumentableSource.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerDocumentableSourceLanguageParser.kt delete mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerSymbolsAnalysisPlugin.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/DRIFactory.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KDocProvider.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysis.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysisProjectProvider.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/ModuleAndPackageDocumentationReader.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolFullClassHierarchyBuilder.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolSyntheticDocumentableDetector.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolsAnalysisPlugin.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/TypeReferenceFactory.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt diff --git a/build-logic/src/main/kotlin/org/jetbrains/conventions/kotlin-jvm.gradle.kts b/build-logic/src/main/kotlin/org/jetbrains/conventions/kotlin-jvm.gradle.kts index a96a004b24..7bd45b66bd 100644 --- a/build-logic/src/main/kotlin/org/jetbrains/conventions/kotlin-jvm.gradle.kts +++ b/build-logic/src/main/kotlin/org/jetbrains/conventions/kotlin-jvm.gradle.kts @@ -25,6 +25,7 @@ tasks.withType().configureEach { "-Xskip-metadata-version-check", // need 1.4 support, otherwise there might be problems with Gradle 6.x (it's bundling Kotlin 1.4) "-Xsuppress-version-warnings", + // "-XXLanguage:+SamConversionForKotlinFunctions", ) ) allWarningsAsErrors.set(true) diff --git a/plugins/base/base-test-utils/build.gradle.kts b/plugins/base/base-test-utils/build.gradle.kts index ef4f9f7b8c..aff6b78484 100644 --- a/plugins/base/base-test-utils/build.gradle.kts +++ b/plugins/base/base-test-utils/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { api(projects.subprojects.analysisKotlinApi) // TODO [beresnev] analysis switcher - runtimeOnly(project(path = ":subprojects:analysis-kotlin-descriptors", configuration = "shadow")) + runtimeOnly(project(path = ":subprojects:analysis-kotlin-symbols", configuration = "shadow")) implementation(kotlin("reflect")) diff --git a/settings.gradle.kts b/settings.gradle.kts index 2320ca6951..57a33caa9c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -68,7 +68,6 @@ include( ":subprojects:analysis-kotlin-descriptors:ide", ":subprojects:analysis-kotlin-symbols", ":subprojects:analysis-kotlin-symbols:compiler", - ":subprojects:analysis-kotlin-symbols:ide", ":subprojects:analysis-markdown-jb", ":runners:gradle-plugin", diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt index 72cf45d9f6..61c1af98ee 100644 --- a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt @@ -27,8 +27,7 @@ internal class DefaultPsiToDocumentableTranslator : AsyncSourceToDocumentableTra val projectProvider = context.plugin().querySingle { projectProvider } val project = projectProvider.getProject(sourceSet, context) - val sourceRootsExtractor = context.plugin().querySingle { sourceRootsExtractor } - val sourceRoots = sourceRootsExtractor.extract(sourceSet, context) + val sourceRoots = sourceSet.sourceRoots.filter { directory -> directory.isDirectory || directory.extension == "java" } val localFileSystem = VirtualFileManager.getInstance().getFileSystem("file") diff --git a/subprojects/analysis-kotlin-symbols/build.gradle.kts b/subprojects/analysis-kotlin-symbols/build.gradle.kts index c000df5871..238d8a6084 100644 --- a/subprojects/analysis-kotlin-symbols/build.gradle.kts +++ b/subprojects/analysis-kotlin-symbols/build.gradle.kts @@ -8,9 +8,27 @@ plugins { } dependencies { + implementation("com.jetbrains.intellij.platform:core:213.7172.25") + + listOf( + "com.jetbrains.intellij.platform:util-rt", + "com.jetbrains.intellij.platform:util-class-loader", + "com.jetbrains.intellij.platform:util-text-matching", + "com.jetbrains.intellij.platform:util", + "com.jetbrains.intellij.platform:util-base", + "com.jetbrains.intellij.platform:util-xml-dom", + "com.jetbrains.intellij.platform:core", + "com.jetbrains.intellij.platform:core-impl", + "com.jetbrains.intellij.platform:extensions", + "com.jetbrains.intellij.java:java-psi", + "com.jetbrains.intellij.java:java-psi-impl" + ).forEach { + implementation("$it:213.7172.25") { isTransitive = false } + } + implementation(projects.subprojects.analysisKotlinApi) implementation(projects.subprojects.analysisKotlinSymbols.compiler) - implementation(projects.subprojects.analysisKotlinSymbols.ide) + //implementation(projects.subprojects.analysisKotlinSymbols.ide) } tasks { diff --git a/subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts b/subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts index 876d87ca7b..debc9185d2 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts +++ b/subprojects/analysis-kotlin-symbols/compiler/build.gradle.kts @@ -3,10 +3,70 @@ plugins { } dependencies { + implementation(project(mapOf("path" to ":core"))) compileOnly(projects.core) compileOnly(projects.subprojects.analysisKotlinApi) - // TODO + listOf( + "com.jetbrains.intellij.platform:util-rt", + "com.jetbrains.intellij.platform:util-class-loader", + "com.jetbrains.intellij.platform:util-text-matching", + "com.jetbrains.intellij.platform:util", + "com.jetbrains.intellij.platform:util-base", + "com.jetbrains.intellij.platform:util-xml-dom", + "com.jetbrains.intellij.platform:core", + "com.jetbrains.intellij.platform:core-impl", + "com.jetbrains.intellij.platform:extensions", + "com.jetbrains.intellij.java:java-psi", + "com.jetbrains.intellij.java:java-psi-impl" + ).forEach { + implementation("$it:213.7172.25") { isTransitive = false } + } + + api("com.jetbrains.intellij.platform:core:213.7172.25") + + implementation(projects.subprojects.analysisMarkdownJb) + implementation(projects.subprojects.analysisJavaPsi) + + + implementation("org.jetbrains.kotlin:high-level-api-for-ide:1.9.0-release-358") { + isTransitive = false // see KTIJ-19820 + } + implementation("org.jetbrains.kotlin:high-level-api-impl-base-for-ide:1.9.0-release-358") { + isTransitive = false // see KTIJ-19820 + } + implementation("org.jetbrains.kotlin:high-level-api-fir-for-ide:1.9.0-release-358") { + isTransitive = false // see KTIJ-19820 + } + implementation("org.jetbrains.kotlin:high-level-api-fe10-for-ide:1.9.0-release-358") { + isTransitive = false // see KTIJ-19820 + } + + implementation("org.jetbrains.kotlin:low-level-api-fir-for-ide:1.9.0-release-358") { + isTransitive = false // see KTIJ-19820 + } + implementation("org.jetbrains.kotlin:analysis-project-structure-for-ide:1.9.0-release-358") { + isTransitive = false // see KTIJ-19820 + } + implementation("org.jetbrains.kotlin:analysis-api-standalone-for-ide:1.9.0-release-358") { + isTransitive = false // see KTIJ-19820 + } + implementation("org.jetbrains.kotlin:analysis-api-providers-for-ide:1.9.0-release-358") { + isTransitive = false // see KTIJ-19820 + } + implementation("org.jetbrains.kotlin:symbol-light-classes-for-ide:1.9.0-release-358") { + isTransitive = false + } + implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.4") + // implementation("com.jetbrains.intellij.platform:concurrency:203.8084.24") + + + ///implementation("org.jetbrains.kotlin:kotlin-compiler:1.9.0") + implementation("org.jetbrains.kotlin:kotlin-compiler-for-ide:1.9.0-release-358") { + isTransitive = false + } + + // 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/AnalysisContext.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/AnalysisContext.kt new file mode 100644 index 0000000000..23ef825f25 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/AnalysisContext.kt @@ -0,0 +1,88 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols + +import com.intellij.openapi.project.Project +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaSourceSetID +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.createAnalysisSession +import org.jetbrains.dokka.model.SourceSetDependent +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession +import java.io.Closeable + + +internal fun ProjectKotlinAnalysis( + sourceSets: List, + context: DokkaContext, +): KotlinAnalysis { + val environments = sourceSets.associateWith { sourceSet -> + createAnalysisContext( + context = context, + sourceSets = sourceSets, + sourceSet = sourceSet + ) + } + return EnvironmentKotlinAnalysis(environments) +} + +@Suppress("UNUSED_PARAMETER") +internal fun createAnalysisContext( + context: DokkaContext, + sourceSets: List, + sourceSet: DokkaConfiguration.DokkaSourceSet +): 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 AnalysisContextImpl(createAnalysisSession(classpath, sources)) +} + + +/** + * First child delegation. It does not close [parent]. + */ +@InternalDokkaApi +abstract class KotlinAnalysis( + private val parent: KotlinAnalysis? = null +) : Closeable { + + operator fun get(key: DokkaConfiguration.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) + } +} + +interface AnalysisContext: Closeable { + val project: Project +} + +class AnalysisContextImpl(private val analysisSession: StandaloneAnalysisAPISession) : AnalysisContext { + override val project: Project + get() = analysisSession.project + + override fun close() { + analysisSession.application.dispose() + analysisSession.project.dispose() + } +} \ No newline at end of file diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/KtPsiDocumentableSource.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/KtPsiDocumentableSource.kt new file mode 100644 index 0000000000..7ec63e64ca --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/KtPsiDocumentableSource.kt @@ -0,0 +1,20 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols + +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import org.jetbrains.dokka.model.DocumentableSource +import org.jetbrains.kotlin.lexer.KtTokens + + +internal class KtPsiDocumentableSource(val psi: PsiElement?) : DocumentableSource { + override val path = psi?.containingFile?.virtualFile?.path ?: "" + + override fun computeLineNumber(): Int? { + return psi?.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) + } + } +} \ No newline at end of file diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerDocumentableSourceLanguageParser.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerDocumentableSourceLanguageParser.kt new file mode 100644 index 0000000000..29c4623bc0 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerDocumentableSourceLanguageParser.kt @@ -0,0 +1,23 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.compiler + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.java.util.PsiDocumentableSource +import org.jetbrains.dokka.analysis.kotlin.symbols.KtPsiDocumentableSource +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 KtPsiDocumentableSource -> DocumentableLanguage.KOTLIN + else -> error("Unknown language sources: ${documentableSource::class}") + } + } +} 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 4a39e0d81e..0000000000 --- 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/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/DRIFactory.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/DRIFactory.kt new file mode 100644 index 0000000000..479a7a4154 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/DRIFactory.kt @@ -0,0 +1,104 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.compiler + +import org.jetbrains.dokka.links.* +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.* +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithTypeParameters +import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId + +internal fun ClassId.createDRI(): DRI = DRI( + packageName = this.packageFqName.asString(), classNames = this.relativeClassName.asString() +) + +private fun CallableId.createDRI(receiver: TypeReference?, params: List): DRI = DRI( + packageName = this.packageName.asString(), + classNames = this.className?.asString(), + callable = Callable( + this.callableName.asString(), + params = params, + receiver = receiver + ) +) + +internal fun getDRIFromNonErrorClassType(nonErrorClassType: KtNonErrorClassType): DRI = nonErrorClassType.classId.createDRI() + +// because od compatibility with oDokka K1, entry is treated as non-callable +internal fun getDRIFromEnumEntry(symbol: KtEnumEntrySymbol): DRI = + symbol.callableIdIfNonLocal?.let { + DRI( + packageName = it.packageName.asString(), + classNames = it.className?.asString() + "." + it.callableName.asString(), + ) + }?.withEnumEntryExtra() + ?: throw IllegalStateException("") + +internal fun KtAnalysisSession.getDRIFromTypeParameter(symbol: KtTypeParameterSymbol): DRI { + val containingSymbol = + symbol.getContainingSymbol() ?: throw IllegalStateException("`getContainingSymbol` is null for type parameter") + val typeParameters = (containingSymbol as? KtSymbolWithTypeParameters)?.typeParameters + val index = typeParameters?.indexOfFirst { symbol.name == it.name } ?: -1 + return if (index == -1) + getDRIFromSymbol(containingSymbol) + else + getDRIFromSymbol(containingSymbol).copy(target = PointingToGenericParameters(index)) +} + +internal fun KtAnalysisSession.getDRIFromConstructor(symbol: KtConstructorSymbol): DRI = + symbol.containingClassIdIfNonLocal?.createDRI()?.copy( + callable = Callable( + name = symbol.containingClassIdIfNonLocal?.relativeClassName?.asString() ?: "", + params = symbol.valueParameters.map { getTypeReferenceFrom(it.returnType) } + )) + ?: throw IllegalStateException("") + +internal fun KtAnalysisSession.getDRIFromVariableLike(symbol: KtVariableLikeSymbol): DRI { + val receiver = symbol.receiverType?.let {// TODO: replace `receiverType` with `receiverParameter` + getTypeReferenceFrom(it) + } + return symbol.callableIdIfNonLocal?.createDRI(receiver, emptyList()) + ?: throw IllegalStateException("") +} + +internal fun KtAnalysisSession.getDRIFromFunctionLike(symbol: KtFunctionLikeSymbol): DRI { + val params = symbol.valueParameters.map { getTypeReferenceFrom(it.returnType) } + val receiver = symbol.receiverType?.let { // TODO: replace `receiverType` with `receiverParameter` + getTypeReferenceFrom(it) + } + return symbol.callableIdIfNonLocal?.createDRI(receiver, params) + ?: getDRIFromLocalFunction(symbol) +} + +internal fun getDRIFromClassLike(symbol: KtClassLikeSymbol): DRI = + symbol.classIdIfNonLocal?.createDRI() ?: throw IllegalStateException() + +internal fun KtAnalysisSession.getDRIFromSymbol(symbol: KtSymbol): DRI = + when (symbol) { + is KtEnumEntrySymbol -> getDRIFromEnumEntry(symbol) + is KtTypeParameterSymbol -> getDRIFromTypeParameter(symbol) + is KtConstructorSymbol -> getDRIFromConstructor(symbol) + is KtVariableLikeSymbol -> getDRIFromVariableLike(symbol) + is KtFunctionLikeSymbol -> getDRIFromFunctionLike(symbol) + is KtClassLikeSymbol -> getDRIFromClassLike(symbol) + else -> throw IllegalStateException("Unknown symbol while creating DRI ") + } + +private fun KtAnalysisSession.getDRIFromLocalFunction(symbol: KtFunctionLikeSymbol): DRI { + /** + * in enum entry: memberSymbol.callableIdIfNonLocal=null + */ + return getDRIFromSymbol(symbol.getContainingSymbol() as KtSymbol).copy( + callable = Callable( + (symbol as? KtNamedSymbol)?.name?.asString() ?: "", + params = symbol.valueParameters.map { getTypeReferenceFrom(it.returnType) }, + receiver = symbol.receiverType?.let { // TODO: replace `receiverType` with `receiverParameter` + getTypeReferenceFrom(it) + } +// receiver = symbol.receiverParameter?.let { +// getTypeReferenceFrom(it) +// } + ) + ) +} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KDocProvider.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KDocProvider.kt new file mode 100644 index 0000000000..28db2c76da --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KDocProvider.kt @@ -0,0 +1,158 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.compiler + +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.parseFromKDocTag + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol +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.getDocumentation(symbol: KtSymbol) = findKDoc(symbol)?.let { + + val ktElement = symbol.psi + val kdocLocation = ktElement?.containingFile?.name?.let { + val name = (symbol as? KtNamedSymbol)?.name?.asString() + if (name != null) "$it/$name" + else it + } + + + parseFromKDocTag( + kDocTag = it.contentTag, + externalDri = { _ -> +// try { +// resolveKDocLink( +// context = resolutionFacade.resolveSession.bindingContext, +// resolutionFacade = resolutionFacade, +// fromDescriptor = this, +// fromSubjectOfTag = null, +// qualifiedName = link.split('.') +// ).firstOrNull()?.let { DRI.from(it) } +// } catch (e1: IllegalArgumentException) { +// logger.warn("Couldn't resolve link for $link") +// null +// } + null + }, + kdocLocation = kdocLocation + ) +} // TODO ?: getJavaDocs())?.takeIf { it.children.isNotEmpty() } + + +// ----------- copy-paste from IDE ---------------------------------------------------------------------------- + +data class KDocContent( + val contentTag: KDocTag, + val sections: List +) + +internal fun KtAnalysisSession.findKDoc(symbol: KtSymbol): KDocContent? { + if (symbol.origin == KtSymbolOrigin.LIBRARY) 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 +} + + +fun KtElement.findKDoc(): KDocContent? { + return try { + this.lookupOwnedKDoc() + ?: this.lookupKDocInContainer() + } catch (e: Throwable) { // Attempt to load text for binary file which doesn't have a decompiler plugged in + // com.intellij.openapi.fileEditor.impl.LoadTextUtil.loadText + null + } +} + +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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysis.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysis.kt new file mode 100644 index 0000000000..25f3679c46 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysis.kt @@ -0,0 +1,214 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.compiler + +import com.intellij.core.CoreApplicationEnvironment +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.StandardFileSystems +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.psi.PsiFileSystemItem +import com.intellij.psi.PsiManager +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.ProjectScope +import com.intellij.util.io.URLUtil +import org.jetbrains.dokka.DokkaDefaults.analysisPlatform +import org.jetbrains.dokka.Platform +import org.jetbrains.kotlin.analysis.api.impl.base.util.LibraryUtils +import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionProvider +import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession +import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession +import org.jetbrains.kotlin.analysis.project.structure.builder.KtModuleBuilder +import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtLibraryModule +import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSdkModule +import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSourceModule +import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.platform.CommonPlatforms +import org.jetbrains.kotlin.platform.js.JsPlatforms +import org.jetbrains.kotlin.platform.jvm.JvmPlatforms +import org.jetbrains.kotlin.platform.konan.NativePlatforms +import org.jetbrains.kotlin.psi.KtFile +import java.io.File +import java.io.IOException +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes + +internal fun Platform.toTargetPlatform() = when (this) { + Platform.js, Platform.wasm -> JsPlatforms.defaultJsPlatform + Platform.common -> CommonPlatforms.defaultCommonPlatform + Platform.native -> NativePlatforms.unspecifiedNativePlatform + Platform.jvm -> JvmPlatforms.defaultJvmPlatform +} + +/** + * Collect source file path from the given [root] store them in [result]. + * + * E.g., for `project/app/src` as a [root], this will walk the file tree and + * collect all `.kt` and `.java` files under that folder. + * + * Note that this util gracefully skips [IOException] during file tree traversal. + */ +internal fun collectSourceFilePaths( + root: Path, + result: MutableSet +) { + // NB: [Files#walk] throws an exception if there is an issue during IO. + // With [Files#walkFileTree] with a custom visitor, we can take control of exception handling. + Files.walkFileTree( + root, + object : SimpleFileVisitor() { + override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult { + return if (Files.isReadable(dir)) + FileVisitResult.CONTINUE + else + FileVisitResult.SKIP_SUBTREE + } + + override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { + if (!Files.isRegularFile(file) || !Files.isReadable(file)) + return FileVisitResult.CONTINUE + val ext = file.toFile().extension + if (ext == KotlinFileType.EXTENSION || ext == "java"/*JavaFileType.DEFAULT_EXTENSION*/) { + result.add(file.toString()) + } + return FileVisitResult.CONTINUE + } + + override fun visitFileFailed(file: Path, exc: IOException?): FileVisitResult { + // TODO: report or log [IOException]? + // NB: this intentionally swallows the exception, hence fail-safe. + // Skipping subtree doesn't make any sense, since this is not a directory. + // Skipping sibling may drop valid file paths afterward, so we just continue. + return FileVisitResult.CONTINUE + } + } + ) +} + +/** + * Collect source file path as [String] from the given source roots in [sourceRoot]. + * + * this util collects all `.kt` and `.java` files under source roots. + */ +fun getSourceFilePaths( + sourceRoot: Collection, + includeDirectoryRoot: Boolean = false, +): Set { + val result = mutableSetOf() + sourceRoot.forEach { srcRoot -> + val path = Paths.get(srcRoot) + if (Files.isDirectory(path)) { + // E.g., project/app/src + collectSourceFilePaths(path, result) + if (includeDirectoryRoot) { + result.add(srcRoot) + } + } else { + // E.g., project/app/src/some/pkg/main.kt + result.add(srcRoot) + } + } + + return result +} + +inline fun getPsiFilesFromPaths( + project: Project, + paths: Collection, +): List { + val fs = StandardFileSystems.local() + val psiManager = PsiManager.getInstance(project) + val result = mutableListOf() + for (path in paths) { + val vFile = fs.findFileByPath(path) ?: continue + val psiFileSystemItem = + if (vFile.isDirectory) + psiManager.findDirectory(vFile) as? T + else + psiManager.findFile(vFile) as? T + psiFileSystemItem?.let { result.add(it) } + } + return result +} + + +fun getJdkHomeFromSystemProperty(): File? { + val javaHome = File(System.getProperty("java.home")) + if (!javaHome.exists()) { + // messageCollector.report(CompilerMessageSeverity.WARNING, "Set existed java.home to use JDK") + return null + } + return File("/home/Vadim.Mishenev/.jdks/adopt-openjdk-11.0.11") +} + +fun createAnalysisSession( + classpath: List, + sources: Set, +): StandaloneAnalysisAPISession { + + val analysisSession = buildStandaloneAnalysisAPISession(withPsiDeclarationFromBinaryModuleProvider = false) { + val project = project + val targetPlatform = analysisPlatform.toTargetPlatform() + fun KtModuleBuilder.addModuleDependencies(moduleName: String) { + val libraryRoots = classpath + addRegularDependency( + buildKtLibraryModule { + contentScope = ProjectScope.getLibrariesScope(project) + this.platform = targetPlatform + this.project = project + binaryRoots = libraryRoots.map { it.toPath() } + libraryName = "Library for $moduleName" + } + ) + getJdkHomeFromSystemProperty()?.let { jdkHome -> + val vfm = VirtualFileManager.getInstance() + val jdkHomePath = jdkHome.toPath() + val jdkHomeVirtualFile = vfm.findFileByNioPath(jdkHome.toPath())//vfm.findFileByPath(jdkHomePath) + val binaryRoots = LibraryUtils.findClassesFromJdkHome(jdkHomePath).map { + Paths.get(URLUtil.extractPath(it)) + } + addRegularDependency( + buildKtSdkModule { + contentScope = GlobalSearchScope.fileScope(project, jdkHomeVirtualFile) + this.platform = targetPlatform + this.project = project + this.binaryRoots = binaryRoots + sdkName = "JDK for $moduleName" + } + ) + } + } + + buildKtModuleProvider { + platform = targetPlatform + this.project = project + addModule(buildKtSourceModule { + //this.languageVersionSettings = it + + //val fs = StandardFileSystems.local() + //val psiManager = PsiManager.getInstance(project) + // TODO: We should handle (virtual) file changes announced via LSP with the VFS + /*val ktFiles = sources + .flatMap { Files.walk(it).toList() } + .mapNotNull { fs.findFileByPath(it.toString()) } + .mapNotNull { psiManager.findFile(it) } + .map { it as KtFile }*/ + val sourcePaths = sources.map { it.absolutePath } + val (ktFilePath, javaFilePath) = getSourceFilePaths(sourcePaths).partition { it.endsWith(KotlinFileType.EXTENSION) } + val javaFiles: List = getPsiFilesFromPaths(project, javaFilePath) + val ktFiles: List = getPsiFilesFromPaths(project, getSourceFilePaths(ktFilePath)) + addSourceRoots(ktFiles + javaFiles) + contentScope = TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, ktFiles) + platform = targetPlatform + moduleName = "" + this.project = project + addModuleDependencies(moduleName) + }) + } + } + @Suppress("UnstableApiUsage") + CoreApplicationEnvironment.registerExtensionPoint( + analysisSession.project.extensionArea, + KtResolveExtensionProvider.EP_NAME.name, + KtResolveExtensionProvider::class.java + ) + return analysisSession +} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysisProjectProvider.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysisProjectProvider.kt new file mode 100644 index 0000000000..fce7e76596 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysisProjectProvider.kt @@ -0,0 +1,15 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.compiler + +import com.intellij.openapi.project.Project +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.java.ProjectProvider +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle + +internal class KotlinAnalysisProjectProvider : ProjectProvider { + override fun getProject(sourceSet: DokkaConfiguration.DokkaSourceSet, context: DokkaContext): Project { + val kotlinAnalysis = context.plugin().querySingle { kotlinAnalysis } + return kotlinAnalysis[sourceSet].project + } +} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/ModuleAndPackageDocumentationReader.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/ModuleAndPackageDocumentationReader.kt new file mode 100644 index 0000000000..1d24346258 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/ModuleAndPackageDocumentationReader.kt @@ -0,0 +1,28 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.compiler + +import org.jetbrains.dokka.DokkaConfiguration +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.plugability.DokkaContext +import org.jetbrains.kotlin.analysis.kotlin.internal.ModuleAndPackageDocumentationReader + +internal fun ModuleAndPackageDocumentationReader(context: DokkaContext): ModuleAndPackageDocumentationReader = + ContextModuleAndPackageDocumentationReader(context) + +private class ContextModuleAndPackageDocumentationReader( + private val context: DokkaContext +) : ModuleAndPackageDocumentationReader { + override fun read(module: DModule): SourceSetDependent { + return emptyMap() + } + + override fun read(pkg: DPackage): SourceSetDependent { + return emptyMap() + } + + override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode? { + return null + } +} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolFullClassHierarchyBuilder.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolFullClassHierarchyBuilder.kt new file mode 100644 index 0000000000..08dead34f5 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolFullClassHierarchyBuilder.kt @@ -0,0 +1,12 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.compiler + +import org.jetbrains.dokka.model.DModule +import org.jetbrains.kotlin.analysis.kotlin.internal.ClassHierarchy +import org.jetbrains.kotlin.analysis.kotlin.internal.FullClassHierarchyBuilder + +class SymbolFullClassHierarchyBuilder : FullClassHierarchyBuilder { + override suspend fun build(module: DModule): ClassHierarchy { + return emptyMap() + } + +} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolSyntheticDocumentableDetector.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolSyntheticDocumentableDetector.kt new file mode 100644 index 0000000000..771709f95f --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolSyntheticDocumentableDetector.kt @@ -0,0 +1,12 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.compiler + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.kotlin.analysis.kotlin.internal.SyntheticDocumentableDetector + +class SymbolSyntheticDocumentableDetector : SyntheticDocumentableDetector { + override fun isSynthetic(documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet): Boolean { + TODO("Not yet implemented") + } + +} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolsAnalysisPlugin.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolsAnalysisPlugin.kt new file mode 100644 index 0000000000..5984cba329 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolsAnalysisPlugin.kt @@ -0,0 +1,110 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.compiler + +import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute +import com.intellij.psi.PsiAnnotation +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.analysis.java.BreakingAbstractionKotlinLightMethodChecker +import org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin +import org.jetbrains.dokka.analysis.kotlin.symbols.KotlinAnalysis +import org.jetbrains.dokka.analysis.kotlin.symbols.ProjectKotlinAnalysis +import org.jetbrains.dokka.analysis.kotlin.symbols.translators.DefaultSymbolToDocumentableTranslator +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.kotlin.analysis.kotlin.internal.InternalKotlinAnalysisPlugin +import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation + +class SymbolsAnalysisPlugin : DokkaPlugin() { + + val kotlinAnalysis by extensionPoint() + + internal val defaultKotlinAnalysis by extending { + kotlinAnalysis providing { ctx -> + ProjectKotlinAnalysis( + sourceSets = ctx.configuration.sourceSets, + context = ctx + ) + } + } + + + val symbolToDocumentableTranslator by extending { + CoreExtensions.sourceToDocumentableTranslator providing ::DefaultSymbolToDocumentableTranslator + } + + + + + 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 documentableAnalyzerImpl by extending { + plugin().documentableSourceLanguageParser providing { CompilerDocumentableSourceLanguageParser() } + } + internal val descriptorFullClassHierarchyBuilder by extending { + plugin().fullClassHierarchyBuilder providing { SymbolFullClassHierarchyBuilder() } + } + + internal val descriptorSyntheticDocumentableDetector by extending { + plugin().syntheticDocumentableDetector providing { SymbolSyntheticDocumentableDetector() } + } + + internal val moduleAndPackageDocumentationReader by extending { + plugin().moduleAndPackageDocumentationReader providing ::ModuleAndPackageDocumentationReader + } + +/* internal val kotlinToJavaMapper by extending { + plugin().kotlinToJavaService providing { DescriptorKotlinToJavaMapper() } + } + + intern val descriptorInheritanceBuilder by extending { + plugin().inheritanceBuilder providing { DescriptorInheritanceBuilder() } + } + + internal val defaultExternalDocumentablesProvider by extending { + plugin().externalDocumentablesProvider providing ::DefaultExternalDocumentablesProvider + }*/ + + @OptIn(DokkaPluginApiPreview::class) + override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement +} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/TypeReferenceFactory.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/TypeReferenceFactory.kt new file mode 100644 index 0000000000..60b581c275 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/TypeReferenceFactory.kt @@ -0,0 +1,68 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.compiler + +import org.jetbrains.dokka.links.RecursiveType +import org.jetbrains.dokka.links.TypeConstructor +import org.jetbrains.dokka.links.TypeParam +import org.jetbrains.dokka.links.TypeReference +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.types.* + +internal fun KtAnalysisSession.getTypeReferenceFrom(type: KtType): TypeReference = + getTypeReferenceFromPossiblyRecursive(type, emptyList()) + + +// see `deep recursive typebound #1342` test +private fun KtAnalysisSession.getTypeReferenceFromPossiblyRecursive(type: KtType, paramTrace: List): TypeReference { + if(type is KtTypeParameterType) { + // compare by symbol since, e.g. T? and T have the different KtType, but the same type parameter + paramTrace.indexOfFirst { it is KtTypeParameterType && type.symbol == it.symbol } + .takeIf { it >= 0 } + ?.let{ return RecursiveType(it) } + } + + return when (type) { + is KtNonErrorClassType -> TypeConstructor( + type.classId.asFqNameString(), // TODO: remove '!!' + type.ownTypeArguments.mapNotNull { + it.type?.let { it1 -> + getTypeReferenceFromPossiblyRecursive( + it1, + paramTrace + ) + } + } // replace `typeArguments ` with ownTypeArguments + ) + is KtTypeParameterType -> TypeParam(bounds = type.symbol.upperBounds.map { + getTypeReferenceFromPossiblyRecursive( + it, + paramTrace + type + ) + }) + is KtClassErrorType -> TypeConstructor(type.errorMessage, emptyList()) + is KtFlexibleType -> getTypeReferenceFromPossiblyRecursive( + type.upperBound, + paramTrace + ) + is KtDefinitelyNotNullType -> getTypeReferenceFromPossiblyRecursive( + type.original, + paramTrace + ) + is KtCapturedType -> TODO() + is KtDynamicType -> TypeConstructor("[dynamic]", emptyList()) + is KtIntegerLiteralType -> TODO() + is KtIntersectionType -> TODO() + is KtTypeErrorType -> TODO() + }.let { + if (type.isMarkedNullable) org.jetbrains.dokka.links.Nullable(it) else it + } + +} + +// TODO +/* +fun KtAnalysisSession.getTypeReferenceFrom(typeProjection: KtTypeProjection): TypeReference = + when (typeProjection) { + is KtStarTypeProjection -> StarProjection + is KtTypeArgumentWithVariance -> getTypeReferenceFrom(typeProjection.type) + } +*/ \ No newline at end of file diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt new file mode 100644 index 0000000000..7c51aeb266 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt @@ -0,0 +1,101 @@ +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.KDocSection +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag + +internal fun parseFromKDocTag( + kDocTag: KDocTag?, + externalDri: (String) -> DRI?, + kdocLocation: String?, + parseWithChildren: Boolean = true +): DocumentationNode { + return if (kDocTag == null) { + DocumentationNode(emptyList()) + } else { + fun parseStringToDocNode(text: String) = + MarkdownParser(externalDri, kdocLocation).parseStringToDocNode(text) + + fun pointedLink(tag: KDocTag): DRI? = (parseStringToDocNode("[${tag.getSubjectName()}]")).let { + val link = it.children[0].children[0] + if (link is DocumentationLink) link.dri else null + } + + val allTags = + listOf(kDocTag) + if (kDocTag.canHaveParent() && parseWithChildren) getAllKDocTags(findParent(kDocTag)) else emptyList() + DocumentationNode( + allTags.map { + when (it.knownTag) { + null -> if (it.name == null) Description(parseStringToDocNode(it.getContent())) else CustomTagWrapper( + parseStringToDocNode(it.getContent()), + it.name!! + ) + KDocKnownTag.AUTHOR -> Author(parseStringToDocNode(it.getContent())) + KDocKnownTag.THROWS -> { + val dri = pointedLink(it) + Throws( + parseStringToDocNode(it.getContent()), + dri?.fqDeclarationName() ?: it.getSubjectName().orEmpty(), + dri, + ) + } + KDocKnownTag.EXCEPTION -> { + val dri = pointedLink(it) + Throws( + parseStringToDocNode(it.getContent()), + dri?.fqDeclarationName() ?: it.getSubjectName().orEmpty(), + dri + ) + } + KDocKnownTag.PARAM -> Param( + parseStringToDocNode(it.getContent()), + it.getSubjectName().orEmpty() + ) + KDocKnownTag.RECEIVER -> Receiver(parseStringToDocNode(it.getContent())) + KDocKnownTag.RETURN -> Return(parseStringToDocNode(it.getContent())) + KDocKnownTag.SEE -> { + val dri = pointedLink(it) + See( + parseStringToDocNode(it.getContent()), + dri?.fqDeclarationName() ?: it.getSubjectName().orEmpty(), + dri, + ) + } + KDocKnownTag.SINCE -> Since(parseStringToDocNode(it.getContent())) + KDocKnownTag.CONSTRUCTOR -> Constructor(parseStringToDocNode(it.getContent())) + KDocKnownTag.PROPERTY -> Property( + parseStringToDocNode(it.getContent()), + it.getSubjectName().orEmpty() + ) + KDocKnownTag.SAMPLE -> Sample( + parseStringToDocNode(it.getContent()), + it.getSubjectName().orEmpty() + ) + KDocKnownTag.SUPPRESS -> Suppress(parseStringToDocNode(it.getContent())) + } + } + ) + } +} + +//Horrible hack but since link resolution is passed as a function i am not able to resolve them otherwise +@kotlin.Suppress("DeprecatedCallableAddReplaceWith") +@Deprecated("This function makes wrong assumptions and is missing a lot of corner cases related to generics, " + + "parameters and static members. This is not supposed to be public API and will not be supported in the future") +internal fun DRI.fqName(): String? = "$packageName.$classNames".takeIf { packageName != null && classNames != null } + +private fun findParent(kDoc: PsiElement): PsiElement = + if (kDoc.canHaveParent()) findParent(kDoc.parent) else kDoc + +private fun PsiElement.canHaveParent(): Boolean = this is KDocSection && knownTag != KDocKnownTag.PROPERTY + +private fun getAllKDocTags(kDocImpl: PsiElement): List = + kDocImpl.children.filterIsInstance().filterNot { it is KDocSection } + kDocImpl.children.flatMap { + getAllKDocTags(it) + } diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt new file mode 100644 index 0000000000..de754e8176 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt @@ -0,0 +1,101 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.translators + +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.createDRI +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.ClassValue +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.annotations.* +import org.jetbrains.kotlin.analysis.api.base.KtConstantValue +import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolOrigin +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtProperty + +internal class AnnotationTranslator { + private fun KtAnalysisSession.getFileLevelAnnotationsFrom(symbol: KtSymbol) = + if (symbol.origin != KtSymbolOrigin.SOURCE) + null + else + (symbol.psi?.containingFile as? KtFile)?.getFileSymbol()?.annotations?.map { toDokkaAnnotation(it) } + + private fun KtAnalysisSession.getDirectAnnotationsFrom(annotated: KtAnnotated) = + annotated.annotations.map { toDokkaAnnotation(it) } + + fun KtAnalysisSession.getAllAnnotationsFrom(annotated: KtAnnotated): List { + val directAnnotations = getDirectAnnotationsFrom(annotated) + val fileLevelAnnotations = (annotated as? KtSymbol)?.let { getFileLevelAnnotationsFrom(it) } ?: emptyList() + return directAnnotations + fileLevelAnnotations + } + + private fun AnnotationUseSiteTarget.toDokkaAnnotationScope(): Annotations.AnnotationScope = when (this) { + AnnotationUseSiteTarget.PROPERTY_GETTER -> Annotations.AnnotationScope.DIRECT // due to compatibility with Dokka K1 + AnnotationUseSiteTarget.PROPERTY_SETTER -> Annotations.AnnotationScope.DIRECT // due to compatibility with Dokka K1 + AnnotationUseSiteTarget.FILE -> Annotations.AnnotationScope.FILE + else -> Annotations.AnnotationScope.DIRECT + } + + private fun KtAnalysisSession.mustBeDocumented(annotationApplication: KtAnnotationApplication): Boolean { + val annotationClass = getClassOrObjectSymbolByClassId(annotationApplication.classId ?: return false) + return annotationClass?.hasAnnotation(ClassId(FqName("kotlin.annotation"), FqName("MustBeDocumented"), false)) + ?: false + } + private fun KtAnalysisSession.toDokkaAnnotation(annotationApplication: KtAnnotationApplication) = Annotations.Annotation( + dri = annotationApplication.classId?.createDRI() ?: throw IllegalStateException("The annotation application does not have class id"), + params = if(annotationApplication is KtAnnotationApplicationWithArgumentsInfo) annotationApplication.arguments.associate { it.name.asString() to toDokkaAnnotationValue(it.expression) } else emptyMap(), + mustBeDocumented = mustBeDocumented(annotationApplication), + scope = annotationApplication.useSiteTarget?.toDokkaAnnotationScope() ?: Annotations.AnnotationScope.DIRECT + ) + + @OptIn(ExperimentalUnsignedTypes::class) + private fun KtAnalysisSession.toDokkaAnnotationValue(annotationValue: KtAnnotationValue): AnnotationParameterValue = when (annotationValue) { + is KtConstantAnnotationValue -> { + when(val value = annotationValue.constantValue) { + is KtConstantValue.KtNullConstantValue -> NullValue + is KtConstantValue.KtFloatConstantValue -> FloatValue(value.value) + is KtConstantValue.KtDoubleConstantValue -> DoubleValue(value.value) + is KtConstantValue.KtLongConstantValue -> LongValue(value.value) + is KtConstantValue.KtIntConstantValue -> IntValue(value.value) + is KtConstantValue.KtBooleanConstantValue -> BooleanValue(value.value) + is KtConstantValue.KtByteConstantValue -> IntValue(value.value.toInt()) + is KtConstantValue.KtCharConstantValue -> StringValue(value.value.toString()) + is KtConstantValue.KtErrorConstantValue -> StringValue(value.renderAsKotlinConstant()) + is KtConstantValue.KtShortConstantValue -> IntValue(value.value.toInt()) + is KtConstantValue.KtStringConstantValue -> StringValue(value.value) + is KtConstantValue.KtUnsignedByteConstantValue -> IntValue(value.value.toInt()) + is KtConstantValue.KtUnsignedIntConstantValue -> IntValue(value.value.toInt()) + is KtConstantValue.KtUnsignedLongConstantValue -> LongValue(value.value.toLong()) + is KtConstantValue.KtUnsignedShortConstantValue -> IntValue(value.value.toInt()) + } + } + is KtEnumEntryAnnotationValue -> EnumValue( + with(annotationValue.callableId) { this?.className?.asString() + "." + this?.callableName?.asString() }, + getDRIFrom(annotationValue) + ) + is KtArrayAnnotationValue -> ArrayValue(annotationValue.values.map { toDokkaAnnotationValue(it) }) + is KtAnnotationApplicationValue -> AnnotationValue(toDokkaAnnotation(annotationValue.annotationValue)) + is KtKClassAnnotationValue.KtNonLocalKClassAnnotationValue -> ClassValue( + annotationValue.classId.relativeClassName.asString(), + annotationValue.classId.createDRI() + ) + is KtKClassAnnotationValue.KtLocalKClassAnnotationValue -> TODO() + is KtKClassAnnotationValue.KtErrorClassAnnotationValue -> TODO() + KtUnsupportedAnnotationValue -> TODO() + } + + private fun getDRIFrom(enumEntry: KtEnumEntryAnnotationValue): DRI { + val callableId = enumEntry.callableId ?: throw IllegalStateException("Can't get `callableId` for enum entry from annotation") + return DRI( + packageName = callableId.packageName.asString(), + classNames = callableId.className?.asString() + "." + callableId.callableName.asString(), + /*callable = Callable( + callableId.callableName.asString(), + params = emptyList(), + )*/ + )//.withEnumEntryExtra() + } +} \ No newline at end of file diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt new file mode 100644 index 0000000000..248f697b17 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt @@ -0,0 +1,1066 @@ +@file:Suppress("UNUSED_PARAMETER") + +package org.jetbrains.dokka.analysis.kotlin.symbols.translators + + +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.* +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getDRIFromNonErrorClassType +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getDRIFromSymbol +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getDRIFromTypeParameter +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getDocumentation +import com.intellij.psi.util.PsiLiteralUtil +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.kotlin.symbols.AnalysisContext +import org.jetbrains.dokka.analysis.kotlin.symbols.KtPsiDocumentableSource +import org.jetbrains.dokka.links.* +import org.jetbrains.dokka.links.Callable +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.Nullable +import org.jetbrains.dokka.model.Visibility +import org.jetbrains.dokka.model.properties.PropertyContainer +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.dokka.transformers.sources.AsyncSourceToDocumentableTranslator +import org.jetbrains.dokka.utilities.DokkaLogger +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.analysis.api.* +import org.jetbrains.kotlin.analysis.api.annotations.* +import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotated +import org.jetbrains.kotlin.analysis.api.base.KtConstantValue +import org.jetbrains.kotlin.analysis.api.symbols.* +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithVisibility +import org.jetbrains.kotlin.analysis.api.types.* +import org.jetbrains.kotlin.analysis.api.types.KtDynamicType +import org.jetbrains.kotlin.analysis.api.types.KtIntersectionType +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.java.JavaVisibilities +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier +import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction +import java.nio.file.Paths + +// 487 / 707 +class DefaultSymbolToDocumentableTranslator(context: DokkaContext) : AsyncSourceToDocumentableTranslator { + private val kotlinAnalysis = context.plugin().querySingle { kotlinAnalysis } + + override suspend fun invokeSuspending( + sourceSet: DokkaConfiguration.DokkaSourceSet, + context: DokkaContext + ): DModule { + val analysisContext = kotlinAnalysis[sourceSet] + @Suppress("unused") + return DokkaSymbolVisitor( + sourceSet = sourceSet, + moduleName = context.configuration.moduleName, + analysisContext = analysisContext, + logger = context.logger + ).visitModule() + } +} + +class TranslatorError(message: String, cause: Throwable?) : IllegalStateException(message, cause) + + fun < R> KtAnalysisSession.withExceptionCatcher(symbol: KtSymbol, action: KtAnalysisSession.() -> R): R = + try { + action() + } catch (e: TranslatorError) { + throw e + } catch (e: Throwable) { + val ktElement = symbol.psi + val file = ktElement?.containingFile?.virtualFile?.path + throw TranslatorError( + "Error in translating of symbol (${(symbol as? KtNamedSymbol)?.name}) $symbol in file: ${file}, ${ktElement?.textRange}", + e + ) + } + +private class DokkaSymbolVisitor( + private val sourceSet: DokkaConfiguration.DokkaSourceSet, + private val moduleName: String, + private val analysisContext: AnalysisContext, + private val logger: DokkaLogger +) { + private var annotationTranslator = AnnotationTranslator() + + /** + * to avoid recursive classes + * e.g. + * open class Klass() { + * object Default : Klass() + * } + */ + private val visitedNamedClassOrObjectSymbol: MutableSet = + mutableSetOf() // or MutableSet + + internal fun T.toSourceSetDependent() = if (this != null) mapOf(sourceSet to this) else emptyMap() + + // KT-54846 will replace + internal val KtDeclarationSymbol.isActual + get() = (psi as? KtModifierListOwner)?.hasActualModifier() == true + internal val KtDeclarationSymbol.isExpect + get() = (psi as? KtModifierListOwner)?.hasExpectModifier() == true + + private fun Collection.filterSymbolsInSourceSet() = filter { + it.psi?.containingFile?.virtualFile?.getPath()?.let { path -> + path.isNotBlank() && sourceSet.sourceRoots.any { root -> + Paths.get(path).startsWith(root.toPath()) + } + } == true + } + + internal fun visitModule(): DModule { + + //val sourceFiles = environment.getSourceFiles() + /* val packageFragments = environment.getSourceFiles().asSequence() + .map { it.packageFqName } + .distinct() + .mapNotNull { facade.resolveSession.getPackageFragment(it) } + .toList()*/ + val ktFiles: List = getPsiFilesFromPaths( + analysisContext.project, + getSourceFilePaths(sourceSet.sourceRoots.map { it.canonicalPath }) + ) + val processedPackages: MutableSet = mutableSetOf() + val packageSymbols: List = ktFiles + .mapNotNull { + analyze(it) { + if (processedPackages.contains(it.packageFqName)) + return@analyze null + processedPackages.add(it.packageFqName) + getPackageSymbolIfPackageExists(it.packageFqName)?.let { it1 -> + visitPackageSymbol( + it1 + ) + } + } + } + + /* @Suppress("UNUSED_VARIABLE") + val pack = ROOT_PACKAGE_SYMBOL.getPackageScope().getPackageSymbols().toList().filterNot { + //val ktModule: KtSourceModule = it.psi?.getKtModule(analysisContext.session.project) as KtSourceModule + + it.fqName.isExcludedFromAutoImport(analysisContext.session.project, null, null) + } + ///packageSymbols2[0].getPackageScope() + // ROOT_PACKAGE_SYMBOL.getPackageScope().getPackageSymbols().toList().map {it.getPackageScope().getAllSymbols().toList().firstOrNull()?.psi}.filterIsInstance().map {it.getKtModule(analysisContext.session.project)} + val fileSymbol = file.getFileSymbol() + val packageSymbols = fileSymbol.getFileScope().getPackageSymbols() + println(packageSymbols.toString())*/ + + + + return DModule( + name = moduleName, + packages = packageSymbols, + documentation = emptyMap(), + expectPresentInSet = null, + sourceSets = setOf(sourceSet) + ) + + } + + fun KtAnalysisSession.visitPackageSymbol(packageSymbol: KtPackageSymbol): DPackage { + val name = packageSymbol.fqName.asString().takeUnless { it.isBlank() } ?: "" + val dri = DRI(packageName = name) + val scope = packageSymbol.getPackageScope() + val callables = scope.getCallableSymbols().toList().filterSymbolsInSourceSet() + val classifiers = scope.getClassifierSymbols().toList().filterSymbolsInSourceSet() + + val functions = callables.filterIsInstance().map { visitFunctionSymbol(it, dri) } + val properties = callables.filterIsInstance().map { visitPropertySymbol(it, dri) } + val classlikes = + classifiers.filterIsInstance() + .map { visitNamedClassOrObjectSymbol(it, dri) } + val typealiases = classifiers.filterIsInstance().map { visitTypeAliasSymbol(it, dri) } + + return DPackage( + dri = dri, + functions = functions, + properties = properties, + classlikes = classlikes, + typealiases = typealiases, + documentation = emptyMap(), // + sourceSets = setOf(sourceSet) + ) + } + + fun KtAnalysisSession.visitTypeAliasSymbol( + typeAliasSymbol: KtTypeAliasSymbol, + parent: DRI + ): DTypeAlias { + val name = typeAliasSymbol.name.asString() + val dri = parent.withClass(name) + + val generics = + typeAliasSymbol.typeParameters.mapIndexed { index, symbol -> visitVariantTypeParameter(index, symbol, dri) } + + return DTypeAlias( + dri = dri, + name = name, + type = GenericTypeConstructor( + dri = dri, + projections = generics.map { it.variantTypeParameter }), // this property can be removed + expectPresentInSet = null, + underlyingType = toBoundFrom(typeAliasSymbol.expandedType).toSourceSetDependent(), + visibility = typeAliasSymbol.getDokkaVisibility().toSourceSetDependent(), + documentation = getDocumentation(typeAliasSymbol)?.toSourceSetDependent() ?: emptyMap(), + sourceSets = setOf(sourceSet), + generics = generics, + sources = typeAliasSymbol.getSource(), + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(typeAliasSymbol)?.toSourceSetDependent()?.toAnnotations() + //info.exceptionInSupertypesOrNull(), + ) + ) + + } + + fun KtAnalysisSession.visitNamedClassOrObjectSymbol( + namedClassOrObjectSymbol: KtNamedClassOrObjectSymbol, + parent: DRI + ): DClasslike = withExceptionCatcher(namedClassOrObjectSymbol) { + visitedNamedClassOrObjectSymbol.add(namedClassOrObjectSymbol) + + val name = namedClassOrObjectSymbol.name.asString() + val dri = parent.withClass(name) + + /** + * TODO: For synthetic Java properties KTIJ-22359 to research: + * + * val syntheticProperties = getSyntheticJavaPropertiesScope() + * ?.getCallableSymbols(getAndSetPrefixesAwareFilter) + * ?.filterIsInstance() + * .orEmpty() + */ + val scope = namedClassOrObjectSymbol.getMemberScope() + val isExpect = namedClassOrObjectSymbol.isExpect + val isActual = namedClassOrObjectSymbol.isActual + val documentation = getDocumentation(namedClassOrObjectSymbol)?.toSourceSetDependent() ?: emptyMap() + + val constructors = scope.getConstructors().map { visitConstructorSymbol(it, dri) }.toList() + + + val callables = scope.getCallableSymbols().toList() + val classifiers = scope.getClassifierSymbols().toList() + + val functions = callables.filterIsInstance().map { visitFunctionSymbol(it, dri) } + val properties = callables.filterIsInstance().map { visitPropertySymbol(it, dri) } + + callables.filterIsInstance().map { visitJavaFieldSymbol(it, dri) } + // TODO KtJavaFieldSymbol + val classlikes = classifiers.filterIsInstance() + .filterNot { visitedNamedClassOrObjectSymbol.contains(it) } + .map { visitNamedClassOrObjectSymbol(it, dri) } + + val generics = namedClassOrObjectSymbol.typeParameters.mapIndexed { index, symbol -> + visitVariantTypeParameter( + index, + symbol, + dri + ) + } + + val ancestryInfo = buildAncestryInformationFrom(namedClassOrObjectSymbol.buildSelfClassType()) + val supertypes = + //(ancestryInfo.interfaces.map{ it.typeConstructor } + listOfNotNull(ancestryInfo.superclass?.typeConstructor)) + namedClassOrObjectSymbol.superTypes.filterNot { it.isAny }.map { toTypeConstructorWithKindFrom(it) } + .toSourceSetDependent() + + return@withExceptionCatcher when (namedClassOrObjectSymbol.classKind) { + KtClassKind.OBJECT, KtClassKind.COMPANION_OBJECT -> + DObject( + dri = dri, + name = name, + functions = functions, + properties = properties, + classlikes = classlikes, + sources = namedClassOrObjectSymbol.getSource(), + expectPresentInSet = sourceSet.takeIf { isExpect }, + visibility = namedClassOrObjectSymbol.getDokkaVisibility().toSourceSetDependent(), + supertypes = supertypes, + documentation = documentation, + sourceSets = setOf(sourceSet), + isExpectActual = (isExpect || isActual), + extra = PropertyContainer.withAll( + namedClassOrObjectSymbol.additionalExtras()?.toSourceSetDependent() + ?.toAdditionalModifiers(), + getDokkaAnnotationsFrom(namedClassOrObjectSymbol)?.toSourceSetDependent()?.toAnnotations(), + ImplementedInterfaces(ancestryInfo.allImplementedInterfaces().toSourceSetDependent()), + //ancestryInfo.exceptionInSupertypesOrNull() + ) + ) + + KtClassKind.CLASS -> DClass( + dri = dri, + name = name, + constructors = constructors, + functions = functions, + properties = properties, + classlikes = classlikes, + sources = namedClassOrObjectSymbol.getSource(), // + expectPresentInSet = sourceSet.takeIf { isExpect }, + visibility = namedClassOrObjectSymbol.getDokkaVisibility().toSourceSetDependent(), + supertypes = supertypes, + generics = generics, + documentation = documentation, + modifier = namedClassOrObjectSymbol.getDokkaModality().toSourceSetDependent(), + companion = namedClassOrObjectSymbol.companionObject?.let { + visitNamedClassOrObjectSymbol( + it, + dri + ) + } as? DObject, + sourceSets = setOf(sourceSet), + isExpectActual = (isExpect || isActual), + extra = PropertyContainer.withAll( + namedClassOrObjectSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(), + getDokkaAnnotationsFrom(namedClassOrObjectSymbol)?.toSourceSetDependent()?.toAnnotations(), + ImplementedInterfaces(ancestryInfo.allImplementedInterfaces().toSourceSetDependent()), + // info.ancestry.exceptionInSupertypesOrNull() + ) + ) + + KtClassKind.INTERFACE -> DInterface( + dri = dri, + name = name, + functions = functions, + properties = properties, + classlikes = classlikes, + sources = namedClassOrObjectSymbol.getSource(), // + expectPresentInSet = sourceSet.takeIf { isExpect }, + visibility = namedClassOrObjectSymbol.getDokkaVisibility().toSourceSetDependent(), + supertypes = supertypes, + generics = generics, + documentation = documentation, + companion = namedClassOrObjectSymbol.companionObject?.let { + visitNamedClassOrObjectSymbol( + it, + dri + ) + } as? DObject, + sourceSets = setOf(sourceSet), + isExpectActual = (isExpect || isActual), + extra = PropertyContainer.withAll( + namedClassOrObjectSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(), + getDokkaAnnotationsFrom(namedClassOrObjectSymbol)?.toSourceSetDependent()?.toAnnotations(), + ImplementedInterfaces(ancestryInfo.allImplementedInterfaces().toSourceSetDependent()), + // info.ancestry.exceptionInSupertypesOrNull() + ) + ) + + KtClassKind.ANNOTATION_CLASS -> DAnnotation( + dri = dri, + name = name, + documentation = documentation, + functions = functions, + properties = properties, + classlikes = classlikes, + expectPresentInSet = sourceSet.takeIf { isExpect }, + sourceSets = setOf(sourceSet), + isExpectActual = (isExpect || isActual), + companion = namedClassOrObjectSymbol.companionObject?.let { + visitNamedClassOrObjectSymbol( + it, + dri + ) + } as? DObject, + visibility = namedClassOrObjectSymbol.getDokkaVisibility().toSourceSetDependent(), + generics = generics, + constructors = constructors, + sources = namedClassOrObjectSymbol.getSource(), // + extra = PropertyContainer.withAll( + namedClassOrObjectSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(), + getDokkaAnnotationsFrom(namedClassOrObjectSymbol)?.toSourceSetDependent()?.toAnnotations(), + ) + ) + + KtClassKind.ANONYMOUS_OBJECT -> TODO() + KtClassKind.ENUM_CLASS -> { + val entries = namedClassOrObjectSymbol.getEnumEntries().map { visitEnumEntrySymbol(it) } + + DEnum( + dri = dri, + name = name, + entries = entries, // TODO + constructors = constructors, + functions = functions, + properties = properties, + classlikes = classlikes, + sources = namedClassOrObjectSymbol.getSource(), // + expectPresentInSet = sourceSet.takeIf { isExpect }, + visibility = namedClassOrObjectSymbol.getDokkaVisibility().toSourceSetDependent(), + supertypes = supertypes, + documentation = documentation, + companion = namedClassOrObjectSymbol.companionObject?.let { + visitNamedClassOrObjectSymbol( + it, + dri + ) + } as? DObject, + sourceSets = setOf(sourceSet), + isExpectActual = (isExpect || isActual), + extra = PropertyContainer.withAll( + namedClassOrObjectSymbol.additionalExtras()?.toSourceSetDependent() + ?.toAdditionalModifiers(), + getDokkaAnnotationsFrom(namedClassOrObjectSymbol)?.toSourceSetDependent()?.toAnnotations(), + ImplementedInterfaces(ancestryInfo.allImplementedInterfaces().toSourceSetDependent()) + ) + ) + } + } + + /* val (regularFunctions, accessors) = splitFunctionsAndInheritedAccessors( + properties = descriptorsWithKind.properties, + functions = descriptorsWithKind.functions + ) + val constructors = async { + descriptor.constructors.parallelMap { + visitConstructorDescriptor( + it, + if (it.isPrimary) DRIWithPlatformInfo(driWithPlatform.dri, actual) + else DRIWithPlatformInfo(driWithPlatform.dri, emptyMap()) + ) + } + }*/ + + } + + fun KtAnalysisSession.visitEnumEntrySymbol( + enumEntrySymbol: KtEnumEntrySymbol + ): DEnumEntry { + val dri = getDRIFromEnumEntry(enumEntrySymbol) + val isExpect = false + + val scope = enumEntrySymbol.getMemberScope() + val callables = scope.getCallableSymbols().toList() + val classifiers = scope.getClassifierSymbols().toList() + // val descriptorsWithKind = scope.getDescriptorsWithKind(true) + + val functions = callables.filterIsInstance().map { visitFunctionSymbol(it, dri) } + + val properties = callables.filterIsInstance().map { visitPropertySymbol(it, dri) } + val classlikes = + classifiers.filterIsInstance() + .map { visitNamedClassOrObjectSymbol(it, dri) } + + return DEnumEntry( + dri = dri, + name = enumEntrySymbol.name.asString(), + documentation = getDocumentation(enumEntrySymbol)?.toSourceSetDependent() ?: emptyMap(), // TODO + functions = functions, + properties = properties, + classlikes = classlikes, + sourceSets = setOf(sourceSet), + expectPresentInSet = sourceSet.takeIf { isExpect }, + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(enumEntrySymbol)?.toSourceSetDependent()?.toAnnotations() + ) + ) + } + + fun KtClassKind.toDokkaClassKind() = when (this) { + KtClassKind.CLASS -> KotlinClassKindTypes.CLASS + KtClassKind.ENUM_CLASS -> KotlinClassKindTypes.ENUM_CLASS + KtClassKind.ANNOTATION_CLASS -> KotlinClassKindTypes.ANNOTATION_CLASS + KtClassKind.OBJECT -> KotlinClassKindTypes.OBJECT + KtClassKind.COMPANION_OBJECT -> KotlinClassKindTypes.OBJECT + KtClassKind.INTERFACE -> KotlinClassKindTypes.INTERFACE + KtClassKind.ANONYMOUS_OBJECT -> KotlinClassKindTypes.OBJECT + } + + private fun KtAnalysisSession.toTypeConstructorWithKindFrom(type: KtType): TypeConstructorWithKind { + try { + return when (type) { + is KtUsualClassType -> + when (val classSymbol = type.classSymbol) { + is KtNamedClassOrObjectSymbol -> TypeConstructorWithKind( + toTypeConstructorFrom(type), + classSymbol.classKind.toDokkaClassKind() + ) + is KtAnonymousObjectSymbol -> TODO() + is KtTypeAliasSymbol -> toTypeConstructorWithKindFrom(classSymbol.expandedType) + } + is KtClassErrorType -> TypeConstructorWithKind( + GenericTypeConstructor( + dri = DRI("[error]", "", null), + projections = emptyList(), // TODO: since 1.8.20 replace `typeArguments ` with `ownTypeArguments` + + ), + KotlinClassKindTypes.CLASS + ) + is KtFunctionalType -> TypeConstructorWithKind( + toFunctionalTypeConstructorFrom(type), + KotlinClassKindTypes.CLASS + ) + is KtDefinitelyNotNullType -> toTypeConstructorWithKindFrom(type.original) + +// is KtCapturedType -> TODO() +// is KtDynamicType -> TODO() +// is KtFlexibleType -> TODO() +// is KtIntegerLiteralType -> TODO() +// is KtIntersectionType -> TODO() +// is KtTypeParameterType -> TODO() + else -> throw IllegalStateException("Unknown ") + } + } catch (e: Throwable) { + logger.error("Type error: ${type.asStringForDebugging()}\n" + e) // TODO + return TypeConstructorWithKind( + GenericTypeConstructor( + dri = DRI("[error]", "", null), + projections = emptyList(), // TODO: since 1.8.20 replace `typeArguments ` with `ownTypeArguments` + + ), + KotlinClassKindTypes.CLASS + ) + //throw IllegalStateException("Type error: ${type.asStringForDebugging()}" + type, e) + } + } + + private fun KtAnalysisSession.visitPropertySymbol(propertySymbol: KtPropertySymbol, parent: DRI): DProperty { + + val dri = createDRIWithOverridden(propertySymbol).origin + val inheritedFrom = dri.getInheritedFromDRI(parent) + val isExpect = propertySymbol.isExpect + val isActual = propertySymbol.isActual + propertySymbol.origin + val generics = + propertySymbol.typeParameters.mapIndexed { index, symbol -> visitVariantTypeParameter(index, symbol, dri) } + + return DProperty( + dri = dri, + name = propertySymbol.name.asString(), + receiver = propertySymbol.receiverType?.let { + visitReceiverParameter( + it, + dri + ) + } // TODO replace `receiverType` with `receiverParameter` + /*functionSymbol.receiverParameter?.let { + visitReceiverParameter(it, dri) + }*/, + sources = propertySymbol.getSource(), + getter = propertySymbol.getter?.let { visitPropertyAccessor(it, propertySymbol, dri) }, + setter = propertySymbol.setter?.let { visitPropertyAccessor(it, propertySymbol, dri) }, + visibility = propertySymbol.visibility.toDokkaVisibility().toSourceSetDependent(), + documentation = getDocumentation(propertySymbol)?.toSourceSetDependent() ?: emptyMap(), // TODO + modifier = propertySymbol.modality.toDokkaModifier().toSourceSetDependent(), + type = toBoundFrom(propertySymbol.returnType), + expectPresentInSet = sourceSet.takeIf { isExpect }, + sourceSets = setOf(sourceSet), + generics = generics, + isExpectActual = (isExpect || isActual), + extra = PropertyContainer.withAll( + listOfNotNull( + propertySymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(), + getDokkaAnnotationsFrom(propertySymbol)?.toSourceSetDependent()?.toAnnotations(), + propertySymbol.getDefaultValue()?.let { DefaultValue(it.toSourceSetDependent()) }, + inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) }, + takeUnless { propertySymbol.isVal }?.let { IsVar }, + takeIf { propertySymbol.psi is KtParameter }?.let { IsAlsoParameter(listOf(sourceSet)) } + ) + ) + ) + } + + private fun KtAnalysisSession.visitJavaFieldSymbol(javaFieldSymbol: KtJavaFieldSymbol, parent: DRI): DProperty { + + val dri = createDRIWithOverridden(javaFieldSymbol).origin + val inheritedFrom = dri.getInheritedFromDRI(parent) + val isExpect = false + val isActual = false + val generics = + javaFieldSymbol.typeParameters.mapIndexed { index, symbol -> visitVariantTypeParameter(index, symbol, dri) } + + return DProperty( + dri = dri, + name = javaFieldSymbol.name.asString(), + receiver = javaFieldSymbol.receiverType?.let { + visitReceiverParameter( + it, + dri + ) + } // TODO replace `receiverType` with `receiverParameter` + /*functionSymbol.receiverParameter?.let { + visitReceiverParameter(it, dri) + }*/, + sources = javaFieldSymbol.getSource(), + getter = null, + setter = null, + visibility = javaFieldSymbol.visibility.toDokkaVisibility().toSourceSetDependent(), + documentation = getDocumentation(javaFieldSymbol)?.toSourceSetDependent() ?: emptyMap(), // TODO + modifier = javaFieldSymbol.modality.toDokkaModifier().toSourceSetDependent(), + type = toBoundFrom(javaFieldSymbol.returnType), + expectPresentInSet = sourceSet.takeIf { isExpect }, + sourceSets = setOf(sourceSet), + generics = generics, + isExpectActual = (isExpect || isActual), + extra = PropertyContainer.withAll( + listOfNotNull( + //propertySymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(), + getDokkaAnnotationsFrom(javaFieldSymbol)?.toSourceSetDependent()?.toAnnotations(), + //javaFieldSymbol.getDefaultValue()?.let { DefaultValue(it.toSourceSetDependent()) }, + inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) }, + IsVar, + ) + ) + ) + } + + private fun KtAnalysisSession.visitPropertyAccessor( + propertyAccessorSymbol: KtPropertyAccessorSymbol, + propertySymbol: KtPropertySymbol, + propertyDRI: DRI + ): DFunction { + val dri = propertyDRI.copy( + callable = Callable("", null, emptyList()) + ) + val isExpect = propertyAccessorSymbol.isExpect + val isActual = propertyAccessorSymbol.isActual + + val generics = propertyAccessorSymbol.typeParameters.mapIndexed { index, symbol -> + visitVariantTypeParameter( + index, + symbol, + dri + ) + } + + return DFunction( + dri = dri, + name = "", //TODO + isConstructor = false, + receiver = propertyAccessorSymbol.receiverType?.let { + visitReceiverParameter( + it, + dri + ) + } // TODO replace `receiverType` with `receiverParameter` + /*functionSymbol.receiverParameter?.let { + visitReceiverParameter(it, dri) + }*/, + parameters = propertyAccessorSymbol.valueParameters.mapIndexed { index, symbol -> + visitValueParameter(index, symbol, dri) + }, + expectPresentInSet = sourceSet.takeIf { isExpect }, + sources = propertyAccessorSymbol.getSource(), + visibility = propertyAccessorSymbol.visibility.toDokkaVisibility().toSourceSetDependent(), + generics = generics, + documentation = getDocumentation(propertyAccessorSymbol)?.toSourceSetDependent() ?: emptyMap(), // TODO + modifier = propertyAccessorSymbol.modality.toDokkaModifier().toSourceSetDependent(), + type = toBoundFrom(propertyAccessorSymbol.returnType), + sourceSets = setOf(sourceSet), + isExpectActual = (isExpect || isActual), + extra = PropertyContainer.withAll( + propertyAccessorSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(), + getDokkaAnnotationsFrom(propertyAccessorSymbol)?.toSourceSetDependent()?.toAnnotations() + /// ObviousMember.takeIf { descriptor.isObvious() }, + ) + ) + } + + private fun KtAnalysisSession.visitConstructorSymbol( + constructorSymbol: KtConstructorSymbol, + parent: DRI + ): DFunction { + val name = constructorSymbol.containingClassIdIfNonLocal?.shortClassName?.asString() + ?: throw IllegalStateException("Unknown containing class of constructor") + val dri = createDRIWithOverridden(constructorSymbol).origin + val isExpect = false // TODO + val isActual = false // TODO + + val generics = constructorSymbol.typeParameters.mapIndexed { index, symbol -> + visitVariantTypeParameter( + index, + symbol, + dri + ) + } + + return DFunction( + dri = dri, + name = name, + isConstructor = true, + receiver = constructorSymbol.receiverType?.let { + visitReceiverParameter( + it, + dri + ) + } // TODO replace `receiverType` with `receiverParameter` + /*functionSymbol.receiverParameter?.let { + visitReceiverParameter(it, dri) + }*/, + parameters = constructorSymbol.valueParameters.mapIndexed { index, symbol -> + visitValueParameter(index, symbol, dri) + }, + expectPresentInSet = sourceSet.takeIf { isExpect }, + sources = constructorSymbol.getSource(), + visibility = constructorSymbol.visibility.toDokkaVisibility().toSourceSetDependent(), + generics = generics, + documentation = getDocumentation(constructorSymbol)?.toSourceSetDependent() ?: emptyMap(), + modifier = KotlinModifier.Empty.toSourceSetDependent(), // CONSIDER + type = toBoundFrom(constructorSymbol.returnType), + sourceSets = setOf(sourceSet), + isExpectActual = (isExpect || isActual), + extra = PropertyContainer.withAll(listOfNotNull( + getDokkaAnnotationsFrom(constructorSymbol)?.toSourceSetDependent()?.toAnnotations(), + takeIf { constructorSymbol.isPrimary }?.let { PrimaryConstructorExtra }) + ) + ) + } + + private fun isObvious(functionSymbol: KtFunctionSymbol): Boolean { + return functionSymbol.origin == KtSymbolOrigin.SOURCE_MEMBER_GENERATED || + !functionSymbol.isOverride && functionSymbol.callableIdIfNonLocal?.classId?.isObvious() == true + } + private fun ClassId.isObvious(): Boolean = with(asString()) { + return this == "kotlin/Any" || this == "kotlin/Enum" + || this == "java.lang/Object" || this == "java.lang/Enum" + } + private fun KtAnalysisSession.visitFunctionSymbol(functionSymbol: KtFunctionSymbol, parent: DRI): DFunction { + val dri = createDRIWithOverridden(functionSymbol).origin + val inheritedFrom = dri.getInheritedFromDRI(parent) + val isExpect = functionSymbol.isExpect + val isActual = functionSymbol.isActual + + val generics = + functionSymbol.typeParameters.mapIndexed { index, symbol -> visitVariantTypeParameter(index, symbol, dri) } + + return DFunction( + dri = dri, + name = functionSymbol.name.asString(), + isConstructor = false, + receiver = functionSymbol.receiverType?.let { + visitReceiverParameter( + it, + dri + ) + } // TODO replace `receiverType` with `receiverParameter` + /*functionSymbol.receiverParameter?.let { + visitReceiverParameter(it, dri) + }*/, + parameters = functionSymbol.valueParameters.mapIndexed { index, symbol -> + visitValueParameter(index, symbol, dri) + }, + expectPresentInSet = sourceSet.takeIf { isExpect }, + sources = functionSymbol.getSource(), + visibility = functionSymbol.getDokkaVisibility().toSourceSetDependent(), + generics = generics, + documentation = getDocumentation(functionSymbol)?.toSourceSetDependent() ?: emptyMap(), + modifier = functionSymbol.getDokkaModality().toSourceSetDependent(), + type = toBoundFrom(functionSymbol.returnType), + sourceSets = setOf(sourceSet), + isExpectActual = (isExpect || isActual), + extra = PropertyContainer.withAll( + inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) }, + functionSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(), + getDokkaAnnotationsFrom(functionSymbol) + ?.toSourceSetDependent()?.toAnnotations(), + ObviousMember.takeIf { isObvious(functionSymbol) }, + ) + ) + } + + private fun KtAnalysisSession.visitValueParameter( + index: Int, valueParameterSymbol: KtValueParameterSymbol, dri: DRI + ) = DParameter( + dri = dri.copy(target = PointingToCallableParameters(index)), + name = valueParameterSymbol.name.asString(), + type = toBoundFrom(valueParameterSymbol.returnType), + expectPresentInSet = null, + documentation = getDocumentation(valueParameterSymbol)?.toSourceSetDependent() ?: emptyMap(), + sourceSets = setOf(sourceSet), + extra = PropertyContainer.withAll(listOfNotNull( + valueParameterSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(), + getDokkaAnnotationsFrom(valueParameterSymbol)?.toSourceSetDependent()?.toAnnotations(), + valueParameterSymbol.getDefaultValue()?.let { DefaultValue(it.toSourceSetDependent()) } + )) + ) + + private fun KtAnalysisSession.visitReceiverParameter( + receiverParameterSymbol: KtReceiverParameterSymbol, dri: DRI + ) = DParameter( + dri = dri.copy(target = PointingToDeclaration), + name = null, + type = toBoundFrom(receiverParameterSymbol.type), + expectPresentInSet = null, + documentation = getDocumentation(receiverParameterSymbol)?.toSourceSetDependent() ?: emptyMap(), // TODO + sourceSets = setOf(sourceSet) + //extra = PropertyContainer.withAll(getAnnotations(receiverParameterSymbol).toSourceSetDependent().toAnnotations()) + ) + + // TODO: this fun should be replaced with a function above + private fun KtAnalysisSession.visitReceiverParameter( + type: KtType, dri: DRI + ) = DParameter( + dri = dri.copy(target = PointingToDeclaration), + name = null, + type = toBoundFrom(type), + expectPresentInSet = null, + documentation = emptyMap(), // TODO + sourceSets = setOf(sourceSet) + //extra = PropertyContainer.withAll(descriptor.getAnnotations().toSourceSetDependent().toAnnotations()) + ) + + private fun KtValueParameterSymbol.getDefaultValue(): Expression? = + (psi as? KtParameter)?.defaultValue?.toDefaultValueExpression() + + private fun KtPropertySymbol.getDefaultValue(): Expression? { + return try { + psi?.children?.filterIsInstance()?.firstOrNull() + ?.toDefaultValueExpression() + } catch (e: Throwable) { + null + } + } + + + + private fun KtExpression.toDefaultValueExpression(): Expression? = when (node?.elementType) { + KtNodeTypes.INTEGER_CONSTANT -> PsiLiteralUtil.parseLong(node?.text)?.let { IntegerConstant(it) } + KtNodeTypes.FLOAT_CONSTANT -> if (node?.text?.toLowerCase()?.endsWith('f') == true) + PsiLiteralUtil.parseFloat(node?.text)?.let { FloatConstant(it) } + else PsiLiteralUtil.parseDouble(node?.text)?.let { DoubleConstant(it) } + + KtNodeTypes.BOOLEAN_CONSTANT -> BooleanConstant(node?.text == "true") + KtNodeTypes.STRING_TEMPLATE -> StringConstant(node.findChildByType(KtNodeTypes.LITERAL_STRING_TEMPLATE_ENTRY)?.text.orEmpty()) + else -> node?.text?.let { ComplexExpression(it) } + } + + + private fun KtAnalysisSession.visitVariantTypeParameter( + index: Int, + typeParameterSymbol: KtTypeParameterSymbol, + dri: DRI + ): DTypeParameter { + // val dri = typeParameterSymbol.getContainingSymbol()?.let { getDRIFrom(it) } ?: throw IllegalStateException("`getContainingSymbol` is null for type parameter") // TODO add PointingToGenericParameters(descriptor.index) + return DTypeParameter( + variantTypeParameter = TypeParameter( + dri = dri.copy(target = PointingToGenericParameters(index)), + name = typeParameterSymbol.name.asString(), + presentableName = typeParameterSymbol.getPresentableName() + ).wrapWithVariance(typeParameterSymbol.variance), + documentation = getDocumentation(typeParameterSymbol)?.toSourceSetDependent() ?: emptyMap(), + expectPresentInSet = null, + bounds = typeParameterSymbol.upperBounds.map { toBoundFrom(it) }, + sourceSets = setOf(sourceSet), + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(typeParameterSymbol)?.toSourceSetDependent()?.toAnnotations() + ) + ) + } + + private fun T.wrapWithVariance(variance: org.jetbrains.kotlin.types.Variance) = + when (variance) { + org.jetbrains.kotlin.types.Variance.INVARIANT -> Invariance(this) + org.jetbrains.kotlin.types.Variance.IN_VARIANCE -> Contravariance(this) + org.jetbrains.kotlin.types.Variance.OUT_VARIANCE -> Covariance(this) + } + + fun KtAnalysisSession.getDokkaAnnotationsFrom(annotated: KtAnnotated): List? = + with(annotationTranslator) { getAllAnnotationsFrom(annotated) }.takeUnless { it.isEmpty() } + + /** + * `createDRI` returns the DRI of the exact element and potential DRI of an element that is overriding it + * (It can be also FAKE_OVERRIDE which is in fact just inheritance of the symbol) + * + * Looking at what PSIs do, they give the DRI of the element within the classnames where it is actually + * declared and inheritedFrom as the same DRI but truncated callable part. + * Therefore, we set callable to null and take the DRI only if it is indeed coming from different class. + */ + private fun DRI.getInheritedFromDRI(dri: DRI): DRI? { + return this.copy(callable = null) + .takeIf { dri.classNames != this.classNames || dri.packageName != this.packageName } + } + + data class DRIWithOverridden(val origin: DRI, val overridden: DRI? = null) + + private fun KtAnalysisSession.createDRIWithOverridden( + callableSymbol: KtCallableSymbol, + wasOverriddenBy: DRI? = null + ): DRIWithOverridden { + if (callableSymbol is KtPropertySymbol && callableSymbol.isOverride + || callableSymbol is KtFunctionSymbol && callableSymbol.isOverride + ) { + return DRIWithOverridden(getDRIFromSymbol(callableSymbol), wasOverriddenBy) + } + + val overriddenSymbols = callableSymbol.getAllOverriddenSymbols() + // For already + return if (overriddenSymbols.isEmpty()) { + DRIWithOverridden(getDRIFromSymbol(callableSymbol), wasOverriddenBy) + } else { + createDRIWithOverridden(overriddenSymbols.first()) + } + } + + @OptIn(UnsafeCastFunction::class) + private fun KtAnnotated.getPresentableName(): String? = + this.annotationsByClassId(ClassId(FqName("kotlin"), FqName("ParameterName"), false)).firstOrNull() + ?.arguments?.firstOrNull { it.name == Name.identifier("") }?.expression + .safeAs()?.value + + +// ----------- Translators of type to bound ---------------------------------------------------------------------------- + // TODO +// private fun KtAnalysisSession.toProjection(typeProjection: KtTypeProjection): Projection = +// when (typeProjection) { +// is KtStarTypeProjection -> Star +// is KtTypeArgumentWithVariance -> toBound(typeProjection.type).wrapWithVariance(typeProjection.variance) +// } + + private fun KtAnalysisSession.toTypeConstructorFrom(classType: KtUsualClassType) = + GenericTypeConstructor( + dri = getDRIFromNonErrorClassType(classType), + projections = classType.ownTypeArguments.mapNotNull { it.type?.let { t -> toBoundFrom(t) } }, // TODO: since 1.8.20 replace `typeArguments ` with `ownTypeArguments` + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(classType)?.toSourceSetDependent()?.toAnnotations() + ) + //type.ownTypeArguments.map{ toProjection(it) } + ) + + private fun KtAnalysisSession.toFunctionalTypeConstructorFrom(functionalType: KtFunctionalType) = + FunctionalTypeConstructor( + dri = getDRIFromNonErrorClassType(functionalType), + projections = functionalType.ownTypeArguments.mapNotNull { it.type?.let { t -> toBoundFrom(t) } }, // TODO: since 1.8.20 replace `typeArguments ` with ownTypeArguments + isExtensionFunction = functionalType.receiverType != null, + isSuspendable = functionalType.isSuspend, + presentableName = functionalType.getPresentableName(), + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(functionalType)?.toSourceSetDependent()?.toAnnotations() + ) + ) + + private fun KtAnalysisSession.toBoundFrom(type: KtType): Bound = + when (type) { + is KtUsualClassType -> toTypeConstructorFrom(type) + is KtTypeParameterType -> TypeParameter( + dri = getDRIFromTypeParameter(type.symbol), + name = type.name.asString(), + presentableName = type.getPresentableName(), + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(type)?.toSourceSetDependent()?.toAnnotations() + ) + ) + + is KtClassErrorType -> UnresolvedBound(type.toString()) + is KtFunctionalType -> toFunctionalTypeConstructorFrom(type) + is KtDynamicType -> Dynamic + is KtDefinitelyNotNullType -> DefinitelyNonNullable( + toBoundFrom(type.original) + ) + + is KtCapturedType -> TODO("Not yet implemented") + is KtFlexibleType -> TypeAliased( + toBoundFrom(type.upperBound), + toBoundFrom(type.lowerBound), + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(type)?.toSourceSetDependent()?.toAnnotations() + ) + ) + + is KtIntegerLiteralType -> TODO("Not yet implemented") + is KtIntersectionType -> TODO("Not yet implemented") + is KtTypeErrorType -> UnresolvedBound(type.toString()) + }.let { + if (type.isMarkedNullable) Nullable(it) else it + } + + private fun KtAnalysisSession.buildAncestryInformationFrom( + type: KtType + ): AncestryNode { + val (interfaces, superclass) = type.getDirectSuperTypes().filterNot { it.isAny } + .partition { + val typeConstructorWithKind = toTypeConstructorWithKindFrom(it) + typeConstructorWithKind.kind == KotlinClassKindTypes.INTERFACE || + typeConstructorWithKind.kind == JavaClassKindTypes.INTERFACE + } + + return AncestryNode( + typeConstructor = toTypeConstructorWithKindFrom(type).typeConstructor, + superclass = superclass.map { buildAncestryInformationFrom(it) }.singleOrNull(), + interfaces = interfaces.map { buildAncestryInformationFrom(it) } + ) + } + + // ----------- Translators of modifiers ---------------------------------------------------------------------------- + private fun KtSymbolWithModality.getDokkaModality() = modality.toDokkaModifier() + private fun KtSymbolWithVisibility.getDokkaVisibility() = visibility.toDokkaVisibility() + private fun KtValueParameterSymbol.additionalExtras() = listOfNotNull( + ExtraModifiers.KotlinOnlyModifiers.NoInline.takeIf { isNoinline }, + ExtraModifiers.KotlinOnlyModifiers.CrossInline.takeIf { isCrossinline }, +//ExtraModifiers.KotlinOnlyModifiers.Const.takeIf { isConst }, +//ExtraModifiers.KotlinOnlyModifiers.LateInit.takeIf { isLateInit }, + ExtraModifiers.KotlinOnlyModifiers.VarArg.takeIf { isVararg } + ).toSet().takeUnless { it.isEmpty() } + + private fun KtPropertyAccessorSymbol.additionalExtras() = listOfNotNull( + ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline }, +//ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() }, +//ExtraModifiers.KotlinOnlyModifiers.TailRec.takeIf { isTailrec }, + ExtraModifiers.KotlinOnlyModifiers.Override.takeIf { isOverride } + ).toSet().takeUnless { it.isEmpty() } + + private fun KtPropertySymbol.additionalExtras() = listOfNotNull( + ExtraModifiers.KotlinOnlyModifiers.Const.takeIf { (this as? KtKotlinPropertySymbol)?.isConst == true }, + ExtraModifiers.KotlinOnlyModifiers.LateInit.takeIf { (this as? KtKotlinPropertySymbol)?.isLateInit == true }, + //ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() }, + //ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal }, + //ExtraModifiers.KotlinOnlyModifiers.Static.takeIf { isStatic }, + ExtraModifiers.KotlinOnlyModifiers.Override.takeIf { isOverride } + ).toSet().takeUnless { it.isEmpty() } + + private fun KtFunctionSymbol.additionalExtras() = listOfNotNull( + ExtraModifiers.KotlinOnlyModifiers.Infix.takeIf { isInfix }, + ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline }, + ExtraModifiers.KotlinOnlyModifiers.Suspend.takeIf { isSuspend }, + ExtraModifiers.KotlinOnlyModifiers.Operator.takeIf { isOperator }, +//ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() }, + ExtraModifiers.KotlinOnlyModifiers.TailRec.takeIf { (psi as? KtNamedFunction)?.hasModifier(KtTokens.TAILREC_KEYWORD) == true }, + ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal }, + ExtraModifiers.KotlinOnlyModifiers.Override.takeIf { isOverride } + ).toSet().takeUnless { it.isEmpty() } + + private fun KtNamedClassOrObjectSymbol.additionalExtras() = listOfNotNull( + ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { (this.psi as? KtClass)?.isInline() == true }, + ExtraModifiers.KotlinOnlyModifiers.Value.takeIf { (this.psi as? KtClass)?.isValue() == true }, + ExtraModifiers.KotlinOnlyModifiers.External.takeIf { isExternal }, + ExtraModifiers.KotlinOnlyModifiers.Inner.takeIf { isInner }, + ExtraModifiers.KotlinOnlyModifiers.Data.takeIf { isData }, + ExtraModifiers.KotlinOnlyModifiers.Fun.takeIf { isFun }, + ).toSet().takeUnless { it.isEmpty() } + + private fun Modality.toDokkaModifier() = when (this) { + Modality.FINAL -> KotlinModifier.Final + Modality.SEALED -> KotlinModifier.Sealed + Modality.OPEN -> KotlinModifier.Open + Modality.ABSTRACT -> KotlinModifier.Abstract + else -> KotlinModifier.Empty + } + + private fun org.jetbrains.kotlin.descriptors.Visibility.toDokkaVisibility(): Visibility = when (this) { + Visibilities.Public -> KotlinVisibility.Public + Visibilities.Protected -> KotlinVisibility.Protected + Visibilities.Internal -> KotlinVisibility.Internal + Visibilities.Private, Visibilities.PrivateToThis -> KotlinVisibility.Private + JavaVisibilities.ProtectedAndPackage -> KotlinVisibility.Protected + JavaVisibilities.ProtectedStaticVisibility -> KotlinVisibility.Protected + JavaVisibilities.PackageVisibility -> JavaVisibility.Default + else -> KotlinVisibility.Public + } + + private fun KtSymbol.getSource() = KtPsiDocumentableSource(psi).toSourceSetDependent() +} + + + + + + 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 index 47163f6e21..dd94d7aa04 100644 --- 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 @@ -1 +1 @@ -org.jetbrains.dokka.analysis.kotlin.symbols.compiler.CompilerSymbolsAnalysisPlugin +org.jetbrains.dokka.analysis.kotlin.symbols.compiler.SymbolsAnalysisPlugin From 6a676f850ddd889bd180603d7358cc9373b99a17 Mon Sep 17 00:00:00 2001 From: vmishenev Date: Sat, 22 Jul 2023 22:10:07 +0300 Subject: [PATCH 02/30] Introduce TypeTranslator --- .../kotlin/symbols/compiler/DRIFactory.kt | 14 +- .../symbols/compiler/TypeReferenceFactory.kt | 45 ++-- .../translators/AnnotationTranslator.kt | 7 +- .../DefaultSymbolToDocumentableTranslator.kt | 207 ++++-------------- .../symbols/translators/TypeTranslator.kt | 163 ++++++++++++++ .../kotlin/symbols/utils/isException.kt | 18 ++ 6 files changed, 260 insertions(+), 194 deletions(-) create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/DRIFactory.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/DRIFactory.kt index 479a7a4154..f1ce847d5a 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/DRIFactory.kt +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/DRIFactory.kt @@ -72,16 +72,28 @@ internal fun KtAnalysisSession.getDRIFromFunctionLike(symbol: KtFunctionLikeSymb } internal fun getDRIFromClassLike(symbol: KtClassLikeSymbol): DRI = - symbol.classIdIfNonLocal?.createDRI() ?: throw IllegalStateException() + symbol.classIdIfNonLocal?.createDRI() ?: throw IllegalStateException("Can not get class Id due to it is local") + +internal fun getDRIFromPackage(symbol: KtPackageSymbol): DRI = + DRI(packageName = symbol.fqName.asString()) + +internal fun KtAnalysisSession.getDRIFromValueParameter(symbol: KtValueParameterSymbol): DRI { + val function = symbol.getContainingSymbol() as KtFunctionLikeSymbol + val index = function.valueParameters.indexOfFirst { it.name == symbol.name } + val funDRI = getDRIFromFunctionLike(function) + return funDRI.copy(target = PointingToCallableParameters(index)) +} internal fun KtAnalysisSession.getDRIFromSymbol(symbol: KtSymbol): DRI = when (symbol) { is KtEnumEntrySymbol -> getDRIFromEnumEntry(symbol) is KtTypeParameterSymbol -> getDRIFromTypeParameter(symbol) is KtConstructorSymbol -> getDRIFromConstructor(symbol) + is KtValueParameterSymbol -> getDRIFromValueParameter(symbol) is KtVariableLikeSymbol -> getDRIFromVariableLike(symbol) is KtFunctionLikeSymbol -> getDRIFromFunctionLike(symbol) is KtClassLikeSymbol -> getDRIFromClassLike(symbol) + is KtPackageSymbol -> getDRIFromPackage(symbol) else -> throw IllegalStateException("Unknown symbol while creating DRI ") } diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/TypeReferenceFactory.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/TypeReferenceFactory.kt index 60b581c275..7861c79480 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/TypeReferenceFactory.kt +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/TypeReferenceFactory.kt @@ -1,10 +1,10 @@ package org.jetbrains.dokka.analysis.kotlin.symbols.compiler -import org.jetbrains.dokka.links.RecursiveType -import org.jetbrains.dokka.links.TypeConstructor -import org.jetbrains.dokka.links.TypeParam -import org.jetbrains.dokka.links.TypeReference +import org.jetbrains.dokka.links.* import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.KtStarTypeProjection +import org.jetbrains.kotlin.analysis.api.KtTypeArgumentWithVariance +import org.jetbrains.kotlin.analysis.api.KtTypeProjection import org.jetbrains.kotlin.analysis.api.types.* internal fun KtAnalysisSession.getTypeReferenceFrom(type: KtType): TypeReference = @@ -23,21 +23,23 @@ private fun KtAnalysisSession.getTypeReferenceFromPossiblyRecursive(type: KtType return when (type) { is KtNonErrorClassType -> TypeConstructor( type.classId.asFqNameString(), // TODO: remove '!!' - type.ownTypeArguments.mapNotNull { - it.type?.let { it1 -> - getTypeReferenceFromPossiblyRecursive( - it1, - paramTrace - ) - } - } // replace `typeArguments ` with ownTypeArguments + type.ownTypeArguments.map { + getTypeReferenceFromTypeProjection( + it, + paramTrace + ) + } ) - is KtTypeParameterType -> TypeParam(bounds = type.symbol.upperBounds.map { - getTypeReferenceFromPossiblyRecursive( - it, - paramTrace + type - ) - }) + is KtTypeParameterType -> { + val upperBoundsOrNullableAny = type.symbol.upperBounds.takeIf { it.isNotEmpty() } ?: listOf(this.builtinTypes.NULLABLE_ANY) + + TypeParam(bounds = upperBoundsOrNullableAny.map { + getTypeReferenceFromPossiblyRecursive( + it, + paramTrace + type + ) + }) + } is KtClassErrorType -> TypeConstructor(type.errorMessage, emptyList()) is KtFlexibleType -> getTypeReferenceFromPossiblyRecursive( type.upperBound, @@ -59,10 +61,9 @@ private fun KtAnalysisSession.getTypeReferenceFromPossiblyRecursive(type: KtType } // TODO -/* -fun KtAnalysisSession.getTypeReferenceFrom(typeProjection: KtTypeProjection): TypeReference = + +private fun KtAnalysisSession.getTypeReferenceFromTypeProjection(typeProjection: KtTypeProjection, paramTrace: List): TypeReference = when (typeProjection) { is KtStarTypeProjection -> StarProjection - is KtTypeArgumentWithVariance -> getTypeReferenceFrom(typeProjection.type) + is KtTypeArgumentWithVariance -> getTypeReferenceFromPossiblyRecursive(typeProjection.type, paramTrace) } -*/ \ No newline at end of file diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt index de754e8176..45aa721248 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt @@ -7,21 +7,20 @@ import org.jetbrains.dokka.model.ClassValue import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.annotations.* import org.jetbrains.kotlin.analysis.api.base.KtConstantValue -import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolOrigin import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtProperty internal class AnnotationTranslator { private fun KtAnalysisSession.getFileLevelAnnotationsFrom(symbol: KtSymbol) = if (symbol.origin != KtSymbolOrigin.SOURCE) null else - (symbol.psi?.containingFile as? KtFile)?.getFileSymbol()?.annotations?.map { toDokkaAnnotation(it) } + (symbol.psi?.containingFile as? KtFile)?.getFileSymbol()?.annotations + ?.map { toDokkaAnnotation(it) } private fun KtAnalysisSession.getDirectAnnotationsFrom(annotated: KtAnnotated) = annotated.annotations.map { toDokkaAnnotation(it) } @@ -32,6 +31,7 @@ internal class AnnotationTranslator { return directAnnotations + fileLevelAnnotations } + private fun KtAnnotationApplication.isNoExistedInSource() = psi == null private fun AnnotationUseSiteTarget.toDokkaAnnotationScope(): Annotations.AnnotationScope = when (this) { AnnotationUseSiteTarget.PROPERTY_GETTER -> Annotations.AnnotationScope.DIRECT // due to compatibility with Dokka K1 AnnotationUseSiteTarget.PROPERTY_SETTER -> Annotations.AnnotationScope.DIRECT // due to compatibility with Dokka K1 @@ -40,6 +40,7 @@ internal class AnnotationTranslator { } private fun KtAnalysisSession.mustBeDocumented(annotationApplication: KtAnnotationApplication): Boolean { + if(annotationApplication.isNoExistedInSource()) return false val annotationClass = getClassOrObjectSymbolByClassId(annotationApplication.classId ?: return false) return annotationClass?.hasAnnotation(ClassId(FqName("kotlin.annotation"), FqName("MustBeDocumented"), false)) ?: false diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt index 248f697b17..540daa2b97 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt @@ -4,18 +4,16 @@ package org.jetbrains.dokka.analysis.kotlin.symbols.translators import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.* -import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getDRIFromNonErrorClassType import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getDRIFromSymbol -import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getDRIFromTypeParameter -import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getDocumentation +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.getDocumentation import com.intellij.psi.util.PsiLiteralUtil import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.analysis.kotlin.symbols.AnalysisContext import org.jetbrains.dokka.analysis.kotlin.symbols.KtPsiDocumentableSource +import org.jetbrains.dokka.analysis.kotlin.symbols.utils.typeConstructorsBeingExceptions import org.jetbrains.dokka.links.* import org.jetbrains.dokka.links.Callable import org.jetbrains.dokka.model.* -import org.jetbrains.dokka.model.Nullable import org.jetbrains.dokka.model.Visibility import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.plugability.DokkaContext @@ -27,14 +25,11 @@ import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.analysis.api.* import org.jetbrains.kotlin.analysis.api.annotations.* import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotated -import org.jetbrains.kotlin.analysis.api.base.KtConstantValue import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithVisibility import org.jetbrains.kotlin.analysis.api.types.* -import org.jetbrains.kotlin.analysis.api.types.KtDynamicType -import org.jetbrains.kotlin.analysis.api.types.KtIntersectionType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.java.JavaVisibilities import org.jetbrains.kotlin.lexer.KtTokens @@ -43,11 +38,10 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier -import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier -import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction import java.nio.file.Paths +// 747 / 839 // 487 / 707 class DefaultSymbolToDocumentableTranslator(context: DokkaContext) : AsyncSourceToDocumentableTranslator { private val kotlinAnalysis = context.plugin().querySingle { kotlinAnalysis } @@ -82,14 +76,27 @@ class TranslatorError(message: String, cause: Throwable?) : IllegalStateExceptio e ) } +internal fun T.wrapWithVariance(variance: org.jetbrains.kotlin.types.Variance) = + when (variance) { + org.jetbrains.kotlin.types.Variance.INVARIANT -> Invariance(this) + org.jetbrains.kotlin.types.Variance.IN_VARIANCE -> Contravariance(this) + org.jetbrains.kotlin.types.Variance.OUT_VARIANCE -> Covariance(this) + } + +val parameterName = ClassId(FqName("kotlin"), FqName("ParameterName"), false) +internal fun KtAnnotated.getPresentableName(): String? = + this.annotationsByClassId(parameterName) + .firstOrNull()?.arguments?.firstOrNull { it.name == Name.identifier("name") }?.expression?.let { it as? KtConstantAnnotationValue } + ?.let { it.constantValue.value.toString() } -private class DokkaSymbolVisitor( +internal class DokkaSymbolVisitor( private val sourceSet: DokkaConfiguration.DokkaSourceSet, private val moduleName: String, private val analysisContext: AnalysisContext, private val logger: DokkaLogger ) { private var annotationTranslator = AnnotationTranslator() + private var typeTranslator = TypeTranslator(sourceSet, annotationTranslator) /** * to avoid recursive classes @@ -169,8 +176,7 @@ private class DokkaSymbolVisitor( } fun KtAnalysisSession.visitPackageSymbol(packageSymbol: KtPackageSymbol): DPackage { - val name = packageSymbol.fqName.asString().takeUnless { it.isBlank() } ?: "" - val dri = DRI(packageName = name) + val dri = getDRIFromPackage(packageSymbol) val scope = packageSymbol.getPackageScope() val callables = scope.getCallableSymbols().toList().filterSymbolsInSourceSet() val classifiers = scope.getClassifierSymbols().toList().filterSymbolsInSourceSet() @@ -200,6 +206,8 @@ private class DokkaSymbolVisitor( val name = typeAliasSymbol.name.asString() val dri = parent.withClass(name) + val ancestryInfo = with(typeTranslator) { buildAncestryInformationFrom(typeAliasSymbol.expandedType) } + val generics = typeAliasSymbol.typeParameters.mapIndexed { index, symbol -> visitVariantTypeParameter(index, symbol, dri) } @@ -217,8 +225,8 @@ private class DokkaSymbolVisitor( generics = generics, sources = typeAliasSymbol.getSource(), extra = PropertyContainer.withAll( - getDokkaAnnotationsFrom(typeAliasSymbol)?.toSourceSetDependent()?.toAnnotations() - //info.exceptionInSupertypesOrNull(), + getDokkaAnnotationsFrom(typeAliasSymbol)?.toSourceSetDependent()?.toAnnotations(), + ancestryInfo.exceptionInSupertypesOrNull(), ) ) @@ -268,10 +276,10 @@ private class DokkaSymbolVisitor( ) } - val ancestryInfo = buildAncestryInformationFrom(namedClassOrObjectSymbol.buildSelfClassType()) + val ancestryInfo = with(typeTranslator) { buildAncestryInformationFrom(namedClassOrObjectSymbol.buildSelfClassType()) } val supertypes = //(ancestryInfo.interfaces.map{ it.typeConstructor } + listOfNotNull(ancestryInfo.superclass?.typeConstructor)) - namedClassOrObjectSymbol.superTypes.filterNot { it.isAny }.map { toTypeConstructorWithKindFrom(it) } + namedClassOrObjectSymbol.superTypes.filterNot { it.isAny }.map { with(typeTranslator) { toTypeConstructorWithKindFrom(it) } } .toSourceSetDependent() return@withExceptionCatcher when (namedClassOrObjectSymbol.classKind) { @@ -294,7 +302,7 @@ private class DokkaSymbolVisitor( ?.toAdditionalModifiers(), getDokkaAnnotationsFrom(namedClassOrObjectSymbol)?.toSourceSetDependent()?.toAnnotations(), ImplementedInterfaces(ancestryInfo.allImplementedInterfaces().toSourceSetDependent()), - //ancestryInfo.exceptionInSupertypesOrNull() + ancestryInfo.exceptionInSupertypesOrNull() ) ) @@ -324,7 +332,7 @@ private class DokkaSymbolVisitor( namedClassOrObjectSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(), getDokkaAnnotationsFrom(namedClassOrObjectSymbol)?.toSourceSetDependent()?.toAnnotations(), ImplementedInterfaces(ancestryInfo.allImplementedInterfaces().toSourceSetDependent()), - // info.ancestry.exceptionInSupertypesOrNull() + ancestryInfo.exceptionInSupertypesOrNull() ) ) @@ -352,7 +360,7 @@ private class DokkaSymbolVisitor( namedClassOrObjectSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(), getDokkaAnnotationsFrom(namedClassOrObjectSymbol)?.toSourceSetDependent()?.toAnnotations(), ImplementedInterfaces(ancestryInfo.allImplementedInterfaces().toSourceSetDependent()), - // info.ancestry.exceptionInSupertypesOrNull() + ancestryInfo.exceptionInSupertypesOrNull() ) ) @@ -466,63 +474,9 @@ private class DokkaSymbolVisitor( ) } - fun KtClassKind.toDokkaClassKind() = when (this) { - KtClassKind.CLASS -> KotlinClassKindTypes.CLASS - KtClassKind.ENUM_CLASS -> KotlinClassKindTypes.ENUM_CLASS - KtClassKind.ANNOTATION_CLASS -> KotlinClassKindTypes.ANNOTATION_CLASS - KtClassKind.OBJECT -> KotlinClassKindTypes.OBJECT - KtClassKind.COMPANION_OBJECT -> KotlinClassKindTypes.OBJECT - KtClassKind.INTERFACE -> KotlinClassKindTypes.INTERFACE - KtClassKind.ANONYMOUS_OBJECT -> KotlinClassKindTypes.OBJECT - } - private fun KtAnalysisSession.toTypeConstructorWithKindFrom(type: KtType): TypeConstructorWithKind { - try { - return when (type) { - is KtUsualClassType -> - when (val classSymbol = type.classSymbol) { - is KtNamedClassOrObjectSymbol -> TypeConstructorWithKind( - toTypeConstructorFrom(type), - classSymbol.classKind.toDokkaClassKind() - ) - is KtAnonymousObjectSymbol -> TODO() - is KtTypeAliasSymbol -> toTypeConstructorWithKindFrom(classSymbol.expandedType) - } - is KtClassErrorType -> TypeConstructorWithKind( - GenericTypeConstructor( - dri = DRI("[error]", "", null), - projections = emptyList(), // TODO: since 1.8.20 replace `typeArguments ` with `ownTypeArguments` - ), - KotlinClassKindTypes.CLASS - ) - is KtFunctionalType -> TypeConstructorWithKind( - toFunctionalTypeConstructorFrom(type), - KotlinClassKindTypes.CLASS - ) - is KtDefinitelyNotNullType -> toTypeConstructorWithKindFrom(type.original) - -// is KtCapturedType -> TODO() -// is KtDynamicType -> TODO() -// is KtFlexibleType -> TODO() -// is KtIntegerLiteralType -> TODO() -// is KtIntersectionType -> TODO() -// is KtTypeParameterType -> TODO() - else -> throw IllegalStateException("Unknown ") - } - } catch (e: Throwable) { - logger.error("Type error: ${type.asStringForDebugging()}\n" + e) // TODO - return TypeConstructorWithKind( - GenericTypeConstructor( - dri = DRI("[error]", "", null), - projections = emptyList(), // TODO: since 1.8.20 replace `typeArguments ` with `ownTypeArguments` - - ), - KotlinClassKindTypes.CLASS - ) - //throw IllegalStateException("Type error: ${type.asStringForDebugging()}" + type, e) - } - } + private fun KtAnalysisSession.visitPropertySymbol(propertySymbol: KtPropertySymbol, parent: DRI): DProperty { @@ -821,8 +775,6 @@ private class DokkaSymbolVisitor( } } - - private fun KtExpression.toDefaultValueExpression(): Expression? = when (node?.elementType) { KtNodeTypes.INTEGER_CONSTANT -> PsiLiteralUtil.parseLong(node?.text)?.let { IntegerConstant(it) } KtNodeTypes.FLOAT_CONSTANT -> if (node?.text?.toLowerCase()?.endsWith('f') == true) @@ -835,11 +787,13 @@ private class DokkaSymbolVisitor( } + private fun KtAnalysisSession.visitVariantTypeParameter( index: Int, typeParameterSymbol: KtTypeParameterSymbol, dri: DRI ): DTypeParameter { + val upperBoundsOrNullableAny = typeParameterSymbol.upperBounds.takeIf { it.isNotEmpty() } ?: listOf(this.builtinTypes.NULLABLE_ANY) // val dri = typeParameterSymbol.getContainingSymbol()?.let { getDRIFrom(it) } ?: throw IllegalStateException("`getContainingSymbol` is null for type parameter") // TODO add PointingToGenericParameters(descriptor.index) return DTypeParameter( variantTypeParameter = TypeParameter( @@ -849,7 +803,7 @@ private class DokkaSymbolVisitor( ).wrapWithVariance(typeParameterSymbol.variance), documentation = getDocumentation(typeParameterSymbol)?.toSourceSetDependent() ?: emptyMap(), expectPresentInSet = null, - bounds = typeParameterSymbol.upperBounds.map { toBoundFrom(it) }, + bounds = upperBoundsOrNullableAny.map { toBoundFrom(it) }, sourceSets = setOf(sourceSet), extra = PropertyContainer.withAll( getDokkaAnnotationsFrom(typeParameterSymbol)?.toSourceSetDependent()?.toAnnotations() @@ -857,16 +811,12 @@ private class DokkaSymbolVisitor( ) } - private fun T.wrapWithVariance(variance: org.jetbrains.kotlin.types.Variance) = - when (variance) { - org.jetbrains.kotlin.types.Variance.INVARIANT -> Invariance(this) - org.jetbrains.kotlin.types.Variance.IN_VARIANCE -> Contravariance(this) - org.jetbrains.kotlin.types.Variance.OUT_VARIANCE -> Covariance(this) - } - fun KtAnalysisSession.getDokkaAnnotationsFrom(annotated: KtAnnotated): List? = with(annotationTranslator) { getAllAnnotationsFrom(annotated) }.takeUnless { it.isEmpty() } + fun KtAnalysisSession.toBoundFrom(type: KtType) = + with(typeTranslator) { toBoundFrom(type) } + /** * `createDRI` returns the DRI of the exact element and potential DRI of an element that is overriding it * (It can be also FAKE_OVERRIDE which is in fact just inheritance of the symbol) @@ -901,94 +851,11 @@ private class DokkaSymbolVisitor( } } - @OptIn(UnsafeCastFunction::class) - private fun KtAnnotated.getPresentableName(): String? = - this.annotationsByClassId(ClassId(FqName("kotlin"), FqName("ParameterName"), false)).firstOrNull() - ?.arguments?.firstOrNull { it.name == Name.identifier("") }?.expression - .safeAs()?.value - - -// ----------- Translators of type to bound ---------------------------------------------------------------------------- - // TODO -// private fun KtAnalysisSession.toProjection(typeProjection: KtTypeProjection): Projection = -// when (typeProjection) { -// is KtStarTypeProjection -> Star -// is KtTypeArgumentWithVariance -> toBound(typeProjection.type).wrapWithVariance(typeProjection.variance) -// } - - private fun KtAnalysisSession.toTypeConstructorFrom(classType: KtUsualClassType) = - GenericTypeConstructor( - dri = getDRIFromNonErrorClassType(classType), - projections = classType.ownTypeArguments.mapNotNull { it.type?.let { t -> toBoundFrom(t) } }, // TODO: since 1.8.20 replace `typeArguments ` with `ownTypeArguments` - extra = PropertyContainer.withAll( - getDokkaAnnotationsFrom(classType)?.toSourceSetDependent()?.toAnnotations() - ) - //type.ownTypeArguments.map{ toProjection(it) } - ) - - private fun KtAnalysisSession.toFunctionalTypeConstructorFrom(functionalType: KtFunctionalType) = - FunctionalTypeConstructor( - dri = getDRIFromNonErrorClassType(functionalType), - projections = functionalType.ownTypeArguments.mapNotNull { it.type?.let { t -> toBoundFrom(t) } }, // TODO: since 1.8.20 replace `typeArguments ` with ownTypeArguments - isExtensionFunction = functionalType.receiverType != null, - isSuspendable = functionalType.isSuspend, - presentableName = functionalType.getPresentableName(), - extra = PropertyContainer.withAll( - getDokkaAnnotationsFrom(functionalType)?.toSourceSetDependent()?.toAnnotations() - ) - ) - private fun KtAnalysisSession.toBoundFrom(type: KtType): Bound = - when (type) { - is KtUsualClassType -> toTypeConstructorFrom(type) - is KtTypeParameterType -> TypeParameter( - dri = getDRIFromTypeParameter(type.symbol), - name = type.name.asString(), - presentableName = type.getPresentableName(), - extra = PropertyContainer.withAll( - getDokkaAnnotationsFrom(type)?.toSourceSetDependent()?.toAnnotations() - ) - ) - is KtClassErrorType -> UnresolvedBound(type.toString()) - is KtFunctionalType -> toFunctionalTypeConstructorFrom(type) - is KtDynamicType -> Dynamic - is KtDefinitelyNotNullType -> DefinitelyNonNullable( - toBoundFrom(type.original) - ) - is KtCapturedType -> TODO("Not yet implemented") - is KtFlexibleType -> TypeAliased( - toBoundFrom(type.upperBound), - toBoundFrom(type.lowerBound), - extra = PropertyContainer.withAll( - getDokkaAnnotationsFrom(type)?.toSourceSetDependent()?.toAnnotations() - ) - ) - - is KtIntegerLiteralType -> TODO("Not yet implemented") - is KtIntersectionType -> TODO("Not yet implemented") - is KtTypeErrorType -> UnresolvedBound(type.toString()) - }.let { - if (type.isMarkedNullable) Nullable(it) else it - } - - private fun KtAnalysisSession.buildAncestryInformationFrom( - type: KtType - ): AncestryNode { - val (interfaces, superclass) = type.getDirectSuperTypes().filterNot { it.isAny } - .partition { - val typeConstructorWithKind = toTypeConstructorWithKindFrom(it) - typeConstructorWithKind.kind == KotlinClassKindTypes.INTERFACE || - typeConstructorWithKind.kind == JavaClassKindTypes.INTERFACE - } +// ----------- Translators of type to bound ---------------------------------------------------------------------------- - return AncestryNode( - typeConstructor = toTypeConstructorWithKindFrom(type).typeConstructor, - superclass = superclass.map { buildAncestryInformationFrom(it) }.singleOrNull(), - interfaces = interfaces.map { buildAncestryInformationFrom(it) } - ) - } // ----------- Translators of modifiers ---------------------------------------------------------------------------- private fun KtSymbolWithModality.getDokkaModality() = modality.toDokkaModifier() @@ -1057,6 +924,10 @@ private class DokkaSymbolVisitor( } private fun KtSymbol.getSource() = KtPsiDocumentableSource(psi).toSourceSetDependent() + + private fun AncestryNode.exceptionInSupertypesOrNull(): ExceptionInSupertypes? = + typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() }?.let { ExceptionInSupertypes(it.toSourceSetDependent()) } + } diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt new file mode 100644 index 0000000000..b1791f7e49 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt @@ -0,0 +1,163 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.translators + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getDRIFromNonErrorClassType +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getDRIFromTypeParameter +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.properties.PropertyContainer +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.KtStarTypeProjection +import org.jetbrains.kotlin.analysis.api.KtTypeArgumentWithVariance +import org.jetbrains.kotlin.analysis.api.KtTypeProjection +import org.jetbrains.kotlin.analysis.api.annotations.* +import org.jetbrains.kotlin.analysis.api.symbols.* +import org.jetbrains.kotlin.analysis.api.types.* + +internal class TypeTranslator( + private val sourceSet: DokkaConfiguration.DokkaSourceSet, + private val annotationTranslator: AnnotationTranslator +) { + + private fun T.toSourceSetDependent() = if (this != null) mapOf(sourceSet to this) else emptyMap() + + + private fun KtAnalysisSession.toProjection(typeProjection: KtTypeProjection): Projection = + when (typeProjection) { + is KtStarTypeProjection -> Star + is KtTypeArgumentWithVariance -> toBoundFrom(typeProjection.type).wrapWithVariance(typeProjection.variance) + } + + private fun KtAnalysisSession.toTypeConstructorFrom(classType: KtUsualClassType) = + GenericTypeConstructor( + dri = getDRIFromNonErrorClassType(classType), + projections = classType.ownTypeArguments.map { toProjection(it) }, + presentableName = classType.getPresentableName(), + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(classType)?.toSourceSetDependent()?.toAnnotations() + ) + ) + + private fun KtAnalysisSession.toFunctionalTypeConstructorFrom(functionalType: KtFunctionalType) = + FunctionalTypeConstructor( + dri = getDRIFromNonErrorClassType(functionalType), + projections = functionalType.ownTypeArguments.map { toProjection(it) }, + isExtensionFunction = functionalType.receiverType != null, + isSuspendable = functionalType.isSuspend, + presentableName = functionalType.getPresentableName(), + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(functionalType)?.toSourceSetDependent()?.toAnnotations() + ) + ) + + internal fun KtAnalysisSession.toBoundFrom(type: KtType): Bound = + when (type) { + is KtUsualClassType -> toTypeConstructorFrom(type) + is KtTypeParameterType -> TypeParameter( + dri = getDRIFromTypeParameter(type.symbol), + name = type.name.asString(), + presentableName = type.getPresentableName(), + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(type)?.toSourceSetDependent()?.toAnnotations() + ) + ) + + is KtClassErrorType -> UnresolvedBound(type.toString()) + is KtFunctionalType -> toFunctionalTypeConstructorFrom(type) + is KtDynamicType -> Dynamic + is KtDefinitelyNotNullType -> DefinitelyNonNullable( + toBoundFrom(type.original) + ) + + is KtCapturedType -> TODO("Not yet implemented") + is KtFlexibleType -> TypeAliased( + toBoundFrom(type.upperBound), + toBoundFrom(type.lowerBound), + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(type)?.toSourceSetDependent()?.toAnnotations() + ) + ) + + is KtIntegerLiteralType -> TODO("Not yet implemented") + is KtIntersectionType -> TODO("Not yet implemented") + is KtTypeErrorType -> UnresolvedBound(type.toString()) + }.let { + if (type.isMarkedNullable) Nullable(it) else it + } + + fun KtAnalysisSession.buildAncestryInformationFrom( + type: KtType + ): AncestryNode { + val (interfaces, superclass) = type.getDirectSuperTypes().filterNot { it.isAny } + .partition { + val typeConstructorWithKind = toTypeConstructorWithKindFrom(it) + typeConstructorWithKind.kind == KotlinClassKindTypes.INTERFACE || + typeConstructorWithKind.kind == JavaClassKindTypes.INTERFACE + } + + return AncestryNode( + typeConstructor = toTypeConstructorWithKindFrom(type).typeConstructor, + superclass = superclass.map { buildAncestryInformationFrom(it) }.singleOrNull(), + interfaces = interfaces.map { buildAncestryInformationFrom(it) } + ) + } + + internal fun KtAnalysisSession.toTypeConstructorWithKindFrom(type: KtType): TypeConstructorWithKind { + try { + return when (type) { + is KtUsualClassType -> + when (val classSymbol = type.classSymbol) { + is KtNamedClassOrObjectSymbol -> TypeConstructorWithKind( + toTypeConstructorFrom(type), + classSymbol.classKind.toDokkaClassKind() + ) + is KtAnonymousObjectSymbol -> TODO() + is KtTypeAliasSymbol -> toTypeConstructorWithKindFrom(classSymbol.expandedType) + } + is KtClassErrorType -> TypeConstructorWithKind( + GenericTypeConstructor( + dri = DRI("[error]", "", null), + projections = emptyList(), // TODO: since 1.8.20 replace `typeArguments ` with `ownTypeArguments` + + ), + KotlinClassKindTypes.CLASS + ) + is KtFunctionalType -> TypeConstructorWithKind( + toFunctionalTypeConstructorFrom(type), + KotlinClassKindTypes.CLASS + ) + is KtDefinitelyNotNullType -> toTypeConstructorWithKindFrom(type.original) + +// is KtCapturedType -> TODO() +// is KtDynamicType -> TODO() +// is KtFlexibleType -> TODO() +// is KtIntegerLiteralType -> TODO() +// is KtIntersectionType -> TODO() +// is KtTypeParameterType -> TODO() + else -> throw IllegalStateException("Unknown ") + } + } catch (e: Throwable) { + return TypeConstructorWithKind( + GenericTypeConstructor( + dri = DRI("[error]", "", null), + projections = emptyList(), // TODO: since 1.8.20 replace `typeArguments ` with `ownTypeArguments` + + ), + KotlinClassKindTypes.CLASS + ) + //throw IllegalStateException("Type error: ${type.asStringForDebugging()}" + type, e) + } + } + private fun KtAnalysisSession.getDokkaAnnotationsFrom(annotated: KtAnnotated): List? = + with(annotationTranslator) { getAllAnnotationsFrom(annotated) }.takeUnless { it.isEmpty() } + + private fun KtClassKind.toDokkaClassKind() = when (this) { + KtClassKind.CLASS -> KotlinClassKindTypes.CLASS + KtClassKind.ENUM_CLASS -> KotlinClassKindTypes.ENUM_CLASS + KtClassKind.ANNOTATION_CLASS -> KotlinClassKindTypes.ANNOTATION_CLASS + KtClassKind.OBJECT -> KotlinClassKindTypes.OBJECT + KtClassKind.COMPANION_OBJECT -> KotlinClassKindTypes.OBJECT + KtClassKind.INTERFACE -> KotlinClassKindTypes.INTERFACE + KtClassKind.ANONYMOUS_OBJECT -> KotlinClassKindTypes.OBJECT + } +} \ No newline at end of file diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt new file mode 100644 index 0000000000..9333878a82 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt @@ -0,0 +1,18 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.utils + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.AncestryNode +import org.jetbrains.dokka.model.TypeConstructor + +internal fun AncestryNode.typeConstructorsBeingExceptions(): List { + fun traverseSupertypes(ancestry: AncestryNode): List = + listOf(ancestry.typeConstructor) + (ancestry.superclass?.let(::traverseSupertypes) ?: emptyList()) + + return traverseSupertypes(this).filter { type -> type.dri.isDirectlyAnException() } +} + +internal fun DRI.isDirectlyAnException(): Boolean = + toString().let { stringed -> + stringed == "kotlin/Exception///PointingToDeclaration/" || + stringed == "java.lang/Exception///PointingToDeclaration/" + } From d849de848ca65aa3e3fd6665e2b685584c4175a3 Mon Sep 17 00:00:00 2001 From: vmishenev Date: Mon, 24 Jul 2023 18:24:26 +0300 Subject: [PATCH 03/30] Support samles, modules docs, external documentables, FullClassHierarchyBuilder, java accessors --- .../dokka/analysis/java/util/PsiUtil.kt | 3 +- .../kotlin/symbols/AnalysisContext.kt | 30 +- ...ompilerDocumentableSourceLanguageParser.kt | 4 + .../kotlin/symbols/compiler/KotlinAnalysis.kt | 8 +- .../ModuleAndPackageDocumentationReader.kt | 28 -- .../SymbolFullClassHierarchyBuilder.kt | 12 - .../SymbolSyntheticDocumentableDetector.kt | 32 +- .../symbols/compiler/SymbolsAnalysisPlugin.kt | 36 +- .../{compiler => kdoc}/KDocProvider.kt | 18 +- .../kotlin/symbols/kdoc/ResolveKDocLink.kt | 42 +++ .../java/DescriptorDocumentationContent.kt | 15 + .../java/DescriptorKotlinDocCommentCreator.kt | 16 + .../symbols/kdoc/java/KotlinDocComment.kt | 81 +++++ .../kdoc/java/KotlinDocCommentParser.kt | 58 +++ .../KotlinInheritDocTagContentProvider.kt | 31 ++ .../IllegalModuleAndPackageDocumentation.kt | 7 + .../ModuleAndPackageDocumentation.kt | 11 + .../ModuleAndPackageDocumentationFragment.kt | 9 + ...leAndPackageDocumentationParsingContext.kt | 79 +++++ .../ModuleAndPackageDocumentationReader.kt | 110 ++++++ .../ModuleAndPackageDocumentationSource.kt | 14 + .../parseModuleAndPackageDocumentation.kt | 12 + ...eModuleAndPackageDocumentationFragments.kt | 55 +++ .../services/DefaultSamplesTransformer.kt | 35 ++ .../services/SamplesTransformerImpl.kt | 146 ++++++++ .../SymbolExternalDocumentablesProvider.kt | 42 +++ .../SymbolFullClassHierarchyBuilder.kt | 86 +++++ .../translators/AnnotationTranslator.kt | 10 +- .../{compiler => translators}/DRIFactory.kt | 38 +- .../DefaultSymbolToDocumentableTranslator.kt | 329 +++++++++++------- .../SymbolAccessorConventionUtil.kt | 148 ++++++++ .../TypeReferenceFactory.kt | 4 +- .../symbols/translators/TypeTranslator.kt | 2 - .../symbols/utils/CollectionExtensions.kt | 12 + 34 files changed, 1357 insertions(+), 206 deletions(-) delete mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/ModuleAndPackageDocumentationReader.kt delete mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolFullClassHierarchyBuilder.kt rename subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/{compiler => kdoc}/KDocProvider.kt (91%) create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/DefaultSamplesTransformer.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SamplesTransformerImpl.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolFullClassHierarchyBuilder.kt rename subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/{compiler => translators}/DRIFactory.kt (73%) create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/SymbolAccessorConventionUtil.kt rename subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/{compiler => translators}/TypeReferenceFactory.kt (94%) create mode 100644 subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/CollectionExtensions.kt 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 ed58eb56c4..eb2058d82d 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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/AnalysisContext.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/AnalysisContext.kt index 23ef825f25..4780af6134 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/AnalysisContext.kt +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/AnalysisContext.kt @@ -9,7 +9,26 @@ import org.jetbrains.dokka.model.SourceSetDependent import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession import java.io.Closeable +import java.io.File +@Suppress("FunctionName", "UNUSED_PARAMETER") +internal fun SamplesKotlinAnalysis( + sourceSets: List, + context: DokkaContext, + projectKotlinAnalysis: KotlinAnalysis +): KotlinAnalysis { + val environments = sourceSets + .filter { it.samples.isNotEmpty() } + .associateWith { sourceSet -> + createAnalysisContext( + classpath = sourceSet.classpath, + sourceRoots = sourceSet.samples, + sourceSet = sourceSet + ) + } + + return EnvironmentKotlinAnalysis(environments, projectKotlinAnalysis) +} internal fun ProjectKotlinAnalysis( sourceSets: List, @@ -25,6 +44,7 @@ internal fun ProjectKotlinAnalysis( return EnvironmentKotlinAnalysis(environments) } + @Suppress("UNUSED_PARAMETER") internal fun createAnalysisContext( context: DokkaContext, @@ -35,7 +55,15 @@ internal fun createAnalysisContext( val classpath = sourceSet.classpath + parentSourceSets.flatMap { it.classpath } val sources = sourceSet.sourceRoots + parentSourceSets.flatMap { it.sourceRoots } - return AnalysisContextImpl(createAnalysisSession(classpath, sources)) + return createAnalysisContext(classpath, sources, sourceSet) +} + +internal fun createAnalysisContext( + classpath: List, + sourceRoots: Set, + sourceSet: DokkaConfiguration.DokkaSourceSet +): AnalysisContext { + return AnalysisContextImpl(createAnalysisSession(classpath, sourceRoots, sourceSet.analysisPlatform)) } diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerDocumentableSourceLanguageParser.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerDocumentableSourceLanguageParser.kt index 29c4623bc0..f15c1472e7 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerDocumentableSourceLanguageParser.kt +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/CompilerDocumentableSourceLanguageParser.kt @@ -9,6 +9,10 @@ import org.jetbrains.kotlin.analysis.kotlin.internal.DocumentableLanguage import org.jetbrains.kotlin.analysis.kotlin.internal.DocumentableSourceLanguageParser internal class CompilerDocumentableSourceLanguageParser : DocumentableSourceLanguageParser { + + /** + * For members inherited from Java in Kotlin - it returns [DocumentableLanguage.KOTLIN] + */ override fun getLanguage( documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet, diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysis.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysis.kt index 25f3679c46..f5440e2bd3 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysis.kt +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KotlinAnalysis.kt @@ -9,7 +9,6 @@ import com.intellij.psi.PsiManager import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.ProjectScope import com.intellij.util.io.URLUtil -import org.jetbrains.dokka.DokkaDefaults.analysisPlatform import org.jetbrains.dokka.Platform import org.jetbrains.kotlin.analysis.api.impl.base.util.LibraryUtils import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionProvider @@ -141,7 +140,8 @@ fun getJdkHomeFromSystemProperty(): File? { fun createAnalysisSession( classpath: List, - sources: Set, + sourceRoots: Set, + analysisPlatform: Platform ): StandaloneAnalysisAPISession { val analysisSession = buildStandaloneAnalysisAPISession(withPsiDeclarationFromBinaryModuleProvider = false) { @@ -191,7 +191,7 @@ fun createAnalysisSession( .mapNotNull { fs.findFileByPath(it.toString()) } .mapNotNull { psiManager.findFile(it) } .map { it as KtFile }*/ - val sourcePaths = sources.map { it.absolutePath } + val sourcePaths = sourceRoots.map { it.absolutePath } val (ktFilePath, javaFilePath) = getSourceFilePaths(sourcePaths).partition { it.endsWith(KotlinFileType.EXTENSION) } val javaFiles: List = getPsiFilesFromPaths(project, javaFilePath) val ktFiles: List = getPsiFilesFromPaths(project, getSourceFilePaths(ktFilePath)) @@ -204,7 +204,7 @@ fun createAnalysisSession( }) } } - @Suppress("UnstableApiUsage") + // TODO remove further CoreApplicationEnvironment.registerExtensionPoint( analysisSession.project.extensionArea, KtResolveExtensionProvider.EP_NAME.name, diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/ModuleAndPackageDocumentationReader.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/ModuleAndPackageDocumentationReader.kt deleted file mode 100644 index 1d24346258..0000000000 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/ModuleAndPackageDocumentationReader.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.jetbrains.dokka.analysis.kotlin.symbols.compiler - -import org.jetbrains.dokka.DokkaConfiguration -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.plugability.DokkaContext -import org.jetbrains.kotlin.analysis.kotlin.internal.ModuleAndPackageDocumentationReader - -internal fun ModuleAndPackageDocumentationReader(context: DokkaContext): ModuleAndPackageDocumentationReader = - ContextModuleAndPackageDocumentationReader(context) - -private class ContextModuleAndPackageDocumentationReader( - private val context: DokkaContext -) : ModuleAndPackageDocumentationReader { - override fun read(module: DModule): SourceSetDependent { - return emptyMap() - } - - override fun read(pkg: DPackage): SourceSetDependent { - return emptyMap() - } - - override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode? { - return null - } -} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolFullClassHierarchyBuilder.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolFullClassHierarchyBuilder.kt deleted file mode 100644 index 08dead34f5..0000000000 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolFullClassHierarchyBuilder.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.jetbrains.dokka.analysis.kotlin.symbols.compiler - -import org.jetbrains.dokka.model.DModule -import org.jetbrains.kotlin.analysis.kotlin.internal.ClassHierarchy -import org.jetbrains.kotlin.analysis.kotlin.internal.FullClassHierarchyBuilder - -class SymbolFullClassHierarchyBuilder : FullClassHierarchyBuilder { - override suspend fun build(module: DModule): ClassHierarchy { - return emptyMap() - } - -} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolSyntheticDocumentableDetector.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolSyntheticDocumentableDetector.kt index 771709f95f..43793bcd9c 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolSyntheticDocumentableDetector.kt +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolSyntheticDocumentableDetector.kt @@ -1,12 +1,42 @@ package org.jetbrains.dokka.analysis.kotlin.symbols.compiler +import com.intellij.psi.PsiElement import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.java.util.PsiDocumentableSource +import org.jetbrains.dokka.analysis.kotlin.symbols.KtPsiDocumentableSource import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.InheritedMember +import org.jetbrains.dokka.model.WithSources +import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.kotlin.analysis.kotlin.internal.SyntheticDocumentableDetector class SymbolSyntheticDocumentableDetector : SyntheticDocumentableDetector { + + /** + * Currently, it's used only for [org.jetbrains.dokka.base.transformers.documentables.ReportUndocumentedTransformer] + * + * For so-called fake-ovveride declarations - we have [InheritedMember] extra. + * For synthesized declaration - we do not have PSI source. + * + * @see org.jetbrains.kotlin.analysis.api.symbols.KtSymbolOrigin.SOURCE_MEMBER_GENERATED + */ override fun isSynthetic(documentable: Documentable, sourceSet: DokkaConfiguration.DokkaSourceSet): Boolean { - TODO("Not yet implemented") + @Suppress("UNCHECKED_CAST") + val extra = (documentable as? WithExtraProperties)?.extra + val isInherited = extra?.get(InheritedMember)?.inheritedFrom?.get(sourceSet) != null + // TODO the same for JAVA? + val isSynthesized = documentable.getPsi(sourceSet) == null + return isInherited || isSynthesized + } + + private fun Documentable.getPsi(sourceSet: DokkaConfiguration.DokkaSourceSet): PsiElement? { + val documentableSource = (this as? WithSources)?.sources?.get(sourceSet) ?: return null + return when (documentableSource) { + is PsiDocumentableSource -> documentableSource.psi + is KtPsiDocumentableSource -> documentableSource.psi + else -> error("Unknown language sources: ${documentableSource::class}") + } } + } diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolsAnalysisPlugin.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolsAnalysisPlugin.kt index 5984cba329..e33f213039 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolsAnalysisPlugin.kt +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/SymbolsAnalysisPlugin.kt @@ -5,16 +5,23 @@ import com.intellij.psi.PsiAnnotation import org.jetbrains.dokka.CoreExtensions import org.jetbrains.dokka.analysis.java.BreakingAbstractionKotlinLightMethodChecker import org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java.KotlinInheritDocTagContentProvider import org.jetbrains.dokka.analysis.kotlin.symbols.KotlinAnalysis import org.jetbrains.dokka.analysis.kotlin.symbols.ProjectKotlinAnalysis +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java.DescriptorKotlinDocCommentCreator +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java.KotlinDocCommentParser +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs.ModuleAndPackageDocumentationReader +import org.jetbrains.dokka.analysis.kotlin.symbols.services.DefaultSamplesTransformer +import org.jetbrains.dokka.analysis.kotlin.symbols.services.SymbolExternalDocumentablesProvider +import org.jetbrains.dokka.analysis.kotlin.symbols.services.SymbolFullClassHierarchyBuilder import org.jetbrains.dokka.analysis.kotlin.symbols.translators.DefaultSymbolToDocumentableTranslator 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.kotlin.analysis.kotlin.internal.InternalKotlinAnalysisPlugin import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation +@Suppress("unused") class SymbolsAnalysisPlugin : DokkaPlugin() { val kotlinAnalysis by extensionPoint() @@ -45,24 +52,23 @@ class SymbolsAnalysisPlugin : DokkaPlugin() { internal val sourceRootsExtractor by extending { javaAnalysisPlugin.sourceRootsExtractor providing { KotlinAnalysisSourceRootsExtractor() } } - +*/ internal val kotlinDocCommentCreator by extending { javaAnalysisPlugin.docCommentCreators providing { - DescriptorKotlinDocCommentCreator(querySingle { kdocFinder }, querySingle { descriptorFinder }) + DescriptorKotlinDocCommentCreator() } } internal val kotlinDocCommentParser by extending { javaAnalysisPlugin.docCommentParsers providing { context -> - DescriptorKotlinDocCommentParser( - context, - context.logger + KotlinDocCommentParser( + context ) } } internal val inheritDocTagProvider by extending { javaAnalysisPlugin.inheritDocTagContentProviders providing ::KotlinInheritDocTagContentProvider - }*/ + } internal val kotlinLightMethodChecker by extending { javaAnalysisPlugin.kotlinLightMethodChecker providing { object : BreakingAbstractionKotlinLightMethodChecker { @@ -78,14 +84,14 @@ class SymbolsAnalysisPlugin : DokkaPlugin() { } - internal val documentableAnalyzerImpl by extending { + internal val symbolAnalyzerImpl by extending { plugin().documentableSourceLanguageParser providing { CompilerDocumentableSourceLanguageParser() } } - internal val descriptorFullClassHierarchyBuilder by extending { + internal val symbolFullClassHierarchyBuilder by extending { plugin().fullClassHierarchyBuilder providing { SymbolFullClassHierarchyBuilder() } } - internal val descriptorSyntheticDocumentableDetector by extending { + internal val symbolSyntheticDocumentableDetector by extending { plugin().syntheticDocumentableDetector providing { SymbolSyntheticDocumentableDetector() } } @@ -99,11 +105,15 @@ class SymbolsAnalysisPlugin : DokkaPlugin() { intern val descriptorInheritanceBuilder by extending { plugin().inheritanceBuilder providing { DescriptorInheritanceBuilder() } + } +*/ + internal val symbolExternalDocumentablesProvider by extending { + plugin().externalDocumentablesProvider providing ::SymbolExternalDocumentablesProvider } - internal val defaultExternalDocumentablesProvider by extending { - plugin().externalDocumentablesProvider providing ::DefaultExternalDocumentablesProvider - }*/ + internal val defaultSamplesTransformer by extending { + CoreExtensions.pageTransformer providing ::DefaultSamplesTransformer + } @OptIn(DokkaPluginApiPreview::class) override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KDocProvider.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt similarity index 91% rename from subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KDocProvider.kt rename to subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt index 28db2c76da..8348a6e526 100644 --- a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/compiler/KDocProvider.kt +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt @@ -1,8 +1,7 @@ -package org.jetbrains.dokka.analysis.kotlin.symbols.compiler +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.parseFromKDocTag - +import org.jetbrains.dokka.analysis.kotlin.symbols.translators.getDRIFromSymbol import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol @@ -32,7 +31,12 @@ internal fun KtAnalysisSession.getDocumentation(symbol: KtSymbol) = findKDoc(sym parseFromKDocTag( kDocTag = it.contentTag, - externalDri = { _ -> + externalDri = { link -> + val linkedSymbol = resolveKDocLink(link, symbol) + if (linkedSymbol == null) null + else getDRIFromSymbol(linkedSymbol) + + // try { // resolveKDocLink( // context = resolutionFacade.resolveSession.bindingContext, @@ -45,13 +49,14 @@ internal fun KtAnalysisSession.getDocumentation(symbol: KtSymbol) = findKDoc(sym // logger.warn("Couldn't resolve link for $link") // null // } - null }, kdocLocation = kdocLocation ) } // TODO ?: getJavaDocs())?.takeIf { it.children.isNotEmpty() } + + // ----------- copy-paste from IDE ---------------------------------------------------------------------------- data class KDocContent( @@ -60,7 +65,8 @@ data class KDocContent( ) internal fun KtAnalysisSession.findKDoc(symbol: KtSymbol): KDocContent? { - if (symbol.origin == KtSymbolOrigin.LIBRARY) return null + // for generated function (e.g. `copy`) psi returns class, see test `data class kdocs over generated methods` + if (symbol.origin == KtSymbolOrigin.LIBRARY || symbol.origin == KtSymbolOrigin.SOURCE_MEMBER_GENERATED) return null val ktElement = symbol.psi as? KtElement ktElement?.findKDoc()?.let { return it diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt new file mode 100644 index 0000000000..9da4116368 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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 org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.scopes.KtScope +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithMembers +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtElement + +/** + * Give priority to callable independent of scope order + */ +private fun KtAnalysisSession.searchInScope(scope: KtScope, inndexOfLinkSegment: Int, linkSegments: List): KtSymbol? { + val identifier = linkSegments[inndexOfLinkSegment] + val isLastPart = inndexOfLinkSegment == linkSegments.size - 1 + if(isLastPart) { + val callableSymbols = scope.getCallableSymbols { name -> name == identifier } + callableSymbols.firstOrNull()?.let { return it } + } + + val classifierSymbols = scope.getClassifierSymbols { name -> name == identifier } + if(isLastPart) classifierSymbols.firstOrNull()?.let{ return it } + val packageSymbols = scope.getPackageSymbols { name -> name == identifier } + if(isLastPart) return packageSymbols.firstOrNull() + + val packageAndClassifierScopes = packageSymbols.map { it.getPackageScope() } + classifierSymbols.filterIsInstance().map { it.getDeclaredMemberScope() } + val compositeScope = packageAndClassifierScopes.toList().asCompositeScope() + + return searchInScope(compositeScope, inndexOfLinkSegment + 1, linkSegments) +} +internal fun KtAnalysisSession.resolveKDocLink(link: String, contextSymbol: KtSymbol) = + (contextSymbol.psi as? KtElement)?.let { ktElement -> resolveKDocLink(link, ktElement) } + +internal fun KtAnalysisSession.resolveKDocLink(link: String, contextElement: KtElement): KtSymbol? { + val linkParts = link.split(".").map { Name.identifier(it) } + + val file = contextElement.containingKtFile + val scope = file.getScopeContextForPosition(contextElement).getCompositeScope() + + return searchInScope(scope, 0, linkParts) + ?: searchInScope(ROOT_PACKAGE_SYMBOL.getPackageScope(), 0, linkParts) +} \ No newline at end of file diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt new file mode 100644 index 0000000000..a36516030e --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt new file mode 100644 index 0000000000..399d005ae3 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt new file mode 100644 index 0000000000..385b5328bc --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt new file mode 100644 index 0000000000..b8d9def4e3 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt @@ -0,0 +1,58 @@ +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.compiler.SymbolsAnalysisPlugin +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getPsiFilesFromPaths +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getSourceFilePaths +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.parseFromKDocTag +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocLink +import org.jetbrains.dokka.analysis.kotlin.symbols.translators.getDRIFromSymbol +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 +import org.jetbrains.kotlin.psi.KtFile + +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 } + val someKtFile = getPsiFilesFromPaths( + kotlinAnalysis[sourceSet].project, + getSourceFilePaths(sourceSet.sourceRoots.map { it.canonicalPath }) + ).firstOrNull() ?: throw IllegalStateException() + + analyze(someKtFile) { + return parseFromKDocTag( + kDocTag = element.comment, + externalDri = { link: String -> + val linkedSymbol = resolveKDocLink(link, element.resolveDocContext.ktElement) + if (linkedSymbol == null) null + else getDRIFromSymbol(linkedSymbol) + }, + kdocLocation = null, + parseWithChildren = parseWithChildren + ) + } + } +} + diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt new file mode 100644 index 0000000000..6d2bfff4f3 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt new file mode 100644 index 0000000000..6e3f215acb --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt new file mode 100644 index 0000000000..46f714cb95 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt new file mode 100644 index 0000000000..1eafc6880f --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt new file mode 100644 index 0000000000..260d7c88ea --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt @@ -0,0 +1,79 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.kotlin.symbols.KotlinAnalysis +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getPsiFilesFromPaths +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getSourceFilePaths +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.resolveKDocLink +import org.jetbrains.dokka.analysis.kotlin.symbols.translators.getDRIFromSymbol +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 +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtFile + +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 { + // TODO research another ways to get AnalysisSession + val analysisContext = kotlinAnalysis[sourceSet] + val someKtFile = getPsiFilesFromPaths( + analysisContext.project, + getSourceFilePaths(sourceSet.sourceRoots.map { it.canonicalPath }) + ).firstOrNull() + + if (someKtFile == null) + MarkdownParser(externalDri = { null }, sourceLocation) + else + analyze(someKtFile) { + val contextSymbol = when (fragment.classifier) { + Module -> getPackageSymbolIfPackageExists(FqName.topLevel(Name.identifier(""))) + Package -> getPackageSymbolIfPackageExists(FqName(fragment.name)) + } + + val externalDri = { link: String -> + try { + val linkedSymbol = contextSymbol?.let { resolveKDocLink(link, it) } + if (linkedSymbol == null) null + else getDRIFromSymbol(linkedSymbol) + } catch (e1: IllegalArgumentException) { + logger.warn("Couldn't resolve link for $link") + null + } + } + + MarkdownParser(externalDri = externalDri, sourceLocation) + } + } +} +/* +private fun Collection.sorted() = sortedWith( + compareBy( + { it is ClassDescriptor }, + { (it as? FunctionDescriptor)?.name }, + { (it as? FunctionDescriptor)?.valueParameters?.size }, + { (it as? FunctionDescriptor)?.valueParameters?.joinToString { it.type.toString() } } + ) +) + */ diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt new file mode 100644 index 0000000000..d8cceb926d --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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.compiler.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.kotlin.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] // 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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt new file mode 100644 index 0000000000..d7877559da --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt new file mode 100644 index 0000000000..b92adf4a68 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/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/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt new file mode 100644 index 0000000000..ae728a2838 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt @@ -0,0 +1,55 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs + +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 java.io.File + +internal fun parseModuleAndPackageDocumentationFragments(source: File): List { + return parseModuleAndPackageDocumentationFragments(ModuleAndPackageDocumentationFile(source)) +} + +internal fun parseModuleAndPackageDocumentationFragments( + source: ModuleAndPackageDocumentationSource +): List { + val fragmentStrings = source.documentation.split(Regex("(|^)#\\s*(?=(Module|Package))")) + return fragmentStrings + .filter(String::isNotBlank) + .map { fragmentString -> parseModuleAndPackageDocFragment(source, fragmentString) } +} + +private fun parseModuleAndPackageDocFragment( + source: ModuleAndPackageDocumentationSource, + fragment: String +): ModuleAndPackageDocumentationFragment { + val firstLineAndDocumentation = fragment.split("\r\n", "\n", "\r", limit = 2) + val firstLine = firstLineAndDocumentation[0] + + val classifierAndName = firstLine.split(Regex("\\s+"), limit = 2) + + val classifier = when (classifierAndName[0].trim()) { + "Module" -> Module + "Package" -> Package + else -> throw IllegalStateException( + """Unexpected classifier: "${classifierAndName[0]}", expected either "Module" or "Package". + |For more information consult the specification: https://kotlinlang.org/docs/dokka-module-and-package-docs.html""".trimMargin() + ) + } + + if (classifierAndName.size != 2 && classifier == Module) { + throw IllegalModuleAndPackageDocumentation(source, "Missing Module name") + } + + val name = classifierAndName.getOrNull(1)?.trim().orEmpty() + if (classifier == Package && name.contains(Regex("\\s"))) { + throw IllegalModuleAndPackageDocumentation( + source, "Package name cannot contain whitespace in '$firstLine'" + ) + } + + return ModuleAndPackageDocumentationFragment( + name = name, + classifier = classifier, + documentation = firstLineAndDocumentation.getOrNull(1)?.trim().orEmpty(), + source = source + ) +} diff --git a/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/DefaultSamplesTransformer.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/DefaultSamplesTransformer.kt new file mode 100644 index 0000000000..92d4c67596 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/DefaultSamplesTransformer.kt @@ -0,0 +1,35 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.services + +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-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SamplesTransformerImpl.kt b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SamplesTransformerImpl.kt new file mode 100644 index 0000000000..a07b48a104 --- /dev/null +++ b/subprojects/analysis-kotlin-symbols/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SamplesTransformerImpl.kt @@ -0,0 +1,146 @@ +package org.jetbrains.dokka.analysis.kotlin.symbols.services + +import com.intellij.psi.PsiElement +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.kotlin.symbols.KotlinAnalysis +import org.jetbrains.dokka.analysis.kotlin.symbols.SamplesKotlinAnalysis +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.SymbolsAnalysisPlugin +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getPsiFilesFromPaths +import org.jetbrains.dokka.analysis.kotlin.symbols.compiler.getSourceFilePaths +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.analysis.api.analyze +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtFile + + +internal const val KOTLIN_PLAYGROUND_SCRIPT = "" + +internal abstract class SamplesTransformerImpl(val context: DokkaContext) : PageTransformer { + + abstract fun processBody(psiElement: PsiElement): String + abstract fun processImports(psiElement: PsiElement): String + + final override fun invoke(input: RootPageNode): RootPageNode { + + val analysis = SamplesKotlinAnalysis( + sourceSets = context.configuration.sourceSets, + context = context, + projectKotlinAnalysis = context.plugin().querySingle { kotlinAnalysis } + ) + return 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, analysis), + embeddedResources = acc.embeddedResources + KOTLIN_PLAYGROUND_SCRIPT + ) + } ?: page + } + } + + + private fun ContentNode.addSample( + contentPage: ContentPage, + sourceSet: DokkaConfiguration.DokkaSourceSet, + fqName: String, + kotlinAnalysis: KotlinAnalysis + ): ContentNode { + val analysisContext = kotlinAnalysis[sourceSet] + // TODO research another ways to get AnalysisSession + val someKtFile = getPsiFilesFromPaths( + analysisContext.project, + getSourceFilePaths(sourceSet.samples.map { it.canonicalPath }) + ).first() + // TODO Potential bug + val psiElement = analyze(someKtFile) { + val lastDotIndex = fqName.lastIndexOf('.') + + val functionName = if (lastDotIndex == -1) fqName else fqName.substring(lastDotIndex + 1, fqName.length) + val packageName = if (lastDotIndex == -1) "" else fqName.substring(0, lastDotIndex) + getTopLevelCallableSymbols(FqName(packageName), Name.identifier(functionName)).firstOrNull()?.psi + } + ?: 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 contentCode( + sourceSets: Set, + dri: Set, + content: String, + language: String, + styles: Set