aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Tyrin <andrei.tyrin@jetbrains.com>2022-10-31 13:50:29 +0100
committerGitHub <noreply@github.com>2022-10-31 13:50:29 +0100
commitfaca33f69872cfd3abe84e3b7e1d4a9e309d7abc (patch)
treed3bf5df9f4da8939995e980c5b0227dadf441807
parentc0aece910e9b012a45ef577136a3f986c52df23e (diff)
downloaddokka-faca33f69872cfd3abe84e3b7e1d4a9e309d7abc.tar.gz
dokka-faca33f69872cfd3abe84e3b7e1d4a9e309d7abc.tar.bz2
dokka-faca33f69872cfd3abe84e3b7e1d4a9e309d7abc.zip
Suppress useless companion for Kotlin-As-Java (#2681)
-rw-r--r--plugins/kotlin-as-java/src/main/kotlin/converters/KotlinCompanion.kt70
-rw-r--r--plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt109
-rw-r--r--plugins/kotlin-as-java/src/main/kotlin/jvmStatic.kt12
-rw-r--r--plugins/kotlin-as-java/src/test/kotlin/CompanionAsJavaTest.kt548
-rw-r--r--plugins/kotlin-as-java/src/test/kotlin/JvmFieldTest.kt61
5 files changed, 768 insertions, 32 deletions
diff --git a/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinCompanion.kt b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinCompanion.kt
new file mode 100644
index 00000000..dec1591a
--- /dev/null
+++ b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinCompanion.kt
@@ -0,0 +1,70 @@
+package org.jetbrains.dokka.kotlinAsJava.converters
+
+import org.jetbrains.dokka.links.Callable
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.properties.PropertyContainer
+
+private const val DEFAULT_COMPANION_NAME = "Companion"
+
+internal fun DObject?.staticFunctionsForJava(): List<DFunction> {
+ if (this == null) return emptyList()
+ return functions.filter { it.isJvmStatic }
+}
+
+/**
+ * @return properties that will be visible as static for java.
+ * See [Static fields](https://kotlinlang.org/docs/java-to-kotlin-interop.html#static-fields)
+ */
+internal fun DObject?.staticPropertiesForJava(): List<DProperty> {
+ if (this == null) return emptyList()
+ return properties.filter { it.isJvmField || it.isConst || it.isLateInit }
+}
+
+internal fun DObject.companionInstancePropertyForJava(): DProperty? {
+ if (hasNothingToRender()) return null // do not show if companion not rendered
+
+ return DProperty(
+ name = name ?: DEFAULT_COMPANION_NAME,
+ modifier = sourceSets.associateWith { JavaModifier.Final },
+ dri = dri.copy(callable = Callable(name ?: DEFAULT_COMPANION_NAME, null, emptyList())),
+ documentation = emptyMap(),
+ sources = emptyMap(),
+ visibility = sourceSets.associateWith {
+ JavaVisibility.Public
+ },
+ type = GenericTypeConstructor(dri, emptyList()),
+ setter = null,
+ getter = null,
+ sourceSets = sourceSets,
+ receiver = null,
+ generics = emptyList(),
+ expectPresentInSet = expectPresentInSet,
+ isExpectActual = false,
+ extra = PropertyContainer.withAll(sourceSets.map {
+ mapOf(it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)).toAdditionalModifiers()
+ })
+ )
+}
+
+internal fun DObject.companionAsJava(): DObject? {
+ if (hasNothingToRender()) return null
+
+ return asJava(
+ excludedProps = staticPropertiesForJava(),
+ excludedFunctions = staticFunctionsForJava()
+ )
+}
+
+/**
+ * Hide companion object if there isn't members of parents.
+ * Properties and functions that are moved to outer class are not counted as members.
+ */
+private fun DObject.hasNothingToRender(): Boolean {
+ val nonStaticPropsCount = properties.size - staticPropertiesForJava().size
+ val nonStaticFunctionsCount = functions.size - staticFunctionsForJava().size
+ val classLikesCount = classlikes.size
+ val superTypesCount = supertypes.values.firstOrNull()?.size ?: 0
+
+ return nonStaticFunctionsCount + nonStaticPropsCount +
+ classLikesCount + superTypesCount == 0
+}
diff --git a/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt
index 4a21aeae..5f98494e 100644
--- a/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt
+++ b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt
@@ -1,8 +1,6 @@
package org.jetbrains.dokka.kotlinAsJava.converters
-import org.jetbrains.dokka.kotlinAsJava.hasJvmOverloads
-import org.jetbrains.dokka.kotlinAsJava.hasJvmSynthetic
-import org.jetbrains.dokka.kotlinAsJava.jvmField
+import org.jetbrains.dokka.kotlinAsJava.*
import org.jetbrains.dokka.kotlinAsJava.transformers.JvmNameProvider
import org.jetbrains.dokka.kotlinAsJava.transformers.withCallableName
import org.jetbrains.dokka.links.Callable
@@ -17,17 +15,20 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
val jvmNameProvider = JvmNameProvider()
+internal const val OBJECT_INSTANCE_NAME = "INSTANCE"
-private val DProperty.isConst: Boolean
- get() = extra[AdditionalModifiers]
- ?.content
- ?.any { (_, modifiers) ->
- ExtraModifiers.KotlinOnlyModifiers.Const in modifiers
- } == true
+internal val DProperty.isConst: Boolean
+ get() = hasModifier(ExtraModifiers.KotlinOnlyModifiers.Const)
+
+internal val DProperty.isLateInit: Boolean
+ get() = hasModifier(ExtraModifiers.KotlinOnlyModifiers.LateInit)
-private val DProperty.isJvmField: Boolean
+internal val DProperty.isJvmField: Boolean
get() = jvmField() != null
+internal val DFunction.isJvmStatic: Boolean
+ get() = jvmStatic() != null
+
internal fun DPackage.asJava(): DPackage {
val syntheticClasses =
(properties.map { jvmNameProvider.nameForSyntheticClass(it) to it }
@@ -76,7 +77,11 @@ internal fun DPackage.asJava(): DPackage {
)
}
-internal fun DProperty.asJava(isTopLevel: Boolean = false, relocateToClass: String? = null) =
+internal fun DProperty.asJava(
+ isTopLevel: Boolean = false,
+ relocateToClass: String? = null,
+ isFromObjectOrCompanion: Boolean = false
+) =
copy(
dri = if (relocateToClass.isNullOrBlank()) {
dri
@@ -85,9 +90,7 @@ internal fun DProperty.asJava(isTopLevel: Boolean = false, relocateToClass: Stri
},
modifier = javaModifierFromSetter(),
visibility = visibility.mapValues {
- if (isTopLevel && isConst) {
- JavaVisibility.Public
- } else if (jvmField() != null || (getter == null && setter == null)) {
+ if (isConst || isJvmField || (getter == null && setter == null) || (isFromObjectOrCompanion && isLateInit)) {
it.value.asJava()
} else {
it.value.propertyVisibilityAsJava()
@@ -96,12 +99,12 @@ internal fun DProperty.asJava(isTopLevel: Boolean = false, relocateToClass: Stri
type = type.asJava(), // TODO: check
setter = null,
getter = null, // Removing getters and setters as they will be available as functions
- extra = if (isTopLevel) extra +
- extra.mergeAdditionalModifiers(
- sourceSets.associateWith {
- setOf(ExtraModifiers.JavaOnlyModifiers.Static)
- }
- )
+ extra = if (isTopLevel || isConst || (isFromObjectOrCompanion && isJvmField) || (isFromObjectOrCompanion && isLateInit))
+ extra + extra.mergeAdditionalModifiers(
+ sourceSets.associateWith {
+ setOf(ExtraModifiers.JavaOnlyModifiers.Static)
+ }
+ )
else extra
)
@@ -192,7 +195,7 @@ private fun DFunction.asJava(
parameters = listOfNotNull(receiver?.asJava()) + parameters.map { it.asJava() },
visibility = visibility.map { (sourceSet, visibility) -> Pair(sourceSet, visibility.asJava()) }.toMap(),
receiver = null,
- extra = if (isTopLevel) {
+ extra = if (isTopLevel || isJvmStatic) {
extra + extra.mergeAdditionalModifiers(
sourceSets.associateWith {
setOf(ExtraModifiers.JavaOnlyModifiers.Static)
@@ -259,24 +262,51 @@ internal fun DClass.asJava(): DClass = copy(
)
}, // name may not always be valid here, however classNames should always be not null
functions = functionsInJava(),
- properties = properties
- .filterNot { it.hasJvmSynthetic() }
- .map { it.asJava() },
- classlikes = classlikes.map { it.asJava() },
+ properties = propertiesInJava(),
+ classlikes = classlikesInJava(),
generics = generics.map { it.asJava() },
+ companion = companion?.companionAsJava(),
supertypes = supertypes.mapValues { it.value.map { it.asJava() } },
modifier = if (modifier.all { (_, v) -> v is KotlinModifier.Empty }) sourceSets.associateWith { JavaModifier.Final }
else sourceSets.associateWith { modifier.values.first() }
)
+/**
+ * Companion objects requires some custom logic for rendering as Java.
+ * They are excluded from usual classlikes rendering and added after.
+ */
+internal fun DClass.classlikesInJava(): List<DClasslike> {
+ val classlikes = classlikes
+ .filter { it.name != companion?.name }
+ .map { it.asJava() }
+
+ val companionAsJava = companion?.companionAsJava()
+ return if (companionAsJava != null) classlikes.plus(companionAsJava) else classlikes
+}
+
+
internal fun DClass.functionsInJava(): List<DFunction> =
properties
- .filter { it.jvmField() == null && !it.hasJvmSynthetic() }
+ .filter { !it.isJvmField && !it.hasJvmSynthetic() }
.flatMap { property -> listOfNotNull(property.getter, property.setter) }
.plus(functions)
+ .plus(companion.staticFunctionsForJava())
.filterNot { it.hasJvmSynthetic() }
.flatMap { it.asJava(it.dri.classNames ?: it.name) }
+internal fun DClass.propertiesInJava(): List<DProperty> {
+ val propertiesFromCompanion = companion
+ .staticPropertiesForJava()
+ .filterNot { it.hasJvmSynthetic() }
+ .map { it.asJava(isFromObjectOrCompanion = true) }
+ val companionInstanceProperty = companion?.companionInstancePropertyForJava()
+ val ownProperties = properties
+ .filterNot { it.hasJvmSynthetic() }
+ .map { it.asJava() }
+
+ return propertiesFromCompanion + ownProperties + listOfNotNull(companionInstanceProperty)
+}
+
private fun DTypeParameter.asJava(): DTypeParameter = copy(
variantTypeParameter = variantTypeParameter.withDri(dri.possiblyAsJava()),
bounds = bounds.map { it.asJava() }
@@ -318,7 +348,7 @@ internal fun DEnum.asJava(): DEnum = copy(
functions = functions
.plus(
properties
- .filter { it.jvmField() == null && !it.hasJvmSynthetic() }
+ .filter { !it.isJvmField && !it.hasJvmSynthetic() }
.flatMap { listOf(it.getter, it.setter) }
)
.filterNotNull()
@@ -332,23 +362,33 @@ internal fun DEnum.asJava(): DEnum = copy(
// , entries = entries.map { it.asJava() }
)
-internal fun DObject.asJava(): DObject = copy(
+/**
+ * Parameters [excludedProps] and [excludedFunctions] used for rendering companion objects
+ * where some members (that lifted to outer class) are not rendered
+ */
+internal fun DObject.asJava(
+ excludedProps: List<DProperty> = emptyList(),
+ excludedFunctions: List<DFunction> = emptyList()
+): DObject = copy(
functions = functions
.plus(
properties
- .filter { it.jvmField() == null && !it.hasJvmSynthetic() }
+ .filterNot { it in excludedProps }
+ .filter { !it.isJvmField && !it.isConst && !it.isLateInit && !it.hasJvmSynthetic() }
.flatMap { listOf(it.getter, it.setter) }
)
.filterNotNull()
+ .filterNot { it in excludedFunctions }
.filterNot { it.hasJvmSynthetic() }
.flatMap { it.asJava(dri.classNames ?: name.orEmpty()) },
properties = properties
.filterNot { it.hasJvmSynthetic() }
- .map { it.asJava() } +
+ .filterNot { it in excludedProps }
+ .map { it.asJava(isFromObjectOrCompanion = true) } +
DProperty(
- name = "INSTANCE",
+ name = OBJECT_INSTANCE_NAME,
modifier = sourceSets.associateWith { JavaModifier.Final },
- dri = dri.copy(callable = Callable("INSTANCE", null, emptyList())),
+ dri = dri.copy(callable = Callable(OBJECT_INSTANCE_NAME, null, emptyList())),
documentation = emptyMap(),
sources = emptyMap(),
visibility = sourceSets.associateWith {
@@ -450,3 +490,8 @@ private fun AdditionalModifiers.squash(second: AdditionalModifiers) =
internal fun ClassId.classNames(): String =
shortClassName.identifier + (outerClassId?.classNames()?.let { ".$it" } ?: "")
+
+private fun DProperty.hasModifier(modifier: ExtraModifiers.KotlinOnlyModifiers): Boolean =
+ extra[AdditionalModifiers]
+ ?.content
+ ?.any { (_, modifiers) -> modifier in modifiers } == true \ No newline at end of file
diff --git a/plugins/kotlin-as-java/src/main/kotlin/jvmStatic.kt b/plugins/kotlin-as-java/src/main/kotlin/jvmStatic.kt
new file mode 100644
index 00000000..10372ac9
--- /dev/null
+++ b/plugins/kotlin-as-java/src/main/kotlin/jvmStatic.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.kotlinAsJava
+
+import org.jetbrains.dokka.model.Annotations
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+import org.jetbrains.kotlin.util.firstNotNullResult
+
+internal fun WithExtraProperties<out Documentable>.jvmStatic(): Annotations.Annotation? =
+ extra[Annotations]?.directAnnotations?.entries?.firstNotNullResult { (_, annotations) -> annotations.jvmStaticAnnotation() }
+
+internal fun List<Annotations.Annotation>.jvmStaticAnnotation(): Annotations.Annotation? =
+ firstOrNull { it.dri.packageName == "kotlin.jvm" && it.dri.classNames == "JvmStatic" } \ No newline at end of file
diff --git a/plugins/kotlin-as-java/src/test/kotlin/CompanionAsJavaTest.kt b/plugins/kotlin-as-java/src/test/kotlin/CompanionAsJavaTest.kt
new file mode 100644
index 00000000..3b2a8e89
--- /dev/null
+++ b/plugins/kotlin-as-java/src/test/kotlin/CompanionAsJavaTest.kt
@@ -0,0 +1,548 @@
+package kotlinAsJavaPlugin
+
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.*
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+private const val COMPANION_NAME = "C"
+
+class CompanionAsJavaTest : BaseAbstractTest() {
+ private val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath += jvmStdlibPath!!
+ }
+ }
+ }
+
+ @Test
+ fun `empty companion object should not be rendered`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {}
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertCompanionNotRendered(parentClass)
+ }
+ }
+ }
+
+ @Test
+ fun `companion object with only jvmField should not be rendered`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | @JvmField val jvmFieldProp: String = ""
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertCompanionNotRendered(parentClass)
+ }
+ }
+ }
+
+ @Test
+ fun `companion property with jvmField should be static`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | @JvmField val jvmFieldProp: String = ""
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ val parentClassProperty = parentClass.properties.firstOrNull { it.name == "jvmFieldProp" }
+ assertNotNull(parentClassProperty, "Parent class should contain the companion jvmField property")
+ assertIsStatic(parentClassProperty)
+ }
+ }
+ }
+
+ @Test
+ fun `companion object with only const should not be rendered`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | const val constProp: Int = 0
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertCompanionNotRendered(parentClass)
+ }
+ }
+ }
+
+ @Test
+ fun `companion property with const should be static`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | const val constProp: Int = 0
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ val parentClassProperty = parentClass.properties.firstOrNull { it.name == "constProp" }
+ assertNotNull(parentClassProperty, "Parent class should contain the companion const property")
+ assertIsStatic(parentClassProperty)
+ }
+ }
+ }
+
+ @Test
+ fun `companion object with only lateinit not rendered`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | lateinit var lateInitProp: String
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertCompanionNotRendered(parentClass)
+ }
+ }
+ }
+
+ @Test
+ fun `companion property with lateinit should be static`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | lateinit var lateInitProp: String
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ val parentClassProperty = parentClass.properties.firstOrNull { it.name == "lateInitProp" }
+ assertNotNull(parentClassProperty, "Parent class should contain the companion lateinit property")
+ assertIsStatic(parentClassProperty)
+ }
+ }
+ }
+
+ @Test
+ fun `companion object with only jvmStatic fun not rendered`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | @JvmStatic fun staticFun(): String = ""
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertCompanionNotRendered(parentClass)
+ }
+ }
+ }
+
+ @Test
+ fun `companion function with JvmStatic should be static`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | @JvmStatic fun staticFun(): String = ""
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ val parentClassFunction = parentClass.functions.firstOrNull { it.name == "staticFun" }
+ assertNotNull(parentClassFunction, "Parent class should contains the companion jvmStatic function")
+ assertIsStatic(parentClassFunction)
+ }
+ }
+ }
+
+ @Test
+ fun `companion object with nested classes is rendered`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | @JvmStatic
+ | fun staticFun1(): String = ""
+ |
+ | const val CONST_VAL: Int = 100
+ |
+ | class NestedClass
+ | object NestedObject
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertCompanionRendered(parentClass)
+
+ val classLikes = parentClass.companion?.classlikes
+ assertNotNull(classLikes)
+ assertEquals(2, classLikes.size,
+ "Classlike list should contains nested class and object")
+ assertTrue(classLikes.any { it.name == "NestedClass" })
+ assertTrue(classLikes.any { it.name == "NestedObject" })
+
+ }
+ }
+ }
+
+ @Test
+ fun `companion object with supertype is rendered`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |
+ |class Parent
+ |interface IParent
+ |class MyClass {
+ | companion object $COMPANION_NAME : Parent(), IParent {
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertCompanionRendered(parentClass)
+ }
+ }
+ }
+
+ @Test
+ fun `companion object rendered for own properties`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | @JvmField
+ | val jvmField: String = ""
+ | const val contVal: Int = 0
+ | lateinit var lateInit: String
+ |
+ | val rendered: Int = TODO()
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertCompanionRendered(parentClass)
+
+ val properties = parentClass.companion?.properties
+
+ assertNotNull(properties)
+ assertEquals(2, properties.size) // including INSTANCE
+ assertTrue(properties.any { it.name == "rendered" })
+ assertTrue(properties.none { it.name == "jvmField1" })
+ assertTrue(properties.none { it.name == "contVal" })
+ assertTrue(properties.none { it.name == "lateInit" })
+ }
+ }
+ }
+
+ @Test
+ fun `companion object rendered for own functions`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | @JvmStatic
+ | fun staticFun(): String = ""
+ |
+ | fun renderedFun(): String = ""
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertCompanionRendered(parentClass)
+
+ val functions = parentClass.companion?.functions
+
+ assertNotNull(functions)
+ assertEquals(1, functions.size)
+ assertTrue(functions.any { it.name == "renderedFun" })
+ assertTrue(functions.none { it.name == "staticFun" })
+ }
+ }
+ }
+
+ @Test
+ fun `companion const value should be rendered as public by default`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | const val constProp: String = ""
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertEquals(
+ JavaVisibility.Public,
+ parentClass.properties.firstOrNull()?.visibility?.values?.first()
+ )
+ assertNull(parentClass.findFunction("constProp"), "There is no getter for the cont field")
+ }
+ }
+ }
+
+ @Test
+ fun `companion const value should preserve Java modifier`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | protected const val constProp: String = ""
+ | }
+ |}
+ """.trimMargin(),
+ dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath += jvmStdlibPath!!
+ documentedVisibilities = setOf(
+ org.jetbrains.dokka.DokkaConfiguration.Visibility.PUBLIC,
+ org.jetbrains.dokka.DokkaConfiguration.Visibility.PROTECTED
+ )
+ }
+ }
+ },
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertEquals(
+ JavaVisibility.Protected,
+ parentClass.properties.firstOrNull()?.visibility?.values?.first()
+ )
+ assertNull(parentClass.findFunction("constProp"), "There is no getter for the cont field")
+ }
+ }
+ }
+
+ @Test
+ fun `companion lateinit value should be rendered as public by default`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | lateinit var lateInitProp: String
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertEquals(
+ JavaVisibility.Public,
+ parentClass.properties.firstOrNull()?.visibility?.values?.first()
+ )
+ assertNull(parentClass.findFunction("lateInitProp"), "There is no getter for the cont field")
+ }
+ }
+ }
+
+ @Test
+ fun `named companion instance property should be rendered if companion rendered`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | var property: String = ""
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertNotNull(parentClass.properties.any { it.name == COMPANION_NAME })
+ }
+ }
+ }
+
+ @Test
+ fun `default named companion instance property should be rendered if companion rendered`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object {
+ | var property: String = ""
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertTrue(parentClass.properties.any { it.name == "Companion" })
+ }
+ }
+ }
+
+ @Test
+ fun `companion instance property should be hidden if companion not rendered`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |class MyClass {
+ | companion object $COMPANION_NAME {
+ | const val property: String = ""
+ | }
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val parentClass = module.findClass("MyClass")
+
+ assertTrue(parentClass.properties.none { it.name == COMPANION_NAME })
+ }
+ }
+ }
+}
+
+private fun DModule.findClass(name: String) = packages.flatMap { it.classlikes }
+ .firstOrNull { it.name == name } as DClass
+
+private fun DClass.findFunction(name: String) = functions.firstOrNull { it.name.contains(name, ignoreCase = true) }
+
+private fun assertCompanionRendered(parentClass: DClass) {
+ assertNotNull(parentClass.companion, "Companion should not be null")
+ assertTrue(
+ parentClass.classlikes.any { it.name == COMPANION_NAME },
+ "Companion should be in classlikes list"
+ )
+}
+
+private fun assertCompanionNotRendered(parentClass: DClass) {
+ assertNull(parentClass.companion, "Companion should be null")
+ assertTrue(
+ parentClass.classlikes.none { it.name == COMPANION_NAME },
+ "Companion should not be in classlikes list"
+ )
+}
+
+private fun assertIsStatic(property: DProperty) {
+ val extra = property.extra[AdditionalModifiers]
+ assertNotNull(extra, "extra for property is present")
+ assertTrue(
+ extra.content.values.contains(setOf(ExtraModifiers.JavaOnlyModifiers.Static)),
+ "Property contains extra modifier static"
+ )
+}
+
+private fun assertIsStatic(function: DFunction) {
+ val extra = function.extra[AdditionalModifiers]
+ assertNotNull(extra, "extra for property is present")
+ assertTrue(
+ extra.content.values.contains(setOf(ExtraModifiers.JavaOnlyModifiers.Static)),
+ "Function contains extra modifier static"
+ )
+} \ No newline at end of file
diff --git a/plugins/kotlin-as-java/src/test/kotlin/JvmFieldTest.kt b/plugins/kotlin-as-java/src/test/kotlin/JvmFieldTest.kt
index 99ea017b..f0c44530 100644
--- a/plugins/kotlin-as-java/src/test/kotlin/JvmFieldTest.kt
+++ b/plugins/kotlin-as-java/src/test/kotlin/JvmFieldTest.kt
@@ -1,11 +1,14 @@
package kotlinAsJavaPlugin
import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.AdditionalModifiers
+import org.jetbrains.dokka.model.ExtraModifiers
import org.jetbrains.dokka.model.JavaVisibility
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
+import kotlin.test.assertTrue
class JvmFieldTest : BaseAbstractTest() {
val configuration = dokkaConfiguration {
@@ -106,4 +109,62 @@ class JvmFieldTest : BaseAbstractTest() {
}
}
}
+
+ @Test
+ fun `enum jvmfield property should have no getters`(){
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |enum class MyEnum {
+ | ITEM;
+ |
+ | @JvmField
+ | val property: String = TODO()
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val classLike = module.packages.flatMap { it.classlikes }.first()
+ val property = classLike.properties.singleOrNull { it.name == "property" }
+ assertNotNull(property)
+ assertEquals(
+ emptyList(),
+ classLike.functions
+ .filter{ it.name.contains("property", ignoreCase = true) }
+ .map { it.name }
+ )
+ assertNull(property.getter)
+ assertNull(property.setter)
+ }
+ }
+ }
+
+
+ @Test
+ fun `object jvmfield property should be static`(){
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |object MyObject {
+ | @JvmField
+ | val property: String = TODO()
+ |}
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val classLike = module.packages.flatMap { it.classlikes }.first()
+ val property = classLike.properties.singleOrNull { it.name == "property" }
+ assertNotNull(property)
+
+ val extra = property.extra[AdditionalModifiers]
+ assertNotNull(extra, "Additional modifiers for property are exist")
+ assertTrue(extra.content.values.contains(setOf(ExtraModifiers.JavaOnlyModifiers.Static)),
+ "Extra modifiers contains static")
+ }
+ }
+ }
}