aboutsummaryrefslogtreecommitdiff
path: root/plugins/kotlin-as-java
diff options
context:
space:
mode:
authorPaweł Marks <pmarks@virtuslab.com>2020-07-17 16:36:09 +0200
committerPaweł Marks <pmarks@virtuslab.com>2020-07-17 16:36:09 +0200
commit6996b1135f61c7d2cb60b0652c6a2691dda31990 (patch)
treed568096c25e31c28d14d518a63458b5a7526b896 /plugins/kotlin-as-java
parentde56cab76f556e5b4af0b8c8cb08d8b482b86d0a (diff)
parent1c3530dcbb50c347f80bef694829dbefe89eca77 (diff)
downloaddokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.tar.gz
dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.tar.bz2
dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.zip
Merge branch 'dev-0.11.0'
Diffstat (limited to 'plugins/kotlin-as-java')
-rw-r--r--plugins/kotlin-as-java/build.gradle.kts12
-rw-r--r--plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPlugin.kt19
-rw-r--r--plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt298
-rw-r--r--plugins/kotlin-as-java/src/main/kotlin/signatures/JavaSignatureProvider.kt175
-rw-r--r--plugins/kotlin-as-java/src/main/kotlin/signatures/JavaSignatureUtils.kt35
-rw-r--r--plugins/kotlin-as-java/src/main/kotlin/transformers/KotlinAsJavaDocumentableTransformer.kt11
-rw-r--r--plugins/kotlin-as-java/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin1
-rw-r--r--plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt298
8 files changed, 849 insertions, 0 deletions
diff --git a/plugins/kotlin-as-java/build.gradle.kts b/plugins/kotlin-as-java/build.gradle.kts
new file mode 100644
index 00000000..eda6114f
--- /dev/null
+++ b/plugins/kotlin-as-java/build.gradle.kts
@@ -0,0 +1,12 @@
+import org.jetbrains.registerDokkaArtifactPublication
+
+dependencies {
+ implementation(project(":plugins:base"))
+ testImplementation(project(":plugins:base"))
+ testImplementation(project(":plugins:base:test-utils"))
+ testImplementation(project(":test-tools"))
+}
+
+registerDokkaArtifactPublication("kotlinAsJavaPlugin") {
+ artifactId = "kotlin-as-java-plugin"
+}
diff --git a/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPlugin.kt b/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPlugin.kt
new file mode 100644
index 00000000..5f06852e
--- /dev/null
+++ b/plugins/kotlin-as-java/src/main/kotlin/KotlinAsJavaPlugin.kt
@@ -0,0 +1,19 @@
+package org.jetbrains.dokka.kotlinAsJava
+
+import org.jetbrains.dokka.CoreExtensions
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.kotlinAsJava.signatures.JavaSignatureProvider
+import org.jetbrains.dokka.kotlinAsJava.transformers.KotlinAsJavaDocumentableTransformer
+import org.jetbrains.dokka.plugability.DokkaPlugin
+
+class KotlinAsJavaPlugin : DokkaPlugin() {
+ val kotlinAsJavaDocumentableToPageTranslator by extending {
+ CoreExtensions.documentableTransformer with KotlinAsJavaDocumentableTransformer()
+ }
+ val javaSignatureProvider by extending {
+ val dokkaBasePlugin = plugin<DokkaBase>()
+ dokkaBasePlugin.signatureProvider providing { ctx ->
+ JavaSignatureProvider(ctx.single(dokkaBasePlugin.commentsToContentConverter), ctx.logger)
+ } override dokkaBasePlugin.kotlinSignatureProvider
+ }
+} \ No newline at end of file
diff --git a/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt
new file mode 100644
index 00000000..e66266a2
--- /dev/null
+++ b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt
@@ -0,0 +1,298 @@
+package org.jetbrains.dokka.kotlinAsJava.converters
+
+import org.jetbrains.dokka.links.*
+import org.jetbrains.dokka.links.Callable
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.DAnnotation
+import org.jetbrains.dokka.model.DEnum
+import org.jetbrains.dokka.model.DFunction
+import org.jetbrains.dokka.model.Nullable
+import org.jetbrains.dokka.model.TypeConstructor
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
+import java.lang.IllegalStateException
+
+private fun <T : WithExpectActual> List<T>.groupedByLocation() =
+ map { it.sources to it }
+ .groupBy({ (location, _) ->
+ location.let {
+ it.entries.first().value.path.split("/").last().split(".").first() + "Kt"
+ } // TODO: first() does not look reasonable
+ }) { it.second }
+
+internal fun DPackage.asJava(): DPackage {
+ @Suppress("UNCHECKED_CAST")
+ val syntheticClasses = ((properties + functions) as List<WithExpectActual>)
+ .groupedByLocation()
+ .map { (syntheticClassName, nodes) ->
+ DClass(
+ dri = dri.withClass(syntheticClassName),
+ name = syntheticClassName,
+ properties = nodes.filterIsInstance<DProperty>().map { it.asJava() },
+ constructors = emptyList(),
+ functions = (
+ nodes.filterIsInstance<DProperty>()
+ .flatMap { it.javaAccessors() } +
+ nodes.filterIsInstance<DFunction>()
+ .map { it.asJava(syntheticClassName) }), // TODO: methods are static and receiver is a param
+ classlikes = emptyList(),
+ sources = emptyMap(),
+ expectPresentInSet = null,
+ visibility = sourceSets.map {
+ it to JavaVisibility.Public
+ }.toMap(),
+ companion = null,
+ generics = emptyList(),
+ supertypes = emptyMap(),
+ documentation = emptyMap(),
+ modifier = sourceSets.map { it to JavaModifier.Final }.toMap(),
+ sourceSets = sourceSets,
+ extra = PropertyContainer.empty()
+ )
+ }
+
+ return copy(
+ functions = emptyList(),
+ properties = emptyList(),
+ classlikes = classlikes.map { it.asJava() } + syntheticClasses
+ )
+}
+
+internal fun DProperty.asJava(isTopLevel: Boolean = false, relocateToClass: String? = null) =
+ copy(
+ dri = if (relocateToClass.isNullOrBlank()) {
+ dri
+ } else {
+ dri.withClass(relocateToClass)
+ },
+ modifier = javaModifierFromSetter(),
+ visibility = visibility.mapValues { it.value.propertyVisibilityAsJava() },
+ 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.map {
+ it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)
+ }.toMap()
+ )
+ else extra
+ )
+
+internal fun DProperty.javaModifierFromSetter() =
+ modifier.mapValues {
+ when {
+ it.value is JavaModifier -> it.value
+ setter == null -> JavaModifier.Final
+ else -> JavaModifier.Empty
+ }
+ }
+
+internal fun DProperty.javaAccessors(isTopLevel: Boolean = false, relocateToClass: String? = null): List<DFunction> =
+ listOfNotNull(
+ getter?.copy(
+ dri = if (relocateToClass.isNullOrBlank()) {
+ dri
+ } else {
+ dri.withClass(relocateToClass)
+ },
+ name = "get" + name.capitalize(),
+ modifier = javaModifierFromSetter(),
+ visibility = visibility.mapValues { JavaVisibility.Public },
+ type = type.asJava(), // TODO: check
+ extra = if (isTopLevel) getter!!.extra +
+ getter!!.extra.mergeAdditionalModifiers(
+ sourceSets.map {
+ it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)
+ }.toMap()
+ )
+ else getter!!.extra
+ ),
+ setter?.copy(
+ dri = if (relocateToClass.isNullOrBlank()) {
+ dri
+ } else {
+ dri.withClass(relocateToClass)
+ },
+ name = "set" + name.capitalize(),
+ modifier = javaModifierFromSetter(),
+ visibility = visibility.mapValues { JavaVisibility.Public },
+ type = type.asJava(), // TODO: check
+ extra = if (isTopLevel) setter!!.extra + setter!!.extra.mergeAdditionalModifiers(
+ sourceSets.map {
+ it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)
+ }.toMap()
+ )
+ else setter!!.extra
+ )
+ )
+
+
+internal fun DFunction.asJava(containingClassName: String): DFunction {
+ val newName = when {
+ isConstructor -> containingClassName
+ else -> name
+ }
+ return copy(
+// dri = dri.copy(callable = dri.callable?.asJava()),
+ name = newName,
+ type = type.asJava(),
+ modifier = if (modifier.all { (_, v) -> v is KotlinModifier.Final } && isConstructor)
+ sourceSets.map { it to JavaModifier.Empty }.toMap()
+ else sourceSets.map { it to modifier.values.first() }.toMap(),
+ parameters = listOfNotNull(receiver?.asJava()) + parameters.map { it.asJava() },
+ receiver = null
+ ) // TODO static if toplevel
+}
+
+internal fun DClasslike.asJava(): DClasslike = when (this) {
+ is DClass -> asJava()
+ is DEnum -> asJava()
+ is DAnnotation -> asJava()
+ is DObject -> asJava()
+ is DInterface -> asJava()
+ else -> throw IllegalArgumentException("$this shouldn't be here")
+}
+
+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)
+ },
+ properties = properties.map { it.asJava() },
+ classlikes = classlikes.map { it.asJava() },
+ generics = generics.map { it.asJava() },
+ supertypes = supertypes.mapValues { it.value.map { it.asJava() } },
+ modifier = if (modifier.all { (_, v) -> v is KotlinModifier.Empty }) sourceSets.map { it to JavaModifier.Final }
+ .toMap()
+ else sourceSets.map { it to modifier.values.first() }.toMap()
+)
+
+private fun DTypeParameter.asJava(): DTypeParameter = copy(
+ dri = dri.possiblyAsJava(),
+ bounds = bounds.map { it.asJava() }
+)
+
+private fun Bound.asJava(): Bound = when (this) {
+ is TypeConstructor -> copy(
+ dri = dri.possiblyAsJava()
+ )
+ is Nullable -> copy(
+ inner = inner.asJava()
+ )
+ else -> this
+}
+
+internal fun DEnum.asJava(): DEnum = copy(
+ constructors = constructors.map { it.asJava(name) },
+ functions = (functions + properties.map { it.getter } + properties.map { it.setter }).filterNotNull().map {
+ it.asJava(name)
+ },
+ properties = properties.map { it.asJava() },
+ classlikes = classlikes.map { it.asJava() },
+ supertypes = supertypes.mapValues { it.value.map { it.asJava() } }
+// , entries = entries.map { it.asJava() }
+)
+
+internal fun DObject.asJava(): DObject = copy(
+ functions = (functions + properties.map { it.getter } + properties.map { it.setter })
+ .filterNotNull()
+ .map { it.asJava(name.orEmpty()) },
+ properties = properties.map { it.asJava() } +
+ DProperty(
+ name = "INSTANCE",
+ modifier = sourceSets.map { it to JavaModifier.Final }.toMap(),
+ dri = dri.copy(callable = Callable("INSTANCE", null, emptyList())),
+ documentation = emptyMap(),
+ sources = emptyMap(),
+ visibility = sourceSets.map {
+ it to JavaVisibility.Public
+ }.toMap(),
+ type = TypeConstructor(dri, emptyList()),
+ setter = null,
+ getter = null,
+ sourceSets = sourceSets,
+ receiver = null,
+ generics = emptyList(),
+ expectPresentInSet = expectPresentInSet,
+ extra = PropertyContainer.withAll(sourceSets.map {
+ mapOf(it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)).toAdditionalModifiers()
+ })
+ ),
+ classlikes = classlikes.map { it.asJava() },
+ supertypes = supertypes.mapValues { it.value.map { it.asJava() } }
+)
+
+internal fun DInterface.asJava(): DInterface = copy(
+ functions = (functions + properties.map { it.getter } + properties.map { it.setter })
+ .filterNotNull()
+ .map { it.asJava(name) },
+ properties = emptyList(),
+ classlikes = classlikes.map { it.asJava() }, // TODO: public static final class DefaultImpls with impls for methods
+ generics = generics.map { it.asJava() },
+ supertypes = supertypes.mapValues { it.value.map { it.asJava() } }
+)
+
+internal fun DAnnotation.asJava(): DAnnotation = copy(
+ properties = properties.map { it.asJava() },
+ constructors = emptyList(),
+ classlikes = classlikes.map { it.asJava() }
+) // TODO investigate if annotation class can have methods and properties not from constructor
+
+internal fun DParameter.asJava(): DParameter = copy(
+ type = type.asJava(),
+ name = if (name.isNullOrBlank()) "\$self" else name
+)
+
+internal fun Visibility.propertyVisibilityAsJava(): Visibility =
+ if(this is JavaVisibility) this
+ else JavaVisibility.Private
+
+internal fun String.getAsPrimitive(): JvmPrimitiveType? = org.jetbrains.kotlin.builtins.PrimitiveType.values()
+ .find { it.typeFqName.asString() == this }
+ ?.let { JvmPrimitiveType.get(it) }
+
+private fun DRI.partialFqName() = packageName?.let { "$it." } + classNames
+private fun DRI.possiblyAsJava() = this.partialFqName().mapToJava()?.toDRI(this) ?: this
+
+private fun String.mapToJava(): ClassId? =
+ JavaToKotlinClassMap.mapKotlinToJava(FqName(this).toUnsafe())
+
+internal fun ClassId.toDRI(dri: DRI?): DRI = DRI(
+ packageName = packageFqName.asString(),
+ classNames = classNames(),
+ callable = dri?.callable,//?.asJava(), TODO: check this
+ extra = null,
+ target = PointingToDeclaration
+)
+
+internal fun DriWithKind.asJava(): DriWithKind =
+ DriWithKind(
+ dri = dri.possiblyAsJava(),
+ kind = kind.asJava()
+ )
+
+internal fun ClassKind.asJava(): ClassKind {
+ return when(this){
+ is JavaClassKindTypes -> this
+ KotlinClassKindTypes.CLASS -> JavaClassKindTypes.CLASS
+ KotlinClassKindTypes.INTERFACE -> JavaClassKindTypes.INTERFACE
+ KotlinClassKindTypes.ENUM_CLASS -> JavaClassKindTypes.ENUM_CLASS
+ KotlinClassKindTypes.ENUM_ENTRY -> JavaClassKindTypes.ENUM_ENTRY
+ KotlinClassKindTypes.ANNOTATION_CLASS -> JavaClassKindTypes.ANNOTATION_CLASS
+ KotlinClassKindTypes.OBJECT -> JavaClassKindTypes.CLASS
+ else -> throw IllegalStateException("Non exchaustive match while trying to convert $this to Java")
+ }
+}
+
+private fun PropertyContainer<out Documentable>.mergeAdditionalModifiers(second: SourceSetDependent<Set<ExtraModifiers>>) =
+ this[AdditionalModifiers]?.squash(AdditionalModifiers(second)) ?: AdditionalModifiers(second)
+
+private fun AdditionalModifiers.squash(second: AdditionalModifiers) =
+ AdditionalModifiers(content + second.content)
+
+internal fun ClassId.classNames(): String =
+ shortClassName.identifier + (outerClassId?.classNames()?.let { ".$it" } ?: "") \ No newline at end of file
diff --git a/plugins/kotlin-as-java/src/main/kotlin/signatures/JavaSignatureProvider.kt b/plugins/kotlin-as-java/src/main/kotlin/signatures/JavaSignatureProvider.kt
new file mode 100644
index 00000000..158dda22
--- /dev/null
+++ b/plugins/kotlin-as-java/src/main/kotlin/signatures/JavaSignatureProvider.kt
@@ -0,0 +1,175 @@
+package org.jetbrains.dokka.kotlinAsJava.signatures
+
+import org.jetbrains.dokka.base.signatures.JvmSignatureUtils
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.sureClassNames
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+import org.jetbrains.dokka.pages.ContentKind
+import org.jetbrains.dokka.pages.ContentNode
+import org.jetbrains.dokka.pages.TextStyle
+import org.jetbrains.dokka.utilities.DokkaLogger
+import kotlin.text.Typography.nbsp
+
+class JavaSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLogger) : SignatureProvider,
+ JvmSignatureUtils by JavaSignatureUtils {
+ private val contentBuilder = PageContentBuilder(ctcc, this, logger)
+
+ private val ignoredVisibilities = setOf(JavaVisibility.Default)
+
+ private val ignoredModifiers =
+ setOf(KotlinModifier.Open, JavaModifier.Empty, KotlinModifier.Empty, KotlinModifier.Sealed)
+
+ override fun signature(documentable: Documentable): List<ContentNode> = when (documentable) {
+ is DFunction -> signature(documentable)
+ is DProperty -> signature(documentable)
+ is DClasslike -> signature(documentable)
+ is DEnumEntry -> signature(documentable)
+ is DTypeParameter -> signature(documentable)
+ else -> throw NotImplementedError(
+ "Cannot generate signature for ${documentable::class.qualifiedName} ${documentable.name}"
+ )
+ }
+
+ private fun signature(e: DEnumEntry) =
+ e.sourceSets.map {
+ contentBuilder.contentFor(
+ e,
+ ContentKind.Symbol,
+ setOf(TextStyle.Monospace) + e.stylesIfDeprecated(it),
+ sourceSets = setOf(it)
+ ) {
+ link(e.name, e.dri)
+ }
+ }
+
+ private fun signature(c: DClasslike) =
+ c.sourceSets.map {
+ contentBuilder.contentFor(
+ c,
+ ContentKind.Symbol,
+ setOf(TextStyle.Monospace) + ((c as? WithExtraProperties<out Documentable>)?.stylesIfDeprecated(it)
+ ?: emptySet()),
+ sourceSets = setOf(it)
+ ) {
+ text(c.visibility[it]?.takeIf { it !in ignoredVisibilities }?.name?.plus(" ") ?: "")
+
+ if (c is DClass) {
+ text(c.modifier[it]?.takeIf { it !in ignoredModifiers }?.name?.plus(" ") ?: "")
+ text(c.modifiers()[it]?.toSignatureString() ?: "")
+ }
+
+ when (c) {
+ is DClass -> text("class ")
+ is DInterface -> text("interface ")
+ is DEnum -> text("enum ")
+ is DObject -> text("class ")
+ is DAnnotation -> text("@interface ")
+ }
+ link(c.name!!, c.dri)
+ if (c is WithGenerics) {
+ list(c.generics, prefix = "<", suffix = ">") {
+ +buildSignature(it)
+ }
+ }
+ if (c is WithSupertypes) {
+ c.supertypes.map { (p, dris) ->
+ val (classes, interfaces) = dris.partition { it.kind == JavaClassKindTypes.CLASS }
+ list(classes, prefix = " extends ", sourceSets = setOf(p)) {
+ link(it.dri.sureClassNames, it.dri, sourceSets = setOf(p))
+ }
+ list(interfaces, prefix = " implements ", sourceSets = setOf(p)){
+ link(it.dri.sureClassNames, it.dri, sourceSets = setOf(p))
+ }
+ }
+ }
+ }
+ }
+
+ private fun signature(p: DProperty) =
+ p.sourceSets.map {
+ contentBuilder.contentFor(
+ p,
+ ContentKind.Symbol,
+ setOf(TextStyle.Monospace, TextStyle.Block) + p.stylesIfDeprecated(it),
+ sourceSets = setOf(it)
+ ) {
+ annotationsBlock(p)
+ text(p.visibility[it]?.takeIf { it !in ignoredVisibilities }?.name?.plus(" ") ?: "")
+ text(p.modifier[it]?.name + " ")
+ text(p.modifiers()[it]?.toSignatureString() ?: "")
+ signatureForProjection(p.type)
+ text(nbsp.toString())
+ link(p.name, p.dri)
+ }
+ }
+
+ private fun signature(f: DFunction) =
+ f.sourceSets.map {
+ contentBuilder.contentFor(
+ f,
+ ContentKind.Symbol,
+ setOf(TextStyle.Monospace, TextStyle.Block) + f.stylesIfDeprecated(it),
+ sourceSets = setOf(it)
+ ) {
+ annotationsBlock(f)
+ text(f.modifier[it]?.takeIf { it !in ignoredModifiers }?.name?.plus(" ") ?: "")
+ text(f.modifiers()[it]?.toSignatureString() ?: "")
+ val returnType = f.type
+ signatureForProjection(returnType)
+ text(nbsp.toString())
+ link(f.name, f.dri)
+ list(f.generics, prefix = "<", suffix = ">") {
+ +buildSignature(it)
+ }
+ text("(")
+ list(f.parameters) {
+ annotationsInline(it)
+ text(it.modifiers()[it]?.toSignatureString() ?: "")
+ signatureForProjection(it.type)
+ text(nbsp.toString())
+ link(it.name!!, it.dri)
+ }
+ text(")")
+ }
+ }
+
+ private fun signature(t: DTypeParameter) =
+ t.sourceSets.map {
+ contentBuilder.contentFor(t, styles = t.stylesIfDeprecated(it), sourceSets = setOf(it)) {
+ text(t.name.substringAfterLast("."))
+ list(t.bounds, prefix = " extends ") {
+ signatureForProjection(it)
+ }
+ }
+
+ }
+
+ private fun PageContentBuilder.DocumentableContentBuilder.signatureForProjection(p: Projection): Unit = when (p) {
+ is OtherParameter -> link(p.name, p.declarationDRI)
+
+ is TypeConstructor -> group(styles = emptySet()) {
+ link(p.dri.classNames.orEmpty(), p.dri)
+ list(p.projections, prefix = "<", suffix = ">") {
+ signatureForProjection(it)
+ }
+ }
+
+ is Variance -> group(styles = emptySet()) {
+ text(p.kind.toString() + " ") // TODO: "super" && "extends"
+ signatureForProjection(p.inner)
+ }
+
+ is Star -> text("?")
+
+ is Nullable -> signatureForProjection(p.inner)
+
+ is JavaObject, is Dynamic -> link("Object", DRI("java.lang", "Object"))
+ is Void -> text("void")
+ is PrimitiveJavaType -> text(p.name)
+ is UnresolvedBound -> text(p.name)
+ }
+} \ No newline at end of file
diff --git a/plugins/kotlin-as-java/src/main/kotlin/signatures/JavaSignatureUtils.kt b/plugins/kotlin-as-java/src/main/kotlin/signatures/JavaSignatureUtils.kt
new file mode 100644
index 00000000..ecb97617
--- /dev/null
+++ b/plugins/kotlin-as-java/src/main/kotlin/signatures/JavaSignatureUtils.kt
@@ -0,0 +1,35 @@
+package org.jetbrains.dokka.kotlinAsJava.signatures
+
+import org.jetbrains.dokka.base.signatures.All
+import org.jetbrains.dokka.base.signatures.JvmSignatureUtils
+import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+
+object JavaSignatureUtils : JvmSignatureUtils {
+
+ private val ignoredAnnotations = setOf(
+ Annotations.Annotation(DRI("kotlin.jvm", "Transient"), emptyMap()),
+ Annotations.Annotation(DRI("kotlin.jvm", "Volatile"), emptyMap()),
+ Annotations.Annotation(DRI("kotlin.jvm", "Transitive"), emptyMap()),
+ Annotations.Annotation(DRI("kotlin.jvm", "Strictfp"), emptyMap()),
+ Annotations.Annotation(DRI("kotlin.jvm", "JvmStatic"), emptyMap())
+ )
+
+ private val strategy = All
+ private val listBrackets = Pair('{', '}')
+ private val classExtension = ".class"
+
+ override fun PageContentBuilder.DocumentableContentBuilder.annotationsBlock(d: Documentable) =
+ annotationsBlockWithIgnored(d, ignoredAnnotations, strategy, listBrackets, classExtension)
+
+ override fun PageContentBuilder.DocumentableContentBuilder.annotationsInline(d: Documentable) =
+ annotationsInlineWithIgnored(d, ignoredAnnotations, strategy, listBrackets, classExtension)
+
+ override fun <T : Documentable> WithExtraProperties<T>.modifiers() =
+ extra[AdditionalModifiers]?.content?.entries?.map {
+ it.key to it.value.filterIsInstance<ExtraModifiers.JavaOnlyModifiers>().toSet()
+ }?.toMap() ?: emptyMap()
+
+} \ No newline at end of file
diff --git a/plugins/kotlin-as-java/src/main/kotlin/transformers/KotlinAsJavaDocumentableTransformer.kt b/plugins/kotlin-as-java/src/main/kotlin/transformers/KotlinAsJavaDocumentableTransformer.kt
new file mode 100644
index 00000000..8b07670f
--- /dev/null
+++ b/plugins/kotlin-as-java/src/main/kotlin/transformers/KotlinAsJavaDocumentableTransformer.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.dokka.kotlinAsJava.transformers
+
+import org.jetbrains.dokka.kotlinAsJava.converters.asJava
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer
+
+class KotlinAsJavaDocumentableTransformer : DocumentableTransformer {
+ override fun invoke(original: DModule, context: DokkaContext): DModule =
+ original.copy(packages = original.packages.map { it.asJava() })
+}
diff --git a/plugins/kotlin-as-java/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/plugins/kotlin-as-java/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
new file mode 100644
index 00000000..8ff3df82
--- /dev/null
+++ b/plugins/kotlin-as-java/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
@@ -0,0 +1 @@
+org.jetbrains.dokka.kotlinAsJava.KotlinAsJavaPlugin
diff --git a/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt b/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt
new file mode 100644
index 00000000..af66a48e
--- /dev/null
+++ b/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt
@@ -0,0 +1,298 @@
+package kotlinAsJavaPlugin
+
+import org.jetbrains.dokka.model.dfs
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest
+import org.jetbrains.kotlin.utils.addToStdlib.cast
+import org.junit.jupiter.api.Test
+import matchers.content.*
+
+class KotlinAsJavaPluginTest : AbstractCoreTest() {
+
+ @Test
+ fun topLevelTest() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/Test.kt
+ |package kotlinAsJavaPlugin
+ |
+ |object TestObj {}
+ |
+ |fun testFL(l: List<String>) = l
+ |fun testF() {}
+ |fun testF2(i: Int) = i
+ |fun testF3(to: TestObj) = to
+ |fun <T : Char> testF4(t: T) = listOf(t)
+ |val testV = 1
+ """,
+ configuration,
+ cleanupOutput = true
+ ) {
+ pagesGenerationStage = { root ->
+ val content = (root.children.single().children.first { it.name == "TestKt" } as ContentPage).content
+
+ val children = content.mainContents.first().cast<ContentGroup>()
+ .children.filterIsInstance<ContentTable>()
+ .filter { it.children.isNotEmpty() }
+
+ children.assertCount(2)
+ }
+ }
+ }
+
+ @Test
+ fun topLevelWithClassTest() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/Test.kt
+ |package kotlinAsJavaPlugin
+ |
+ |class Test {
+ | fun testFC() {}
+ | val testVC = 1
+ |}
+ |
+ |fun testF(i: Int) = i
+ |val testV = 1
+ """,
+ configuration,
+ cleanupOutput = true
+ ) {
+ pagesGenerationStage = { root ->
+ val contentList = root.children
+ .flatMap { it.children<ContentPage>() }
+ .map { it.content }
+
+ val children = contentList.flatMap { content ->
+ content.mainContents.first().cast<ContentGroup>().children
+ .filterIsInstance<ContentTable>()
+ .filter { it.children.isNotEmpty() }
+ }.filterNot { it.toString().contains("<init>") }
+
+ children.assertCount(4)
+ }
+ }
+ }
+
+ @Test
+ fun kotlinAndJavaTest() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/Test.kt
+ |package kotlinAsJavaPlugin
+ |
+ |fun testF(i: Int) = i
+ |/src/main/kotlin/kotlinAsJavaPlugin/TestJ.java
+ |package kotlinAsJavaPlugin
+ |
+ |class TestJ {
+ | int testF(int i) { return i; }
+ |}
+ """,
+ configuration,
+ cleanupOutput = true
+ ) {
+ pagesGenerationStage = { root ->
+ val classes = root.children.first().children.associateBy { it.name }
+ classes.values.assertCount(2, "Class count: ")
+
+ classes["TestKt"].let {
+ it?.children.orEmpty().assertCount(1, "(Kotlin) TestKt members: ")
+ it!!.children.first()
+ .let { assert(it.name == "testF") { "(Kotlin) Expected method name: testF, got: ${it.name}" } }
+ }
+
+ classes["TestJ"].let {
+ it?.children.orEmpty().assertCount(1, "(Java) TestJ members: ")
+ it!!.children.first()
+ .let { assert(it.name == "testF") { "(Java) Expected method name: testF, got: ${it.name}" } }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `public kotlin properties should have a getter with same visibilities`(){
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/Test.kt
+ |package kotlinAsJavaPlugin
+ |
+ |class Test {
+ | public val publicProperty: String = ""
+ |}
+ """,
+ configuration,
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { rootPageNode ->
+ val propertyGetter = rootPageNode.dfs { it is MemberPageNode && it.name == "getPublicProperty" } as? MemberPageNode
+ assert(propertyGetter != null)
+ propertyGetter!!.content.assertNode {
+ group {
+ header(1) {
+ +"getPublicProperty"
+ }
+ }
+ divergentGroup {
+ divergentInstance {
+ divergent {
+ group {
+ +"final"
+ group {
+ link {
+ +" String"
+ }
+ }
+ link {
+ +"getPublicProperty"
+ }
+ +"()"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `java properties should keep its modifiers`(){
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/TestJ.java
+ |package kotlinAsJavaPlugin
+ |
+ |class TestJ {
+ | public Int publicProperty = 1;
+ |}
+ """,
+ configuration,
+ cleanupOutput = true
+ ) {
+ pagesGenerationStage = { root ->
+ val testClass = root.dfs { it.name == "TestJ" } as? ClasslikePageNode
+ assert(testClass != null)
+ (testClass!!.content as ContentGroup).children.last().assertNode {
+ group {
+ header(2){
+ +"Properties"
+ }
+ table {
+ group {
+ link {
+ +"publicProperty"
+ }
+ platformHinted {
+ group {
+ +"public Int"
+ link {
+ +"publicProperty"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `koltin interfaces and classes should be split to extends and implements`(){
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/Test.kt
+ |package kotlinAsJavaPlugin
+ |
+ | open class A { }
+ | interface B
+ | class C : A(), B
+ """,
+ configuration,
+ cleanupOutput = true
+ ) {
+ pagesGenerationStage = { root ->
+ val testClass = root.dfs { it.name == "C" } as? ClasslikePageNode
+ assert(testClass != null)
+ testClass!!.content.assertNode {
+ group {
+ header(expectedLevel = 1) {
+ +"C"
+ }
+ platformHinted {
+ group {
+ +"public final class"
+ link {
+ +"C"
+ }
+ +" extends "
+ link {
+ +"A"
+ }
+ +" implements "
+ link {
+ +"B"
+ }
+ }
+ }
+ }
+ skipAllNotMatching()
+ }
+ }
+ }
+ }
+
+ private fun <T> Collection<T>.assertCount(n: Int, prefix: String = "") =
+ assert(count() == n) { "${prefix}Expected $n, got ${count()}" }
+
+}
+
+private val ContentNode.mainContents: List<ContentNode>
+ get() = (this as ContentGroup).children
+ .filterIsInstance<ContentGroup>()
+ .single { it.dci.kind == ContentKind.Main }.children