Skip to content

Commit

Permalink
Fix gathering inherited properties in PSI
Browse files Browse the repository at this point in the history
  • Loading branch information
BarkingBad committed Feb 10, 2022
1 parent b0faaf0 commit cb2360a
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 20 deletions.
7 changes: 7 additions & 0 deletions core/src/main/kotlin/model/JvmField.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.jetbrains.dokka.model

import org.jetbrains.dokka.links.DRI

fun DRI.isJvmField(): Boolean = packageName == "kotlin.jvm" && classNames == "JvmField"

fun Annotations.Annotation.isJvmField(): Boolean = dri.isJvmName()
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,25 @@ import org.jetbrains.dokka.model.AnnotationTarget
import org.jetbrains.dokka.model.doc.DocumentationNode
import org.jetbrains.dokka.model.doc.Param
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.model.properties.WithExtraProperties
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.plugin
import org.jetbrains.dokka.plugability.querySingle
import org.jetbrains.dokka.transformers.sources.AsyncSourceToDocumentableTranslator
import org.jetbrains.dokka.transformers.sources.SourceToDocumentableTranslator
import org.jetbrains.dokka.utilities.DokkaLogger
import org.jetbrains.dokka.utilities.parallelForEach
import org.jetbrains.dokka.utilities.parallelMap
import org.jetbrains.dokka.utilities.parallelMapNotNull
import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation
import org.jetbrains.kotlin.builtins.functions.FunctionClassDescriptor
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.java.JavaVisibilities
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName
import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.io.File

