From 6b0cdf3102b1f1dd213ca0c2e2c333f8756be6b4 Mon Sep 17 00:00:00 2001 From: Marcin Aman Date: Mon, 4 Jan 2021 10:27:10 +0100 Subject: JvmField annotation (#1678) --- .../kotlin/converters/KotlinToJavaConverter.kt | 27 ++++++-- plugins/kotlin-as-java/src/main/kotlin/jvmField.kt | 12 ++++ .../main/kotlin/transformers/JvmNameProvider.kt | 13 ++-- .../kotlin-as-java/src/test/kotlin/JvmFieldTest.kt | 81 ++++++++++++++++++++++ 4 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 plugins/kotlin-as-java/src/main/kotlin/jvmField.kt create mode 100644 plugins/kotlin-as-java/src/test/kotlin/JvmFieldTest.kt (limited to 'plugins') 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 4ecc84a7..8b21e44d 100644 --- a/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt +++ b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt @@ -1,5 +1,6 @@ package org.jetbrains.dokka.kotlinAsJava.converters +import org.jetbrains.dokka.kotlinAsJava.jvmField import org.jetbrains.dokka.kotlinAsJava.transformers.JvmNameProvider import org.jetbrains.dokka.kotlinAsJava.transformers.withCallableName import org.jetbrains.dokka.links.Callable @@ -22,6 +23,9 @@ private val DProperty.isConst: Boolean ExtraModifiers.KotlinOnlyModifiers.Const in modifiers } == true +private val DProperty.isJvmField: Boolean + get() = jvmField() != null + internal fun DPackage.asJava(): DPackage { val syntheticClasses = (properties.map { jvmNameProvider.nameForSyntheticClass(it) to it } @@ -36,7 +40,7 @@ internal fun DPackage.asJava(): DPackage { functions = ( nodes .filterIsInstance() - .filterNot { it.isConst } + .filterNot { it.isConst || it.isJvmField } .flatMap { it.javaAccessors(relocateToClass = syntheticClassName.name) } + nodes.filterIsInstance() .map { it.asJava(syntheticClassName.name) }), // TODO: methods are static and receiver is a param @@ -76,6 +80,8 @@ internal fun DProperty.asJava(isTopLevel: Boolean = false, relocateToClass: Stri visibility = visibility.mapValues { if (isTopLevel && isConst) { JavaVisibility.Public + } else if (jvmField() != null) { + it.value.asJava() } else { it.value.propertyVisibilityAsJava() } @@ -92,6 +98,14 @@ internal fun DProperty.asJava(isTopLevel: Boolean = false, relocateToClass: Stri else extra ) +internal fun Visibility.asJava() = + when (this) { + is JavaVisibility -> this + is KotlinVisibility.Public, KotlinVisibility.Internal -> JavaVisibility.Public + is KotlinVisibility.Private -> JavaVisibility.Private + is KotlinVisibility.Protected -> JavaVisibility.Protected + } + internal fun DProperty.javaModifierFromSetter() = modifier.mapValues { when { @@ -169,6 +183,7 @@ internal fun DFunction.asJava(containingClassName: String): DFunction { sourceSets.map { it to JavaModifier.Empty }.toMap() else sourceSets.map { it to modifier.values.first() }.toMap(), parameters = listOfNotNull(receiver?.asJava()) + parameters.map { it.asJava() }, + visibility = visibility.map { (sourceSet, visibility) -> Pair(sourceSet, visibility.asJava()) }.toMap(), receiver = null ) // TODO static if toplevel } @@ -184,9 +199,7 @@ internal fun DClasslike.asJava(): DClasslike = when (this) { internal fun DClass.asJava(): DClass = copy( constructors = constructors.map { it.asJava(name) }, - functions = (functions + properties.map { it.getter } + properties.map { it.setter }).filterNotNull().map { - it.asJava(name) - }, + functions = functionsInJava(), properties = properties.map { it.asJava() }, classlikes = classlikes.map { it.asJava() }, generics = generics.map { it.asJava() }, @@ -196,6 +209,12 @@ internal fun DClass.asJava(): DClass = copy( else sourceSets.map { it to modifier.values.first() }.toMap() ) +internal fun DClass.functionsInJava(): List = + (properties.filter { it.jvmField() == null } + .flatMap { property -> listOfNotNull(property.getter, property.setter) } + functions).map { + it.asJava(name) + } + private fun DTypeParameter.asJava(): DTypeParameter = copy( variantTypeParameter = variantTypeParameter.withDri(dri.possiblyAsJava()), bounds = bounds.map { it.asJava() } diff --git a/plugins/kotlin-as-java/src/main/kotlin/jvmField.kt b/plugins/kotlin-as-java/src/main/kotlin/jvmField.kt new file mode 100644 index 00000000..9a66eb27 --- /dev/null +++ b/plugins/kotlin-as-java/src/main/kotlin/jvmField.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.utils.addToStdlib.firstNotNullResult + +internal fun WithExtraProperties.jvmField(): Annotations.Annotation? = + extra[Annotations]?.directAnnotations?.entries?.firstNotNullResult { (_, annotations) -> annotations.jvmFieldAnnotation() } + +internal fun List.jvmFieldAnnotation(): Annotations.Annotation? = + firstOrNull { it.dri.packageName == "kotlin.jvm" && it.dri.classNames == "JvmField" } \ No newline at end of file diff --git a/plugins/kotlin-as-java/src/main/kotlin/transformers/JvmNameProvider.kt b/plugins/kotlin-as-java/src/main/kotlin/transformers/JvmNameProvider.kt index 4e0ff7d7..31252ae0 100644 --- a/plugins/kotlin-as-java/src/main/kotlin/transformers/JvmNameProvider.kt +++ b/plugins/kotlin-as-java/src/main/kotlin/transformers/JvmNameProvider.kt @@ -1,12 +1,12 @@ package org.jetbrains.dokka.kotlinAsJava.transformers import org.jetbrains.dokka.kotlinAsJava.directlyAnnotatedJvmName +import org.jetbrains.dokka.kotlinAsJava.fileLevelJvmName import org.jetbrains.dokka.kotlinAsJava.jvmNameAsString import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.properties.WithExtraProperties -import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult -data class Name(val fqName: String){ +data class Name(val fqName: String) { val name = fqName.substringAfterLast(".") } @@ -16,12 +16,9 @@ class JvmNameProvider { ?: entry.name ?: throw IllegalStateException("Failed to provide a name for ${entry.javaClass.canonicalName}") - fun nameForSyntheticClass(entry: T): Name where T : WithSources, T : WithExtraProperties, T: Documentable { - val name = entry.extra[Annotations]?.let { - it.fileLevelAnnotations.entries.firstNotNullResult { (_, annotations) -> - annotations.jvmNameAnnotation()?.jvmNameAsString() - } - } ?: entry.sources.entries.first().value.path.split("/").last().split(".").first().capitalize() + "Kt" + fun nameForSyntheticClass(entry: T): Name where T : WithSources, T : WithExtraProperties, T : Documentable { + val name: String = (entry.fileLevelJvmName()?.params?.get("name") as? StringValue)?.value + ?: entry.sources.entries.first().value.path.split("/").last().split(".").first().capitalize() + "Kt" return Name("${entry.dri.packageName}.$name") } diff --git a/plugins/kotlin-as-java/src/test/kotlin/JvmFieldTest.kt b/plugins/kotlin-as-java/src/test/kotlin/JvmFieldTest.kt new file mode 100644 index 00000000..154083d7 --- /dev/null +++ b/plugins/kotlin-as-java/src/test/kotlin/JvmFieldTest.kt @@ -0,0 +1,81 @@ +package kotlinAsJavaPlugin + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.JavaVisibility +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class JvmFieldTest : BaseAbstractTest() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath += jvmStdlibPath!! + } + } + } + + @Test + fun `should keep properties annotated with JvmField as properties`() { + testInline( + """ + |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt + |package kotlinAsJavaPlugin + |class SampleClass(@JvmField val property: String, val otherProperty: String) + """.trimMargin(), + configuration, + ) { + documentablesTransformationStage = { module -> + val classLike = module.packages.flatMap { it.classlikes }.first() + assertNotNull(classLike.properties.firstOrNull { it.name == "property" }) + assertEquals( + listOf("getOtherProperty", "equals", "hashCode", "toString"), + classLike.functions.map { it.name }) + } + } + } + + @Test + fun `should work for top-level property`(){ + testInline( + """ + |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt + |package kotlinAsJavaPlugin + |@JvmField + |val property: String = TODO() + """.trimMargin(), + configuration, + ) { + documentablesTransformationStage = { module -> + val classLike = module.packages.flatMap { it.classlikes }.first() + assertNotNull(classLike.properties.firstOrNull { it.name == "property" }) + assertEquals( + emptyList(), + classLike.functions.map { it.name }) + } + } + } + + @Test + fun `properties without JvmName should be kept private`() { + testInline( + """ + |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt + |package kotlinAsJavaPlugin + |class SampleClass(val property: String) + """.trimMargin(), + configuration, + ) { + documentablesTransformationStage = { module -> + val classLike = module.packages.flatMap { it.classlikes }.first() + assertEquals(JavaVisibility.Private, classLike.properties.firstOrNull()?.visibility?.values?.first()) + assertNotNull(classLike.functions.firstOrNull { it.name.startsWith("get") }) + assertEquals( + JavaVisibility.Public, + classLike.functions.first { it.name.startsWith("get") }.visibility.values.first() + ) + } + } + } +} \ No newline at end of file -- cgit