aboutsummaryrefslogtreecommitdiff
path: root/plugins/kotlin-as-java
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/kotlin-as-java')
-rw-r--r--plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt82
-rw-r--r--plugins/kotlin-as-java/src/main/kotlin/jvmOverloads.kt14
-rw-r--r--plugins/kotlin-as-java/src/test/kotlin/JvmOverloadsTest.kt56
-rw-r--r--plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt33
4 files changed, 168 insertions, 17 deletions
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 6ccc9ecc..d45d39d9 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.hasJvmOverloads
import org.jetbrains.dokka.kotlinAsJava.jvmField
import org.jetbrains.dokka.kotlinAsJava.transformers.JvmNameProvider
import org.jetbrains.dokka.kotlinAsJava.transformers.withCallableName
@@ -43,7 +44,7 @@ internal fun DPackage.asJava(): DPackage {
.filterNot { it.isConst || it.isJvmField }
.flatMap { it.javaAccessors(relocateToClass = syntheticClassName.name) } +
nodes.filterIsInstance<DFunction>()
- .map { it.asJava(syntheticClassName.name) }), // TODO: methods are static and receiver is a param
+ .flatMap { it.asJava(syntheticClassName.name, true) }), // TODO: methods are static and receiver is a param
classlikes = emptyList(),
sources = emptyMap(),
expectPresentInSet = null,
@@ -169,12 +170,12 @@ internal fun DProperty.javaAccessors(isTopLevel: Boolean = false, relocateToClas
}
)
-
-internal fun DFunction.asJava(containingClassName: String): DFunction {
- val newName = when {
- isConstructor -> containingClassName
- else -> name
- }
+private fun DFunction.asJava(
+ containingClassName: String,
+ newName: String,
+ parameters: List<DParameter>,
+ isTopLevel: Boolean = false
+): DFunction {
return copy(
dri = dri.copy(classNames = containingClassName, callable = dri.callable?.copy(name = newName)),
name = newName,
@@ -184,8 +185,54 @@ internal fun DFunction.asJava(containingClassName: String): DFunction {
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
+ receiver = null,
+ extra = if (isTopLevel) {
+ extra + extra.mergeAdditionalModifiers(
+ sourceSets.map {
+ it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)
+ }.toMap()
+ )
+ } else {
+ extra
+ }
+ )
+}
+
+private fun DFunction.withJvmOverloads(
+ containingClassName: String,
+ newName: String,
+ isTopLevel: Boolean = false
+): List<DFunction>? {
+ val (paramsWithDefaults, paramsWithoutDefaults) = parameters
+ .withIndex()
+ .partition { (_, p) -> p.extra[DefaultValue] != null }
+ return paramsWithDefaults
+ .runningFold(paramsWithoutDefaults) { acc, param -> (acc + param) }
+ .map { params ->
+ asJava(
+ containingClassName,
+ newName,
+ params
+ .sortedBy(IndexedValue<DParameter>::index)
+ .map { it.value },
+ isTopLevel
+ )
+ }
+ .reversed()
+ .takeIf { it.isNotEmpty() }
+}
+
+internal fun DFunction.asJava(containingClassName: String, isTopLevel: Boolean = false): List<DFunction> {
+ val newName = when {
+ isConstructor -> containingClassName
+ else -> name
+ }
+ val baseFunction = asJava(containingClassName, newName, parameters, isTopLevel)
+ return if (hasJvmOverloads()) {
+ withJvmOverloads(containingClassName, newName, isTopLevel) ?: listOf(baseFunction)
+ } else {
+ listOf(baseFunction)
+ }
}
internal fun DClasslike.asJava(): DClasslike = when (this) {
@@ -198,7 +245,7 @@ internal fun DClasslike.asJava(): DClasslike = when (this) {
}
internal fun DClass.asJava(): DClass = copy(
- constructors = constructors.map { it.asJava(dri.classNames ?: name) }, // name may not always be valid here, however classNames should always be not null
+ constructors = constructors.flatMap { it.asJava(dri.classNames ?: name) }, // name may not always be valid here, however classNames should always be not null
functions = functionsInJava(),
properties = properties.map { it.asJava() },
classlikes = classlikes.map { it.asJava() },
@@ -211,9 +258,10 @@ internal fun DClass.asJava(): DClass = copy(
internal fun DClass.functionsInJava(): List<DFunction> =
(properties.filter { it.jvmField() == null }
- .flatMap { property -> listOfNotNull(property.getter, property.setter) } + functions).map {
- it.asJava(dri.classNames ?: name)
- }
+ .flatMap { property -> listOfNotNull(property.getter, property.setter) } + functions)
+ .flatMap {
+ it.asJava(dri.classNames ?: name)
+ }
private fun DTypeParameter.asJava(): DTypeParameter = copy(
variantTypeParameter = variantTypeParameter.withDri(dri.possiblyAsJava()),
@@ -251,8 +299,8 @@ private fun Bound.asJava(): Bound = when (this) {
}
internal fun DEnum.asJava(): DEnum = copy(
- constructors = constructors.map { it.asJava(dri.classNames ?: name) },
- functions = (functions + properties.map { it.getter } + properties.map { it.setter }).filterNotNull().map {
+ constructors = constructors.flatMap { it.asJava(dri.classNames ?: name) },
+ functions = (functions + properties.map { it.getter } + properties.map { it.setter }).filterNotNull().flatMap {
it.asJava(dri.classNames ?: name)
},
properties = properties.map { it.asJava() },
@@ -264,7 +312,7 @@ internal fun DEnum.asJava(): DEnum = copy(
internal fun DObject.asJava(): DObject = copy(
functions = (functions + properties.map { it.getter } + properties.map { it.setter })
.filterNotNull()
- .map { it.asJava(dri.classNames ?: name.orEmpty()) },
+ .flatMap { it.asJava(dri.classNames ?: name.orEmpty()) },
properties = properties.map { it.asJava() } +
DProperty(
name = "INSTANCE",
@@ -294,7 +342,7 @@ internal fun DObject.asJava(): DObject = copy(
internal fun DInterface.asJava(): DInterface = copy(
functions = (functions + properties.map { it.getter } + properties.map { it.setter })
.filterNotNull()
- .map { it.asJava(dri.classNames ?: name) },
+ .flatMap { it.asJava(dri.classNames ?: name) },
properties = emptyList(),
classlikes = classlikes.map { it.asJava() }, // TODO: public static final class DefaultImpls with impls for methods
generics = generics.map { it.asJava() },
diff --git a/plugins/kotlin-as-java/src/main/kotlin/jvmOverloads.kt b/plugins/kotlin-as-java/src/main/kotlin/jvmOverloads.kt
new file mode 100644
index 00000000..d8e4f67c
--- /dev/null
+++ b/plugins/kotlin-as-java/src/main/kotlin/jvmOverloads.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.dokka.kotlinAsJava
+
+import org.jetbrains.dokka.model.Annotations
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+
+internal fun WithExtraProperties<out Documentable>.hasJvmOverloads(): Boolean {
+ return extra[Annotations]
+ ?.directAnnotations
+ ?.entries
+ ?.any { (_, annotations) ->
+ annotations.any { it.dri.packageName == "kotlin.jvm" && it.dri.classNames == "JvmOverloads" }
+ } == true
+} \ No newline at end of file
diff --git a/plugins/kotlin-as-java/src/test/kotlin/JvmOverloadsTest.kt b/plugins/kotlin-as-java/src/test/kotlin/JvmOverloadsTest.kt
new file mode 100644
index 00000000..79619215
--- /dev/null
+++ b/plugins/kotlin-as-java/src/test/kotlin/JvmOverloadsTest.kt
@@ -0,0 +1,56 @@
+package kotlinAsJavaPlugin
+
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+
+class JvmOverloadsTest : BaseAbstractTest() {
+ private val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath += jvmStdlibPath!!
+ }
+ }
+ }
+
+ @Test
+ fun `should generate multiple functions`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |@JvmOverloads
+ |fun sample(a: Int = 0, b: String, c: Int = 0): String = ""
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val functions = module.packages.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(3, functions.size)
+ assertEquals(3, functions[0].parameters.size)
+ assertEquals(2, functions[1].parameters.size)
+ assertEquals(1, functions[2].parameters.size)
+ }
+ }
+ }
+
+ @Test
+ fun `should do nothing if there is no default values`() {
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/sample.kt
+ |package kotlinAsJavaPlugin
+ |@JvmOverloads
+ |fun sample(a: Int, b: String, c: Int): String = ""
+ """.trimMargin(),
+ configuration,
+ ) {
+ documentablesTransformationStage = { module ->
+ val functions = module.packages.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(1, functions.size)
+ assertEquals(3, functions[0].parameters.size)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt b/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt
index 8e7b798a..760bfcff 100644
--- a/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt
+++ b/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt
@@ -404,6 +404,39 @@ class KotlinAsJavaPluginTest : BaseAbstractTest() {
}
}
}
+
+ @Test
+ fun `function in top level`() {
+ val writerPlugin = TestOutputWriterPlugin()
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ externalDocumentationLinks = listOf(
+ DokkaConfiguration.ExternalDocumentationLink.jdk(8),
+ stdlibExternalDocumentationLink
+ )
+ }
+ }
+ }
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/Test.kt
+ |package kotlinAsJavaPlugin
+ |
+ |fun sample(a: Int) = ""
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin),
+ cleanupOutput = true
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/kotlinAsJavaPlugin/-test-kt/sample.html").signature().first().match(
+ "final static ", A("String"), A("sample"), "(", A("Integer"), "a)", Span()
+ )
+ }
+ }
+ }
}
private val ContentNode.mainContents: List<ContentNode>