diff options
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> | 
