Skip to content

Commit

Permalink
Fix special "is" case for accessors and refactor logic in general
Browse files Browse the repository at this point in the history
  • Loading branch information
IgnatBeresnev committed May 19, 2022
1 parent b8ec1d2 commit fb37558
Show file tree
Hide file tree
Showing 6 changed files with 389 additions and 243 deletions.
12 changes: 12 additions & 0 deletions plugins/base/src/main/kotlin/translators/CollectionExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.jetbrains.dokka.base.translators

// TODO [beresnev] remove this copy-paste and use the same method from stdlib instead after updating to 1.5
internal inline fun <T, R : Any> Iterable<T>.firstNotNullOfOrNull(transform: (T) -> R?): R? {
for (element in this) {
val result = transform(element)
if (result != null) {
return result
}
}
return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
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.JvmAbi
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName
import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName
import org.jetbrains.kotlin.load.kotlin.toSourceElement
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
Expand Down Expand Up @@ -384,11 +380,10 @@ private class DokkaDescriptorVisitor(
return coroutineScope {
val descriptorsWithKind = scope.getDescriptorsWithKind()

val propertiesByName = descriptorsWithKind.properties.associateBy { it.name.asString() }
val (accessors, regularFunctions) = descriptorsWithKind.functions.partition {
val property = propertiesByName[it.toPossiblePropertyName()]
property != null && (it.isGetterFor(property) || it.isSetterFor(property))
}
val (regularFunctions, accessors) = splitFunctionsAndAccessors(
properties = descriptorsWithKind.properties,
functions = descriptorsWithKind.functions
)

val functions = async { regularFunctions.visitFunctions(driWithPlatform) }
val properties = async { descriptorsWithKind.properties.visitProperties(driWithPlatform, accessors) }
Expand Down Expand Up @@ -431,45 +426,6 @@ private class DokkaDescriptorVisitor(
}
}

private fun FunctionDescriptor.toPossiblePropertyName(): String? {
val stringName = this.name.asString()
return when {
JvmAbi.isSetterName(stringName) -> propertyNamesBySetMethodName(this.name).firstOrNull()?.asString()
JvmAbi.isGetterName(stringName) -> {
// In java, the convention for boolean property accessors is as follows:
// - `private boolean active;`
// - `private boolean isActive();`
//
// Whereas in Kotlin, because there are no explicit accessors, the convention is
// - `val isActive: Boolean`
//
// This makes it difficult to guess the name of the accessor property in case of Java
if (this is JavaMethodDescriptor && JvmAbi.startsWithIsPrefix(stringName)) {
stringName.removePrefix("is").let { newName ->
newName.replaceFirst(newName[0], newName[0].toLowerCase())
}
} else {
propertyNameByGetMethodName(this.name)?.asString()
}
}
else -> null
}
}

private fun FunctionDescriptor.isGetterFor(property: PropertyDescriptor): Boolean {
return this.returnType == property.returnType
&& this.valueParameters.isEmpty()
&& !property.visibility.isPublicAPI
&& this.visibility.isPublicAPI
}

private fun FunctionDescriptor.isSetterFor(property: PropertyDescriptor): Boolean {
return this.valueParameters.size == 1
&& this.valueParameters[0].type == property.returnType
&& !property.visibility.isPublicAPI
&& this.visibility.isPublicAPI
}

/**
* @param implicitAccessors getters/setters that are not part of the property descriptor, for instance
* average methods inherited from java sources
Expand Down Expand Up @@ -865,12 +821,11 @@ private class DokkaDescriptorVisitor(

private suspend fun List<PropertyDescriptor>.visitProperties(
parent: DRIWithPlatformInfo,
implicitAccessors: List<FunctionDescriptor> = emptyList(),
implicitAccessors: Map<PropertyDescriptor, List<FunctionDescriptor>> = emptyMap(),
): List<DProperty> {
val accessorsByPropertyName = implicitAccessors.groupBy { it.toPossiblePropertyName() }
return coroutineScope {
parallelMap {
val propertyAccessors = accessorsByPropertyName[it.name.asString()] ?: emptyList()
val propertyAccessors = implicitAccessors[it] ?: emptyList()
visitPropertyDescriptor(it, propertyAccessors, parent)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.jetbrains.dokka.base.translators.descriptors

import org.jetbrains.dokka.base.translators.firstNotNullOfOrNull
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName
import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName


internal data class DescriptorFunctionsHolder(
val regularFunctions: List<FunctionDescriptor>,
val accessors: Map<PropertyDescriptor, List<FunctionDescriptor>>
)

internal fun splitFunctionsAndAccessors(
properties: List<PropertyDescriptor>,
functions: List<FunctionDescriptor>
): DescriptorFunctionsHolder {
val fieldsByName = properties.associateBy { it.name.asString() }
val regularFunctions = mutableListOf<FunctionDescriptor>()
val accessors = mutableMapOf<PropertyDescriptor, MutableList<FunctionDescriptor>>()
functions.forEach { function ->
val possiblePropertyNamesForFunction = function.toPossiblePropertyNames()
val field = possiblePropertyNamesForFunction.firstNotNullOfOrNull { fieldsByName[it] }
if (field != null) {
accessors.getOrPut(field, ::mutableListOf).add(function)
} else {
regularFunctions.add(function)
}
}
return DescriptorFunctionsHolder(regularFunctions, accessors)
}

internal fun FunctionDescriptor.toPossiblePropertyNames(): List<String> {
val stringName = this.name.asString()
return when {
JvmAbi.isSetterName(stringName) -> propertyNamesBySetMethodName(this.name).map { it.asString() }
JvmAbi.isGetterName(stringName) -> propertyNamesByGetMethod(this)
else -> listOf()
}
}

internal 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;`
// - `private boolean isActive();`
//
// Whereas in Kotlin, because there are no explicit accessors, the convention is
// - `val isActive: Boolean`
//
// This makes it difficult to guess the name of the accessor property in case of Java
val javaPropName = if (functionDescriptor is JavaMethodDescriptor && JvmAbi.startsWithIsPrefix(stringName)) {
val javaPropName = stringName.removePrefix("is").let { newName ->
newName.replaceFirst(newName[0], newName[0].toLowerCase())
}
javaPropName
} else {
null
}
val kotlinPropName = propertyNameByGetMethodName(functionDescriptor.name)?.asString()
return listOfNotNull(javaPropName, kotlinPropName)
}

internal 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 {
return this.valueParameters.size == 1
&& this.valueParameters[0].type == property.returnType
&& !property.visibility.isPublicAPI
&& this.visibility.isPublicAPI
}

Loading

0 comments on commit fb37558

Please sign in to comment.