Skip to content

Commit

Permalink
Unify DRIs for Kotlin and Java enums. Add EnumEntry linking tests
Browse files Browse the repository at this point in the history
  • Loading branch information
BarkingBad committed Jan 17, 2022
1 parent 7a83e46 commit 175ca17
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 49 deletions.
Expand Up @@ -6,8 +6,6 @@ import org.jetbrains.dokka.links.Callable
import org.jetbrains.dokka.links.JavaClassReference
import org.jetbrains.dokka.links.TypeReference
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor

fun Callable.Companion.from(descriptor: CallableDescriptor, name: String? = null) = with(descriptor) {
Callable(
Expand All @@ -17,18 +15,6 @@ fun Callable.Companion.from(descriptor: CallableDescriptor, name: String? = null
)
}

fun Callable.Companion.from(descriptor: LazyClassDescriptor) = Callable(
descriptor.name.asString(),
null,
emptyList()
)

fun Callable.Companion.from(descriptor: EnumEntrySyntheticClassDescriptor) = Callable(
descriptor.name.asString(),
null,
emptyList()
)

fun Callable.Companion.from(psi: PsiMethod) = with(psi) {
Callable(
name,
Expand Down
Expand Up @@ -6,31 +6,22 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs

fun DRI.Companion.from(descriptor: DeclarationDescriptor) = descriptor.parentsWithSelf.run {
val parameter = firstIsInstanceOrNull<ValueParameterDescriptor>()
val callable = parameter?.containingDeclaration ?: firstIsInstanceOrNull<CallableDescriptor>()

DRI(
packageName = firstIsInstanceOrNull<PackageFragmentDescriptor>()?.fqName?.asString() ?: "",
classNames = (filterIsInstance<ClassDescriptor>().map {
if (it.kind == ClassKind.ENUM_ENTRY)
it.name.asString().split(".").dropLast(1).joinToString(".")
else
it.name.asString()
} + filterIsInstance<TypeAliasDescriptor>().map { it.name.asString() }
).toList()
.filter { it.isNotBlank() }
classNames = (filterIsInstance<ClassDescriptor>() + filterIsInstance<TypeAliasDescriptor>()).toList()
.takeIf { it.isNotEmpty() }
?.asReversed()
?.joinToString(separator = "."),
callable = callable?.let { Callable.from(it) }
?: descriptor.safeAs<LazyClassDescriptor>().takeIf { it?.kind == ClassKind.ENUM_ENTRY }?.let { Callable.from(it) }
?: descriptor.safeAs<EnumEntrySyntheticClassDescriptor>()?.let { Callable.from(it) },
?.joinToString(separator = ".") { it.name.asString() },
callable = callable?.let { Callable.from(it) },
target = DriTarget.from(parameter ?: descriptor),
extra = if (descriptor is EnumEntrySyntheticClassDescriptor)
extra = if (descriptor is EnumEntrySyntheticClassDescriptor || descriptor.safeAs<ClassDescriptor>()?.kind == ClassKind.ENUM_ENTRY)
DRIExtraContainer().also { it[EnumEntryDRIExtra] = EnumEntryDRIExtra }.encode()
else null
)
Expand All @@ -43,9 +34,10 @@ fun DRI.Companion.from(psi: PsiElement) = psi.parentsWithSelf.run {
.toList() // We only want exact PsiClass types, not PsiTypeParameter subtype
DRI(
packageName = classes.lastOrNull()?.qualifiedName?.substringBeforeLast('.', "") ?: "",
classNames = classes.toList().takeIf { it.isNotEmpty() }?.asReversed()?.mapNotNull { it.name }
classNames = (if (psi is PsiEnumConstant) listOfNotNull(psiField?.name) + classes.toList().mapNotNull { it.name } else classes.toList().mapNotNull { it.name })
.takeIf { it.isNotEmpty() }?.asReversed()
?.joinToString("."),
callable = psiMethod?.let { Callable.from(it) } ?: psiField?.let { Callable.from(it) },
callable = psiMethod?.let { Callable.from(it) } ?: psiField?.takeUnless { psi is PsiEnumConstant }?.let { Callable.from(it) },
target = DriTarget.from(psi),
extra = if (psi is PsiEnumConstant)
DRIExtraContainer().also { it[EnumEntryDRIExtra] = EnumEntryDRIExtra }.encode()
Expand Down
Expand Up @@ -30,13 +30,10 @@ open class JavadocExternalLocationProvider(
return "$docWithModule$packageLink/package-summary$extension".htmlEscape()
}

// in Kotlin DRI of enum entry is not callable
if (DRIExtraContainer(extra)[EnumEntryDRIExtra] != null) {
val (classSplit, enumEntityAnchor) = if (callable == null) {
val lastIndex = classNames?.lastIndexOf(".") ?: 0
val lastIndex = classNames?.lastIndexOf(".") ?: 0
val (classSplit, enumEntityAnchor) =
classNames?.substring(0, lastIndex) to classNames?.substring(lastIndex + 1)
} else
classNames to callable?.name

val classLink =
if (packageLink == null) "${classSplit}$extension" else "$packageLink/${classSplit}$extension"
Expand Down
Expand Up @@ -82,6 +82,7 @@ open class DokkaLocationProvider(
val driWithSourceSets = DRIWithSourceSets(dri, setOfNotNull(sourceSet))
getLocalLocation(driWithSourceSets, context)
?: getLocalLocation(driWithSourceSets.copy(dri = dri.copy(target = PointingToDeclaration)), context)
?: getLocalLocation(driWithSourceSets.copy(dri = dri.copy(target = PointingToDeclaration, extra = null)), context)
// Not found in PageGraph, that means it's an external link
?: getExternalLocation(dri, sourceSets)
?: getExternalLocation(dri.copy(target = PointingToDeclaration), sourceSets)
Expand Down
61 changes: 51 additions & 10 deletions plugins/base/src/test/kotlin/linking/EnumValuesLinking.kt
@@ -1,16 +1,23 @@
package linking

import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
import org.jetbrains.dokka.model.dfs
import org.jetbrains.dokka.model.doc.DocumentationLink
import org.jetbrains.dokka.pages.ContentDRILink
import org.jetbrains.dokka.pages.ContentPage
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jsoup.Jsoup
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import java.nio.file.Paths
import org.junit.jupiter.api.Assertions.assertEquals
import utils.TestOutputWriterPlugin
import java.lang.AssertionError

class EnumValuesLinking : BaseAbstractTest() {

@Test
fun `check if enum values are correctly linked`() {
val writerPlugin = TestOutputWriterPlugin()
val testDataDir = getTestDataDir("linking").toAbsolutePath()
testFromData(
dokkaConfiguration {
Expand All @@ -21,7 +28,8 @@ class EnumValuesLinking : BaseAbstractTest() {
name = "jvm"
}
}
}
},
pluginOverrides = listOf(writerPlugin)
) {
documentablesTransformationStage = {
val classlikes = it.packages.single().children
Expand All @@ -31,16 +39,16 @@ class EnumValuesLinking : BaseAbstractTest() {
javaLinker.documentation.values.single().children.run {
when (val kotlinLink = this[0].children[1].children[1]) {
is DocumentationLink -> kotlinLink.dri.run {
assertEquals("KotlinEnum", this.classNames)
assertEquals("ON_CREATE", this.callable?.name)
assertEquals("KotlinEnum.ON_CREATE", this.classNames)
assertEquals(null, this.callable)
}
else -> throw AssertionError("Link node is not DocumentationLink type")
}

when (val javaLink = this[0].children[2].children[1]) {
is DocumentationLink -> javaLink.dri.run {
assertEquals("JavaEnum", this.classNames)
assertEquals("ON_DECEIT", this.callable?.name)
assertEquals("JavaEnum.ON_DECEIT", this.classNames)
assertEquals(null, this.callable)
}
else -> throw AssertionError("Link node is not DocumentationLink type")
}
Expand All @@ -50,20 +58,53 @@ class EnumValuesLinking : BaseAbstractTest() {
kotlinLinker.documentation.values.single().children.run {
when (val kotlinLink = this[0].children[0].children[5]) {
is DocumentationLink -> kotlinLink.dri.run {
assertEquals("KotlinEnum", this.classNames)
assertEquals("ON_CREATE", this.callable?.name)
assertEquals("KotlinEnum.ON_CREATE", this.classNames)
assertEquals(null, this.callable)
}
else -> throw AssertionError("Link node is not DocumentationLink type")
}

when (val javaLink = this[0].children[0].children[9]) {
is DocumentationLink -> javaLink.dri.run {
assertEquals("JavaEnum", this.classNames)
assertEquals("ON_DECEIT", this.callable?.name)
assertEquals("JavaEnum.ON_DECEIT", this.classNames)
assertEquals(null, this.callable)
}
else -> throw AssertionError("Link node is not DocumentationLink type")
}
}

assertEquals(
javaLinker.documentation.values.single().children[0].children[1].children[1].safeAs<DocumentationLink>()?.dri,
kotlinLinker.documentation.values.single().children[0].children[0].children[5].safeAs<DocumentationLink>()?.dri
)

assertEquals(
javaLinker.documentation.values.single().children[0].children[2].children[1].safeAs<DocumentationLink>()?.dri,
kotlinLinker.documentation.values.single().children[0].children[0].children[9].safeAs<DocumentationLink>()?.dri
)
}

renderingStage = { rootPageNode, _ ->
val classlikes = rootPageNode.children.single().children
assertEquals(4, classlikes.size)

val javaLinker = classlikes.single { it.name == "JavaLinker" }
(javaLinker as ContentPage).run {
assertNotNull(content.dfs { it is ContentDRILink && it.address.classNames == "KotlinEnum.ON_CREATE" })
assertNotNull(content.dfs { it is ContentDRILink && it.address.classNames == "JavaEnum.ON_DECEIT" })
}

val kotlinLinker = classlikes.single { it.name == "KotlinLinker" }
(kotlinLinker as ContentPage).run {
assertNotNull(content.dfs { it is ContentDRILink && it.address.classNames == "KotlinEnum.ON_CREATE" })
assertNotNull(content.dfs { it is ContentDRILink && it.address.classNames == "JavaEnum.ON_DECEIT" })
}

// single method will throw an exception if there is no single element (0 or 2+)
Jsoup.parse(writerPlugin.writer.contents["root/linking.source/-java-linker/index.html"]).select("a[href=\"../-kotlin-enum/-o-n_-c-r-e-a-t-e/index.html\"]").single()
Jsoup.parse(writerPlugin.writer.contents["root/linking.source/-java-linker/index.html"]).select("a[href=\"../-java-enum/-o-n_-d-e-c-e-i-t/index.html\"]").single()
Jsoup.parse(writerPlugin.writer.contents["root/linking.source/-kotlin-linker/index.html"]).select("a[href=\"../-kotlin-enum/-o-n_-c-r-e-a-t-e/index.html\"]").single()
Jsoup.parse(writerPlugin.writer.contents["root/linking.source/-kotlin-linker/index.html"]).select("a[href=\"../-java-enum/-o-n_-d-e-c-e-i-t/index.html\"]").single()
}
}
}
Expand Down
Expand Up @@ -45,8 +45,8 @@ class JavadocExternalLocationProviderTest : BaseAbstractTest() {
)
val javaDri = DRI(
"java.nio.file",
"StandardOpenOption",
Callable("CREATE", null, emptyList()),
"StandardOpenOption.CREATE",
null,
PointingToDeclaration,
DRIExtraContainer().also { it[EnumEntryDRIExtra] = EnumEntryDRIExtra }.encode()
)
Expand Down
4 changes: 2 additions & 2 deletions plugins/base/src/test/kotlin/model/JavaTest.kt
Expand Up @@ -391,8 +391,8 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
"RUNTIME",
DRI(
"java.lang.annotation",
"RetentionPolicy",
DRICallable("RUNTIME", null, emptyList()),
"RetentionPolicy.RUNTIME",
null,
PointingToDeclaration,
DRIExtraContainer().also { it[EnumEntryDRIExtra] = EnumEntryDRIExtra }.encode()
)
Expand Down

0 comments on commit 175ca17

Please sign in to comment.