Skip to content

Commit

Permalink
Apply requested changes.
Browse files Browse the repository at this point in the history
  • Loading branch information
BarkingBad committed Mar 2, 2022
1 parent d2675bf commit 13b1ab0
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 38 deletions.
5 changes: 4 additions & 1 deletion core/src/main/kotlin/model/JvmField.kt
Expand Up @@ -2,6 +2,9 @@ package org.jetbrains.dokka.model

import org.jetbrains.dokka.links.DRI

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

fun DRI.isJvmField(): Boolean = packageName == JVM_FIELD_PACKAGE_NAME && classNames == JVM_FIELD_CLASS_NAMES

fun Annotations.Annotation.isJvmField(): Boolean = dri.isJvmName()
Expand Up @@ -7,42 +7,46 @@ import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName
import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName
import org.jetbrains.kotlin.name.Name

/**
* This transformer is used to merge the backing fields and accessors (getters and setters)
* obtained from Java sources. This way, we could generate more coherent documentation,
* since the model is now aware of the relationship between accessors and the fields.
* This way if we generate Kotlin output we get rid of spare getters and setters,
* and from Kotlin-as-Java perspective we can collect accessors of each property.
*/
class PropertiesMergerTransformer : PreMergeDocumentableTransformer {

override fun invoke(modules: List<DModule>) =
modules.map { it.copy(packages = it.packages.map {
it.mergeBeansAndField().copy(
classlikes = it.classlikes.map { it.mergeBeansAndField() }
it.mergeAccessorsAndField().copy(
classlikes = it.classlikes.map { it.mergeAccessorsAndField() }
)
}) }

private fun <T : Documentable> T.mergeBeansAndField(): T = when (this) {
is DClass -> {
val (functions, properties) = mergePotentialBeansAndField(this.functions, this.properties)
this.copy(functions = functions, properties = properties)
}
is DEnum -> {
val (functions, properties) = mergePotentialBeansAndField(this.functions, this.properties)
this.copy(functions = functions, properties = properties)
}
is DInterface -> {
val (functions, properties) = mergePotentialBeansAndField(this.functions, this.properties)
this.copy(functions = functions, properties = properties)
}
is DObject -> {
val (functions, properties) = mergePotentialBeansAndField(this.functions, this.properties)
this.copy(functions = functions, properties = properties)
}
is DAnnotation -> {
val (functions, properties) = mergePotentialBeansAndField(this.functions, this.properties)
this.copy(functions = functions, properties = properties)
}
is DPackage -> {
val (functions, properties) = mergePotentialBeansAndField(this.functions, this.properties)
this.copy(functions = functions, properties = properties)
}
else -> this
} as T
private fun <T : WithScope> T.mergeAccessorsAndField(): T {
val (functions, properties) = mergePotentialAccessorsAndField(this.functions, this.properties)
return when (this) {
is DClass -> {
this.copy(functions = functions, properties = properties)
}
is DEnum -> {
this.copy(functions = functions, properties = properties)
}
is DInterface -> {
this.copy(functions = functions, properties = properties)
}
is DObject -> {
this.copy(functions = functions, properties = properties)
}
is DAnnotation -> {
this.copy(functions = functions, properties = properties)
}
is DPackage -> {
this.copy(functions = functions, properties = properties)
}
else -> this
} as T
}

private fun DFunction.getPropertyNameForFunction() =
when {
Expand All @@ -52,13 +56,16 @@ class PropertiesMergerTransformer : PreMergeDocumentableTransformer {
else -> null
}

private fun mergePotentialBeansAndField(
private fun mergePotentialAccessorsAndField(
functions: List<DFunction>,
fields: List<DProperty>
): Pair<List<DFunction>, List<DProperty>> {
val fieldNames = fields.associateBy { it.name }
val accessors = mutableMapOf<DProperty, MutableList<DFunction>>()

// Regular methods are methods that are not getters or setters
val regularMethods = mutableListOf<DFunction>()
// Accessors are methods that are getters or setters
val accessors = mutableMapOf<DProperty, MutableList<DFunction>>()
functions.forEach { method ->
val field = method.getPropertyNameForFunction()?.let { name -> fieldNames[name] }
if (field != null) {
Expand All @@ -67,7 +74,11 @@ class PropertiesMergerTransformer : PreMergeDocumentableTransformer {
regularMethods.add(method)
}
}
return regularMethods.toList() to accessors.map { (dProperty, dFunctions) ->

// Properties are triples of field and its getters and/or setters.
// They are wrapped up in DProperty class,
// so we copy accessors into its dedicated DProperty data class properties
val propertiesWithAccessors = accessors.map { (dProperty, dFunctions) ->
if (dProperty.visibility.values.all { it is KotlinVisibility.Private }) {
dFunctions.flatMap { it.visibility.values }.toSet().singleOrNull()?.takeIf {
it in listOf(KotlinVisibility.Public, KotlinVisibility.Protected)
Expand All @@ -81,6 +92,15 @@ class PropertiesMergerTransformer : PreMergeDocumentableTransformer {
} else {
dProperty
}
} + fields.toSet().minus(accessors.keys.toSet())
}

// The above logic is driven by accessors list
// Therefore, if there was no getter or setter, we missed processing the field itself.
// To include them, we collect all fields that have no accessors
val remainingFields = fields.toSet().minus(accessors.keys.toSet())

val allProperties = propertiesWithAccessors + remainingFields

return regularMethods.toList() to allProperties
}
}
Expand Up @@ -431,6 +431,14 @@ private class DokkaDescriptorVisitor(
parent: DRIWithPlatformInfo
): DProperty {
val (dri, _) = originalDescriptor.createDRI()
/**
* `createDRI` returns the DRI of the exact element and potential DRI of an element that is overriding it
* (It can be also FAKE_OVERRIDE which is in fact just inheritance of the symbol)
*
* Looking at what PSIs do, they give the DRI of the element within the classnames where it is actually
* declared and inheritedFrom as the same DRI but truncated callable part.
* Therefore, we set callable to null and take the DRI only if it is indeed coming from different class.
*/
val inheritedFrom = dri.copy(callable = null).takeIf { parent.dri.classNames != dri.classNames }
val descriptor = originalDescriptor.getConcreteDescriptor()
val isExpect = descriptor.isExpect
Expand Down Expand Up @@ -487,6 +495,10 @@ private class DokkaDescriptorVisitor(
parent: DRIWithPlatformInfo
): DFunction {
val (dri, _) = originalDescriptor.createDRI()
/**
* To avoid redundant docs, please visit [visitPropertyDescriptor] inheritedFrom
* local val documentation.
*/
val inheritedFrom = dri.copy(callable = null).takeIf { parent.dri.classNames != dri.classNames }
val descriptor = originalDescriptor.getConcreteDescriptor()
val isExpect = descriptor.isExpect
Expand Down Expand Up @@ -650,7 +662,17 @@ private class DokkaDescriptorVisitor(
return coroutineScope {
val generics = async { descriptor.typeParameters.parallelMap { it.toVariantTypeParameter() } }

fun SourceSetDependent<DocumentationNode>.translateParamToDescription(): SourceSetDependent<DocumentationNode> {
/**
* Workaround for problem with inheriting TagWrappers.
* There is an issue if one declare documentation in the class header for
* property using this syntax: `@property`
* The compiler will propagate it withing this tag to property and to its getters and setters.
*
* Actually, the problem impacts more of these tags, yet this particular tag was blocker for
* some opens-source plugin creators.
* TODO: Should rethink if we could fix it globally in dokka or in compiler itself.
*/
fun SourceSetDependent<DocumentationNode>.translatePropertyToDescription(): SourceSetDependent<DocumentationNode> {
return this.mapValues { (_, value) ->
value.copy(children = value.children.map {
when (it) {
Expand All @@ -667,7 +689,7 @@ private class DokkaDescriptorVisitor(
isConstructor = false,
parameters = parameters,
visibility = descriptor.visibility.toDokkaVisibility().toSourceSetDependent(),
documentation = descriptor.resolveDescriptorData().translateParamToDescription(),
documentation = descriptor.resolveDescriptorData().translatePropertyToDescription(),
type = descriptor.returnType!!.toBound(),
generics = generics.await(),
modifier = descriptor.modifier().toSourceSetDependent(),
Expand Down
Expand Up @@ -474,9 +474,9 @@ class DefaultPsiToDocumentableTranslator(
* Workaround for getting JvmField Kotlin annotation in PSIs
*/
private fun Collection<PsiAnnotation>.getJvmFieldAnnotation() = filter {
it.qualifiedName == "kotlin.jvm.JvmField"
it.qualifiedName == "$JVM_FIELD_PACKAGE_NAME.$JVM_FIELD_CLASS_NAMES"
}.map {
Annotations.Annotation(DRI("kotlin.jvm", "JvmField"), emptyMap())
Annotations.Annotation(DRI(JVM_FIELD_PACKAGE_NAME, JVM_FIELD_CLASS_NAMES), emptyMap())
}.distinct()

private fun <T : AnnotationTarget> PsiTypeParameter.annotations(): PropertyContainer<T> = this.annotations.toList().toListOfAnnotations().annotations()
Expand Down

0 comments on commit 13b1ab0

Please sign in to comment.