diff options
author | Ignat Beresnev <ignat.beresnev@jetbrains.com> | 2023-11-10 11:46:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-10 11:46:54 +0100 |
commit | 8e5c63d035ef44a269b8c43430f43f5c8eebfb63 (patch) | |
tree | 1b915207b2b9f61951ddbf0ff2e687efd053d555 /dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains | |
parent | a44efd4ba0c2e4ab921ff75e0f53fc9335aa79db (diff) | |
download | dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.gz dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.bz2 dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.zip |
Restructure the project to utilize included builds (#3174)
* Refactor and simplify artifact publishing
* Update Gradle to 8.4
* Refactor and simplify convention plugins and build scripts
Fixes #3132
---------
Co-authored-by: Adam <897017+aSemy@users.noreply.github.com>
Co-authored-by: Oleg Yukhnevich <whyoleg@gmail.com>
Diffstat (limited to 'dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains')
37 files changed, 3408 insertions, 0 deletions
diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt new file mode 100644 index 00000000..d8a4e476 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KDocProvider.kt @@ -0,0 +1,174 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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).ifUnresolved { logger.logUnresolvedLink(link.getLinkText(), kdocLocation) } }, + 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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt new file mode 100644 index 00000000..ad29f5f0 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/KdocMarkdownParser.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt new file mode 100644 index 00000000..9a0b81bd --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/ResolveKDocLink.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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 + +/** + * Util to print a message about unresolved [link] + */ +internal fun DokkaLogger.logUnresolvedLink(link: String, location: String?) { + warn("Couldn't resolve link for $link" + if (location != null) " in $location" else "") +} + +internal inline fun DRI?.ifUnresolved(action: () -> Unit): DRI? = this ?: run { + action() + null +} + +/** + * Resolves KDoc link via creating PSI. + * If the [link] is ambiguous, i.e. leads to more than one declaration, + * it returns deterministically any declaration. + * + * @return [DRI] or null if the [link] is unresolved + */ +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) } +} + +/** + * If the [link] is ambiguous, i.e. leads to more than one declaration, + * it returns deterministically any declaration. + * + * @return [DRI] or null if the [link] is unresolved + */ +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 + else getDRIFromSymbol(linkedSymbol) +} diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt new file mode 100644 index 00000000..4622ffd8 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/SyntheticKDocProvider.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt new file mode 100644 index 00000000..4db17b28 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorDocumentationContent.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt new file mode 100644 index 00000000..7a43972c --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/DescriptorKotlinDocCommentCreator.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt new file mode 100644 index 00000000..63c55869 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocComment.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt new file mode 100644 index 00000000..0ee95e45 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinDocCommentParser.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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.* +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.logUnresolvedLink +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.parseFromKDocTag +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.resolveKDocLink +import org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin +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 } + val elementName = element.resolveDocContext.ktElement.name + return analyze(kotlinAnalysis.getModule(sourceSet)) { + parseFromKDocTag( + kDocTag = element.comment, + externalDri = { link -> resolveKDocLink(link).ifUnresolved { context.logger.logUnresolvedLink(link.getLinkText(), elementName) } }, + kdocLocation = null, + parseWithChildren = parseWithChildren + ) + } + } +} + diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt new file mode 100644 index 00000000..4c2b7afd --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/java/KotlinInheritDocTagContentProvider.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt new file mode 100644 index 00000000..a090e4d5 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/IllegalModuleAndPackageDocumentation.kt @@ -0,0 +1,11 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt new file mode 100644 index 00000000..4036d3d0 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentation.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt new file mode 100644 index 00000000..be7c915c --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationFragment.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt new file mode 100644 index 00000000..f5cfbdb9 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationParsingContext.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.moduledocs + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.ifUnresolved +import org.jetbrains.dokka.analysis.kotlin.symbols.kdoc.logUnresolvedLink +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 sourceModule = kotlinAnalysis.getModule(sourceSet) + val contextPsi = analyze(sourceModule) { + val contextSymbol = when (fragment.classifier) { + Module -> ROOT_PACKAGE_SYMBOL + Package -> getPackageSymbolIfPackageExists(FqName(fragment.name)) + } + contextSymbol?.psi + } + MarkdownParser( + externalDri = { link -> + analyze(sourceModule) { + resolveKDocTextLink( + link, + contextPsi + ).ifUnresolved { + logger.logUnresolvedLink(link, fragment.name.ifBlank { "module documentation" }) + } + + } + }, + sourceLocation + ) + + } +} diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt new file mode 100644 index 00000000..ef79e885 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationReader.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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.getModule(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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt new file mode 100644 index 00000000..f994eb36 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/ModuleAndPackageDocumentationSource.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt new file mode 100644 index 00000000..176a487b --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentation.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt new file mode 100644 index 00000000..621b06f0 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/kdoc/moduledocs/parseModuleAndPackageDocumentationFragments.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/AnalysisContext.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/AnalysisContext.kt new file mode 100644 index 00000000..191c5f92 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/AnalysisContext.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.symbols.plugin + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer +import org.jetbrains.dokka.DokkaConfiguration +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 + +internal fun SamplesKotlinAnalysis( + sourceSets: List<DokkaConfiguration.DokkaSourceSet>, + context: DokkaContext, +): KotlinAnalysis = createAnalysisSession( + sourceSets = sourceSets, + logger = context.logger, + isSampleProject = true +) + +internal fun ProjectKotlinAnalysis( + sourceSets: List<DokkaConfiguration.DokkaSourceSet>, + context: DokkaContext, +): KotlinAnalysis = createAnalysisSession( + sourceSets = sourceSets, + logger = context.logger +) + +internal class KotlinAnalysis( + private val sourceModules: SourceSetDependent<KtSourceModule>, + private val analysisSession: StandaloneAnalysisAPISession, + private val applicationDisposable: Disposable, + private val projectDisposable: Disposable +) : Closeable { + + fun getModule(sourceSet: DokkaConfiguration.DokkaSourceSet) = + sourceModules[sourceSet] ?: error("Missing a source module for sourceSet ${sourceSet.displayName} with id ${sourceSet.sourceSetID}") + + fun getModuleOrNull(sourceSet: DokkaConfiguration.DokkaSourceSet) = + sourceModules[sourceSet] + + val modulesWithFiles + get() = analysisSession.modulesWithFiles + + override fun close() { + Disposer.dispose(applicationDisposable) + Disposer.dispose(projectDisposable) + } +}
\ No newline at end of file diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/KotlinAnalysis.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/KotlinAnalysis.kt new file mode 100644 index 00000000..e074a142 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/KotlinAnalysis.kt @@ -0,0 +1,175 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.symbols.plugin + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaSourceSetID +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.utilities.DokkaLogger +import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals +import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider +import org.jetbrains.kotlin.analysis.api.standalone.KtAlwaysAccessibleLifetimeTokenProvider +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.config.* +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 java.io.File + +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(logger: DokkaLogger): File? { + val javaHome = File(System.getProperty("java.home")) + if (!javaHome.exists()) { + logger.error("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 + ) + ) +} + +@OptIn(KtAnalysisApiInternals::class) +internal fun createAnalysisSession( + sourceSets: List<DokkaConfiguration.DokkaSourceSet>, + logger: DokkaLogger, + applicationDisposable: Disposable = Disposer.newDisposable("StandaloneAnalysisAPISession.application"), + projectDisposable: Disposable = Disposer.newDisposable("StandaloneAnalysisAPISession.project"), + isSampleProject: Boolean = false +): KotlinAnalysis { + val sourcesModule = mutableMapOf<DokkaConfiguration.DokkaSourceSet, KtSourceModule>() + + val analysisSession = buildStandaloneAnalysisAPISession( + applicationDisposable = applicationDisposable, + projectDisposable = projectDisposable, + withPsiDeclarationFromBinaryModuleProvider = false + ) { + registerProjectService(KtLifetimeTokenProvider::class.java, KtAlwaysAccessibleLifetimeTokenProvider()) + + val sortedSourceSets = topologicalSortByDependantSourceSets(sourceSets, logger) + + val sourcesModuleBySourceSetId = mutableMapOf<DokkaSourceSetID, KtSourceModule>() + + buildKtModuleProvider { + val jdkModule = getJdkHomeFromSystemProperty(logger)?.let { jdkHome -> + buildKtSdkModule { + this.platform = Platform.jvm.toTargetPlatform() + addBinaryRootsFromJdkHome(jdkHome.toPath(), isJre = true) + sdkName = "JDK" + } + } + + fun KtModuleBuilder.addModuleDependencies(sourceSet: DokkaConfiguration.DokkaSourceSet) { + val targetPlatform = sourceSet.analysisPlatform.toTargetPlatform() + addRegularDependency( + buildKtLibraryModule { + this.platform = targetPlatform + addBinaryRoots(sourceSet.classpath.map { it.toPath() }) + libraryName = "Library for ${sourceSet.displayName}" + } + ) + if (sourceSet.analysisPlatform == Platform.jvm) { + jdkModule?.let { addRegularDependency(it) } + } + sourceSet.dependentSourceSets.forEach { + addRegularDependency( + sourcesModuleBySourceSetId[it] + ?: error("There is no source module for $it") + ) + } + } + + for (sourceSet in sortedSourceSets) { + val targetPlatform = sourceSet.analysisPlatform.toTargetPlatform() + val sourceModule = buildKtSourceModule { + languageVersionSettings = + getLanguageVersionSettings(sourceSet.languageVersion, sourceSet.apiVersion) + platform = targetPlatform + moduleName = "<module ${sourceSet.displayName}>" + if (isSampleProject) + addSourceRoots(sourceSet.samples.map { it.toPath() }) + else + addSourceRoots(sourceSet.sourceRoots.map { it.toPath() }) + addModuleDependencies( + sourceSet, + ) + } + sourcesModule[sourceSet] = sourceModule + sourcesModuleBySourceSetId[sourceSet.sourceSetID] = sourceModule + addModule(sourceModule) + } + platform = sourceSets.map { it.analysisPlatform }.distinct().singleOrNull()?.toTargetPlatform() + ?: Platform.common.toTargetPlatform() + } + } + return KotlinAnalysis(sourcesModule, analysisSession, applicationDisposable, projectDisposable) +} + +private enum class State { + UNVISITED, + VISITING, + VISITED; +} + +internal fun topologicalSortByDependantSourceSets( + sourceSets: List<DokkaConfiguration.DokkaSourceSet>, + logger: DokkaLogger +): List<DokkaConfiguration.DokkaSourceSet> { + val result = mutableListOf<DokkaConfiguration.DokkaSourceSet>() + + val verticesAssociatedWithState = sourceSets.associateWithTo(mutableMapOf()) { State.UNVISITED } + fun dfs(souceSet: DokkaConfiguration.DokkaSourceSet) { + when (verticesAssociatedWithState[souceSet]) { + State.VISITED -> return + State.VISITING -> { + logger.error("Detected cycle in source set graph") + return + } + + else -> { + val dependentSourceSets = + souceSet.dependentSourceSets.mapNotNull { dependentSourceSetId -> + sourceSets.find { it.sourceSetID == dependentSourceSetId } + // just skip + ?: null.also { logger.error("Unknown source set Id $dependentSourceSetId in dependencies of ${souceSet.sourceSetID}") } + } + verticesAssociatedWithState[souceSet] = State.VISITING + dependentSourceSets.forEach(::dfs) + verticesAssociatedWithState[souceSet] = State.VISITED + result += souceSet + } + } + } + sourceSets.forEach(::dfs) + return result +}
\ No newline at end of file diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt new file mode 100644 index 00000000..122e0b10 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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 { SymbolKotlinToJavaMapper() } + } + + internal val symbolInheritanceBuilder by extending { + plugin<InternalKotlinAnalysisPlugin>().inheritanceBuilder providing ::SymbolInheritanceBuilder + } + + 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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisProjectProvider.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisProjectProvider.kt new file mode 100644 index 00000000..398d48ee --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisProjectProvider.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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.getModule(sourceSet).project + } +} diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisSourceRootsExtractor.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisSourceRootsExtractor.kt new file mode 100644 index 00000000..01084eab --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinAnalysisSourceRootsExtractor.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinDocumentableSourceLanguageParser.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinDocumentableSourceLanguageParser.kt new file mode 100644 index 00000000..ae0d79ee --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinDocumentableSourceLanguageParser.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt new file mode 100644 index 00000000..e453c72d --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KotlinSampleProvider.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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.analysis.project.structure.KtSourceModule +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 + +public class KotlinSampleProviderFactory( + public 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 +public open class KotlinSampleProvider( + public val context: DokkaContext +): SampleProvider { + private val kotlinAnalysisOfRegularSources = context.plugin<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis } + + private val kotlinAnalysisOfSamples = SamplesKotlinAnalysis( + sourceSets = context.configuration.sourceSets, context = context + ) + + 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? { + return kotlinAnalysisOfSamples.getModuleOrNull(sourceSet)?.let { getSampleFromModule(it, fqLink) } + ?: getSampleFromModule( + kotlinAnalysisOfRegularSources.getModule(sourceSet), fqLink + ) + } + private fun getSampleFromModule(module: KtSourceModule, fqLink: String): SampleProvider.SampleSnippet? { + val psiElement = analyze(module) { + 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() { + kotlinAnalysisOfSamples.close() + } +} diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KtPsiDocumentableSource.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KtPsiDocumentableSource.kt new file mode 100644 index 00000000..284da189 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/KtPsiDocumentableSource.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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) + } + } +} diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt new file mode 100644 index 00000000..1473a7da --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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) + + return analyze(kotlinAnalysis.getModule(sourceSet)) { + val symbol = getClassOrObjectSymbolByClassId(classId) as? KtNamedClassOrObjectSymbol?: return@analyze null + val translator = DokkaSymbolVisitor(sourceSet, sourceSet.displayName, kotlinAnalysis, 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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolFullClassHierarchyBuilder.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolFullClassHierarchyBuilder.kt new file mode 100644 index 00000000..0b68ac81 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolFullClassHierarchyBuilder.kt @@ -0,0 +1,148 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.symbols.services + +import com.intellij.psi.PsiClass +import org.jetbrains.dokka.DokkaConfiguration +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.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin +import org.jetbrains.dokka.analysis.kotlin.symbols.translators.AnnotationTranslator +import org.jetbrains.dokka.analysis.kotlin.symbols.translators.TypeTranslator +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.kotlin.psi.KtClassOrObject +import java.util.concurrent.ConcurrentHashMap + + +internal class SymbolFullClassHierarchyBuilder(context: DokkaContext) : FullClassHierarchyBuilder { + private val kotlinAnalysis = context.plugin<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis } + + 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 + if (supersMap[dri] == null) { + val supertypes = kotlinType.getDirectSuperTypes(shouldApproximate = true).filterNot { it.isAny } + val supertypesDriWithKType = supertypes.mapNotNull { supertype -> + supertype.expandedClassSymbol?.let { + getDRIFromClassLike(it) to supertype + } + } + 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(kotlinAnalysis.getModule(sourceSet)) { + 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) } + } + } + } + } + + internal class SuperclassesWithKind( + val typeConstructorWithKind: TypeConstructorWithKind, + val superclasses: List<TypeConstructorWithKind> + ) + + /** + * Currently, it works only for Symbols + */ + internal fun collectKotlinSupertypesWithKind( + documentable: Iterable<Documentable>, + sourceSet: DokkaConfiguration.DokkaSourceSet + ): Map<DRI, SuperclassesWithKind> { + val typeTranslator = TypeTranslator(sourceSet, AnnotationTranslator()) + val hierarchy = mutableMapOf<DRI, SuperclassesWithKind>() + + analyze(kotlinAnalysis.getModule(sourceSet)) { + documentable.filterIsInstance<DClasslike>().forEach { + val source = it.sources[sourceSet] + if (source is KtPsiDocumentableSource) { + (source.psi as? KtClassOrObject)?.let { psi -> + val type = psi.getNamedClassOrObjectSymbol()?.buildSelfClassType() ?: return@analyze + collectSupertypesWithKindFromKtType(typeTranslator, with(typeTranslator) { + toTypeConstructorWithKindFrom(type) + } to type, hierarchy) + } + } // else if (source is PsiDocumentableSource) TODO val psi = source.psi as? PsiClass + } + } + return hierarchy + } + + private fun KtAnalysisSession.collectSupertypesWithKindFromKtType( + typeTranslator: TypeTranslator, + typeConstructorWithKindWithKType: Pair<TypeConstructorWithKind, KtType>, + supersMap: MutableMap<DRI, SuperclassesWithKind> + ) { + val (typeConstructorWithKind, kotlinType) = typeConstructorWithKindWithKType + + if (supersMap[typeConstructorWithKind.typeConstructor.dri] == null) { + val supertypes = kotlinType.getDirectSuperTypes(shouldApproximate = true).filterNot { it.isAny } + + val supertypesDriWithKType = supertypes.map { supertype -> + with(typeTranslator) { + toTypeConstructorWithKindFrom(supertype) + } to supertype + } + supersMap[typeConstructorWithKind.typeConstructor.dri] = + SuperclassesWithKind(typeConstructorWithKind, supertypesDriWithKType.map { it.first }) + supertypesDriWithKType.forEach { collectSupertypesWithKindFromKtType(typeTranslator, it, supersMap) } + } + } +} diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolInheritanceBuilder.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolInheritanceBuilder.kt new file mode 100644 index 00000000..540f08a7 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolInheritanceBuilder.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.symbols.services + +import com.intellij.psi.PsiClass +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.analysis.java.util.from +import org.jetbrains.dokka.analysis.java.util.PsiDocumentableSource +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.analysis.kotlin.internal.InheritanceBuilder +import org.jetbrains.dokka.analysis.kotlin.internal.InheritanceNode +import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle + +/** + * This is copy-pasted from org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.DescriptorInheritanceBuilder and adapted for symbols + */ +internal class SymbolInheritanceBuilder(context: DokkaContext) : InheritanceBuilder { + private val symbolFullClassHierarchyBuilder = + context.plugin<InternalKotlinAnalysisPlugin>().querySingle { fullClassHierarchyBuilder } + + override fun build(documentables: Map<DRI, Documentable>): List<InheritanceNode> { + + // this statement is copy-pasted from the version for Descriptors + val psiInheritanceTree = + documentables.flatMap { (_, v) -> (v as? WithSources)?.sources?.values.orEmpty() } + .filterIsInstance<PsiDocumentableSource>().mapNotNull { it.psi as? PsiClass } + .flatMap(::gatherPsiClasses) + .flatMap { entry -> entry.second.map { it to entry.first } } + .let { + it + it.map { it.second to null } + } + .groupBy({ it.first }) { it.second } + .map { it.key to it.value.filterNotNull().distinct() } + .map { (k, v) -> + InheritanceNode( + DRI.from(k), + v.map { InheritanceNode(DRI.from(it)) }, + k.supers.filter { it.isInterface }.map { DRI.from(it) }, + k.isInterface + ) + + } + + // copy-pasted from stdlib 1.5 + fun <T, R : Any> Iterable<T>.firstNotNullOfOrNull(transform: (T) -> R?): R? { + for (element in this) { + val result = transform(element) + if (result != null) { + return result + } + } + return null + } + + val jvmSourceSet = + documentables.values.firstNotNullOfOrNull { it.sourceSets.find { it.analysisPlatform == Platform.jvm } } + if (jvmSourceSet == null) + return psiInheritanceTree + + val typeConstructorsMap = + (symbolFullClassHierarchyBuilder as? SymbolFullClassHierarchyBuilder)?.collectKotlinSupertypesWithKind( + documentables.values, + jvmSourceSet + ) + ?: throw IllegalStateException("Unexpected symbolFullClassHierarchyBuildertype") // TODO: https://github.com/Kotlin/dokka/issues/3225 Unify FullClassHierarchyBuilder and InheritanceBuilder into one builder + + fun ClassKind.isInterface() = this == KotlinClassKindTypes.INTERFACE || this == JavaClassKindTypes.INTERFACE + val symbolsInheritanceTree = typeConstructorsMap.map { (dri, superclasses) -> + InheritanceNode( + dri, + superclasses.superclasses.map { InheritanceNode(it.typeConstructor.dri) }, + superclasses.superclasses.filter { it.kind.isInterface() }.map { it.typeConstructor.dri }, + isInterface = superclasses.typeConstructorWithKind.kind.isInterface() + ) + } + + return psiInheritanceTree + symbolsInheritanceTree + } + + private fun gatherPsiClasses(psi: PsiClass): List<Pair<PsiClass, List<PsiClass>>> = psi.supers.toList().let { l -> + listOf(psi to l) + l.flatMap { gatherPsiClasses(it) } + } +} diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolKotlinToJavaMapper.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolKotlinToJavaMapper.kt new file mode 100644 index 00000000..77ede87f --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolKotlinToJavaMapper.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.symbols.services + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.PointingToDeclaration +import org.jetbrains.dokka.analysis.kotlin.internal.KotlinToJavaService +import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap // or import kotlin.reflect.jvm.internal.impl.builtins.jvm.JavaToKotlinClassMap see https://github.com/Kotlin/dokka/issues/3226 +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName + +/** + * This is copy-pasted from org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.DescriptorKotlinToJavaMapper + */ +internal class SymbolKotlinToJavaMapper : KotlinToJavaService { + + override fun findAsJava(kotlinDri: DRI): DRI? { + return kotlinDri.partialFqName().mapToJava()?.toDRI(kotlinDri) + } + + private fun DRI.partialFqName() = packageName?.let { "$it." } + classNames + + private fun String.mapToJava(): ClassId? = + JavaToKotlinClassMap.mapKotlinToJava(FqName(this).toUnsafe()) + + private fun ClassId.toDRI(dri: DRI?): DRI = DRI( + packageName = packageFqName.asString(), + classNames = classNames(), + callable = dri?.callable,//?.asJava(), TODO: check this + extra = null, + target = PointingToDeclaration + ) + + private fun ClassId.classNames(): String = + shortClassName.identifier + (outerClassId?.classNames()?.let { ".$it" } ?: "") +} diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSyntheticDocumentableDetector.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSyntheticDocumentableDetector.kt new file mode 100644 index 00000000..d281e9c0 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolSyntheticDocumentableDetector.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt new file mode 100644 index 00000000..c9882487 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/AnnotationTranslator.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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.KtPropertySymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolOrigin +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.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) } + + /** + * The examples of annotations from backing field are [JvmField], [JvmSynthetic]. + * + * @return direct annotations, annotations from backing field and file-level annotations + */ + fun KtAnalysisSession.getAllAnnotationsFrom(annotated: KtAnnotated): List<Annotations.Annotation> { + val directAnnotations = getDirectAnnotationsFrom(annotated) + val backingFieldAnnotations = + (annotated as? KtPropertySymbol)?.backingFieldSymbol?.let { getDirectAnnotationsFrom(it) }.orEmpty() + val fileLevelAnnotations = (annotated as? KtSymbol)?.let { getFileLevelAnnotationsFrom(it) }.orEmpty() + return directAnnotations + backingFieldAnnotations + 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 -> ClassValue( + "<Unsupported Annotation Value>", + DRI(packageName = "", classNames = ERROR_CLASS_NAME) + ) + } + + 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 + * @see ParameterName + */ + 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() } + } +} diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DRIFactory.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DRIFactory.kt new file mode 100644 index 00000000..a2cb423a --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DRIFactory.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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 $symbol") + } + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt new file mode 100644 index 00000000..298d0182 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/DefaultSymbolToDocumentableTranslator.kt @@ -0,0 +1,957 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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.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 + @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: KotlinAnalysis, + 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 sourceModule = analysisContext.getModule(sourceSet) + val ktFiles = analysisContext.modulesWithFiles[sourceModule]?.filterIsInstance<KtFile>() ?: throw IllegalStateException("No source files for a source module ${sourceModule.moduleName} of source set ${sourceSet.sourceSetID}") + val processedPackages: MutableSet<FqName> = mutableSetOf() + return analyze(sourceModule) { + 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, classlikesWithoutCompanion) = getDokkaScopeFrom(namedClassOrObjectSymbol, dri) + + val companionObject = namedClassOrObjectSymbol.companionObject?.let { + visitNamedClassOrObjectSymbol( + it, + dri + ) + } as? DObject + val classlikes = if (companionObject == null) classlikesWithoutCompanion else classlikesWithoutCompanion + companionObject + + 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 = companionObject, + 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 = companionObject, + 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 = companionObject, + 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 -> { + /** + * See https://github.com/Kotlin/dokka/issues/3129 + * + * e.g. the `A` enum entry in the `enum E` is + * ``` + * static val A: E = object : E() { + * val x: Int = 5 + * } + * ``` + * it needs to exclude all static members like `values` and `valueOf` from the enum class's scope + */ + val enumEntryScope = lazy { + getDokkaScopeFrom(namedClassOrObjectSymbol, dri, includeStaticScope = false).let { + it.copy( + functions = it.functions.map { it.withNewExtras( it.extra + InheritedMember(dri.copy(callable = null).toSourceSetDependent())) }, + properties = it.properties.map { it.withNewExtras( it.extra + InheritedMember(dri.copy(callable = null).toSourceSetDependent())) } + ) + } + } + + val entries = + namedClassOrObjectSymbol.getEnumEntries().map { + visitEnumEntrySymbol(it, enumEntryScope.value) + } + + 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 classlikesWithoutCompanion: List<DClasslike> + ) + + /** + * @param includeStaticScope flag to add static members, e.g. `valueOf`, `values` and `entries` members for Enum + */ + private fun KtAnalysisSession.getDokkaScopeFrom( + namedClassOrObjectSymbol: KtNamedClassOrObjectSymbol, + dri: DRI, + includeStaticScope: Boolean = true + ): DokkaScope { + // e.g. getStaticMemberScope contains `valueOf`, `values` and `entries` members for Enum + val scope = if(includeStaticScope) listOf(namedClassOrObjectSymbol.getMemberScope(), namedClassOrObjectSymbol.getStaticMemberScope()).asCompositeScope() else namedClassOrObjectSymbol.getMemberScope() + 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) } + + + fun List<KtNamedClassOrObjectSymbol>.filterOutCompanion() = + filterNot { + it.classKind == KtClassKind.COMPANION_OBJECT + } + + 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>() + .filterOutCompanion() // also, this is a hack to filter out companion for enum + .filterOutAndMarkAlreadyVisited() + .map { visitNamedClassOrObjectSymbol(it, dri) } + + return DokkaScope( + constructors = constructors, + functions = functions, + properties = properties, + classlikesWithoutCompanion = classlikes + ) + } + + private fun KtAnalysisSession.visitEnumEntrySymbol( + enumEntrySymbol: KtEnumEntrySymbol, scope: DokkaScope + ): DEnumEntry = withExceptionCatcher(enumEntrySymbol) { + val dri = getDRIFromEnumEntry(enumEntrySymbol) + val isExpect = false + + return DEnumEntry( + dri = dri, + name = enumEntrySymbol.name.asString(), + documentation = getDocumentation(enumEntrySymbol)?.toSourceSetDependent() ?: emptyMap(), + functions = scope.functions, + properties = scope.properties, + classlikes = emptyList(), // always empty, see https://github.com/Kotlin/dokka/issues/3129 + 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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TranslatorError.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TranslatorError.kt new file mode 100644 index 00000000..eceb7a38 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TranslatorError.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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 + ) + } diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeReferenceFactory.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeReferenceFactory.kt new file mode 100644 index 00000000..ea9a18c9 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeReferenceFactory.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt new file mode 100644 index 00000000..f7366294 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/translators/TypeTranslator.kt @@ -0,0 +1,201 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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(classType.fullyExpandedType), + extra = PropertyContainer.withAll( + getDokkaAnnotationsFrom(classType)?.toSourceSetDependent()?.toAnnotations() + ) + ) else + throw IllegalStateException("Expected type alias symbol in type") + } + + private fun KtAnalysisSession.toTypeConstructorFrom(classType: KtNonErrorClassType) = + 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 -> { + /** + * For example + * `typealias CompletionHandler = (cause: Throwable?) -> Unit` + * has functional type with no type arguments in K2 + * In K1 we have a usual generic type + */ + if (type.ownTypeArguments.isEmpty()) + toTypeConstructorFrom(type) + else + 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 + } +} diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt new file mode 100644 index 00000000..08a2aaad --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/utils/isException.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +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/" + } |