diff options
author | Ignat Beresnev <ignat.beresnev@jetbrains.com> | 2022-06-17 15:16:03 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-17 15:16:03 +0200 |
commit | 9f67dcf75d3b86fa6e4e352d2cebc4f9e17b8048 (patch) | |
tree | 5b30ddf007113d5b3da04390e093dc8023c04c79 /plugins/base/src/main/kotlin/translators/descriptors | |
parent | b783439932372e823c36776c53cda5bdc8d0ccae (diff) | |
download | dokka-9f67dcf75d3b86fa6e4e352d2cebc4f9e17b8048.tar.gz dokka-9f67dcf75d3b86fa6e4e352d2cebc4f9e17b8048.tar.bz2 dokka-9f67dcf75d3b86fa6e4e352d2cebc4f9e17b8048.zip |
Handle more corner cases for inherited accessors (#2532)
Diffstat (limited to 'plugins/base/src/main/kotlin/translators/descriptors')
2 files changed, 109 insertions, 30 deletions
diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt index bfd755cd..38992ba0 100644 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt @@ -21,6 +21,7 @@ import org.jetbrains.dokka.links.Callable import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.AnnotationTarget import org.jetbrains.dokka.model.Nullable +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 @@ -40,9 +41,11 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.descriptors.java.JavaVisibilities import org.jetbrains.kotlin.idea.kdoc.findKDoc import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi +import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor import org.jetbrains.kotlin.load.kotlin.toSourceElement import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* @@ -379,7 +382,7 @@ private class DokkaDescriptorVisitor( return coroutineScope { val descriptorsWithKind = scope.getDescriptorsWithKind() - val (regularFunctions, accessors) = splitFunctionsAndAccessors( + val (regularFunctions, accessors) = splitFunctionsAndInheritedAccessors( properties = descriptorsWithKind.properties, functions = descriptorsWithKind.functions ) @@ -427,11 +430,11 @@ private class DokkaDescriptorVisitor( /** * @param implicitAccessors getters/setters that are not part of the property descriptor, for instance - * average methods inherited from java sources + * average methods inherited from java sources that access the property */ private suspend fun visitPropertyDescriptor( originalDescriptor: PropertyDescriptor, - implicitAccessors: List<FunctionDescriptor>, + implicitAccessors: DescriptorAccessorHolder?, parent: DRIWithPlatformInfo ): DProperty { val (dri, _) = originalDescriptor.createDRI() @@ -451,9 +454,7 @@ private class DokkaDescriptorVisitor( } suspend fun getImplicitAccessorGetter() = - implicitAccessors - .firstOrNull { it.isGetterFor(originalDescriptor) } - ?.let { visitFunctionDescriptor(it, parent) } + implicitAccessors?.getter?.let { visitFunctionDescriptor(it, parent) } // example - generated setter that comes with data classes suspend fun getDescriptorSetter() = @@ -464,9 +465,7 @@ private class DokkaDescriptorVisitor( } suspend fun getImplicitAccessorSetter() = - implicitAccessors - .firstOrNull { it.isSetterFor(originalDescriptor) } - ?.let { visitFunctionDescriptor(it, parent) } + implicitAccessors?.setter?.let { visitFunctionDescriptor(it, parent) } return coroutineScope { val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } } @@ -480,7 +479,7 @@ private class DokkaDescriptorVisitor( sources = actual, getter = getDescriptorGetter() ?: getImplicitAccessorGetter(), setter = getDescriptorSetter() ?: getImplicitAccessorSetter(), - visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(), + visibility = descriptor.getVisibility(implicitAccessors).toSourceSetDependent(), documentation = descriptor.resolveDescriptorData(), modifier = descriptor.modifier().toSourceSetDependent(), type = descriptor.returnType!!.toBound(), @@ -502,6 +501,22 @@ private class DokkaDescriptorVisitor( } } + private fun PropertyDescriptor.getVisibility(implicitAccessors: DescriptorAccessorHolder?): Visibility { + val isNonPublicJavaProperty = this is JavaPropertyDescriptor && !this.visibility.isPublicAPI + val visibility = + if (isNonPublicJavaProperty) { + // only try to take implicit getter's visibility if it's a java property + // because it's not guaranteed that implicit accessor will be used + // for the kotlin property, as it may have an explicit accessor of its own, + // i.e in data classes or with get() and set() are overridden + (implicitAccessors?.getter?.visibility ?: this.visibility) + } else { + this.visibility + } + + return visibility.toDokkaVisibility() + } + private fun CallableMemberDescriptor.createDRI(wasOverridenBy: DRI? = null): Pair<DRI, DRI?> = if (kind == CallableMemberDescriptor.Kind.DECLARATION || overriddenDescriptors.isEmpty()) Pair(DRI.from(this), wasOverridenBy) @@ -818,12 +833,11 @@ private class DokkaDescriptorVisitor( private suspend fun List<PropertyDescriptor>.visitProperties( parent: DRIWithPlatformInfo, - implicitAccessors: Map<PropertyDescriptor, List<FunctionDescriptor>> = emptyMap(), + implicitAccessors: Map<PropertyDescriptor, DescriptorAccessorHolder> = emptyMap(), ): List<DProperty> { return coroutineScope { parallelMap { - val propertyAccessors = implicitAccessors[it] ?: emptyList() - visitPropertyDescriptor(it, propertyAccessors, parent) + visitPropertyDescriptor(it, implicitAccessors[it], parent) } } } @@ -1151,11 +1165,14 @@ private class DokkaDescriptorVisitor( }) + ancestry.interfaces.map { TypeConstructorWithKind(it.typeConstructor, KotlinClassKindTypes.INTERFACE) } } - private fun DescriptorVisibility.toDokkaVisibility(): org.jetbrains.dokka.model.Visibility = when (this.delegate) { + private fun DescriptorVisibility.toDokkaVisibility(): Visibility = when (this.delegate) { Visibilities.Public -> KotlinVisibility.Public Visibilities.Protected -> KotlinVisibility.Protected Visibilities.Internal -> KotlinVisibility.Internal Visibilities.Private -> KotlinVisibility.Private + JavaVisibilities.ProtectedAndPackage -> KotlinVisibility.Protected + JavaVisibilities.ProtectedStaticVisibility -> KotlinVisibility.Protected + JavaVisibilities.PackageVisibility -> JavaVisibility.Default else -> KotlinVisibility.Public } diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DescriptorAccessorConventionUtil.kt b/plugins/base/src/main/kotlin/translators/descriptors/DescriptorAccessorConventionUtil.kt index f182b9be..292dbfca 100644 --- a/plugins/base/src/main/kotlin/translators/descriptors/DescriptorAccessorConventionUtil.kt +++ b/plugins/base/src/main/kotlin/translators/descriptors/DescriptorAccessorConventionUtil.kt @@ -10,29 +10,93 @@ import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName internal data class DescriptorFunctionsHolder( val regularFunctions: List<FunctionDescriptor>, - val accessors: Map<PropertyDescriptor, List<FunctionDescriptor>> + val accessors: Map<PropertyDescriptor, DescriptorAccessorHolder> ) -internal fun splitFunctionsAndAccessors( +internal data class DescriptorAccessorHolder( + val getter: FunctionDescriptor? = null, + val setter: FunctionDescriptor? = null +) + +/** + * Separate regular Kotlin/Java functions and inherited Java accessors + * to properly display properties inherited from Java. + * + * Take this example: + * ``` + * // java + * public class JavaClass { + * private int a = 1; + * public int getA() { return a; } + * public void setA(int a) { this.a = a; } + * } + * + * // kotlin + * class Bar : JavaClass() { + * fun foo() {} + * } + * ``` + * + * It should result in: + * - 1 regular function `foo` + * - Map a=[`getA`, `setA`] + */ +internal fun splitFunctionsAndInheritedAccessors( properties: List<PropertyDescriptor>, functions: List<FunctionDescriptor> ): DescriptorFunctionsHolder { + val (javaMethods, kotlinFunctions) = functions.partition { it is JavaMethodDescriptor } + if (javaMethods.isEmpty()) { + return DescriptorFunctionsHolder(regularFunctions = kotlinFunctions, emptyMap()) + } + val propertiesByName = properties.associateBy { it.name.asString() } - val regularFunctions = mutableListOf<FunctionDescriptor>() - val accessors = mutableMapOf<PropertyDescriptor, MutableList<FunctionDescriptor>>() - functions.forEach { function -> + val regularFunctions = ArrayList<FunctionDescriptor>(kotlinFunctions) + + val accessors = mutableMapOf<PropertyDescriptor, DescriptorAccessorHolder>() + javaMethods.forEach { function -> val possiblePropertyNamesForFunction = function.toPossiblePropertyNames() val property = possiblePropertyNamesForFunction.firstNotNullOfOrNull { propertiesByName[it] } if (property != null && function.isAccessorFor(property)) { - accessors.getOrPut(property, ::mutableListOf).add(function) + accessors.compute(property) { prop, accessorHolder -> + if (function.isGetterFor(prop)) + accessorHolder?.copy(getter = function) ?: DescriptorAccessorHolder(getter = function) + else + accessorHolder?.copy(setter = function) ?: DescriptorAccessorHolder(setter = function) + } } else { regularFunctions.add(function) } } + + val accessorLookalikes = removeNonAccessorsReturning(accessors) + regularFunctions.addAll(accessorLookalikes) + return DescriptorFunctionsHolder(regularFunctions, accessors) } -internal fun FunctionDescriptor.toPossiblePropertyNames(): List<String> { +/** + * If a field has no getter, it's not accessible as a property from Kotlin's perspective, + * but it still might have a setter lookalike. In this case, this "setter" should be just a regular function + * + * @return removed elements + */ +private fun removeNonAccessorsReturning( + propertyAccessors: MutableMap<PropertyDescriptor, DescriptorAccessorHolder> +): List<FunctionDescriptor> { + val nonAccessors = mutableListOf<FunctionDescriptor>() + propertyAccessors.entries.removeIf { (_, accessors) -> + if (accessors.getter == null && accessors.setter != null) { + nonAccessors.add(accessors.setter) + true + } else { + false + } + } + return nonAccessors +} + +private fun FunctionDescriptor.toPossiblePropertyNames(): List<String> { val stringName = this.name.asString() return when { JvmAbi.isSetterName(stringName) -> propertyNamesBySetMethodName(this.name).map { it.asString() } @@ -41,7 +105,7 @@ internal fun FunctionDescriptor.toPossiblePropertyNames(): List<String> { } } -internal fun propertyNamesByGetMethod(functionDescriptor: FunctionDescriptor): List<String> { +private fun propertyNamesByGetMethod(functionDescriptor: FunctionDescriptor): List<String> { val stringName = functionDescriptor.name.asString() // In java, the convention for boolean property accessors is as follows: // - `private boolean active;` @@ -63,21 +127,19 @@ internal fun propertyNamesByGetMethod(functionDescriptor: FunctionDescriptor): L return listOfNotNull(javaPropName, kotlinPropName) } -internal fun FunctionDescriptor.isAccessorFor(property: PropertyDescriptor): Boolean { - return this.isGetterFor(property) || this.isSetterFor(property) +private fun FunctionDescriptor.isAccessorFor(property: PropertyDescriptor): Boolean { + return (this.isGetterFor(property) || this.isSetterFor(property)) + && !property.visibility.isPublicAPI + && this.visibility.isPublicAPI } -internal fun FunctionDescriptor.isGetterFor(property: PropertyDescriptor): Boolean { +private fun FunctionDescriptor.isGetterFor(property: PropertyDescriptor): Boolean { return this.returnType == property.returnType && this.valueParameters.isEmpty() - && !property.visibility.isPublicAPI - && this.visibility.isPublicAPI } -internal fun FunctionDescriptor.isSetterFor(property: PropertyDescriptor): Boolean { +private fun FunctionDescriptor.isSetterFor(property: PropertyDescriptor): Boolean { return this.valueParameters.size == 1 && this.valueParameters[0].type == property.returnType - && !property.visibility.isPublicAPI - && this.visibility.isPublicAPI } |