From 72541d1f1717e79534d9f24cbc7263bebde24885 Mon Sep 17 00:00:00 2001 From: Ignat Beresnev Date: Mon, 17 Jul 2023 11:22:35 +0200 Subject: [PATCH] Hardcode documentation for the synthetic Enum.entries property (#3071) --- .../dokka/docs/kdoc/EnumEntries.kt.template | 3 + ...tDescriptorToDocumentableTranslatorTest.kt | 63 +++++++++++++++++++ ...faultDescriptorToDocumentableTranslator.kt | 24 +++---- ...yntheticDescriptorDocumentationProvider.kt | 42 +++++++++++-- 4 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 plugins/base/src/main/resources/dokka/docs/kdoc/EnumEntries.kt.template diff --git a/plugins/base/src/main/resources/dokka/docs/kdoc/EnumEntries.kt.template b/plugins/base/src/main/resources/dokka/docs/kdoc/EnumEntries.kt.template new file mode 100644 index 0000000000..20d1642199 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/docs/kdoc/EnumEntries.kt.template @@ -0,0 +1,3 @@ +Returns a representation of an immutable list of all enum entries, in the order they're declared. + +This method may be used to iterate over the enum entries. diff --git a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt index c7e2bc21d0..9a96ad293d 100644 --- a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt +++ b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt @@ -917,6 +917,69 @@ val soapXml = node("soap-env:Envelope", soapAttrs, } } + @Test + fun `should have documentation for synthetic Enum entries property`() { + testInline( + """ + |/src/main/kotlin/test/KotlinEnum.kt + |package test + | + |enum class KotlinEnum { + | FOO, BAR; + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + val kotlinEnum = module.packages.find { it.name == "test" } + ?.classlikes + ?.single { it.name == "KotlinEnum" } + + checkNotNull(kotlinEnum) + + val entriesProperty = kotlinEnum.properties.single { it.name == "entries" } + val expectedEntriesType = GenericTypeConstructor( + dri = DRI( + packageName = "kotlin.enums", + classNames = "EnumEntries" + ), + projections = listOf( + Invariance( + GenericTypeConstructor( + dri = DRI( + packageName = "test", + classNames = "KotlinEnum" + ), + projections = emptyList() + ) + ) + ) + ) + assertEquals(expectedEntriesType, entriesProperty.type) + + val expectedDocumentation = DocumentationNode(listOf( + Description( + CustomDocTag( + children = listOf( + P(listOf( + Text( + "Returns a representation of an immutable list of all enum entries, " + + "in the order they're declared." + ), + )), + P(listOf( + Text("This method may be used to iterate over the enum entries.") + )) + ), + name = "MARKDOWN_FILE" + ) + ) + )) + assertEquals(expectedDocumentation, entriesProperty.documentation.values.single()) + } + } + } + @Test fun `should have documentation for synthetic Enum valueOf functions`() { testInline( diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultDescriptorToDocumentableTranslator.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultDescriptorToDocumentableTranslator.kt index 7633a93f77..cb52236e30 100644 --- a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultDescriptorToDocumentableTranslator.kt +++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultDescriptorToDocumentableTranslator.kt @@ -319,11 +319,13 @@ private class DokkaDescriptorVisitor( val descriptorsWithKind = this.unsubstitutedMemberScope.getDescriptorsWithKind() val staticScopeForKotlinEnum = (this.staticScope as? StaticScopeForKotlinEnum) ?: return descriptorsWithKind - // synthetic values() and valueOf() functions are not present among average class functions - val enumSyntheticFunctions = staticScopeForKotlinEnum.getContributedDescriptors { true } - .filterIsInstance() - - return descriptorsWithKind.copy(functions = descriptorsWithKind.functions + enumSyntheticFunctions) + // synthetic declarations, such as `entries`, `values()` and `valueOf()`, + // are not present among real in-code declarationg + val contributedDescriptors = staticScopeForKotlinEnum.getContributedDescriptors { true } + return descriptorsWithKind.copy( + properties = descriptorsWithKind.properties + contributedDescriptors.filterIsInstance(), + functions = descriptorsWithKind.functions + contributedDescriptors.filterIsInstance() + ) } private suspend fun visitEnumEntryDescriptor(descriptor: ClassDescriptor, parent: DRIWithPlatformInfo): DEnumEntry { @@ -507,7 +509,7 @@ private class DokkaDescriptorVisitor( getter = getter, setter = setter, visibility = descriptor.getVisibility(implicitAccessors).toSourceSetDependent(), - documentation = descriptor.resolveDescriptorData(), + documentation = descriptor.getDocumentation(), modifier = descriptor.modifier().toSourceSetDependent(), type = descriptor.returnType!!.toBound(), expectPresentInSet = sourceSet.takeIf { isExpect }, @@ -609,8 +611,8 @@ private class DokkaDescriptorVisitor( } } - private fun FunctionDescriptor.getDocumentation(): SourceSetDependent { - val isSynthesized = this.kind == CallableMemberDescriptor.Kind.SYNTHESIZED + private fun DeclarationDescriptor.getDocumentation(): SourceSetDependent { + val isSynthesized = this is CallableMemberDescriptor && this.kind == CallableMemberDescriptor.Kind.SYNTHESIZED return if (isSynthesized) { syntheticDocProvider.getDocumentation(this)?.toSourceSetDependent() ?: emptyMap() } else { @@ -914,7 +916,7 @@ private class DokkaDescriptorVisitor( coroutineScope { parallelMap { visitEnumEntryDescriptor(it, parent) } } private fun DeclarationDescriptor.resolveDescriptorData(): SourceSetDependent = - getDocumentation()?.toSourceSetDependent() ?: emptyMap() + resolveDocumentation()?.toSourceSetDependent() ?: emptyMap() private suspend fun toTypeConstructor(kt: KotlinType) = @@ -1038,7 +1040,7 @@ private class DokkaDescriptorVisitor( return effectiveReferencedDescriptors.firstOrNull()?.let { DescriptorToSourceUtils.getSourceFromDescriptor(it) } } - private fun DeclarationDescriptor.getDocumentation(): DocumentationNode? { + private fun DeclarationDescriptor.resolveDocumentation(): DocumentationNode? { val find = with(kDocFinder) { find(::descriptorToAnyDeclaration) } @@ -1050,7 +1052,7 @@ private class DokkaDescriptorVisitor( try { val kdocLink = with(kDocFinder) { resolveKDocLink( - fromDescriptor = this@getDocumentation, + fromDescriptor = this@resolveDocumentation, qualifiedName = link, sourceSet = sourceSet ) diff --git a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/SyntheticDescriptorDocumentationProvider.kt b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/SyntheticDescriptorDocumentationProvider.kt index 3b21f77178..b844ddd829 100644 --- a/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/SyntheticDescriptorDocumentationProvider.kt +++ b/subprojects/analysis-kotlin-descriptors/compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/SyntheticDescriptorDocumentationProvider.kt @@ -6,10 +6,15 @@ import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.fr import org.jetbrains.dokka.analysis.markdown.jb.MarkdownParser import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.resolve.DescriptorFactory +import org.jetbrains.kotlin.resolve.DescriptorUtils +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" @@ -17,14 +22,41 @@ internal class SyntheticDescriptorDocumentationProvider( private val kDocFinder: KDocFinder, private val sourceSet: DokkaConfiguration.DokkaSourceSet ) { - fun isDocumented(descriptor: DeclarationDescriptor): Boolean = descriptor is FunctionDescriptor - && (DescriptorFactory.isEnumValuesMethod(descriptor) || DescriptorFactory.isEnumValueOfMethod(descriptor)) + fun isDocumented(descriptor: DeclarationDescriptor): Boolean { + return when(descriptor) { + is PropertyDescriptor -> descriptor.isEnumEntries() + is FunctionDescriptor -> { + DescriptorFactory.isEnumValuesMethod(descriptor) || DescriptorFactory.isEnumValueOfMethod(descriptor) + } + else -> false + } + } + + private fun PropertyDescriptor.isEnumEntries(): Boolean { + return this.name == StandardNames.ENUM_ENTRIES + && this.kind == CallableMemberDescriptor.Kind.SYNTHESIZED + && DescriptorUtils.isEnumClass(this.containingDeclaration) + } fun getDocumentation(descriptor: DeclarationDescriptor): DocumentationNode? { - val function = descriptor as? FunctionDescriptor ?: return null + return when(descriptor) { + is PropertyDescriptor -> descriptor.getDocumentation() + is FunctionDescriptor -> descriptor.getDocumentation() + else -> null + } + } + + private fun PropertyDescriptor.getDocumentation(): DocumentationNode? { + return when { + this.isEnumEntries() -> loadTemplate(this, ENUM_ENTRIES_TEMPLATE_PATH) + else -> null + } + } + + private fun FunctionDescriptor.getDocumentation(): DocumentationNode? { return when { - DescriptorFactory.isEnumValuesMethod(function) -> loadTemplate(descriptor, ENUM_VALUES_TEMPLATE_PATH) - DescriptorFactory.isEnumValueOfMethod(function) -> loadTemplate(descriptor, ENUM_VALUEOF_TEMPLATE_PATH) + DescriptorFactory.isEnumValuesMethod(this) -> loadTemplate(this, ENUM_VALUES_TEMPLATE_PATH) + DescriptorFactory.isEnumValueOfMethod(this) -> loadTemplate(this, ENUM_VALUEOF_TEMPLATE_PATH) else -> null } }