Skip to content

Commit

Permalink
[K2] Display enum entry members (#3180)
Browse files Browse the repository at this point in the history
  • Loading branch information
vmishenev authored Oct 10, 2023
1 parent 7503402 commit 562a595
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 38 deletions.
44 changes: 42 additions & 2 deletions plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import org.jetbrains.dokka.pages.ContentGroup
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import signatures.renderedContent
import utils.TestOutputWriter
import utils.TestOutputWriterPlugin
import utils.*
import java.net.URL
import kotlin.test.Test
import kotlin.test.assertEquals
Expand Down Expand Up @@ -328,6 +327,47 @@ class KotlinEnumsTest : BaseAbstractTest() {
}
}

@Test
@OnlyDescriptors("K2 has `compareTo`, that should be suppressed, due to #3196")
fun `enum should have functions on page`() {
val configuration = dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
}
}
}

testInline(
"""
|/src/main/kotlin/basic/TestEnum.kt
|package testpackage
|
|
|interface Sample {
| fun toBeImplemented(): String
|}
|
|enum class TestEnum: Sample {
| E1 {
| override fun toBeImplemented(): String = "e1"
| }
|}
""".trimMargin(),
configuration
) {
pagesTransformationStage = { root ->
root.contentPage<ClasslikePageNode>("E1") {
assertHasFunctions("toBeImplemented")
}

root.contentPage<ClasslikePageNode>("TestEnum") {
assertHasFunctions("toBeImplemented", "valueOf", "values")
}
}
}
}

