aboutsummaryrefslogtreecommitdiff
path: root/subprojects/analysis-kotlin-symbols/src/main/kotlin/org
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/analysis-kotlin-symbols/src/main/kotlin/org')
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt170
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt97
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt42
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt61
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt15
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt16
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt81
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt46
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt31
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt7
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt11
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt9
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt47
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt110
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt14
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt12
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt55
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/AnalysisContext.kt139
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/KotlinAnalysis.kt245
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt121
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisProjectProvider.kt16
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisSourceRootsExtractor.kt12
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinDocumentableSourceLanguageParser.kt26
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt85
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KtPsiDocumentableSource.kt20
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt34
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolFullClassHierarchyBuilder.kt85
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSyntheticDocumentableDetector.kt41
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt134
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DRIFactory.kt140
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt949
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TranslatorError.kt29
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeReferenceFactory.kt78
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt186
-rw-r--r--subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt18
35 files changed, 3182 insertions, 0 deletions
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt
new file mode 100644
index 00000000..c1b8651a
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt
@@ -0,0 +1,170 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc
+
+import com.intellij.psi.PsiNamedElement
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.dokka.analysis.java.parsers.JavadocParser
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
+import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
+import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
+import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolOrigin
+import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
+import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
+import org.jetbrains.kotlin.kdoc.psi.api.KDoc
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
+import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
+import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
+import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter
+import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
+
+internal fun KtAnalysisSession.getJavaDocDocumentationFrom(
+ symbol: KtSymbol,
+ javadocParser: JavadocParser
+): DocumentationNode? {
+ if (symbol.origin == KtSymbolOrigin.JAVA) {
+ return (symbol.psi as? PsiNamedElement)?.let {
+ javadocParser.parseDocumentation(it)
+ }
+ } else if (symbol.origin == KtSymbolOrigin.SOURCE && symbol is KtCallableSymbol) {
+ // Note: javadocParser searches in overridden JAVA declarations for JAVA method, not Kotlin
+ symbol.getAllOverriddenSymbols().forEach { overrider ->
+ if (overrider.origin == KtSymbolOrigin.JAVA)
+ return@getJavaDocDocumentationFrom (overrider.psi as? PsiNamedElement)?.let {
+ javadocParser.parseDocumentation(it)
+ }
+ }
+ }
+ return null
+}
+
+internal fun KtAnalysisSession.getKDocDocumentationFrom(symbol: KtSymbol, logger: DokkaLogger) = findKDoc(symbol)?.let { kDocContent ->
+
+ val ktElement = symbol.psi
+ val kdocLocation = ktElement?.containingFile?.name?.let {
+ val name = when(symbol) {
+ is KtCallableSymbol -> symbol.callableIdIfNonLocal?.toString()
+ is KtClassOrObjectSymbol -> symbol.classIdIfNonLocal?.toString()
+ is KtNamedSymbol -> symbol.name.asString()
+ else -> null
+ }?.replace('/', '.') // replace to be compatible with K1
+
+ if (name != null) "$it/$name"
+ else it
+ }
+
+
+ parseFromKDocTag(
+ kDocTag = kDocContent.contentTag,
+ externalDri = { link -> resolveKDocLink(link).logIfNotResolved(link.getLinkText(), logger) },
+ kdocLocation = kdocLocation
+ )
+}
+
+
+
+
+// ----------- copy-paste from IDE ----------------------------------------------------------------------------
+
+internal data class KDocContent(
+ val contentTag: KDocTag,
+ val sections: List<KDocSection>
+)
+
+internal fun KtAnalysisSession.findKDoc(symbol: KtSymbol): KDocContent? {
+ // for generated function (e.g. `copy`) psi returns class, see test `data class kdocs over generated methods`
+ if (symbol.origin != KtSymbolOrigin.SOURCE) return null
+ val ktElement = symbol.psi as? KtElement
+ ktElement?.findKDoc()?.let {
+ return it
+ }
+
+ if (symbol is KtCallableSymbol) {
+ symbol.getAllOverriddenSymbols().forEach { overrider ->
+ findKDoc(overrider)?.let {
+ return it
+ }
+ }
+ }
+ return null
+}
+
+
+internal fun KtElement.findKDoc(): KDocContent? = this.lookupOwnedKDoc()
+ ?: this.lookupKDocInContainer()
+
+
+
+private fun KtElement.lookupOwnedKDoc(): KDocContent? {
+ // KDoc for primary constructor is located inside of its class KDoc
+ val psiDeclaration = when (this) {
+ is KtPrimaryConstructor -> getContainingClassOrObject()
+ else -> this
+ }
+
+ if (psiDeclaration is KtDeclaration) {
+ val kdoc = psiDeclaration.docComment
+ if (kdoc != null) {
+ if (this is KtConstructor<*>) {
+ // ConstructorDescriptor resolves to the same JetDeclaration
+ val constructorSection = kdoc.findSectionByTag(KDocKnownTag.CONSTRUCTOR)
+ if (constructorSection != null) {
+ // if annotated with @constructor tag and the caret is on constructor definition,
+ // then show @constructor description as the main content, and additional sections
+ // that contain @param tags (if any), as the most relatable ones
+ // practical example: val foo = Fo<caret>o("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<KDocSection> {
+ return getChildrenOfType<KDocSection>()
+ .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<KDoc>()
+ if (containerKDoc == null || subjectName == null) return null
+ val propertySection = containerKDoc.findSectionByTag(KDocKnownTag.PROPERTY, subjectName)
+ val paramTag =
+ containerKDoc.findDescendantOfType<KDocTag> { it.knownTag == KDocKnownTag.PARAM && it.getSubjectName() == subjectName }
+
+ val primaryContent = when {
+ // class Foo(val <caret>s: String)
+ this is KtParameter && this.isPropertyParameter() -> propertySection ?: paramTag
+ // fun some(<caret>f: String) || class Some<<caret>T: Base> || Foo(<caret>s = "argument")
+ this is KtParameter || this is KtTypeParameter -> paramTag
+ // if this property is declared separately (outside primary constructor), but it's for some reason
+ // annotated as @property in class's description, instead of having its own KDoc
+ this is KtProperty && containingDeclaration is KtClassOrObject -> propertySection
+ else -> null
+ }
+ return primaryContent?.let {
+ // makes little sense to include any other sections, since we found
+ // documentation for a very specific element, like a property/param
+ KDocContent(it, sections = emptyList())
+ }
+}
+
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt
new file mode 100644
index 00000000..ec23b0c8
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt
@@ -0,0 +1,97 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser
+import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser.Companion.fqDeclarationName
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.doc.Suppress
+import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
+import org.jetbrains.kotlin.psi.psiUtil.allChildren
+
+internal fun parseFromKDocTag(
+ kDocTag: KDocTag?,
+ externalDri: (KDocLink) -> DRI?,
+ kdocLocation: String?,
+ parseWithChildren: Boolean = true
+): DocumentationNode {
+ return if (kDocTag == null) {
+ DocumentationNode(emptyList())
+ } else {
+ fun parseStringToDocNode(text: String, externalDRIProvider: (String) -> DRI?) =
+ MarkdownParser(externalDRIProvider, kdocLocation).parseStringToDocNode(text)
+
+ fun pointedLink(tag: KDocTag): DRI? = tag.getSubjectLink()?.let(externalDri)
+
+ val allTags =
+ listOf(kDocTag) + if (kDocTag.canHaveParent() && parseWithChildren) getAllKDocTags(findParent(kDocTag)) else emptyList()
+ DocumentationNode(
+ allTags.map { tag ->
+ val links = tag.allChildren.filterIsInstance<KDocLink>().associate { it.getLinkText() to externalDri(it) }
+ val externalDRIProvider = { linkText: String -> links[linkText] }
+
+ when (tag.knownTag) {
+ null -> if (tag.name == null) Description(parseStringToDocNode(tag.getContent(), externalDRIProvider)) else CustomTagWrapper(
+ parseStringToDocNode(tag.getContent(), externalDRIProvider),
+ tag.name!!
+ )
+ KDocKnownTag.AUTHOR -> Author(parseStringToDocNode(tag.getContent(), externalDRIProvider))
+ KDocKnownTag.THROWS -> {
+ val dri = pointedLink(tag)
+ Throws(
+ parseStringToDocNode(tag.getContent(), externalDRIProvider),
+ dri?.fqDeclarationName() ?: tag.getSubjectName().orEmpty(),
+ dri,
+ )
+ }
+ KDocKnownTag.EXCEPTION -> {
+ val dri = pointedLink(tag)
+ Throws(
+ parseStringToDocNode(tag.getContent(), externalDRIProvider),
+ dri?.fqDeclarationName() ?: tag.getSubjectName().orEmpty(),
+ dri
+ )
+ }
+ KDocKnownTag.PARAM -> Param(
+ parseStringToDocNode(tag.getContent(), externalDRIProvider),
+ tag.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.RECEIVER -> Receiver(parseStringToDocNode(tag.getContent(), externalDRIProvider))
+ KDocKnownTag.RETURN -> Return(parseStringToDocNode(tag.getContent(), externalDRIProvider))
+ KDocKnownTag.SEE -> {
+ val dri = pointedLink(tag)
+ See(
+ parseStringToDocNode(tag.getContent(), externalDRIProvider),
+ dri?.fqDeclarationName() ?: tag.getSubjectName().orEmpty(),
+ dri,
+ )
+ }
+ KDocKnownTag.SINCE -> Since(parseStringToDocNode(tag.getContent(), externalDRIProvider))
+ KDocKnownTag.CONSTRUCTOR -> Constructor(parseStringToDocNode(tag.getContent(), externalDRIProvider))
+ KDocKnownTag.PROPERTY -> Property(
+ parseStringToDocNode(tag.getContent(), externalDRIProvider),
+ tag.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.SAMPLE -> Sample(
+ parseStringToDocNode(tag.getContent(), externalDRIProvider),
+ tag.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.SUPPRESS -> Suppress(parseStringToDocNode(tag.getContent(), externalDRIProvider))
+ }
+ }
+ )
+ }
+}
+
+private fun findParent(kDoc: PsiElement): PsiElement =
+ if (kDoc.canHaveParent()) findParent(kDoc.parent) else kDoc
+
+private fun PsiElement.canHaveParent(): Boolean = this is KDocSection && knownTag != KDocKnownTag.PROPERTY
+
+private fun getAllKDocTags(kDocImpl: PsiElement): List<KDocTag> =
+ kDocImpl.children.filterIsInstance<KDocTag>().filterNot { it is KDocSection } + kDocImpl.children.flatMap {
+ getAllKDocTags(it)
+ }
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt
new file mode 100644
index 00000000..9d26d917
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt
@@ -0,0 +1,42 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.dokka.analysis.kotlin.symbols.translators.getDRIFromSymbol
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.idea.references.mainReference
+import org.jetbrains.kotlin.kdoc.psi.api.KDoc
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
+import org.jetbrains.kotlin.psi.KtPsiFactory
+
+internal fun DRI?.logIfNotResolved(link: String, logger: DokkaLogger): DRI? {
+ if(this == null)
+ logger.warn("Couldn't resolve link for $link")
+ return this
+}
+
+/**
+ * It resolves KDoc link via creating PSI.
+ *
+ */
+internal fun KtAnalysisSession.resolveKDocTextLink(link: String, context: PsiElement? = null): DRI? {
+ val psiFactory = context?.let { KtPsiFactory.contextual(it) } ?: KtPsiFactory(this.useSiteModule.project)
+ val kDoc = psiFactory.createComment(
+ """
+ /**
+ * [$link]
+ */
+ """.trimIndent()
+ ) as? KDoc
+ val kDocLink = kDoc?.getDefaultSection()?.children?.filterIsInstance<KDocLink>()?.singleOrNull()
+ return kDocLink?.let { resolveKDocLink(it) }
+}
+
+internal fun KtAnalysisSession.resolveKDocLink(link: KDocLink): DRI? {
+ val lastNameSegment = link.children.filterIsInstance<KDocName>().lastOrNull()
+ val linkedSymbol = lastNameSegment?.mainReference?.resolveToSymbols()?.firstOrNull()
+ return if (linkedSymbol == null) null // logger.warn("Couldn't resolve link for $link")
+ else getDRIFromSymbol(linkedSymbol)
+} \ No newline at end of file
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt
new file mode 100644
index 00000000..9b108f80
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt
@@ -0,0 +1,61 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc
+
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin
+import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.symbols.*
+import org.jetbrains.kotlin.analysis.api.symbols.markers.KtPossibleMemberSymbol
+import org.jetbrains.kotlin.builtins.StandardNames
+
+private const val ENUM_ENTRIES_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumEntries.kt.template"
+private const val ENUM_VALUEOF_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumValueOf.kt.template"
+private const val ENUM_VALUES_TEMPLATE_PATH = "/dokka/docs/kdoc/EnumValues.kt.template"
+
+internal fun KtAnalysisSession.hasGeneratedKDocDocumentation(symbol: KtSymbol): Boolean =
+ getDocumentationTemplatePath(symbol) != null
+
+private fun KtAnalysisSession.getDocumentationTemplatePath(symbol: KtSymbol): String? =
+ when (symbol) {
+ is KtPropertySymbol -> if (isEnumEntriesProperty(symbol)) ENUM_ENTRIES_TEMPLATE_PATH else null
+ is KtFunctionSymbol -> {
+ when {
+ isEnumValuesMethod(symbol) -> ENUM_VALUES_TEMPLATE_PATH
+ isEnumValueOfMethod(symbol) -> ENUM_VALUEOF_TEMPLATE_PATH
+ else -> null
+ }
+ }
+
+ else -> null
+ }
+
+private fun KtAnalysisSession.isEnumSpecialMember(symbol: KtPossibleMemberSymbol): Boolean =
+ symbol.origin == KtSymbolOrigin.SOURCE_MEMBER_GENERATED
+ && (symbol.getContainingSymbol() as? KtClassOrObjectSymbol)?.classKind == KtClassKind.ENUM_CLASS
+
+private fun KtAnalysisSession.isEnumEntriesProperty(symbol: KtPropertySymbol): Boolean =
+ symbol.name == StandardNames.ENUM_ENTRIES && isEnumSpecialMember(symbol)
+
+private fun KtAnalysisSession.isEnumValuesMethod(symbol: KtFunctionSymbol): Boolean =
+ symbol.name == StandardNames.ENUM_VALUES && isEnumSpecialMember(symbol)
+
+private fun KtAnalysisSession.isEnumValueOfMethod(symbol: KtFunctionSymbol): Boolean =
+ symbol.name == StandardNames.ENUM_VALUE_OF && isEnumSpecialMember(symbol)
+
+internal fun KtAnalysisSession.getGeneratedKDocDocumentationFrom(symbol: KtSymbol): DocumentationNode? {
+ val templatePath = getDocumentationTemplatePath(symbol) ?: return null
+ return loadTemplate(templatePath)
+}
+
+private fun KtAnalysisSession.loadTemplate(filePath: String): DocumentationNode? {
+ val kdoc = loadContent(filePath) ?: return null
+ val externalDriProvider = { link: String ->
+ resolveKDocTextLink(link)
+ }
+
+ val parser = MarkdownParser(externalDriProvider, filePath)
+ return parser.parse(kdoc)
+}
+
+private fun loadContent(filePath: String): String? =
+ SymbolsAnalysisPlugin::class.java.getResource(filePath)?.readText()
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt
new file mode 100644
index 00000000..a3651603
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt
@@ -0,0 +1,15 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java
+
+import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent
+import org.jetbrains.dokka.analysis.java.JavadocTag
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
+
+internal data class DescriptorDocumentationContent(
+ val resolveDocContext: ResolveDocContext,
+ val element: KDocTag,
+ override val tag: JavadocTag,
+) : DocumentationContent {
+ override fun resolveSiblings(): List<DocumentationContent> {
+ return listOf(this)
+ }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt
new file mode 100644
index 00000000..399d005a
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt
@@ -0,0 +1,16 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java
+
+import com.intellij.psi.PsiNamedElement
+import org.jetbrains.dokka.analysis.java.doccomment.DocComment
+import org.jetbrains.dokka.analysis.java.doccomment.DocCommentCreator
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.findKDoc
+import org.jetbrains.kotlin.psi.KtElement
+
+internal class DescriptorKotlinDocCommentCreator : DocCommentCreator {
+ override fun create(element: PsiNamedElement): DocComment? {
+ val ktElement = element.navigationElement as? KtElement ?: return null
+ val kdoc = ktElement.findKDoc() ?: return null
+
+ return KotlinDocComment(kdoc.contentTag, ResolveDocContext(ktElement))
+ }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt
new file mode 100644
index 00000000..385b5328
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt
@@ -0,0 +1,81 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java
+
+import org.jetbrains.dokka.analysis.java.*
+import org.jetbrains.dokka.analysis.java.doccomment.DocComment
+import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
+import org.jetbrains.kotlin.psi.KtElement
+
+internal class ResolveDocContext(val ktElement: KtElement)
+
+internal class KotlinDocComment(
+ val comment: KDocTag,
+ val resolveDocContext: ResolveDocContext
+) : DocComment {
+
+ private val tagsWithContent: List<KDocTag> = comment.children.mapNotNull { (it as? KDocTag) }
+
+ override fun hasTag(tag: JavadocTag): Boolean {
+ return when (tag) {
+ is DescriptionJavadocTag -> comment.getContent().isNotEmpty()
+ is ThrowingExceptionJavadocTag -> tagsWithContent.any { it.hasException(tag) }
+ else -> tagsWithContent.any { it.text.startsWith("@${tag.name}") }
+ }
+ }
+
+ private fun KDocTag.hasException(tag: ThrowingExceptionJavadocTag) =
+ text.startsWith("@${tag.name}") && getSubjectName() == tag.exceptionQualifiedName
+
+ override fun resolveTag(tag: JavadocTag): List<DocumentationContent> {
+ return when (tag) {
+ is DescriptionJavadocTag -> listOf(DescriptorDocumentationContent(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<DescriptorDocumentationContent> {
+ val exceptionName = tag.exceptionQualifiedName ?: return resolveGeneric(tag)
+
+ return comment.children
+ .filterIsInstance<KDocTag>()
+ .filter { it.name == tag.name && it.getSubjectName() == exceptionName }
+ .map { DescriptorDocumentationContent(resolveDocContext, it, tag) }
+ }
+
+ private fun resolveGeneric(tag: JavadocTag): List<DescriptorDocumentationContent> {
+ return comment.children.mapNotNull { element ->
+ if (element is KDocTag && element.name == tag.name) {
+ DescriptorDocumentationContent(resolveDocContext, element, tag)
+ } else {
+ null
+ }
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as KotlinDocComment
+
+ if (comment != other.comment) return false
+ //if (resolveDocContext.name != other.resolveDocContext.name) return false
+ if (tagsWithContent != other.tagsWithContent) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = comment.hashCode()
+ // result = 31 * result + resolveDocContext.name.hashCode()
+ result = 31 * result + tagsWithContent.hashCode()
+ return result
+ }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt
new file mode 100644
index 00000000..c8ecc786
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt
@@ -0,0 +1,46 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java
+
+import com.intellij.psi.PsiNamedElement
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.java.doccomment.DocComment
+import org.jetbrains.dokka.analysis.java.parsers.DocCommentParser
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.logIfNotResolved
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.parseFromKDocTag
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocLink
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.kotlin.analysis.api.analyze
+
+internal class KotlinDocCommentParser(
+ private val context: DokkaContext
+) : DocCommentParser {
+
+ override fun canParse(docComment: DocComment): Boolean {
+ return docComment is KotlinDocComment
+ }
+
+ override fun parse(docComment: DocComment, context: PsiNamedElement): DocumentationNode {
+ val kotlinDocComment = docComment as KotlinDocComment
+ return parseDocumentation(kotlinDocComment)
+ }
+
+ fun parseDocumentation(element: KotlinDocComment, parseWithChildren: Boolean = true): DocumentationNode {
+ val sourceSet = context.configuration.sourceSets.let { sourceSets ->
+ sourceSets.firstOrNull { it.sourceSetID.sourceSetName == "jvmMain" }
+ ?: sourceSets.first { it.analysisPlatform == Platform.jvm }
+ }
+ val kotlinAnalysis = context.plugin<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis }
+ return analyze(kotlinAnalysis[sourceSet].mainModule) {
+ parseFromKDocTag(
+ kDocTag = element.comment,
+ externalDri = { link -> resolveKDocLink(link).logIfNotResolved(link.getLinkText(), context.logger) },
+ kdocLocation = null,
+ parseWithChildren = parseWithChildren
+ )
+ }
+ }
+}
+
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt
new file mode 100644
index 00000000..6d2bfff4
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt
@@ -0,0 +1,31 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java
+
+import org.jetbrains.dokka.analysis.java.doccomment.DocumentationContent
+import org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin
+import org.jetbrains.dokka.analysis.java.parsers.doctag.DocTagParserContext
+import org.jetbrains.dokka.analysis.java.parsers.doctag.InheritDocTagContentProvider
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.query
+
+internal class KotlinInheritDocTagContentProvider(
+ context: DokkaContext
+) : InheritDocTagContentProvider {
+
+ val parser: KotlinDocCommentParser by lazy {
+ context.plugin<JavaAnalysisPlugin>().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 """<inheritdoc id="$id"/>"""
+ }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt
new file mode 100644
index 00000000..6e3f215a
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt
@@ -0,0 +1,7 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs
+
+import org.jetbrains.dokka.DokkaException
+
+internal class IllegalModuleAndPackageDocumentation(
+ source: ModuleAndPackageDocumentationSource, message: String
+) : DokkaException("[$source] $message")
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt
new file mode 100644
index 00000000..46f714cb
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs
+
+import org.jetbrains.dokka.model.doc.DocumentationNode
+
+internal data class ModuleAndPackageDocumentation(
+ val name: String,
+ val classifier: Classifier,
+ val documentation: DocumentationNode
+) {
+ enum class Classifier { Module, Package }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt
new file mode 100644
index 00000000..1eafc688
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt
@@ -0,0 +1,9 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs
+
+
+internal data class ModuleAndPackageDocumentationFragment(
+ val name: String,
+ val classifier: ModuleAndPackageDocumentation.Classifier,
+ val documentation: String,
+ val source: ModuleAndPackageDocumentationSource
+)
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt
new file mode 100644
index 00000000..cf627e77
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt
@@ -0,0 +1,47 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.logIfNotResolved
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.KotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs.ModuleAndPackageDocumentation.Classifier.Module
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs.ModuleAndPackageDocumentation.Classifier.Package
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocTextLink
+import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.name.FqName
+
+internal fun interface ModuleAndPackageDocumentationParsingContext {
+ fun markdownParserFor(fragment: ModuleAndPackageDocumentationFragment, location: String): MarkdownParser
+}
+
+internal fun ModuleAndPackageDocumentationParsingContext.parse(
+ fragment: ModuleAndPackageDocumentationFragment
+): DocumentationNode {
+ return markdownParserFor(fragment, fragment.source.sourceDescription).parse(fragment.documentation)
+}
+
+internal fun ModuleAndPackageDocumentationParsingContext(
+ logger: DokkaLogger,
+ kotlinAnalysis: KotlinAnalysis? = null,
+ sourceSet: DokkaConfiguration.DokkaSourceSet? = null
+) = ModuleAndPackageDocumentationParsingContext { fragment, sourceLocation ->
+
+ if(kotlinAnalysis == null || sourceSet == null) {
+ MarkdownParser(externalDri = { null }, sourceLocation)
+ } else {
+ val analysisContext = kotlinAnalysis[sourceSet]
+ analyze(analysisContext.mainModule) {
+ val contextSymbol = when (fragment.classifier) {
+ Module -> ROOT_PACKAGE_SYMBOL
+ Package -> getPackageSymbolIfPackageExists(FqName(fragment.name))
+ }
+
+ MarkdownParser(
+ externalDri = { resolveKDocTextLink(it, contextSymbol?.psi).logIfNotResolved(it, logger) },
+ sourceLocation
+ )
+ }
+ }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt
new file mode 100644
index 00000000..82259b89
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt
@@ -0,0 +1,110 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs.ModuleAndPackageDocumentation.Classifier
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.model.DPackage
+import org.jetbrains.dokka.model.SourceSetDependent
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.doc.Deprecated
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.utilities.associateWithNotNull
+import org.jetbrains.dokka.analysis.kotlin.internal.ModuleAndPackageDocumentationReader
+
+internal fun ModuleAndPackageDocumentationReader(context: DokkaContext): ModuleAndPackageDocumentationReader =
+ ContextModuleAndPackageDocumentationReader(context)
+
+private class ContextModuleAndPackageDocumentationReader(
+ private val context: DokkaContext
+) : ModuleAndPackageDocumentationReader {
+
+ private val kotlinAnalysis = context.plugin<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis }
+
+ private val documentationFragments: SourceSetDependent<List<ModuleAndPackageDocumentationFragment>> =
+ context.configuration.sourceSets.associateWith { sourceSet ->
+ sourceSet.includes.flatMap { include -> parseModuleAndPackageDocumentationFragments(include) }
+ }
+
+ private fun findDocumentationNodes(
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet>,
+ predicate: (ModuleAndPackageDocumentationFragment) -> Boolean
+ ): SourceSetDependent<DocumentationNode> {
+ return sourceSets.associateWithNotNull { sourceSet ->
+ val fragments = documentationFragments[sourceSet].orEmpty().filter(predicate)
+ kotlinAnalysis[sourceSet] // test: to throw exception for unknown sourceSet
+ val documentations = fragments.map { fragment ->
+ parseModuleAndPackageDocumentation(
+ context = ModuleAndPackageDocumentationParsingContext(context.logger, kotlinAnalysis, sourceSet),
+ fragment = fragment
+ )
+ }
+ when (documentations.size) {
+ 0 -> null
+ 1 -> documentations.single().documentation
+ else -> DocumentationNode(documentations.flatMap { it.documentation.children }
+ .mergeDocumentationNodes())
+ }
+ }
+ }
+
+ private val ModuleAndPackageDocumentationFragment.canonicalPackageName: String
+ get() {
+ check(classifier == Classifier.Package)
+ if (name == "[root]") return ""
+ return name
+ }
+
+ override fun read(module: DModule): SourceSetDependent<DocumentationNode> {
+ return findDocumentationNodes(module.sourceSets) { fragment ->
+ fragment.classifier == Classifier.Module && (fragment.name == module.name)
+ }
+ }
+
+ override fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> {
+ return findDocumentationNodes(pkg.sourceSets) { fragment ->
+ fragment.classifier == Classifier.Package && fragment.canonicalPackageName == pkg.dri.packageName
+ }
+ }
+
+ override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode? {
+ val parsingContext = ModuleAndPackageDocumentationParsingContext(context.logger)
+
+ val documentationFragment = module.includes
+ .flatMap { include -> parseModuleAndPackageDocumentationFragments(include) }
+ .firstOrNull { fragment -> fragment.classifier == Classifier.Module && fragment.name == module.name }
+ ?: return null
+
+ val moduleDocumentation = parseModuleAndPackageDocumentation(parsingContext, documentationFragment)
+ return moduleDocumentation.documentation
+ }
+
+ private fun List<TagWrapper>.mergeDocumentationNodes(): List<TagWrapper> =
+ groupBy { it::class }.values.map {
+ it.reduce { acc, tagWrapper ->
+ val newRoot = CustomDocTag(
+ acc.children + tagWrapper.children,
+ name = (tagWrapper as? NamedTagWrapper)?.name.orEmpty()
+ )
+ when (acc) {
+ is See -> acc.copy(newRoot)
+ is Param -> acc.copy(newRoot)
+ is Throws -> acc.copy(newRoot)
+ is Sample -> acc.copy(newRoot)
+ is Property -> acc.copy(newRoot)
+ is CustomTagWrapper -> acc.copy(newRoot)
+ is Description -> acc.copy(newRoot)
+ is Author -> acc.copy(newRoot)
+ is Version -> acc.copy(newRoot)
+ is Since -> acc.copy(newRoot)
+ is Return -> acc.copy(newRoot)
+ is Receiver -> acc.copy(newRoot)
+ is Constructor -> acc.copy(newRoot)
+ is Deprecated -> acc.copy(newRoot)
+ is org.jetbrains.dokka.model.doc.Suppress -> acc.copy(newRoot)
+ }
+ }
+ }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt
new file mode 100644
index 00000000..d7877559
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs
+
+import java.io.File
+
+internal abstract class ModuleAndPackageDocumentationSource {
+ abstract val sourceDescription: String
+ abstract val documentation: String
+ override fun toString(): String = sourceDescription
+}
+
+internal data class ModuleAndPackageDocumentationFile(private val file: File) : ModuleAndPackageDocumentationSource() {
+ override val sourceDescription: String = file.path
+ override val documentation: String by lazy(LazyThreadSafetyMode.PUBLICATION) { file.readText() }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt
new file mode 100644
index 00000000..b92adf4a
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs
+
+internal fun parseModuleAndPackageDocumentation(
+ context: ModuleAndPackageDocumentationParsingContext,
+ fragment: ModuleAndPackageDocumentationFragment
+): ModuleAndPackageDocumentation {
+ return ModuleAndPackageDocumentation(
+ name = fragment.name,
+ classifier = fragment.classifier,
+ documentation = context.parse(fragment)
+ )
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt
new file mode 100644
index 00000000..ae728a28
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/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<ModuleAndPackageDocumentationFragment> {
+ return parseModuleAndPackageDocumentationFragments(ModuleAndPackageDocumentationFile(source))
+}
+
+internal fun parseModuleAndPackageDocumentationFragments(
+ source: ModuleAndPackageDocumentationSource
+): List<ModuleAndPackageDocumentationFragment> {
+ val fragmentStrings = source.documentation.split(Regex("(|^)#\\s*(?=(Module|Package))"))
+ return fragmentStrings
+ .filter(String::isNotBlank)
+ .map { fragmentString -> parseModuleAndPackageDocFragment(source, fragmentString) }
+}
+
+private fun parseModuleAndPackageDocFragment(
+ source: ModuleAndPackageDocumentationSource,
+ fragment: String
+): ModuleAndPackageDocumentationFragment {
+ val firstLineAndDocumentation = fragment.split("\r\n", "\n", "\r", limit = 2)
+ val firstLine = firstLineAndDocumentation[0]
+
+ val classifierAndName = firstLine.split(Regex("\\s+"), limit = 2)
+
+ val classifier = when (classifierAndName[0].trim()) {
+ "Module" -> Module
+ "Package" -> Package
+ else -> throw IllegalStateException(
+ """Unexpected classifier: "${classifierAndName[0]}", expected either "Module" or "Package".
+ |For more information consult the specification: https://kotlinlang.org/docs/dokka-module-and-package-docs.html""".trimMargin()
+ )
+ }
+
+ if (classifierAndName.size != 2 && classifier == Module) {
+ throw IllegalModuleAndPackageDocumentation(source, "Missing Module name")
+ }
+
+ val name = classifierAndName.getOrNull(1)?.trim().orEmpty()
+ if (classifier == Package && name.contains(Regex("\\s"))) {
+ throw IllegalModuleAndPackageDocumentation(
+ source, "Package name cannot contain whitespace in '$firstLine'"
+ )
+ }
+
+ return ModuleAndPackageDocumentationFragment(
+ name = name,
+ classifier = classifier,
+ documentation = firstLineAndDocumentation.getOrNull(1)?.trim().orEmpty(),
+ source = source
+ )
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/AnalysisContext.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/AnalysisContext.kt
new file mode 100644
index 00000000..eb7c5d70
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/AnalysisContext.kt
@@ -0,0 +1,139 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.plugin
+
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.Disposer
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.dokka.model.SourceSetDependent
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession
+import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
+import java.io.Closeable
+import java.io.File
+
+@Suppress("FunctionName", "UNUSED_PARAMETER")
+internal fun SamplesKotlinAnalysis(
+ sourceSets: List<DokkaConfiguration.DokkaSourceSet>,
+ 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<DokkaConfiguration.DokkaSourceSet>,
+ 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<DokkaConfiguration.DokkaSourceSet>,
+ 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 createAnalysisContext(classpath, sources, sourceSet)
+}
+
+internal fun createAnalysisContext(
+ classpath: List<File>,
+ sourceRoots: Set<File>,
+ sourceSet: DokkaConfiguration.DokkaSourceSet
+): AnalysisContext {
+ val applicationDisposable: Disposable = Disposer.newDisposable("StandaloneAnalysisAPISession.application")
+ val projectDisposable: Disposable = Disposer.newDisposable("StandaloneAnalysisAPISession.project")
+
+ val analysis= createAnalysisSession(
+ classpath = classpath,
+ sourceRoots = sourceRoots,
+ analysisPlatform = sourceSet.analysisPlatform,
+ languageVersion = sourceSet.languageVersion,
+ apiVersion = sourceSet.apiVersion,
+ applicationDisposable = applicationDisposable,
+ projectDisposable = projectDisposable
+ )
+ return AnalysisContextImpl(
+ mainModule = analysis.second,
+ analysisSession = analysis.first,
+ applicationDisposable = applicationDisposable,
+ projectDisposable = projectDisposable
+ )
+}
+
+
+/**
+ * First child delegation. It does not close [parent].
+ */
+internal 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<AnalysisContext>,
+ parent: KotlinAnalysis? = null,
+) : KotlinAnalysis(parent = parent) {
+
+ override fun find(sourceSetID: DokkaSourceSetID): AnalysisContext? =
+ environments.entries.firstOrNull { (sourceSet, _) -> sourceSet.sourceSetID == sourceSetID }?.value
+
+ override fun close() {
+ environments.values.forEach(AnalysisContext::close)
+ }
+}
+
+internal interface AnalysisContext: Closeable {
+ val project: Project
+ val mainModule: KtSourceModule
+}
+
+private class AnalysisContextImpl(
+ override val mainModule: KtSourceModule,
+ private val analysisSession: StandaloneAnalysisAPISession,
+ private val applicationDisposable: Disposable,
+ private val projectDisposable: Disposable
+) : AnalysisContext {
+ override val project: Project
+ get() = analysisSession.project
+
+ override fun close() {
+ Disposer.dispose(applicationDisposable)
+ Disposer.dispose(projectDisposable)
+ }
+} \ No newline at end of file
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/KotlinAnalysis.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/KotlinAnalysis.kt
new file mode 100644
index 00000000..8b9e552d
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/KotlinAnalysis.kt
@@ -0,0 +1,245 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.plugin
+
+import com.intellij.core.CoreApplicationEnvironment
+import com.intellij.openapi.Disposable
+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.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.KtSourceModule
+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.config.*
+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
+}
+
+private 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 javaHome
+}
+
+internal fun getLanguageVersionSettings(
+ languageVersionString: String?,
+ apiVersionString: String?
+): LanguageVersionSettingsImpl {
+ val languageVersion = LanguageVersion.fromVersionString(languageVersionString) ?: LanguageVersion.LATEST_STABLE
+ val apiVersion =
+ apiVersionString?.let { ApiVersion.parse(it) } ?: ApiVersion.createByLanguageVersion(languageVersion)
+ return LanguageVersionSettingsImpl(
+ languageVersion = languageVersion,
+ apiVersion = apiVersion, analysisFlags = hashMapOf(
+ // special flag for Dokka
+ // force to resolve light classes (lazily by default)
+ AnalysisFlags.eagerResolveOfLightClasses to true
+ )
+ )
+}
+
+// it should be changed after https://github.com/Kotlin/dokka/issues/3114
+internal fun createAnalysisSession(
+ classpath: List<File>,
+ sourceRoots: Set<File>,
+ analysisPlatform: Platform,
+ languageVersion: String?,
+ apiVersion: String?,
+ applicationDisposable: Disposable,
+ projectDisposable: Disposable
+): Pair<StandaloneAnalysisAPISession, KtSourceModule> {
+
+ var sourceModule: KtSourceModule? = null
+ val analysisSession = buildStandaloneAnalysisAPISession(
+ applicationDisposable = applicationDisposable,
+ projectDisposable = projectDisposable,
+ 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"
+ }
+ )
+ }
+ }
+ sourceModule = buildKtSourceModule {
+ this.languageVersionSettings = getLanguageVersionSettings(languageVersion, apiVersion)
+
+ //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 = sourceRoots.map { it.absolutePath }
+ val (ktFilePath, javaFilePath) = getSourceFilePaths(sourcePaths).partition { it.endsWith(KotlinFileType.EXTENSION) }
+ val javaFiles: List<PsiFileSystemItem> = getPsiFilesFromPaths(project, javaFilePath)
+ val ktFiles: List<KtFile> = getPsiFilesFromPaths(project, getSourceFilePaths(ktFilePath))
+ addSourceRoots(ktFiles + javaFiles)
+ contentScope = TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, ktFiles)
+ platform = targetPlatform
+ moduleName = "<module>"
+ this.project = project
+ addModuleDependencies(moduleName)
+ }
+
+ buildKtModuleProvider {
+ platform = targetPlatform
+ this.project = project
+ addModule(sourceModule!!)
+ }
+ }
+ // TODO remove further
+ CoreApplicationEnvironment.registerExtensionPoint(
+ analysisSession.project.extensionArea,
+ KtResolveExtensionProvider.EP_NAME.name,
+ KtResolveExtensionProvider::class.java
+ )
+ return Pair(analysisSession, sourceModule ?: throw IllegalStateException())
+}
+
+// ----------- copy-paste from Analysis API ----------------------------------------------------------------------------
+/**
+ * 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<String>
+) {
+ // 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<Path>() {
+ 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.
+ */
+internal fun getSourceFilePaths(
+ sourceRoot: Collection<String>,
+ includeDirectoryRoot: Boolean = false,
+): Set<String> {
+ val result = mutableSetOf<String>()
+ 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
+}
+
+internal inline fun <reified T : PsiFileSystemItem> getPsiFilesFromPaths(
+ project: Project,
+ paths: Collection<String>,
+): List<T> {
+ val fs = StandardFileSystems.local()
+ val psiManager = PsiManager.getInstance(project)
+ val result = mutableListOf<T>()
+ 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
+} \ No newline at end of file
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt
new file mode 100644
index 00000000..71fdfadb
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt
@@ -0,0 +1,121 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.plugin
+
+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.internal.InternalKotlinAnalysisPlugin
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.java.KotlinInheritDocTagContentProvider
+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.KotlinAnalysisSourceRootsExtractor
+import org.jetbrains.dokka.analysis.kotlin.symbols.services.*
+import org.jetbrains.dokka.analysis.kotlin.symbols.services.KotlinDocumentableSourceLanguageParser
+import org.jetbrains.dokka.analysis.kotlin.symbols.services.SymbolExternalDocumentablesProvider
+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.dokka.renderers.PostAction
+import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation
+
+@Suppress("unused")
+public class SymbolsAnalysisPlugin : DokkaPlugin() {
+
+ internal val kotlinAnalysis by extensionPoint<KotlinAnalysis>()
+
+ internal val defaultKotlinAnalysis by extending {
+ kotlinAnalysis providing { ctx ->
+ ProjectKotlinAnalysis(
+ sourceSets = ctx.configuration.sourceSets,
+ context = ctx
+ )
+ }
+ }
+
+ internal val disposeKotlinAnalysisPostAction by extending {
+ CoreExtensions.postActions with PostAction { querySingle { kotlinAnalysis }.close() }
+ }
+
+ internal val symbolToDocumentableTranslator by extending {
+ CoreExtensions.sourceToDocumentableTranslator providing ::DefaultSymbolToDocumentableTranslator
+ }
+
+ private val javaAnalysisPlugin by lazy { plugin<JavaAnalysisPlugin>() }
+
+ internal val projectProvider by extending {
+ javaAnalysisPlugin.projectProvider providing { KotlinAnalysisProjectProvider() }
+ }
+
+ internal val sourceRootsExtractor by extending {
+ javaAnalysisPlugin.sourceRootsExtractor providing { KotlinAnalysisSourceRootsExtractor() }
+ }
+
+ internal val kotlinDocCommentCreator by extending {
+ javaAnalysisPlugin.docCommentCreators providing {
+ DescriptorKotlinDocCommentCreator()
+ }
+ }
+
+ internal val kotlinDocCommentParser by extending {
+ javaAnalysisPlugin.docCommentParsers providing { context ->
+ KotlinDocCommentParser(
+ context
+ )
+ }
+ }
+ 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 symbolAnalyzerImpl by extending {
+ plugin<InternalKotlinAnalysisPlugin>().documentableSourceLanguageParser providing { KotlinDocumentableSourceLanguageParser() }
+ }
+ internal val symbolFullClassHierarchyBuilder by extending {
+ plugin<InternalKotlinAnalysisPlugin>().fullClassHierarchyBuilder providing { SymbolFullClassHierarchyBuilder() }
+ }
+
+ internal val symbolSyntheticDocumentableDetector by extending {
+ plugin<InternalKotlinAnalysisPlugin>().syntheticDocumentableDetector providing { SymbolSyntheticDocumentableDetector() }
+ }
+
+ internal val moduleAndPackageDocumentationReader by extending {
+ plugin<InternalKotlinAnalysisPlugin>().moduleAndPackageDocumentationReader providing ::ModuleAndPackageDocumentationReader
+ }
+
+ /* internal val kotlinToJavaMapper by extending {
+ plugin<InternalKotlinAnalysisPlugin>().kotlinToJavaService providing { DescriptorKotlinToJavaMapper() }
+ }
+
+ intern val descriptorInheritanceBuilder by extending {
+ plugin<InternalKotlinAnalysisPlugin>().inheritanceBuilder providing { DescriptorInheritanceBuilder() }
+ }
+ */
+ internal val symbolExternalDocumentablesProvider by extending {
+ plugin<InternalKotlinAnalysisPlugin>().externalDocumentablesProvider providing ::SymbolExternalDocumentablesProvider
+ }
+
+ internal val kotlinSampleProviderFactory by extending {
+ plugin<InternalKotlinAnalysisPlugin>().sampleProviderFactory providing ::KotlinSampleProviderFactory
+ }
+
+ @OptIn(DokkaPluginApiPreview::class)
+ override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisProjectProvider.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisProjectProvider.kt
new file mode 100644
index 00000000..47bcb156
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisProjectProvider.kt
@@ -0,0 +1,16 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.services
+
+import com.intellij.openapi.project.Project
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.java.ProjectProvider
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin
+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<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis }
+ return kotlinAnalysis[sourceSet].project
+ }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisSourceRootsExtractor.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisSourceRootsExtractor.kt
new file mode 100644
index 00000000..43aceccd
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisSourceRootsExtractor.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.services
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.java.SourceRootsExtractor
+import org.jetbrains.dokka.plugability.DokkaContext
+import java.io.File
+
+internal class KotlinAnalysisSourceRootsExtractor : SourceRootsExtractor {
+ override fun extract(sourceSet: DokkaConfiguration.DokkaSourceSet, context: DokkaContext): List<File> {
+ return sourceSet.sourceRoots.filter { directory -> directory.isDirectory || directory.extension == "java" }
+ }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinDocumentableSourceLanguageParser.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinDocumentableSourceLanguageParser.kt
new file mode 100644
index 00000000..799cd7b6
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinDocumentableSourceLanguageParser.kt
@@ -0,0 +1,26 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.services
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.java.util.PsiDocumentableSource
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.WithSources
+import org.jetbrains.dokka.analysis.kotlin.internal.DocumentableLanguage
+import org.jetbrains.dokka.analysis.kotlin.internal.DocumentableSourceLanguageParser
+
+internal class KotlinDocumentableSourceLanguageParser : DocumentableSourceLanguageParser {
+
+ /**
+ * For members inherited from Java in Kotlin - it returns [DocumentableLanguage.KOTLIN]
+ */
+ 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/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt
new file mode 100644
index 00000000..dc61b35b
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt
@@ -0,0 +1,85 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.services
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.InternalDokkaApi
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.analysis.kotlin.internal.SampleProvider
+import org.jetbrains.dokka.analysis.kotlin.internal.SampleProviderFactory
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SamplesKotlinAnalysis
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi.KtBlockExpression
+import org.jetbrains.kotlin.psi.KtDeclarationWithBody
+import org.jetbrains.kotlin.psi.KtFile
+
+class KotlinSampleProviderFactory(val context: DokkaContext): SampleProviderFactory {
+ override fun build(): SampleProvider {
+ return KotlinSampleProvider(context)
+ }
+
+}
+/**
+ * It's declared as open since StdLib has its own sample transformer
+ * with [processBody] and [processImports]
+ */
+@InternalDokkaApi
+open class KotlinSampleProvider(val context: DokkaContext): SampleProvider {
+ private val kotlinAnalysis = SamplesKotlinAnalysis(
+ sourceSets = context.configuration.sourceSets,
+ context = context,
+ projectKotlinAnalysis = context.plugin<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis }
+ )
+
+ protected open 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).minOfOrNull { it.takeWhile(Char::isWhitespace).count() } ?: 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
+ }
+
+ protected open fun processImports(psiElement: PsiElement): String {
+ val psiFile = psiElement.containingFile
+ return when(val text = (psiFile as? KtFile)?.importList?.text) {
+ is String -> text
+ else -> ""
+ }
+ }
+
+ /**
+ * @return [SampleProvider.SampleSnippet] or null if it has not found by [fqLink]
+ */
+ override fun getSample(sourceSet: DokkaConfiguration.DokkaSourceSet, fqLink: String): SampleProvider.SampleSnippet? {
+ val analysisContext = kotlinAnalysis[sourceSet]
+ val psiElement = analyze(analysisContext.mainModule) {
+ val lastDotIndex = fqLink.lastIndexOf('.')
+
+ val functionName = if (lastDotIndex == -1) fqLink else fqLink.substring(lastDotIndex + 1, fqLink.length)
+ val packageName = if (lastDotIndex == -1) "" else fqLink.substring(0, lastDotIndex)
+ getTopLevelCallableSymbols(FqName(packageName), Name.identifier(functionName)).firstOrNull()?.psi
+ }
+ ?: return null.also { context.logger.warn("Cannot find PsiElement corresponding to $fqLink") }
+ val imports =
+ processImports(psiElement)
+ val body = processBody(psiElement)
+
+ return SampleProvider.SampleSnippet(imports, body)
+ }
+ override fun close() {
+ kotlinAnalysis.close()
+ }
+} \ No newline at end of file
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KtPsiDocumentableSource.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KtPsiDocumentableSource.kt
new file mode 100644
index 00000000..3a819931
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KtPsiDocumentableSource.kt
@@ -0,0 +1,20 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.services
+
+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/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt
new file mode 100644
index 00000000..c328fe57
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt
@@ -0,0 +1,34 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.services
+
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin
+import org.jetbrains.dokka.analysis.kotlin.symbols.translators.DokkaSymbolVisitor
+import org.jetbrains.dokka.analysis.kotlin.symbols.translators.getClassIdFromDRI
+import org.jetbrains.dokka.analysis.kotlin.symbols.translators.getDRIFromSymbol
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.DClasslike
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol
+import org.jetbrains.dokka.analysis.kotlin.internal.ExternalDocumentablesProvider
+
+internal class SymbolExternalDocumentablesProvider(val context: DokkaContext) : ExternalDocumentablesProvider {
+ private val kotlinAnalysis = context.plugin<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis }
+
+ override fun findClasslike(dri: DRI, sourceSet: DokkaSourceSet): DClasslike? {
+ val classId = getClassIdFromDRI(dri)
+
+ val analysisContext = kotlinAnalysis[sourceSet]
+ return analyze(analysisContext.mainModule) {
+ val symbol = getClassOrObjectSymbolByClassId(classId) as? KtNamedClassOrObjectSymbol?: return@analyze null
+ val translator = DokkaSymbolVisitor(sourceSet, sourceSet.displayName, analysisContext, logger = context.logger)
+
+ val parentDRI = symbol.getContainingSymbol()?.let { getDRIFromSymbol(it) } ?: /* top level */ DRI(dri.packageName)
+ with(translator) {
+ return@analyze visitNamedClassOrObjectSymbol(symbol, parentDRI)
+ }
+ }
+ }
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolFullClassHierarchyBuilder.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolFullClassHierarchyBuilder.kt
new file mode 100644
index 00000000..d4269b11
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolFullClassHierarchyBuilder.kt
@@ -0,0 +1,85 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.services
+
+import com.intellij.psi.PsiClass
+import org.jetbrains.dokka.analysis.java.util.PsiDocumentableSource
+import org.jetbrains.dokka.analysis.java.util.from
+import org.jetbrains.dokka.analysis.kotlin.symbols.translators.getDRIFromClassLike
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.analysis.api.types.KtType
+import org.jetbrains.dokka.analysis.kotlin.internal.ClassHierarchy
+import org.jetbrains.dokka.analysis.kotlin.internal.FullClassHierarchyBuilder
+import org.jetbrains.dokka.analysis.kotlin.internal.Supertypes
+import org.jetbrains.kotlin.psi.KtClassOrObject
+import java.util.concurrent.ConcurrentHashMap
+
+
+internal class SymbolFullClassHierarchyBuilder : FullClassHierarchyBuilder {
+ override suspend fun build(module: DModule): ClassHierarchy {
+ val map = module.sourceSets.associateWith { ConcurrentHashMap<DRI, List<DRI>>() }
+ module.packages.forEach { visitDocumentable(it, map) }
+ return map
+ }
+
+ private fun KtAnalysisSession.collectSupertypesFromKtType(
+ driWithKType: Pair<DRI, KtType>,
+ supersMap: MutableMap<DRI, Supertypes>
+ ) {
+ val (dri, kotlinType) = driWithKType
+ val supertypes = kotlinType.getDirectSuperTypes().filterNot { it.isAny }
+ val supertypesDriWithKType = supertypes.mapNotNull { supertype ->
+ supertype.expandedClassSymbol?.let {
+ getDRIFromClassLike(it) to supertype
+ }
+ }
+
+ if (supersMap[dri] == null) {
+ supersMap[dri] = supertypesDriWithKType.map { it.first }
+ supertypesDriWithKType.forEach{ collectSupertypesFromKtType(it, supersMap) }
+ }
+ }
+
+ private fun collectSupertypesFromPsiClass(
+ driWithPsiClass: Pair<DRI, PsiClass>,
+ supersMap: MutableMap<DRI, Supertypes>
+ ) {
+ val (dri, psiClass) = driWithPsiClass
+ val supertypes = psiClass.superTypes.mapNotNull { it.resolve() }
+ .filterNot { it.qualifiedName == "java.lang.Object" }
+ val supertypesDriWithPsiClass = supertypes.map { DRI.from(it) to it }
+
+ if (supersMap[dri] == null) {
+ supersMap[dri] = supertypesDriWithPsiClass.map { it.first }
+ supertypesDriWithPsiClass.forEach { collectSupertypesFromPsiClass(it, supersMap) }
+ }
+ }
+
+ private fun visitDocumentable(
+ documentable: Documentable,
+ hierarchy: SourceSetDependent<MutableMap<DRI, List<DRI>>>
+ ) {
+ if (documentable is WithScope) {
+ documentable.classlikes.forEach { visitDocumentable(it, hierarchy) }
+ }
+ if (documentable is DClasslike) {
+ // to build a full class graph,
+ // using supertypes from Documentable is not enough since it keeps only one level of hierarchy
+ documentable.sources.forEach { (sourceSet, source) ->
+ if (source is KtPsiDocumentableSource) {
+ (source.psi as? KtClassOrObject)?.let { psi ->
+ analyze(psi) {
+ val type = psi.getNamedClassOrObjectSymbol()?.buildSelfClassType() ?: return@analyze
+ hierarchy[sourceSet]?.let { collectSupertypesFromKtType(documentable.dri to type, it) }
+ }
+ }
+ } else if (source is PsiDocumentableSource) {
+ val psi = source.psi as PsiClass
+ hierarchy[sourceSet]?.let { collectSupertypesFromPsiClass(documentable.dri to psi, it) }
+ }
+ }
+ }
+ }
+
+}
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSyntheticDocumentableDetector.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSyntheticDocumentableDetector.kt
new file mode 100644
index 00000000..20d2508b
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSyntheticDocumentableDetector.kt
@@ -0,0 +1,41 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.services
+
+import com.intellij.psi.PsiElement
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.java.util.PsiDocumentableSource
+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.dokka.analysis.kotlin.internal.SyntheticDocumentableDetector
+
+internal 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 {
+ @Suppress("UNCHECKED_CAST")
+ val extra = (documentable as? WithExtraProperties<Documentable>)?.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/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt
new file mode 100644
index 00000000..02198518
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt
@@ -0,0 +1,134 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.translators
+
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.withEnumEntryExtra
+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.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.name.Name
+import org.jetbrains.kotlin.psi.KtFile
+
+/**
+ * Map [KtAnnotationApplication] to Dokka [Annotations.Annotation]
+ */
+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) }
+
+ /**
+ * @return direct annotations and file-level annotations
+ */
+ fun KtAnalysisSession.getAllAnnotationsFrom(annotated: KtAnnotated): List<Annotations.Annotation> {
+ val directAnnotations = getDirectAnnotationsFrom(annotated)
+ val fileLevelAnnotations = (annotated as? KtSymbol)?.let { getFileLevelAnnotationsFrom(it) } ?: emptyList()
+ 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
+ AnnotationUseSiteTarget.FILE -> Annotations.AnnotationScope.FILE
+ else -> Annotations.AnnotationScope.DIRECT
+ }
+
+ private fun KtAnalysisSession.mustBeDocumented(annotationApplication: KtAnnotationApplication): Boolean {
+ if (annotationApplication.isNoExistedInSource()) return false
+ val annotationClass = getClassOrObjectSymbolByClassId(annotationApplication.classId ?: return false)
+ return annotationClass?.hasAnnotation(mustBeDocumentedAnnotation)
+ ?: false
+ }
+
+ private fun KtAnalysisSession.toDokkaAnnotation(annotationApplication: KtAnnotationApplication) =
+ Annotations.Annotation(
+ dri = annotationApplication.classId?.createDRI()
+ ?: DRI(packageName = "", classNames = ERROR_CLASS_NAME), // classId might be null on a non-existing annotation call,
+ 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 -> throw IllegalStateException("Unexpected a local class in annotation")
+ is KtKClassAnnotationValue.KtErrorClassAnnotationValue -> ClassValue(
+ annotationValue.unresolvedQualifierName ?: "",
+ DRI(packageName = "", classNames = ERROR_CLASS_NAME)
+ )
+
+ 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(),
+ ).withEnumEntryExtra()
+ }
+
+ companion object {
+ val mustBeDocumentedAnnotation = ClassId(FqName("kotlin.annotation"), FqName("MustBeDocumented"), false)
+ private val parameterNameAnnotation = ClassId(FqName("kotlin"), FqName("ParameterName"), false)
+
+ /**
+ * Functional types can have **generated** [ParameterName] annotation
+ */
+ internal fun KtAnnotated.getPresentableName(): String? =
+ this.annotationsByClassId(parameterNameAnnotation)
+ .firstOrNull()?.arguments?.firstOrNull { it.name == Name.identifier("name") }?.expression?.let { it as? KtConstantAnnotationValue }
+ ?.let { it.constantValue.value.toString() }
+ }
+} \ No newline at end of file
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DRIFactory.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DRIFactory.kt
new file mode 100644
index 00000000..ed5ed532
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DRIFactory.kt
@@ -0,0 +1,140 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.translators
+
+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.KtSymbolKind
+import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithKind
+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
+import org.jetbrains.kotlin.name.FqName
+
+internal fun ClassId.createDRI(): DRI = DRI(
+ packageName = this.packageFqName.asString(), classNames = this.relativeClassName.asString()
+)
+
+private fun CallableId.createDRI(receiver: TypeReference?, params: List<TypeReference>): 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()
+
+private val KtCallableSymbol.callableId
+ get() = this.callableIdIfNonLocal ?: throw IllegalStateException("Can not get callable Id due to it is local")
+
+// because of compatibility with Dokka K1, DRI of entry is kept as non-callable
+internal fun getDRIFromEnumEntry(symbol: KtEnumEntrySymbol): DRI =
+ symbol.callableId.let {
+ DRI(
+ packageName = it.packageName.asString(),
+ classNames = it.className?.asString() + "." + it.callableName.asString(),
+ )
+ }.withEnumEntryExtra()
+
+
+internal fun KtAnalysisSession.getDRIFromTypeParameter(symbol: KtTypeParameterSymbol): DRI {
+ val containingSymbol =
+ (symbol.getContainingSymbol() as? KtSymbolWithTypeParameters)
+ ?: throw IllegalStateException("Containing symbol is null for type parameter")
+ val typeParameters = containingSymbol.typeParameters
+ val index = typeParameters.indexOfFirst { symbol.name == it.name }
+ return getDRIFromSymbol(containingSymbol).copy(target = PointingToGenericParameters(index))
+}
+
+internal fun KtAnalysisSession.getDRIFromConstructor(symbol: KtConstructorSymbol): DRI =
+ (symbol.containingClassIdIfNonLocal
+ ?: throw IllegalStateException("Can not get class Id due to it is local")).createDRI().copy(
+ callable = Callable(
+ name = symbol.containingClassIdIfNonLocal?.relativeClassName?.asString() ?: "",
+ params = symbol.valueParameters.map { getTypeReferenceFrom(it.returnType) })
+ )
+
+internal fun KtAnalysisSession.getDRIFromVariableLike(symbol: KtVariableLikeSymbol): DRI {
+ val receiver = symbol.receiverType?.let {
+ getTypeReferenceFrom(it)
+ }
+ return symbol.callableId.createDRI(receiver, emptyList())
+}
+
+internal fun KtAnalysisSession.getDRIFromFunctionLike(symbol: KtFunctionLikeSymbol): DRI {
+ val params = symbol.valueParameters.map { getTypeReferenceFrom(it.returnType) }
+ val receiver = symbol.receiverType?.let {
+ getTypeReferenceFrom(it)
+ }
+ return symbol.callableIdIfNonLocal?.createDRI(receiver, params)
+ ?: getDRIFromLocalFunction(symbol)
+}
+
+internal fun getDRIFromClassLike(symbol: KtClassLikeSymbol): DRI =
+ 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)
+ ?: throw IllegalStateException("Containing symbol is null for type parameter")
+ 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 ")
+ }
+
+private fun KtAnalysisSession.getDRIFromNonCallablePossibleLocalSymbol(symbol: KtSymbol): DRI {
+ if ((symbol as? KtSymbolWithKind)?.symbolKind == KtSymbolKind.LOCAL) {
+ return symbol.getContainingSymbol()?.let { getDRIFromNonCallablePossibleLocalSymbol(it) }
+ ?: throw IllegalStateException("Can't get containing symbol for local symbol")
+ }
+ return getDRIFromSymbol(symbol)
+}
+
+/**
+ * Currently, it's used only for functions from enum entry,
+ * For its members: `memberSymbol.callableIdIfNonLocal=null`
+ */
+private fun KtAnalysisSession.getDRIFromLocalFunction(symbol: KtFunctionLikeSymbol): DRI {
+ /**
+ * A function is inside local object
+ */
+ val containingSymbolDRI = symbol.getContainingSymbol()?.let { getDRIFromNonCallablePossibleLocalSymbol(it) }
+ ?: throw IllegalStateException("Can't get containing symbol for local function")
+ return containingSymbolDRI.copy(
+ callable = Callable(
+ (symbol as? KtNamedSymbol)?.name?.asString() ?: "",
+ params = symbol.valueParameters.map { getTypeReferenceFrom(it.returnType) },
+ receiver = symbol.receiverType?.let {
+ getTypeReferenceFrom(it)
+ }
+ )
+ )
+}
+
+// ----------- DRI => compiler identifiers ----------------------------------------------------------------------------
+internal fun getClassIdFromDRI(dri: DRI) = ClassId(
+ FqName(dri.packageName ?: ""),
+ FqName(dri.classNames ?: throw IllegalStateException("DRI must have `classNames` to get ClassID")),
+ false
+)
+
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt
new file mode 100644
index 00000000..0c79b8a0
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt
@@ -0,0 +1,949 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.translators
+
+
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.*
+import com.intellij.psi.util.PsiLiteralUtil
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.java.JavaAnalysisPlugin
+import org.jetbrains.dokka.analysis.java.parsers.JavadocParser
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.getGeneratedKDocDocumentationFrom
+import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.AnalysisContext
+import org.jetbrains.dokka.analysis.kotlin.symbols.services.KtPsiDocumentableSource
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.getJavaDocDocumentationFrom
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.getKDocDocumentationFrom
+import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.hasGeneratedKDocDocumentation
+import org.jetbrains.dokka.analysis.kotlin.symbols.translators.AnnotationTranslator.Companion.getPresentableName
+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.Visibility
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.query
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.transformers.sources.AsyncSourceToDocumentableTranslator
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.KtNodeTypes
+import org.jetbrains.kotlin.analysis.api.*
+import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotated
+import org.jetbrains.kotlin.analysis.api.symbols.*
+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.descriptors.Modality
+import org.jetbrains.kotlin.descriptors.Visibilities
+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.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
+import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier
+import java.nio.file.Paths
+
+internal class DefaultSymbolToDocumentableTranslator(context: DokkaContext) : AsyncSourceToDocumentableTranslator {
+ private val kotlinAnalysis = context.plugin<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis }
+ private val javadocParser = JavadocParser(
+ docCommentParsers = context.plugin<JavaAnalysisPlugin>().query { docCommentParsers },
+ docCommentFinder = context.plugin<JavaAnalysisPlugin>().docCommentFinder
+ )
+
+ 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,
+ javadocParser = javadocParser
+ ).visitModule()
+ }
+}
+
+internal fun <T : Bound> T.wrapWithVariance(variance: org.jetbrains.kotlin.types.Variance) =
+ when (variance) {
+ org.jetbrains.kotlin.types.Variance.INVARIANT -> Invariance(this)
+ org.jetbrains.kotlin.types.Variance.IN_VARIANCE -> Contravariance(this)
+ org.jetbrains.kotlin.types.Variance.OUT_VARIANCE -> Covariance(this)
+ }
+
+/**
+ * Maps [KtSymbol] to Documentable model [Documentable]
+ */
+internal class DokkaSymbolVisitor(
+ private val sourceSet: DokkaConfiguration.DokkaSourceSet,
+ private val moduleName: String,
+ private val analysisContext: AnalysisContext,
+ private val logger: DokkaLogger,
+ private val javadocParser: JavadocParser? = null
+) {
+ private var annotationTranslator = AnnotationTranslator()
+ private var typeTranslator = TypeTranslator(sourceSet, annotationTranslator)
+
+ /**
+ * To avoid recursive classes
+ * e.g.
+ * open class Klass() {
+ * object Default : Klass()
+ * }
+ */
+ private val visitedNamedClassOrObjectSymbol: MutableSet<ClassId> =
+ mutableSetOf()
+
+ private fun <T> T.toSourceSetDependent() = if (this != null) mapOf(sourceSet to this) else emptyMap()
+
+ // KT-54846 will replace
+ private val KtDeclarationSymbol.isActual
+ get() = (psi as? KtModifierListOwner)?.hasActualModifier() == true
+ private val KtDeclarationSymbol.isExpect
+ get() = (psi as? KtModifierListOwner)?.hasExpectModifier() == true
+
+ private fun <T : KtSymbol> Collection<T>.filterSymbolsInSourceSet() = filter {
+ it.psi?.containingFile?.virtualFile?.path?.let { path ->
+ path.isNotBlank() && sourceSet.sourceRoots.any { root ->
+ Paths.get(path).startsWith(root.toPath())
+ }
+ } == true
+ }
+
+ fun visitModule(): DModule {
+ val ktFiles: List<KtFile> = getPsiFilesFromPaths(
+ analysisContext.project,
+ getSourceFilePaths(sourceSet.sourceRoots.map { it.canonicalPath })
+ )
+ val processedPackages: MutableSet<FqName> = mutableSetOf()
+ return analyze(analysisContext.mainModule) {
+ val packageSymbols: List<DPackage> = ktFiles
+ .mapNotNull {
+ if (processedPackages.contains(it.packageFqName))
+ return@mapNotNull null
+ processedPackages.add(it.packageFqName)
+ getPackageSymbolIfPackageExists(it.packageFqName)?.let { it1 ->
+ visitPackageSymbol(
+ it1
+ )
+ }
+ }
+
+ DModule(
+ name = moduleName,
+ packages = packageSymbols,
+ documentation = emptyMap(),
+ expectPresentInSet = null,
+ sourceSets = setOf(sourceSet)
+ )
+ }
+ }
+
+ private fun KtAnalysisSession.visitPackageSymbol(packageSymbol: KtPackageSymbol): DPackage {
+ val dri = getDRIFromPackage(packageSymbol)
+ val scope = packageSymbol.getPackageScope()
+ val callables = scope.getCallableSymbols().toList().filterSymbolsInSourceSet()
+ val classifiers = scope.getClassifierSymbols().toList().filterSymbolsInSourceSet()
+
+ val functions = callables.filterIsInstance<KtFunctionSymbol>().map { visitFunctionSymbol(it, dri) }
+ val properties = callables.filterIsInstance<KtPropertySymbol>().map { visitPropertySymbol(it, dri) }
+ val classlikes =
+ classifiers.filterIsInstance<KtNamedClassOrObjectSymbol>()
+ .map { visitNamedClassOrObjectSymbol(it, dri) }
+ val typealiases = classifiers.filterIsInstance<KtTypeAliasSymbol>().map { visitTypeAliasSymbol(it, dri) }
+
+ return DPackage(
+ dri = dri,
+ functions = functions,
+ properties = properties,
+ classlikes = classlikes,
+ typealiases = typealiases,
+ documentation = emptyMap(),
+ sourceSets = setOf(sourceSet)
+ )
+ }
+
+ private fun KtAnalysisSession.visitTypeAliasSymbol(
+ typeAliasSymbol: KtTypeAliasSymbol,
+ parent: DRI
+ ): DTypeAlias = withExceptionCatcher(typeAliasSymbol) {
+ 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) }
+
+ return DTypeAlias(
+ dri = dri,
+ name = name,
+ type = GenericTypeConstructor(
+ dri = dri,
+ projections = generics.map { it.variantTypeParameter }), // this property can be removed in DTypeAlias
+ 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(),
+ ancestryInfo.exceptionInSupertypesOrNull(),
+ )
+ )
+ }
+
+ fun KtAnalysisSession.visitNamedClassOrObjectSymbol(
+ namedClassOrObjectSymbol: KtNamedClassOrObjectSymbol,
+ parent: DRI
+ ): DClasslike = withExceptionCatcher(namedClassOrObjectSymbol) {
+ namedClassOrObjectSymbol.classIdIfNonLocal?.let { visitedNamedClassOrObjectSymbol.add(it) }
+
+ val name = namedClassOrObjectSymbol.name.asString()
+ val dri = parent.withClass(name)
+
+ val isExpect = namedClassOrObjectSymbol.isExpect
+ val isActual = namedClassOrObjectSymbol.isActual
+ val documentation = getDocumentation(namedClassOrObjectSymbol)?.toSourceSetDependent() ?: emptyMap()
+
+ val (constructors, functions, properties, classlikes) = getDokkaScopeFrom(namedClassOrObjectSymbol, dri)
+
+ val generics = namedClassOrObjectSymbol.typeParameters.mapIndexed { index, symbol ->
+ visitVariantTypeParameter(
+ index,
+ symbol,
+ dri
+ )
+ }
+
+ val ancestryInfo =
+ with(typeTranslator) { buildAncestryInformationFrom(namedClassOrObjectSymbol.buildSelfClassType()) }
+ val supertypes =
+ //(ancestryInfo.interfaces.map{ it.typeConstructor } + listOfNotNull(ancestryInfo.superclass?.typeConstructor))
+ namedClassOrObjectSymbol.superTypes.filterNot { it.isAny }
+ .map { with(typeTranslator) { 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()),
+ ancestryInfo.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()),
+ ancestryInfo.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 -> throw NotImplementedError("ANONYMOUS_OBJECT does not support")
+ KtClassKind.ENUM_CLASS -> {
+ val entries = namedClassOrObjectSymbol.getEnumEntries().map { visitEnumEntrySymbol(it) }
+
+ DEnum(
+ dri = dri,
+ name = name,
+ entries = entries,
+ 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())
+ )
+ )
+ }
+ }
+ }
+
+ private data class DokkaScope(
+ val constructors: List<DFunction>,
+ val functions: List<DFunction>,
+ val properties: List<DProperty>,
+ val classlikes: List<DClasslike>
+ )
+ private fun KtAnalysisSession.getDokkaScopeFrom(
+ namedClassOrObjectSymbol: KtNamedClassOrObjectSymbol,
+ dri: DRI
+ ): DokkaScope {
+ // e.g. getStaticMemberScope contains `valueOf`, `values` and `entries` members for Enum
+ val scope = listOf(namedClassOrObjectSymbol.getMemberScope(), namedClassOrObjectSymbol.getStaticMemberScope()).asCompositeScope()
+ val constructors = scope.getConstructors().map { visitConstructorSymbol(it) }.toList()
+
+ val callables = scope.getCallableSymbols().toList()
+ val classifiers = scope.getClassifierSymbols().toList()
+
+ val syntheticJavaProperties =
+ namedClassOrObjectSymbol.buildSelfClassType().getSyntheticJavaPropertiesScope()?.getCallableSignatures()
+ ?.map { it.symbol }
+ ?.filterIsInstance<KtSyntheticJavaPropertySymbol>() ?: emptySequence()
+
+ fun List<KtJavaFieldSymbol>.filterOutSyntheticJavaPropBackingField() =
+ filterNot { javaField -> syntheticJavaProperties.any { it.hasBackingField && javaField.name == it.name } }
+
+ val javaFields = callables.filterIsInstance<KtJavaFieldSymbol>()
+ .filterOutSyntheticJavaPropBackingField()
+
+ fun List<KtFunctionSymbol>.filterOutSyntheticJavaPropAccessors() = filterNot { fn ->
+ if (fn.origin == KtSymbolOrigin.JAVA && fn.callableIdIfNonLocal != null)
+ syntheticJavaProperties.any { fn.callableIdIfNonLocal == it.javaGetterSymbol.callableIdIfNonLocal || fn.callableIdIfNonLocal == it.javaSetterSymbol?.callableIdIfNonLocal }
+ else false
+ }
+
+ val functions = callables.filterIsInstance<KtFunctionSymbol>()
+ .filterOutSyntheticJavaPropAccessors().map { visitFunctionSymbol(it, dri) }
+
+
+ val properties = callables.filterIsInstance<KtPropertySymbol>().map { visitPropertySymbol(it, dri) } +
+ syntheticJavaProperties.map { visitPropertySymbol(it, dri) } +
+ javaFields.map { visitJavaFieldSymbol(it, dri) }
+
+
+ // hack, by default, compiler adds an empty companion object for enum
+ // TODO check if it is empty
+ fun List<KtNamedClassOrObjectSymbol>.filterOutEnumCompanion() =
+ if (namedClassOrObjectSymbol.classKind == KtClassKind.ENUM_CLASS)
+ filterNot {
+ it.name.asString() == "Companion" && it.classKind == KtClassKind.COMPANION_OBJECT
+ } else this
+
+ fun List<KtNamedClassOrObjectSymbol>.filterOutAndMarkAlreadyVisited() = filterNot { symbol ->
+ visitedNamedClassOrObjectSymbol.contains(symbol.classIdIfNonLocal)
+ .also {
+ if (!it) symbol.classIdIfNonLocal?.let { classId ->
+ visitedNamedClassOrObjectSymbol.add(
+ classId
+ )
+ }
+ }
+ }
+
+ val classlikes = classifiers.filterIsInstance<KtNamedClassOrObjectSymbol>()
+ .filterOutEnumCompanion() // hack to filter out companion for enum
+ .filterOutAndMarkAlreadyVisited()
+ .map { visitNamedClassOrObjectSymbol(it, dri) }
+
+ return DokkaScope(
+ constructors = constructors,
+ functions = functions,
+ properties = properties,
+ classlikes = classlikes
+ )
+ }
+
+ private fun KtAnalysisSession.visitEnumEntrySymbol(
+ enumEntrySymbol: KtEnumEntrySymbol
+ ): DEnumEntry = withExceptionCatcher(enumEntrySymbol) {
+ val dri = getDRIFromEnumEntry(enumEntrySymbol)
+ val isExpect = false
+
+ val scope = enumEntrySymbol.getMemberScope()
+ val callables = scope.getCallableSymbols().toList()
+ val classifiers = scope.getClassifierSymbols().toList()
+
+ val functions = callables.filterIsInstance<KtFunctionSymbol>().map { visitFunctionSymbol(it, dri) }
+ val properties = callables.filterIsInstance<KtPropertySymbol>().map { visitPropertySymbol(it, dri) }
+ val classlikes =
+ classifiers.filterIsInstance<KtNamedClassOrObjectSymbol>()
+ .map { visitNamedClassOrObjectSymbol(it, dri) }
+
+ return DEnumEntry(
+ dri = dri,
+ name = enumEntrySymbol.name.asString(),
+ documentation = getDocumentation(enumEntrySymbol)?.toSourceSetDependent() ?: emptyMap(),
+ functions = functions,
+ properties = properties,
+ classlikes = classlikes,
+ sourceSets = setOf(sourceSet),
+ expectPresentInSet = sourceSet.takeIf { isExpect },
+ extra = PropertyContainer.withAll(
+ getDokkaAnnotationsFrom(enumEntrySymbol)?.toSourceSetDependent()?.toAnnotations()
+ )
+ )
+ }
+
+ private fun KtAnalysisSession.visitPropertySymbol(propertySymbol: KtPropertySymbol, parent: DRI): DProperty =
+ withExceptionCatcher(propertySymbol) {
+ val dri = createDRIWithOverridden(propertySymbol).origin
+ val inheritedFrom = dri.getInheritedFromDRI(parent)
+ val isExpect = propertySymbol.isExpect
+ val isActual = propertySymbol.isActual
+ val generics =
+ propertySymbol.typeParameters.mapIndexed { index, symbol ->
+ visitVariantTypeParameter(
+ index,
+ symbol,
+ dri
+ )
+ }
+
+ return DProperty(
+ dri = dri,
+ name = propertySymbol.name.asString(),
+ receiver = propertySymbol.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(
+ 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 =
+ withExceptionCatcher(javaFieldSymbol) {
+ 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.receiverParameter?.let {
+ visitReceiverParameter(
+ it,
+ dri
+ )
+ },
+ sources = javaFieldSymbol.getSource(),
+ getter = null,
+ setter = null,
+ visibility = javaFieldSymbol.getDokkaVisibility().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(
+ javaFieldSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(),
+ getDokkaAnnotationsFrom(javaFieldSymbol)?.toSourceSetDependent()?.toAnnotations(),
+ //javaFieldSymbol.getDefaultValue()?.let { DefaultValue(it.toSourceSetDependent()) },
+ inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) },
+ // non-final java property should be var
+ takeUnless { javaFieldSymbol.isVal }?.let { IsVar }
+ )
+ )
+ }
+
+ private fun KtAnalysisSession.visitPropertyAccessor(
+ propertyAccessorSymbol: KtPropertyAccessorSymbol,
+ propertySymbol: KtPropertySymbol,
+ propertyDRI: DRI
+ ): DFunction = withExceptionCatcher(propertyAccessorSymbol) {
+ val isGetter = propertyAccessorSymbol is KtPropertyGetterSymbol
+ // it also covers @JvmName annotation
+ val name = (if (isGetter) propertySymbol.javaGetterName else propertySymbol.javaSetterName)?.asString() ?: ""
+
+ // SyntheticJavaProperty has callableIdIfNonLocal, propertyAccessorSymbol.origin = JAVA_SYNTHETIC_PROPERTY
+ // For Kotlin properties callableIdIfNonLocal=null
+ val dri = if (propertyAccessorSymbol.callableIdIfNonLocal != null)
+ getDRIFromFunctionLike(propertyAccessorSymbol)
+ else
+ propertyDRI.copy(
+ callable = Callable(name, null, propertyAccessorSymbol.valueParameters.map { getTypeReferenceFrom(it.returnType) })
+ )
+ // for SyntheticJavaProperty
+ val inheritedFrom = if(propertyAccessorSymbol.origin == KtSymbolOrigin.JAVA_SYNTHETIC_PROPERTY) dri.copy(callable = null) else null
+
+ val isExpect = propertyAccessorSymbol.isExpect
+ val isActual = propertyAccessorSymbol.isActual
+
+ val generics = propertyAccessorSymbol.typeParameters.mapIndexed { index, symbol ->
+ visitVariantTypeParameter(
+ index,
+ symbol,
+ dri
+ )
+ }
+
+ return DFunction(
+ dri = dri,
+ name = name,
+ isConstructor = false,
+ receiver = propertyAccessorSymbol.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(),
+ modifier = propertyAccessorSymbol.modality.toDokkaModifier().toSourceSetDependent(),
+ type = toBoundFrom(propertyAccessorSymbol.returnType),
+ sourceSets = setOf(sourceSet),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll(
+ propertyAccessorSymbol.additionalExtras()?.toSourceSetDependent()?.toAdditionalModifiers(),
+ inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) },
+ getDokkaAnnotationsFrom(propertyAccessorSymbol)?.toSourceSetDependent()?.toAnnotations()
+ )
+ )
+ }
+
+ private fun KtAnalysisSession.visitConstructorSymbol(
+ constructorSymbol: KtConstructorSymbol
+ ): DFunction = withExceptionCatcher(constructorSymbol) {
+ 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
+ )
+ }
+
+ val documentation = getDocumentation(constructorSymbol)?.let { docNode ->
+ if (constructorSymbol.isPrimary) {
+ docNode.copy(children = (docNode.children.find { it is Constructor }?.root?.let { constructor ->
+ listOf(Description(constructor))
+ } ?: emptyList<TagWrapper>()) + docNode.children.filterIsInstance<Param>())
+ } else {
+ docNode
+ }
+ }?.toSourceSetDependent()
+
+ return DFunction(
+ dri = dri,
+ name = name,
+ isConstructor = true,
+ receiver = constructorSymbol.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 = documentation ?: emptyMap(),
+ modifier = KotlinModifier.Empty.toSourceSetDependent(),
+ type = toBoundFrom(constructorSymbol.returnType),
+ sourceSets = setOf(sourceSet),
+ isExpectActual = (isExpect || isActual),
+ extra = PropertyContainer.withAll(
+ getDokkaAnnotationsFrom(constructorSymbol)?.toSourceSetDependent()?.toAnnotations(),
+ takeIf { constructorSymbol.isPrimary }?.let { PrimaryConstructorExtra }
+ )
+ )
+ }
+
+ private fun KtAnalysisSession.visitFunctionSymbol(functionSymbol: KtFunctionSymbol, parent: DRI): DFunction =
+ withExceptionCatcher(functionSymbol) {
+ 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.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(
+ 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(),
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ getDokkaAnnotationsFrom(receiverParameterSymbol)?.toSourceSetDependent()?.toAnnotations()
+ )
+ )
+
+ private fun KtValueParameterSymbol.getDefaultValue(): Expression? =
+ if (origin == KtSymbolOrigin.SOURCE) (psi as? KtParameter)?.defaultValue?.toDefaultValueExpression()
+ else null
+
+ private fun KtPropertySymbol.getDefaultValue(): Expression? =
+ (initializer?.initializerPsi as? KtConstantExpression)?.toDefaultValueExpression() // TODO consider [KtConstantInitializerValue], but should we keep an original format, e.g. 0xff or 0b101?
+
+ 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 upperBoundsOrNullableAny =
+ typeParameterSymbol.upperBounds.takeIf { it.isNotEmpty() } ?: listOf(this.builtinTypes.NULLABLE_ANY)
+ 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 = upperBoundsOrNullableAny.map { toBoundFrom(it) },
+ sourceSets = setOf(sourceSet),
+ extra = PropertyContainer.withAll(
+ getDokkaAnnotationsFrom(typeParameterSymbol)?.toSourceSetDependent()?.toAnnotations()
+ )
+ )
+ }
+ // ----------- Utils ----------------------------------------------------------------------------
+
+ private fun KtAnalysisSession.getDokkaAnnotationsFrom(annotated: KtAnnotated): List<Annotations.Annotation>? =
+ with(annotationTranslator) { getAllAnnotationsFrom(annotated) }.takeUnless { it.isEmpty() }
+
+ private 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)
+ *
+ * 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())
+ }
+ }
+
+ private fun KtAnalysisSession.getDocumentation(symbol: KtSymbol) =
+ if (symbol.origin == KtSymbolOrigin.SOURCE_MEMBER_GENERATED)
+ getGeneratedKDocDocumentationFrom(symbol)
+ else
+ getKDocDocumentationFrom(symbol, logger) ?: javadocParser?.let { getJavaDocDocumentationFrom(symbol, it) }
+
+ private fun KtAnalysisSession.isObvious(functionSymbol: KtFunctionSymbol): Boolean {
+ return functionSymbol.origin == KtSymbolOrigin.SOURCE_MEMBER_GENERATED && !hasGeneratedKDocDocumentation(functionSymbol) ||
+ !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 KtSymbol.getSource() = KtPsiDocumentableSource(psi).toSourceSetDependent()
+
+ private fun AncestryNode.exceptionInSupertypesOrNull(): ExceptionInSupertypes? =
+ typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() }
+ ?.let { ExceptionInSupertypes(it.toSourceSetDependent()) }
+
+
+ // ----------- 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.VarArg.takeIf { isVararg }
+ ).toSet().takeUnless { it.isEmpty() }
+
+ private fun KtPropertyAccessorSymbol.additionalExtras() = listOfNotNull(
+ ExtraModifiers.KotlinOnlyModifiers.Inline.takeIf { isInline },
+//ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isJvmStaticInObjectOrClassOrInterface() },
+ 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 KtJavaFieldSymbol.additionalExtras() = listOfNotNull(
+ ExtraModifiers.JavaOnlyModifiers.Static.takeIf { isStatic }
+ ).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
+ }
+
+
+ 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
+ }
+}
+
+
+
+
+
+
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TranslatorError.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TranslatorError.kt
new file mode 100644
index 00000000..8a8e2261
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TranslatorError.kt
@@ -0,0 +1,29 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.translators
+
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
+import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
+
+internal class TranslatorError(message: String, cause: Throwable?) : IllegalStateException(message, cause)
+
+internal inline fun <R> KtAnalysisSession.withExceptionCatcher(symbol: KtSymbol, action: KtAnalysisSession.() -> R): R =
+ try {
+ action()
+ } catch (e: TranslatorError) {
+ throw e
+ } catch (e: Throwable) {
+ val file = try {
+ symbol.psi?.containingFile?.virtualFile?.path
+ } catch (e: Throwable) {
+ "[$e]"
+ }
+ val textRange = try {
+ symbol.psi?.textRange.toString()
+ } catch (e: Throwable) {
+ "[$e]"
+ }
+ throw TranslatorError(
+ "Error in translating of symbol (${(symbol as? KtNamedSymbol)?.name}) $symbol in file: $file, $textRange",
+ e
+ )
+ } \ No newline at end of file
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeReferenceFactory.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeReferenceFactory.kt
new file mode 100644
index 00000000..5acfeb9e
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeReferenceFactory.kt
@@ -0,0 +1,78 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.translators
+
+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 =
+ getTypeReferenceFromPossiblyRecursive(type, emptyList())
+
+
+// see `deep recursive typebound #1342` test
+private fun KtAnalysisSession.getTypeReferenceFromPossiblyRecursive(
+ type: KtType,
+ paramTrace: List<KtType>
+): 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(),
+ type.ownTypeArguments.map {
+ getTypeReferenceFromTypeProjection(
+ it,
+ paramTrace
+ )
+ }
+ )
+
+ 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("$ERROR_CLASS_NAME $type", emptyList())
+ is KtFlexibleType -> getTypeReferenceFromPossiblyRecursive(
+ type.upperBound,
+ paramTrace
+ )
+
+ is KtDefinitelyNotNullType -> getTypeReferenceFromPossiblyRecursive(
+ type.original,
+ paramTrace
+ )
+
+ is KtDynamicType -> TypeConstructor("[dynamic]", emptyList())
+ is KtTypeErrorType -> TypeConstructor("$ERROR_CLASS_NAME $type", emptyList())
+ is KtCapturedType -> throw NotImplementedError()
+ is KtIntegerLiteralType -> throw NotImplementedError()
+ is KtIntersectionType -> throw NotImplementedError()
+ }.let {
+ if (type.isMarkedNullable) Nullable(it) else it
+ }
+
+}
+
+private fun KtAnalysisSession.getTypeReferenceFromTypeProjection(
+ typeProjection: KtTypeProjection,
+ paramTrace: List<KtType>
+): TypeReference =
+ when (typeProjection) {
+ is KtStarTypeProjection -> StarProjection
+ is KtTypeArgumentWithVariance -> getTypeReferenceFromPossiblyRecursive(typeProjection.type, paramTrace)
+ }
diff --git a/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt
new file mode 100644
index 00000000..2b79498d
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt
@@ -0,0 +1,186 @@
+package org.jetbrains.dokka.analysis.kotlin.symbols.translators
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.kotlin.symbols.translators.AnnotationTranslator.Companion.getPresentableName
+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 const val ERROR_CLASS_NAME = "<ERROR CLASS>"
+
+/**
+ * Maps [KtType] to Dokka [Bound] or [TypeConstructorWithKind].
+ *
+ * Also, build [AncestryNode] tree from [KtType]
+ */
+internal class TypeTranslator(
+ private val sourceSet: DokkaConfiguration.DokkaSourceSet,
+ private val annotationTranslator: AnnotationTranslator
+) {
+
+ private fun <T> 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.toTypeConstructorFromTypeAliased(classType: KtUsualClassType): TypeAliased {
+ val classSymbol = classType.classSymbol
+ return if (classSymbol is KtTypeAliasSymbol)
+ TypeAliased(
+ typeAlias = GenericTypeConstructor(
+ dri = getDRIFromNonErrorClassType(classType),
+ projections = classType.ownTypeArguments.map { toProjection(it) }),
+ inner = toBoundFrom(classSymbol.expandedType),
+ extra = PropertyContainer.withAll(
+ getDokkaAnnotationsFrom(classType)?.toSourceSetDependent()?.toAnnotations()
+ )
+ ) else
+ throw IllegalStateException("Expected type alias symbol in type")
+ }
+
+ 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()
+ )
+ )
+
+ fun KtAnalysisSession.toBoundFrom(type: KtType): Bound =
+ when (type) {
+ is KtUsualClassType -> {
+ if (type.classSymbol is KtTypeAliasSymbol) toTypeConstructorFromTypeAliased(type)
+ else 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 KtFlexibleType -> TypeAliased(
+ toBoundFrom(type.upperBound),
+ toBoundFrom(type.lowerBound),
+ extra = PropertyContainer.withAll(
+ getDokkaAnnotationsFrom(type)?.toSourceSetDependent()?.toAnnotations()
+ )
+ )
+
+ is KtTypeErrorType -> UnresolvedBound(type.toString())
+ is KtCapturedType -> throw NotImplementedError()
+ is KtIntegerLiteralType -> throw NotImplementedError()
+ is KtIntersectionType -> throw NotImplementedError()
+ }.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 = when (type) {
+ is KtUsualClassType ->
+ when (val classSymbol = type.classSymbol) {
+ is KtNamedClassOrObjectSymbol -> TypeConstructorWithKind(
+ toTypeConstructorFrom(type),
+ classSymbol.classKind.toDokkaClassKind()
+ )
+
+ is KtAnonymousObjectSymbol -> throw NotImplementedError()
+ is KtTypeAliasSymbol -> toTypeConstructorWithKindFrom(classSymbol.expandedType)
+ }
+
+ is KtClassErrorType -> TypeConstructorWithKind(
+ GenericTypeConstructor(
+ dri = DRI(packageName = "", classNames = "$ERROR_CLASS_NAME $type"),
+ projections = emptyList(),
+
+ ),
+ KotlinClassKindTypes.CLASS
+ )
+
+ is KtTypeErrorType -> TypeConstructorWithKind(
+ GenericTypeConstructor(
+ dri = DRI(packageName = "", classNames = "$ERROR_CLASS_NAME $type"),
+ projections = emptyList(),
+
+ ),
+ KotlinClassKindTypes.CLASS
+ )
+
+ is KtFunctionalType -> TypeConstructorWithKind(
+ toFunctionalTypeConstructorFrom(type),
+ KotlinClassKindTypes.CLASS
+ )
+
+ is KtDefinitelyNotNullType -> toTypeConstructorWithKindFrom(type.original)
+
+ is KtCapturedType -> throw NotImplementedError()
+ is KtDynamicType -> throw NotImplementedError()
+ is KtFlexibleType -> throw NotImplementedError()
+ is KtIntegerLiteralType -> throw NotImplementedError()
+ is KtIntersectionType -> throw NotImplementedError()
+ is KtTypeParameterType -> throw NotImplementedError()
+ }
+
+ private fun KtAnalysisSession.getDokkaAnnotationsFrom(annotated: KtAnnotated): List<Annotations.Annotation>? =
+ 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/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt b/subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt
new file mode 100644
index 00000000..9333878a
--- /dev/null
+++ b/subprojects/analysis-kotlin-symbols/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<TypeConstructor> {
+ fun traverseSupertypes(ancestry: AncestryNode): List<TypeConstructor> =
+ 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/"
+ }