diff options
Diffstat (limited to 'plugins')
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> |