Expand Down Expand Up @@ -131,6 +122,9 @@ class DefaultPsiToDocumentableTranslator(
private val PsiMethod.hash: Int
get() = "$returnType $name$parameterList".hashCode()

private val PsiField.hash: Int
get() = "$type $name".hashCode()

private val PsiClassType.shouldBeIgnored: Boolean
get() = isClass("java.lang.Enum") || isClass("java.lang.Object")

Expand Down Expand Up @@ -175,10 +169,16 @@ class DefaultPsiToDocumentableTranslator(
val dri = parent.withClass(name.toString())
val superMethodsKeys = hashSetOf<Int>()
val superMethods = mutableListOf<Pair<PsiMethod, DRI>>()
val superFieldsKeys = hashSetOf<Int>()
val superFields = mutableListOf<Pair<PsiField, DRI>>()
methods.asIterable().parallelForEach { superMethodsKeys.add(it.hash) }

/**
* Caution! This method mutates superMethodsKeys and superMethods
* Caution! This method mutates
* - superMethodsKeys
* - superMethods
* - superFieldsKeys
* - superKeys
*/
fun Array<PsiClassType>.getSuperTypesPsiClasses(): List<Pair<PsiClass, JavaClassKindTypes>> {
forEach { type ->
Expand All @@ -193,6 +193,13 @@ class DefaultPsiToDocumentableTranslator(
superMethods.add(Pair(method, definedAt))
}
}
it.fields.forEach { field ->
val hash = field.hash
if (!superFieldsKeys.contains(hash)) {
superFieldsKeys.add(hash)
superFields.add(Pair(field, definedAt))
}
}
}
}
return filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi ->
Expand Down Expand Up @@ -222,16 +229,39 @@ class DefaultPsiToDocumentableTranslator(
}

val ancestry: AncestryNode = parsePsiClass(this)
val (regularFunctions, accessors) = splitFunctionsAndAccessors()
val overriden = regularFunctions.flatMap { it.findSuperMethods().toList() }
val (regularFunctions, accessors) = splitFunctionsAndAccessors(psi.fields, psi.methods)
val (regularSuperFunctions, superAccessors) = splitFunctionsAndAccessors(superFields.map { it.first }.toTypedArray(), superMethods.map { it.first }.toTypedArray())

val regularSuperFunctionsKeys = regularSuperFunctions.map { it.hash }.toSet()

val regularSuperFunctionsWithDRI = superMethods.filter { it.first.hash in regularSuperFunctionsKeys }

val superAccessorsWithDRI = superAccessors
.mapValues { (field, methods) ->
if (field.annotations.mapNotNull { it.toAnnotation() }.any { it.isJvmField() }) {
emptyList()
} else {
methods.mapNotNull { method -> superMethods.find { it.first.hash == method.hash } }
}
}

val overridden = regularFunctions.flatMap { it.findSuperMethods().toList() }
val documentation = javadocParser.parseDocumentation(this).toSourceSetDependent()
val allFunctions = async {
regularFunctions.parallelMapNotNull {
if (!it.isConstructor) parseFunction(
it,
parentDRI = dri
) else null
} + superMethods.filter { it.first !in overriden }.parallelMap { parseFunction(it.first, inheritedFrom = it.second) }
} + regularSuperFunctionsWithDRI.filter { it.first !in overridden }.parallelMap { parseFunction(it.first, inheritedFrom = it.second) }
}
val allFields = async {
fields.toList().parallelMapNotNull { parseField(it, accessors[it].orEmpty()) } +
superFields.parallelMapNotNull { parseFieldWithInheritingAccessors(
it.first,
superAccessorsWithDRI[it.first].orEmpty(),
inheritedFrom = it.second)
}
}
val source = PsiDocumentableSource(this).toSourceSetDependent()
val classlikes = async { innerClasses.asIterable().parallelMap { parseClasslike(it, dri) } }
Expand All @@ -256,7 +286,7 @@ class DefaultPsiToDocumentableTranslator(
null,
source,
allFunctions.await(),
fields.mapNotNull { parseField(it, accessors[it].orEmpty()) },
allFields.await(),
classlikes.await(),
visibility,
null,
Expand Down Expand Up @@ -316,7 +346,7 @@ class DefaultPsiToDocumentableTranslator(
null,
source,
allFunctions.await(),
fields.mapNotNull { parseField(it, accessors[it].orEmpty()) },
allFields.await(),
classlikes.await(),
visibility,
null,
Expand All @@ -336,7 +366,7 @@ class DefaultPsiToDocumentableTranslator(
name.orEmpty(),
constructors.map { parseFunction(it, true) },
allFunctions.await(),
fields.mapNotNull { parseField(it, accessors[it].orEmpty()) },
allFields.await(),
classlikes.await(),
source,
visibility,
Expand Down Expand Up @@ -547,7 +577,7 @@ class DefaultPsiToDocumentableTranslator(
else -> null
}

private fun PsiClass.splitFunctionsAndAccessors(): Pair<MutableList<PsiMethod>, MutableMap<PsiField, MutableList<PsiMethod>>> {
private fun splitFunctionsAndAccessors(fields: Array<PsiField>, methods: Array<PsiMethod>): Pair<MutableList<PsiMethod>, MutableMap<PsiField, MutableList<PsiMethod>>> {
val fieldNames = fields.map { it.name to it }.toMap()
val accessors = mutableMapOf<PsiField, MutableList<PsiMethod>>()
val regularMethods = mutableListOf<PsiMethod>()
Expand All @@ -562,7 +592,21 @@ class DefaultPsiToDocumentableTranslator(
return regularMethods to accessors
}

private fun parseField(psi: PsiField, accessors: List<PsiMethod>): DProperty {
private fun parseFieldWithInheritingAccessors(psi: PsiField, accessors: List<Pair<PsiMethod, DRI>>, inheritedFrom: DRI): DProperty = parseField(
psi,
accessors.firstOrNull { it.first.hasParameters() }?.let { parseFunction(it.first, inheritedFrom = it.second) },
accessors.firstOrNull { it.first.returnType == psi.type }?.let { parseFunction(it.first, inheritedFrom = it.second) },
inheritedFrom
)

private fun parseField(psi: PsiField, accessors: List<PsiMethod>, inheritedFrom: DRI? = null): DProperty = parseField(
psi,
accessors.firstOrNull { it.hasParameters() }?.let { parseFunction(it) },
accessors.firstOrNull { it.returnType == psi.type }?.let { parseFunction(it) },
inheritedFrom
)

private fun parseField(psi: PsiField, getter: DFunction?, setter: DFunction?, inheritedFrom: DRI? = null): DProperty {
val dri = DRI.from(psi)
return DProperty(
dri,
Expand All @@ -573,14 +617,15 @@ class DefaultPsiToDocumentableTranslator(
psi.getVisibility().toSourceSetDependent(),
getBound(psi.type),
null,
accessors.firstOrNull { it.hasParameters() }?.let { parseFunction(it) },
accessors.firstOrNull { it.returnType == psi.type }?.let { parseFunction(it) },
getter,
setter,
psi.getModifier().toSourceSetDependent(),
setOf(sourceSetData),
emptyList(),
false,
psi.additionalExtras().let {
PropertyContainer.withAll<DProperty>(
InheritedMember(inheritedFrom.toSourceSetDependent()),
it.toSourceSetDependent().toAdditionalModifiers(),
(psi.annotations.toList()
.toListOfAnnotations() + it.toListOfAnnotations()).toSourceSetDependent()
Expand Down
110 changes: 110 additions & 0 deletions plugins/base/src/test/kotlin/superFields/PsiSuperFieldsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package superFields

import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test


class PsiSuperFieldsTest : BaseAbstractTest() {

@Test
fun `java inheriting java`() {
testInline(
"""
|/src/test/A.java
|package test;
|public class A {
| public int a = 1;
|}
|
|/src/test/B.java
|package test;
|public class B extends A {}
""".trimIndent(),
dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
analysisPlatform = "jvm"
name = "jvm"
}
}
}
) {
documentablesMergingStage = {
it.packages.single().classlikes.single { it.name == "B" }.properties.single { it.name == "a" }.run(::assertNotNull)
}
}
}

@Test
fun `java inheriting kotlin`() {
testInline(
"""
|/src/test/A.kt
|package test
|open class A {
| var a: Int = 1
|}
|
|/src/test/B.java
|package test;
|public class B extends A {}
""".trimIndent(),
dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
analysisPlatform = "jvm"
name = "jvm"
}
}
}
) {
documentablesMergingStage = {
it.packages.single().classlikes.single { it.name == "B" }.properties.single { it.name == "a" }.run {
assertNotNull(this)
assertNotNull(this.getter)
assertNotNull(this.setter)
}
}
}
}

@Test
fun `java inheriting kotlin with @JvmField should not inherit beans`() {
testInline(
"""
|/src/test/A.kt
|package test
|open class A {
| @kotlin.jvm.JvmField
| var a: Int = 1
|}
|
|/src/test/B.java
|package test;
|public class B extends A {}
""".trimIndent(),
dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
analysisPlatform = "jvm"
name = "jvm"
classpath += jvmStdlibPath!!
}
}
}
) {
documentablesMergingStage = {
it.packages.single().classlikes.single { it.name == "B" }.properties.single { it.name == "a" }.run {
assertNotNull(this)
assertNull(this.getter)
assertNull(this.setter)
}
}
}
}
}

0 comments on commit cb2360a

Please sign in to comment.