@Test
fun enumWithAnnotationsOnEntries() {
val configuration = dokkaConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import org.jetbrains.dokka.model.DEnum
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import utils.OnlyDescriptors

class InheritedEntriesDocumentableFilterTransformerTest : BaseAbstractTest() {
val suppressingInheritedConfiguration = dokkaConfiguration {
Expand Down Expand Up @@ -138,15 +137,15 @@ class InheritedEntriesDocumentableFilterTransformerTest : BaseAbstractTest() {
}
}

@OnlyDescriptors("Entry does not have `name` and `ordinal`") // TODO
@Test
fun `should work with enum entries when not suppressing`(){
testInline(
"""
/src/suppressed/Suppressed.kt
package suppressed
enum class Suppressed {
ENTRY_SUPPRESSED
ENTRY_SUPPRESSED;
class A
}
""".trimIndent(),
nonSuppressingInheritedConfiguration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import org.jetbrains.dokka.model.dfs
import org.jetbrains.dokka.model.firstChildOfType
import org.jetbrains.dokka.pages.*
import utils.assertNotNull
import utils.findSectionWithName
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import utils.OnlyDescriptors

class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() {

Expand Down Expand Up @@ -52,15 +52,6 @@ class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() {
)
}

private fun ClasslikePageNode.findSectionWithName(name: String) : ContentNode? {
var sectionHeader: ContentHeader? = null
return content.dfs { node ->
node.children.filterIsInstance<ContentHeader>().any { header ->
header.children.firstOrNull { it is ContentText && it.text == name }?.also { sectionHeader = header } != null
}
}?.children?.dropWhile { child -> child != sectionHeader }?.drop(1)?.firstOrNull()
}

private fun ContentNode.findTabWithType(type: TabbedContentType): ContentNode? = dfs { node ->
node.children.filterIsInstance<ContentGroup>().any { gr ->
gr.extra[TabbedContentTypeExtra]?.value == type
Expand Down Expand Up @@ -274,7 +265,6 @@ class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() {

fun PageNode.childrenRec(): List<PageNode> = listOf(this) + children.flatMap { it.childrenRec() }

@OnlyDescriptors("Enum entry [SMTH] does not have functions") // TODO
@Test
fun `should merge enum entries`() {
testInline(
Expand Down Expand Up @@ -304,7 +294,8 @@ class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() {
assertNotNull(classPage, "Tested class not found!")

val functions = classPage.findSectionWithName("Functions").assertNotNull("Functions")
val method1 = functions.children.singleOrNull().assertNotNull("method1")
val method1 = functions.children.single { it.sourceSets.size == 2 && it.dci.dri.singleOrNull()?.callable?.name == "method1" }
.assertNotNull("method1")

assertEquals(
2,
Expand Down
25 changes: 21 additions & 4 deletions plugins/base/src/test/kotlin/utils/contentUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
package utils

import matchers.content.*
import org.jetbrains.dokka.pages.BasicTabbedContentType
import org.jetbrains.dokka.pages.ContentGroup
import org.jetbrains.dokka.pages.ContentPage
import org.jetbrains.dokka.pages.RootPageNode
import org.jetbrains.dokka.model.dfs
import org.jetbrains.dokka.pages.*
import kotlin.test.assertEquals

//TODO: Try to unify those functions after update to 1.4
fun ContentMatcherBuilder<*>.functionSignature(
Expand Down Expand Up @@ -327,6 +326,24 @@ fun ContentMatcherBuilder<*>.unwrapAnnotation(elem: Map.Entry<String, Set<String
}
}
}
inline fun<reified T> PageNode.contentPage(name: String, block: T.() -> Unit) {
(dfs { it.name == name } as? T).assertNotNull("The page `$name` is not found").block()
}

fun ClasslikePageNode.assertHasFunctions(vararg expectedFunctionName: String) {
val functions = this.findSectionWithName("Functions").assertNotNull("Functions")
val functionsName = functions.children.map { (it.dfs { it is ContentText } as ContentText).text }
assertEquals(expectedFunctionName.toList(), functionsName)
}

fun ClasslikePageNode.findSectionWithName(name: String) : ContentNode? {
var sectionHeader: ContentHeader? = null
return content.dfs { node ->
node.children.filterIsInstance<ContentHeader>().any { header ->
header.children.firstOrNull { it is ContentText && it.text == name }?.also { sectionHeader = header } != null
}
}?.children?.dropWhile { child -> child != sectionHeader }?.drop(1)?.firstOrNull()
}

data class ParamAttributes(
val annotations: Map<String, Set<String>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,30 @@ internal class DokkaSymbolVisitor(

KtClassKind.ANONYMOUS_OBJECT -> throw NotImplementedError("ANONYMOUS_OBJECT does not support")
KtClassKind.ENUM_CLASS -> {
val entries = namedClassOrObjectSymbol.getEnumEntries().map { visitEnumEntrySymbol(it) }
/**
* 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,
Expand Down Expand Up @@ -383,12 +406,17 @@ internal class DokkaSymbolVisitor(
val properties: List<DProperty>,
val classlikes: 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
dri: DRI,
includeStaticScope: Boolean = true
): DokkaScope {
// e.g. getStaticMemberScope contains `valueOf`, `values` and `entries` members for Enum
val scope = listOf(namedClassOrObjectSymbol.getMemberScope(), namedClassOrObjectSymbol.getStaticMemberScope()).asCompositeScope()
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()
Expand Down Expand Up @@ -453,28 +481,18 @@ internal class DokkaSymbolVisitor(
}

private fun KtAnalysisSession.visitEnumEntrySymbol(
enumEntrySymbol: KtEnumEntrySymbol
enumEntrySymbol: KtEnumEntrySymbol, scope: DokkaScope
): DEnumEntry = withExceptionCatcher(enumEntrySymbol) {
val dri = getDRIFromEnumEntry(enumEntrySymbol)
val isExpect = false

val scope = enumEntrySymbol.getMemberScope()
val callables = scope.getCallableSymbols().toList()
val classifiers = scope.getClassifierSymbols().toList()

val functions = callables.filterIsInstance<KtFunctionSymbol>().map { visitFunctionSymbol(it, dri) }
val properties = callables.filterIsInstance<KtPropertySymbol>().map { visitPropertySymbol(it, dri) }
val classlikes =
classifiers.filterIsInstance<KtNamedClassOrObjectSymbol>()
.map { visitNamedClassOrObjectSymbol(it, dri) }

return DEnumEntry(
dri = dri,
name = enumEntrySymbol.name.asString(),
documentation = getDocumentation(enumEntrySymbol)?.toSourceSetDependent() ?: emptyMap(),
functions = functions,
properties = properties,
classlikes = classlikes,
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(
Expand Down

0 comments on commit 562a595

Please sign in to comment.