aboutsummaryrefslogtreecommitdiff
path: root/dokka-subprojects/plugin-base/src/test/kotlin/model
diff options
context:
space:
mode:
Diffstat (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/model')
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/ClassesTest.kt594
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/CommentTest.kt338
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/ExtensionsTest.kt159
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/FunctionsTest.kt403
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/InheritorsTest.kt428
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt491
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/MultiLanguageInheritanceTest.kt365
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/ObjectTest.kt43
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/PackagesTest.kt123
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/PropertyTest.kt277
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsForParametersTest.kt181
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsTest.kt195
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/KotlinAnnotationsForParametersTest.kt105
13 files changed, 3702 insertions, 0 deletions
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/ClassesTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/ClassesTest.kt
new file mode 100644
index 00000000..c18dfafb
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/ClassesTest.kt
@@ -0,0 +1,594 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model
+
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.TypeConstructor
+import org.jetbrains.dokka.links.sureClassNames
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.KotlinModifier.*
+import kotlin.test.assertNull
+import kotlin.test.Test
+import utils.*
+
+
+class ClassesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "classes") {
+
+ @Test
+ fun emptyClass() {
+ inlineModelTest(
+ """
+ |class Klass {}"""
+ ) {
+ with((this / "classes" / "Klass").cast<DClass>()) {
+ name equals "Klass"
+ children counts 4
+ }
+ }
+ }
+
+ @Test
+ fun classWithConstructor() {
+ inlineModelTest(
+ """
+ |class Klass(name: String)
+ """
+ ) {
+ with((this / "classes" / "Klass").cast<DClass>()) {
+ name equals "Klass"
+ children counts 4
+
+ with(constructors.firstOrNull().assertNotNull("Constructor")) {
+ visibility.values allEquals KotlinVisibility.Public
+ parameters counts 1
+ with(parameters.firstOrNull().assertNotNull("Constructor parameter")) {
+ name equals "name"
+ type.name equals "String"
+ }
+ }
+
+ }
+ }
+ }
+
+ @Test
+ fun classWithFunction() {
+ inlineModelTest(
+ """
+ |class Klass {
+ | fun fn() {}
+ |}
+ """
+ ) {
+ with((this / "classes" / "Klass").cast<DClass>()) {
+ name equals "Klass"
+ children counts 5
+
+ with((this / "fn").cast<DFunction>()) {
+ type.name equals "Unit"
+ parameters counts 0
+ visibility.values allEquals KotlinVisibility.Public
+ }
+ }
+ }
+ }
+
+ @Test
+ fun classWithProperty() {
+ inlineModelTest(
+ """
+ |class Klass {
+ | val name: String = ""
+ |}
+ """
+ ) {
+ with((this / "classes" / "Klass").cast<DClass>()) {
+ name equals "Klass"
+ children counts 5
+
+ with((this / "name").cast<DProperty>()) {
+ name equals "name"
+ // TODO property name
+ }
+ }
+ }
+ }
+
+ @Test
+ fun classWithCompanionObject() {
+ inlineModelTest(
+ """
+ |class Klass() {
+ | companion object {
+ | val x = 1
+ | fun foo() {}
+ | }
+ |}
+ """
+ ) {
+ with((this / "classes" / "Klass").cast<DClass>()) {
+ name equals "Klass"
+ children counts 5
+
+ with((this / "Companion").cast<DObject>()) {
+ name equals "Companion"
+ children counts 5
+
+ with((this / "x").cast<DProperty>()) {
+ name equals "x"
+ }
+
+ with((this / "foo").cast<DFunction>()) {
+ name equals "foo"
+ parameters counts 0
+ type.name equals "Unit"
+ }
+ }
+
+ with((this.companion).cast<DObject>()) {
+ name equals "Companion"
+ children counts 5
+
+ with((this / "x").cast<DProperty>()) {
+ name equals "x"
+ }
+
+ with((this / "foo").cast<DFunction>()) {
+ name equals "foo"
+ parameters counts 0
+ type.name equals "Unit"
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun dataClass() {
+ inlineModelTest(
+ """
+ |data class Klass() {}
+ """
+ ) {
+ with((this / "classes" / "Klass").cast<DClass>()) {
+ name equals "Klass"
+ visibility.values allEquals KotlinVisibility.Public
+ with(extra[AdditionalModifiers]!!.content.entries.single().value.assertNotNull("Extras")) {
+ this counts 1
+ first() equals ExtraModifiers.KotlinOnlyModifiers.Data
+ }
+ }
+ }
+ }
+
+ @Test
+ fun sealedClass() {
+ inlineModelTest(
+ """
+ |sealed class Klass() {}
+ """
+ ) {
+ with((this / "classes" / "Klass").cast<DClass>()) {
+ name equals "Klass"
+ modifier.values.forEach { it equals Sealed }
+ }
+ }
+ }
+
+ @Test
+ fun annotatedClassWithAnnotationParameters() {
+ inlineModelTest(
+ """
+ |@Deprecated("should no longer be used") class Foo() {}
+ """
+ ) {
+ with((this / "classes" / "Foo").cast<DClass>()) {
+ with(extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 1
+ with(first()) {
+ dri.classNames equals "Deprecated"
+ params.entries counts 1
+ (params["message"].assertNotNull("message") as StringValue).value equals "should no longer be used"
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun notOpenClass() {
+ inlineModelTest(
+ """
+ |open class C() {
+ | open fun f() {}
+ |}
+ |
+ |class D() : C() {
+ | override fun f() {}
+ |}
+ """
+ ) {
+ val C = (this / "classes" / "C").cast<DClass>()
+ val D = (this / "classes" / "D").cast<DClass>()
+
+ with(C) {
+ modifier.values.forEach { it equals Open }
+ with((this / "f").cast<DFunction>()) {
+ modifier.values.forEach { it equals Open }
+ }
+ }
+ with(D) {
+ modifier.values.forEach { it equals Final }
+ with((this / "f").cast<DFunction>()) {
+ modifier.values.forEach { it equals Open }
+ }
+ D.supertypes.flatMap { it.component2() }.firstOrNull()?.typeConstructor?.dri equals C.dri
+ }
+ }
+ }
+
+ @Test
+ fun indirectOverride() {
+ inlineModelTest(
+ """
+ |abstract class C() {
+ | abstract fun foo()
+ |}
+ |
+ |abstract class D(): C()
+ |
+ |class E(): D() {
+ | override fun foo() {}
+ |}
+ """
+ ) {
+ val C = (this / "classes" / "C").cast<DClass>()
+ val D = (this / "classes" / "D").cast<DClass>()
+ val E = (this / "classes" / "E").cast<DClass>()
+
+ with(C) {
+ modifier.values.forEach { it equals Abstract }
+ ((this / "foo").cast<DFunction>()).modifier.values.forEach { it equals Abstract }
+ }
+
+ with(D) {
+ modifier.values.forEach { it equals Abstract }
+ }
+
+ with(E) {
+ modifier.values.forEach { it equals Final }
+
+ }
+ D.supers.single().typeConstructor.dri equals C.dri
+ E.supers.single().typeConstructor.dri equals D.dri
+ }
+ }
+
+ @Test
+ fun innerClass() {
+ inlineModelTest(
+ """
+ |class C {
+ | inner class D {}
+ |}
+ """
+ ) {
+ with((this / "classes" / "C").cast<DClass>()) {
+
+ with((this / "D").cast<DClass>()) {
+ with(extra[AdditionalModifiers]!!.content.entries.single().value.assertNotNull("AdditionalModifiers")) {
+ this counts 1
+ first() equals ExtraModifiers.KotlinOnlyModifiers.Inner
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun companionObjectExtension() {
+ inlineModelTest(
+ """
+ |class Klass {
+ | companion object Default {}
+ |}
+ |
+ |/**
+ | * The def
+ | */
+ |val Klass.Default.x: Int get() = 1
+ """
+ ) {
+ with((this / "classes").cast<DPackage>()) {
+ properties.single().name equals "x"
+ (properties.single().receiver?.dri?.callable?.receiver as? TypeConstructor)?.fullyQualifiedName equals "classes.Klass.Default"
+ }
+ }
+ }
+
+ @Test
+ fun secondaryConstructor() {
+ inlineModelTest(
+ """
+ |class C() {
+ | /** This is a secondary constructor. */
+ | constructor(s: String): this() {}
+ |}
+ """
+ ) {
+ with((this / "classes" / "C").cast<DClass>()) {
+ name equals "C"
+ constructors counts 2
+
+ constructors.map { it.name } allEquals "C"
+
+ with(constructors.find { it.parameters.isEmpty() } notNull "C()") {
+ parameters counts 0
+ }
+
+ with(constructors.find { it.parameters.isNotEmpty() } notNull "C(String)") {
+ parameters counts 1
+ with(parameters.firstOrNull() notNull "Constructor parameter") {
+ name equals "s"
+ type.name equals "String"
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun sinceKotlin() {
+ inlineModelTest(
+ """
+ |/**
+ | * Useful
+ | */
+ |@SinceKotlin("1.1")
+ |class C
+ """
+ ) {
+ with((this / "classes" / "C").cast<DClass>()) {
+ with(extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 1
+ with(first()) {
+ dri.classNames equals "SinceKotlin"
+ params.entries counts 1
+ (params["version"].assertNotNull("version") as StringValue).value equals "1.1"
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun privateCompanionObject() {
+ inlineModelTest(
+ """
+ |class Klass {
+ | private companion object {
+ | fun fn() {}
+ | val a = 0
+ | }
+ |}
+ """
+ ) {
+ with((this / "classes" / "Klass").cast<DClass>()) {
+ name equals "Klass"
+ assertNull(companion, "Companion should not be visible by default")
+ }
+ }
+ }
+
+ @Test
+ fun companionObject() {
+ inlineModelTest(
+ """
+ |class Klass {
+ | companion object {
+ | fun fn() {}
+ | val a = 0
+ | }
+ |}
+ """
+ ) {
+ with((this / "classes" / "Klass").cast<DClass>()) {
+ name equals "Klass"
+ with((this / "Companion").cast<DObject>()) {
+ name equals "Companion"
+ visibility.values allEquals KotlinVisibility.Public
+
+ with((this / "fn").cast<DFunction>()) {
+ name equals "fn"
+ parameters counts 0
+ receiver equals null
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun annotatedClass() {
+ inlineModelTest(
+ """@Suppress("abc") class Foo() {}"""
+ ) {
+ with((this / "classes" / "Foo").cast<DClass>()) {
+ with(
+ extra[Annotations]?.directAnnotations?.values?.firstOrNull()?.firstOrNull()
+ .assertNotNull("annotations")
+ ) {
+ dri.toString() equals "kotlin/Suppress///PointingToDeclaration/"
+ (params["names"].assertNotNull("param") as ArrayValue).value equals listOf(StringValue("abc"))
+ }
+ }
+ }
+ }
+
+ @OnlyDescriptors("Bug in descriptors, DRI of entry should have [EnumEntryDRIExtra]")
+ @Test
+ fun javaAnnotationClass() {
+ inlineModelTest(
+ """
+ |import java.lang.annotation.Retention
+ |import java.lang.annotation.RetentionPolicy
+ |
+ |@Retention(RetentionPolicy.SOURCE)
+ |public annotation class throws()
+ """
+ ) {
+ with((this / "classes" / "throws").cast<DAnnotation>()) {
+ with(extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 1
+ with(first()) {
+ dri.classNames equals "Retention"
+ params["value"].assertNotNull("value") equals EnumValue(
+ "RetentionPolicy.SOURCE",
+ DRI("java.lang.annotation", "RetentionPolicy.SOURCE")
+ )
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun genericAnnotationClass() {
+ inlineModelTest(
+ """annotation class Foo<A,B,C,D:Number>() {}"""
+ ) {
+ with((this / "classes" / "Foo").cast<DAnnotation>()) {
+ generics.map { it.name to it.bounds.first().name } equals listOf(
+ "A" to "Any",
+ "B" to "Any",
+ "C" to "Any",
+ "D" to "Number"
+ )
+ }
+ }
+ }
+
+ @Test
+ fun nestedGenericClasses() {
+ inlineModelTest(
+ """
+ |class Outer<OUTER> {
+ | inner class Inner<INNER, T : OUTER> { }
+ |}
+ """.trimMargin()
+ ) {
+ with((this / "classes" / "Outer").cast<DClass>()) {
+ val inner = classlikes.single().cast<DClass>()
+ inner.generics.map { it.name to it.bounds.first().name } equals listOf("INNER" to "Any", "T" to "OUTER")
+ }
+ }
+ }
+
+ @Test
+ fun allImplementedInterfaces() {
+ inlineModelTest(
+ """
+ | interface Highest { }
+ | open class HighestImpl: Highest { }
+ | interface Lower { }
+ | interface LowerImplInterface: Lower { }
+ | class Tested : HighestImpl(), LowerImplInterface { }
+ """.trimIndent()
+ ) {
+ with((this / "classes" / "Tested").cast<DClass>()) {
+ extra[ImplementedInterfaces]?.interfaces?.entries?.single()?.value?.map { it.dri.sureClassNames }
+ ?.sorted() equals listOf("Highest", "Lower", "LowerImplInterface").sorted()
+ }
+ }
+ }
+
+ @Test
+ fun multipleClassInheritance() {
+ inlineModelTest(
+ """
+ | open class A { }
+ | open class B: A() { }
+ | class Tested : B() { }
+ """.trimIndent()
+ ) {
+ with((this / "classes" / "Tested").cast<DClass>()) {
+ supertypes.entries.single().value.map { it.typeConstructor.dri.sureClassNames }.single() equals "B"
+ }
+ }
+ }
+
+ @Test
+ fun multipleClassInheritanceWithInterface() {
+ inlineModelTest(
+ """
+ | open class A { }
+ | open class B: A() { }
+ | interface X { }
+ | interface Y : X { }
+ | class Tested : B(), Y { }
+ """.trimIndent()
+ ) {
+ with((this / "classes" / "Tested").cast<DClass>()) {
+ supertypes.entries.single().value.map { it.typeConstructor.dri.sureClassNames to it.kind }
+ .sortedBy { it.first } equals listOf(
+ "B" to KotlinClassKindTypes.CLASS,
+ "Y" to KotlinClassKindTypes.INTERFACE
+ )
+ }
+ }
+ }
+
+ @Test
+ fun doublyTypealiasedException() {
+ inlineModelTest(
+ """
+ | typealias B = RuntimeException
+ | typealias A = B
+ """.trimMargin()
+ ) {
+ with((this / "classes" / "A").cast<DTypeAlias>()) {
+ extra[ExceptionInSupertypes].assertNotNull("Typealias A should have ExceptionInSupertypes in its extra field")
+ }
+ with((this / "classes" / "B").cast<DTypeAlias>()) {
+ extra[ExceptionInSupertypes].assertNotNull("Typealias B should have ExceptionInSupertypes in its extra field")
+ }
+ }
+ }
+
+ @Test
+ fun `inline classes`() {
+ inlineModelTest(
+ """
+ | inline class X(val example: String)
+ |
+ | @JvmInline
+ | value class InlineTest(val x: String)
+ """.trimMargin()
+ ) {
+ with((this / "classes" / "X").cast<DClass>()) {
+ name equals "X"
+ properties.first().name equals "example"
+ extra[AdditionalModifiers]?.content?.values?.firstOrNull()
+ ?.firstOrNull() equals ExtraModifiers.KotlinOnlyModifiers.Inline
+ }
+ }
+ }
+
+ @Test
+ fun `value classes`() {
+ inlineModelTest(
+ """
+ | @JvmInline
+ | value class InlineTest(val example: String)
+ """.trimMargin()
+ ) {
+ val classlike = packages.flatMap { it.classlikes }.first() as DClass
+ classlike.name equals "InlineTest"
+ classlike.properties.first().name equals "example"
+ classlike.extra[AdditionalModifiers]?.content?.values?.firstOrNull()
+ ?.firstOrNull() equals ExtraModifiers.KotlinOnlyModifiers.Value
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/CommentTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/CommentTest.kt
new file mode 100644
index 00000000..6b00f2f0
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/CommentTest.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model
+
+import org.jetbrains.dokka.model.DClass
+import org.jetbrains.dokka.model.DProperty
+import org.jetbrains.dokka.model.doc.*
+import utils.AbstractModelTest
+import utils.assertNotNull
+import utils.comments
+import utils.docs
+import kotlin.test.Test
+
+class CommentTest : AbstractModelTest("/src/main/kotlin/comment/Test.kt", "comment") {
+
+ @Test
+ fun codeBlockComment() {
+ inlineModelTest(
+ """
+ |/**
+ | * ```brainfuck
+ | * ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.
+ | * ```
+ | */
+ |val prop1 = ""
+ |
+ |
+ |/**
+ | * ```
+ | * a + b - c
+ | * ```
+ | */
+ |val prop2 = ""
+ """
+ ) {
+ with((this / "comment" / "prop1").cast<DProperty>()) {
+ name equals "prop1"
+ with(this.docs().firstOrNull()?.children?.firstOrNull()?.assertNotNull("Code")) {
+ (this?.children?.firstOrNull() as? Text)
+ ?.body equals "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>."
+
+ this?.params?.get("lang") equals "brainfuck"
+ }
+ }
+ with((this / "comment" / "prop2").cast<DProperty>()) {
+ name equals "prop2"
+ with(this.docs().firstOrNull()?.children?.firstOrNull()?.assertNotNull("Code")) {
+ (this?.children?.firstOrNull() as? Text)
+ ?.body equals "a + b - c"
+
+ this?.params?.get("lang") equals null
+ }
+ }
+ }
+ }
+
+ @Test
+ fun codeBlockWithIndentationComment() {
+ inlineModelTest(
+ """
+ |/**
+ | * 1.
+ | * ```
+ | * line 1
+ | * line 2
+ | * ```
+ | */
+ |val prop1 = ""
+ """
+ ) {
+ with((this / "comment" / "prop1").cast<DProperty>()) {
+ name equals "prop1"
+ with(this.docs().firstOrNull()?.children?.firstOrNull()?.assertNotNull("Code")) {
+ val codeBlockChildren = ((this?.children?.firstOrNull() as? Li)?.children?.firstOrNull() as? CodeBlock)?.children
+ (codeBlockChildren?.get(0) as? Text)?.body equals " line 1"
+ (codeBlockChildren?.get(1) as? Br) notNull "Br"
+ (codeBlockChildren?.get(2) as? Text)?.body equals " line 2"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun emptyDoc() {
+ inlineModelTest(
+ """
+ val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ name equals "property"
+ comments() equals ""
+ }
+ }
+ }
+
+ @Test
+ fun emptyDocButComment() {
+ inlineModelTest(
+ """
+ |/* comment */
+ |val property = "test"
+ |fun tst() = property
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals ""
+ }
+ }
+ }
+
+ @Test
+ fun multilineDoc() {
+ inlineModelTest(
+ """
+ |/**
+ | * doc1
+ | *
+ | * doc2
+ | * doc3
+ | */
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals "doc1\ndoc2 doc3\n"
+ }
+ }
+ }
+
+ @Test
+ fun multilineDocWithComment() {
+ inlineModelTest(
+ """
+ |/**
+ | * doc1
+ | *
+ | * doc2
+ | * doc3
+ | */
+ |// comment
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals "doc1\ndoc2 doc3\n"
+ }
+ }
+ }
+
+ @Test
+ fun oneLineDoc() {
+ inlineModelTest(
+ """
+ |/** doc */
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals "doc\n"
+ }
+ }
+ }
+
+ @Test
+ fun oneLineDocWithComment() {
+ inlineModelTest(
+ """
+ |/** doc */
+ |// comment
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals "doc\n"
+ }
+ }
+ }
+
+ @Test
+ fun oneLineDocWithEmptyLine() {
+ inlineModelTest(
+ """
+ |/** doc */
+ |
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals "doc\n"
+ }
+ }
+ }
+
+ @Test
+ fun emptySection() {
+ inlineModelTest(
+ """
+ |/**
+ | * Summary
+ | * @one
+ | */
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals "Summary\n\none: []"
+ with(docs().find { it is CustomTagWrapper && it.name == "one" }.assertNotNull("'one' entry")) {
+ root.children counts 0
+ root.params.keys counts 0
+ }
+ }
+ }
+ }
+
+ @Test
+ fun quotes() {
+ inlineModelTest(
+ """
+ |/** it's "useful" */
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals """it's "useful"
+"""
+ }
+ }
+ }
+
+ @Test
+ fun section1() {
+ inlineModelTest(
+ """
+ |/**
+ | * Summary
+ | * @one section one
+ | */
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals "Summary\n\none: [section one\n]"
+ }
+ }
+ }
+
+
+ @Test
+ fun section2() {
+ inlineModelTest(
+ """
+ |/**
+ | * Summary
+ | * @one section one
+ | * @two section two
+ | */
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals "Summary\n\none: [section one\n]\ntwo: [section two\n]"
+ }
+ }
+ }
+
+ @Test
+ fun multilineSection() {
+ inlineModelTest(
+ """
+ |/**
+ | * Summary
+ | * @one
+ | * line one
+ | * line two
+ | */
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals "Summary\n\none: [line one line two\n]"
+ }
+ }
+ }
+
+ @Test
+ fun `should be space between Markdown nodes`() {
+ inlineModelTest(
+ """
+ |/**
+ | * Rotates paths by `amount` **radians** around (`x`, `y`).
+ | */
+ |val property = "test"
+ """
+ ) {
+ with((this / "comment" / "property").cast<DProperty>()) {
+ comments() equals "Rotates paths by amount radians around (x, y).\n"
+ }
+ }
+ }
+
+ @Test
+ fun `should remove spaces inside indented code block`() {
+ inlineModelTest(
+ """
+ |/**
+ | * Welcome:
+ | *
+ | * ```kotlin
+ | * fun main() {
+ | * println("Hello World!")
+ | * }
+ | * ```
+ | *
+ | * fun thisIsACodeBlock() {
+ | * val butWhy = "per markdown spec, because four-spaces prefix"
+ | * }
+ | */
+ |class Foo
+ """
+ ) {
+ with((this / "comment" / "Foo").cast<DClass>()) {
+ docs()[0].children[2] equals CodeBlock(
+ listOf(
+ Text(
+ "fun thisIsACodeBlock() {\n" +
+ " val butWhy = \"per markdown spec, because four-spaces prefix\"\n" +
+ "}"
+ )
+ )
+ )
+ }
+ }
+ }
+
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/ExtensionsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/ExtensionsTest.kt
new file mode 100644
index 00000000..a428dd1d
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/ExtensionsTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model
+
+import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions
+import org.jetbrains.dokka.model.DClass
+import org.jetbrains.dokka.model.DFunction
+import org.jetbrains.dokka.model.DInterface
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+import utils.AbstractModelTest
+import kotlin.test.Test
+
+class ExtensionsTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "classes") {
+ private fun <T : WithExtraProperties<R>, R : Documentable> T.checkExtension(name: String = "extension") =
+ with(extra[CallableExtensions]?.extensions) {
+ this notNull "extensions"
+ this counts 1
+ (this?.single() as? DFunction)?.name equals name
+ }
+
+ @Test
+ fun `should be extension for subclasses`() {
+ inlineModelTest(
+ """
+ |open class A
+ |open class B: A()
+ |open class C: B()
+ |open class D: C()
+ |fun B.extension() = ""
+ """
+ ) {
+ with((this / "classes" / "B").cast<DClass>()) {
+ checkExtension()
+ }
+ with((this / "classes" / "C").cast<DClass>()) {
+ checkExtension()
+ }
+ with((this / "classes" / "D").cast<DClass>()) {
+ checkExtension()
+ }
+ with((this / "classes" / "A").cast<DClass>()) {
+ extra[CallableExtensions] equals null
+ }
+ }
+ }
+
+ @Test
+ fun `should be extension for interfaces`() {
+ inlineModelTest(
+ """
+ |interface I
+ |interface I2 : I
+ |open class A: I2
+ |fun I.extension() = ""
+ """
+ ) {
+
+ with((this / "classes" / "A").cast<DClass>()) {
+ checkExtension()
+ }
+ with((this / "classes" / "I2").cast<DInterface>()) {
+ checkExtension()
+ }
+ with((this / "classes" / "I").cast<DInterface>()) {
+ checkExtension()
+ }
+ }
+ }
+
+ @Test
+ fun `should be extension for external classes`() {
+ inlineModelTest(
+ """
+ |abstract class A<T>: AbstractList<T>()
+ |fun<T> AbstractCollection<T>.extension() {}
+ |
+ |class B:Exception()
+ |fun Throwable.extension() = ""
+ """
+ ) {
+ with((this / "classes" / "A").cast<DClass>()) {
+ checkExtension()
+ }
+ with((this / "classes" / "B").cast<DClass>()) {
+ checkExtension()
+ }
+ }
+ }
+
+ @Test
+ fun `should be extension for typealias`() {
+ inlineModelTest(
+ """
+ |open class A
+ |open class B: A()
+ |open class C: B()
+ |open class D: C()
+ |typealias B2 = B
+ |fun B2.extension() = ""
+ """
+ ) {
+ with((this / "classes" / "B").cast<DClass>()) {
+ checkExtension()
+ }
+ with((this / "classes" / "C").cast<DClass>()) {
+ checkExtension()
+ }
+ with((this / "classes" / "D").cast<DClass>()) {
+ checkExtension()
+ }
+ with((this / "classes" / "A").cast<DClass>()) {
+ extra[CallableExtensions] equals null
+ }
+ }
+ }
+
+ @Test
+ fun `should be extension for java classes`() {
+ val testConfiguration = dokkaConfiguration {
+ suppressObviousFunctions = false
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/main/kotlin/")
+ classpath += jvmStdlibPath!!
+ }
+ }
+ }
+ testInline(
+ """
+ |/src/main/kotlin/classes/Test.kt
+ | package classes
+ | fun A.extension() = ""
+ |
+ |/src/main/kotlin/classes/A.java
+ | package classes;
+ | public class A {}
+ |
+ | /src/main/kotlin/classes/B.java
+ | package classes;
+ | public class B extends A {}
+ """,
+ configuration = testConfiguration
+ ) {
+ documentablesTransformationStage = {
+ it.run {
+ with((this / "classes" / "B").cast<DClass>()) {
+ checkExtension()
+ }
+ with((this / "classes" / "A").cast<DClass>()) {
+ checkExtension()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/FunctionsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/FunctionsTest.kt
new file mode 100644
index 00000000..a6291bb1
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/FunctionsTest.kt
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model
+
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import utils.AbstractModelTest
+import utils.assertNotNull
+import utils.comments
+import utils.OnlyDescriptors
+import utils.name
+import kotlin.test.Test
+
+class FunctionTest : AbstractModelTest("/src/main/kotlin/function/Test.kt", "function") {
+
+ @Test
+ fun function() {
+ inlineModelTest(
+ """
+ |/**
+ | * Function fn
+ | */
+ |fun fn() {}
+ """
+ ) {
+ with((this / "function" / "fn").cast<DFunction>()) {
+ name equals "fn"
+ type.name equals "Unit"
+ this.children.assertCount(0, "Function children: ")
+ }
+ }
+ }
+
+ @Test
+ fun overloads() {
+ inlineModelTest(
+ """
+ |/**
+ | * Function fn
+ | */
+ |fun fn() {}
+ | /**
+ | * Function fn(Int)
+ | */
+ |fun fn(i: Int) {}
+ """
+ ) {
+ with((this / "function").cast<DPackage>()) {
+ val fn1 = functions.find {
+ it.name == "fn" && it.parameters.isEmpty()
+ }.assertNotNull("fn()")
+ val fn2 = functions.find {
+ it.name == "fn" && it.parameters.isNotEmpty()
+ }.assertNotNull("fn(Int)")
+
+ with(fn1) {
+ name equals "fn"
+ parameters.assertCount(0)
+ }
+
+ with(fn2) {
+ name equals "fn"
+ parameters.assertCount(1)
+ parameters.first().type.name equals "Int"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun functionWithReceiver() {
+ inlineModelTest(
+ """
+ |/**
+ | * Function with receiver
+ | */
+ |fun String.fn() {}
+ |
+ |/**
+ | * Function with receiver
+ | */
+ |fun String.fn(x: Int) {}
+ """
+ ) {
+ with((this / "function").cast<DPackage>()) {
+ val fn1 = functions.find {
+ it.name == "fn" && it.parameters.isEmpty()
+ }.assertNotNull("fn()")
+ val fn2 = functions.find {
+ it.name == "fn" && it.parameters.count() == 1
+ }.assertNotNull("fn(Int)")
+
+ with(fn1) {
+ name equals "fn"
+ parameters counts 0
+ receiver.assertNotNull("fn() receiver")
+ }
+
+ with(fn2) {
+ name equals "fn"
+ parameters counts 1
+ receiver.assertNotNull("fn(Int) receiver")
+ parameters.first().type.name equals "Int"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun functionWithParams() {
+ inlineModelTest(
+ """
+ |/**
+ | * Multiline
+ | *
+ | * Function
+ | * Documentation
+ | */
+ |fun function(/** parameter */ x: Int) {
+ |}
+ """
+ ) {
+ with((this / "function" / "function").cast<DFunction>()) {
+ comments() equals "Multiline\nFunction Documentation\n"
+
+ name equals "function"
+ parameters counts 1
+ parameters.firstOrNull().assertNotNull("Parameter: ").also {
+ it.name equals "x"
+ it.type.name equals "Int"
+ it.comments() equals "parameter\n"
+ }
+
+ type.assertNotNull("Return type: ").name equals "Unit"
+ }
+ }
+ }
+
+ @Test
+ fun functionWithNotDocumentedAnnotation() {
+ inlineModelTest(
+ """
+ |@Suppress("FOO") fun f() {}
+ """
+ ) {
+ with((this / "function" / "f").cast<DFunction>()) {
+ with(extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 1
+ with(first()) {
+ dri.classNames equals "Suppress"
+ params.entries counts 1
+ (params["names"].assertNotNull("param") as ArrayValue).value equals listOf(StringValue("FOO"))
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun inlineFunction() {
+ inlineModelTest(
+ """
+ |inline fun f(a: () -> String) {}
+ """
+ ) {
+ with((this / "function" / "f").cast<DFunction>()) {
+ extra[AdditionalModifiers]!!.content.entries.single().value counts 1
+ extra[AdditionalModifiers]!!.content.entries.single().value exists ExtraModifiers.KotlinOnlyModifiers.Inline
+ }
+ }
+ }
+
+ @Test
+ fun suspendFunction() {
+ inlineModelTest(
+ """
+ |suspend fun f() {}
+ """
+ ) {
+ with((this / "function" / "f").cast<DFunction>()) {
+ extra[AdditionalModifiers]!!.content.entries.single().value counts 1
+ extra[AdditionalModifiers]!!.content.entries.single().value exists ExtraModifiers.KotlinOnlyModifiers.Suspend
+ }
+ }
+ }
+
+ @Test
+ fun suspendInlineFunctionOrder() {
+ inlineModelTest(
+ """
+ |suspend inline fun f(a: () -> String) {}
+ """
+ ) {
+ with((this / "function" / "f").cast<DFunction>()) {
+ extra[AdditionalModifiers]!!.content.entries.single().value counts 2
+ extra[AdditionalModifiers]!!.content.entries.single().value exists ExtraModifiers.KotlinOnlyModifiers.Suspend
+ extra[AdditionalModifiers]!!.content.entries.single().value exists ExtraModifiers.KotlinOnlyModifiers.Inline
+ }
+ }
+ }
+
+ @Test
+ fun inlineSuspendFunctionOrderChanged() {
+ inlineModelTest(
+ """
+ |inline suspend fun f(a: () -> String) {}
+ """
+ ) {
+ with((this / "function" / "f").cast<DFunction>()) {
+ with(extra[AdditionalModifiers]!!.content.entries.single().value.assertNotNull("AdditionalModifiers")) {
+ this counts 2
+ this exists ExtraModifiers.KotlinOnlyModifiers.Suspend
+ this exists ExtraModifiers.KotlinOnlyModifiers.Inline
+ }
+ }
+ }
+ }
+
+ @OnlyDescriptors("Bug in descriptors, DRI of entry should have [EnumEntryDRIExtra]")
+ @Test
+ fun functionWithAnnotatedParam() {
+ inlineModelTest(
+ """
+ |@Target(AnnotationTarget.VALUE_PARAMETER)
+ |@Retention(AnnotationRetention.SOURCE)
+ |@MustBeDocumented
+ |public annotation class Fancy
+ |
+ |fun function(@Fancy notInlined: () -> Unit) {}
+ """
+ ) {
+ with((this / "function" / "Fancy").cast<DAnnotation>()) {
+ with(extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 3
+ with(associate { it.dri.classNames to it }) {
+ with(this["Target"].assertNotNull("Target")) {
+ (params["allowedTargets"].assertNotNull("allowedTargets") as ArrayValue).value equals listOf(
+ EnumValue(
+ "AnnotationTarget.VALUE_PARAMETER",
+ DRI("kotlin.annotation", "AnnotationTarget.VALUE_PARAMETER")
+ )
+ )
+ }
+ with(this["Retention"].assertNotNull("Retention")) {
+ (params["value"].assertNotNull("value") as EnumValue) equals EnumValue(
+ "AnnotationRetention.SOURCE",
+ DRI("kotlin.annotation", "AnnotationRetention.SOURCE")
+ )
+ }
+ this["MustBeDocumented"].assertNotNull("MustBeDocumented").params.entries counts 0
+ }
+ }
+
+ }
+ with((this / "function" / "function" / "notInlined").cast<DParameter>()) {
+ with(this.extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 1
+ with(first()) {
+ dri.classNames equals "Fancy"
+ params.entries counts 0
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun functionWithNoinlineParam() {
+ inlineModelTest(
+ """
+ |fun f(noinline notInlined: () -> Unit) {}
+ """
+ ) {
+ with((this / "function" / "f" / "notInlined").cast<DParameter>()) {
+ extra[AdditionalModifiers]!!.content.entries.single().value counts 1
+ extra[AdditionalModifiers]!!.content.entries.single().value exists ExtraModifiers.KotlinOnlyModifiers.NoInline
+ }
+ }
+ }
+
+ @OnlyDescriptors("Bug in descriptors, DRI of entry should have [EnumEntryDRIExtra]")
+ @Test
+ fun annotatedFunctionWithAnnotationParameters() {
+ inlineModelTest(
+ """
+ |@Target(AnnotationTarget.VALUE_PARAMETER)
+ |@Retention(AnnotationRetention.SOURCE)
+ |@MustBeDocumented
+ |public annotation class Fancy(val size: Int)
+ |
+ |@Fancy(1) fun f() {}
+ """
+ ) {
+ with((this / "function" / "Fancy").cast<DAnnotation>()) {
+ constructors counts 1
+ with(constructors.first()) {
+ parameters counts 1
+ with(parameters.first()) {
+ type.name equals "Int"
+ name equals "size"
+ }
+ }
+
+ with(extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 3
+ with(associate { it.dri.classNames to it }) {
+ with(this["Target"].assertNotNull("Target")) {
+ (params["allowedTargets"].assertNotNull("allowedTargets") as ArrayValue).value equals listOf(
+ EnumValue(
+ "AnnotationTarget.VALUE_PARAMETER",
+ DRI("kotlin.annotation", "AnnotationTarget.VALUE_PARAMETER")
+ )
+ )
+ }
+ with(this["Retention"].assertNotNull("Retention")) {
+ (params["value"].assertNotNull("value") as EnumValue) equals EnumValue(
+ "AnnotationRetention.SOURCE",
+ DRI("kotlin.annotation", "AnnotationRetention.SOURCE")
+ )
+ }
+ this["MustBeDocumented"].assertNotNull("MustBeDocumented").params.entries counts 0
+ }
+ }
+
+ }
+ with((this / "function" / "f").cast<DFunction>()) {
+ with(this.extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 1
+ with(this.first()) {
+ dri.classNames equals "Fancy"
+ params.entries counts 1
+ (params["size"] as IntValue).value equals 1
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun functionWithDefaultStringParameter() {
+ inlineModelTest(
+ """
+ |/src/main/kotlin/function/Test.kt
+ |package function
+ |fun f(x: String = "") {}
+ """
+ ) {
+ with((this / "function" / "f").cast<DFunction>()) {
+ parameters.forEach { p ->
+ p.name equals "x"
+ p.type.name.assertNotNull("Parameter type: ") equals "String"
+ p.extra[DefaultValue]?.expression?.get(sourceSets.single()) equals StringConstant("")
+ }
+ }
+ }
+ }
+
+ @Test
+ fun functionWithDefaultFloatParameter() {
+ inlineModelTest(
+ """
+ |/src/main/kotlin/function/Test.kt
+ |package function
+ |fun f(x: Float = 3.14f) {}
+ """
+ ) {
+ with((this / "function" / "f").cast<DFunction>()) {
+ parameters.forEach { p ->
+ p.name equals "x"
+ p.type.name.assertNotNull("Parameter type: ") equals "Float"
+ p.extra[DefaultValue]?.expression?.get(sourceSets.single()) equals FloatConstant(3.14f)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun sinceKotlin() {
+ inlineModelTest(
+ """
+ |/**
+ | * Quite useful [String]
+ | */
+ |@SinceKotlin("1.1")
+ |fun f(): String = "1.1 rulezz"
+ """
+ ) {
+ with((this / "function" / "f").cast<DFunction>()) {
+ with(extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 1
+ with(first()) {
+ dri.classNames equals "SinceKotlin"
+ params.entries counts 1
+ (params["version"].assertNotNull("version") as StringValue).value equals "1.1"
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/InheritorsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/InheritorsTest.kt
new file mode 100644
index 00000000..459dd9ac
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/InheritorsTest.kt
@@ -0,0 +1,428 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model
+
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo
+import org.jetbrains.dokka.model.DClass
+import org.jetbrains.dokka.model.DFunction
+import org.jetbrains.dokka.model.DInterface
+import org.jetbrains.dokka.model.doc.P
+import org.jetbrains.dokka.model.doc.Text
+import utils.AbstractModelTest
+import utils.assertNotNull
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+class InheritorsTest : AbstractModelTest("/src/main/kotlin/inheritors/Test.kt", "inheritors") {
+
+ @Test
+ fun simple() {
+ inlineModelTest(
+ """|interface A{}
+ |class B() : A {}
+ """.trimMargin(),
+ ) {
+ with((this / "inheritors" / "A").cast<DInterface>()) {
+ val map = extra[InheritorsInfo].assertNotNull("InheritorsInfo").value
+ with(map.keys.also { it counts 1 }.find { it.analysisPlatform == Platform.jvm }.assertNotNull("jvm key").let { map[it]!! }
+ ) {
+ this counts 1
+ first().classNames equals "B"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun sealed() {
+ inlineModelTest(
+ """|sealed class A {}
+ |class B() : A() {}
+ |class C() : A() {}
+ |class D()
+ """.trimMargin(),
+ ) {
+ with((this / "inheritors" / "A").cast<DClass>()) {
+ val map = extra[InheritorsInfo].assertNotNull("InheritorsInfo").value
+ with(map.keys.also { it counts 1 }.find { it.analysisPlatform == Platform.jvm }.assertNotNull("jvm key").let { map[it]!! }
+ ) {
+ this counts 2
+ mapNotNull { it.classNames }.sorted() equals listOf("B", "C")
+ }
+ }
+ }
+ }
+
+ @Test
+ fun multiplatform() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ val commonSourceSet = sourceSet {
+ name = "common"
+ sourceRoots = listOf("common/src/")
+ analysisPlatform = "common"
+ }
+ sourceSet {
+ name = "jvm"
+ sourceRoots = listOf("jvm/src/")
+ analysisPlatform = "jvm"
+ dependentSourceSets = setOf(commonSourceSet.value.sourceSetID)
+ }
+ sourceSet {
+ name = "js"
+ sourceRoots = listOf("js/src/")
+ analysisPlatform = "js"
+ dependentSourceSets = setOf(commonSourceSet.value.sourceSetID)
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/common/src/main/kotlin/inheritors/Test.kt
+ |package inheritors
+ |interface A{}
+ |/jvm/src/main/kotlin/inheritors/Test.kt
+ |package inheritors
+ |class B() : A {}
+ |/js/src/main/kotlin/inheritors/Test.kt
+ |package inheritors
+ |class B() : A {}
+ |class C() : A {}
+ """.trimMargin(),
+ configuration,
+ cleanupOutput = false,
+ ) {
+ documentablesTransformationStage = { m ->
+ with((m / "inheritors" / "A").cast<DInterface>()) {
+ val map = extra[InheritorsInfo].assertNotNull("InheritorsInfo").value
+ with(map.keys.also { it counts 2 }) {
+ with(find { it.analysisPlatform == Platform.jvm }.assertNotNull("jvm key").let { map[it]!! }) {
+ this counts 1
+ first().classNames equals "B"
+ }
+ with(find { it.analysisPlatform == Platform.js }.assertNotNull("js key").let { map[it]!! }) {
+ this counts 2
+ val classes = listOf("B", "C")
+ assertTrue(all { classes.contains(it.classNames) }, "One of subclasses missing in js" )
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `should inherit docs`() {
+ val expectedDoc = listOf(P(listOf(Text("some text"))))
+ inlineModelTest(
+ """|interface A<out E> {
+ | /**
+ | * some text
+ | */
+ | val a: Int
+ |
+ | /**
+ | * some text
+ | */
+ | fun b(): E
+ |}
+ |open class C
+ |class B<out E>() : C(), A<out E> {
+ | val a = 0
+ | override fun b(): E {}
+ |}
+ """.trimMargin(),
+ platform = Platform.common.toString()
+ ) {
+ with((this / "inheritors" / "A").cast<DInterface>()) {
+ with(this / "a") {
+ val propDoc = this?.documentation?.values?.single()?.children?.first()?.children
+ propDoc equals expectedDoc
+ }
+ with(this / "b") {
+ val funDoc = this?.documentation?.values?.single()?.children?.first()?.children
+ funDoc equals expectedDoc
+ }
+
+ }
+
+ with((this / "inheritors" / "B").cast<DClass>()) {
+ with(this / "a") {
+ val propDoc = this?.documentation?.values?.single()?.children?.first()?.children
+ propDoc equals expectedDoc
+ }
+ }
+ }
+ }
+
+// TODO [beresnev] fix, needs access to analysis
+// class IgnoreCommonBuiltInsPlugin : DokkaPlugin() {
+// private val kotlinAnalysisPlugin by lazy { plugin<DescriptorKotlinAnalysisPlugin>() }
+// @Suppress("unused")
+// val stdLibKotlinAnalysis by extending {
+// kotlinAnalysisPlugin.kotlinAnalysis providing { ctx ->
+// ProjectKotlinAnalysis(
+// sourceSets = ctx.configuration.sourceSets,
+// logger = ctx.logger,
+// analysisConfiguration = DokkaAnalysisConfiguration(ignoreCommonBuiltIns = true)
+// )
+// } override kotlinAnalysisPlugin.defaultKotlinAnalysis
+// }
+//
+// @OptIn(DokkaPluginApiPreview::class)
+// override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement =
+// PluginApiPreviewAcknowledgement
+// }
+// @Test
+// fun `should inherit docs for stdLib #2638`() {
+// val testConfiguration = dokkaConfiguration {
+// suppressObviousFunctions = false
+// sourceSets {
+// sourceSet {
+// sourceRoots = listOf("src/")
+// analysisPlatform = "common"
+// languageVersion = "1.4"
+// }
+// }
+// }
+//
+// inlineModelTest(
+// """
+// package kotlin.collections
+//
+// import kotlin.internal.PlatformDependent
+//
+// /**
+// * Classes that inherit from this interface can be represented as a sequence of elements that can
+// * be iterated over.
+// * @param T the type of element being iterated over. The iterator is covariant in its element type.
+// */
+// public interface Iterable<out T> {
+// /**
+// * Returns an iterator over the elements of this object.
+// */
+// public operator fun iterator(): Iterator<T>
+// }
+//
+// /**
+// * Classes that inherit from this interface can be represented as a sequence of elements that can
+// * be iterated over and that supports removing elements during iteration.
+// * @param T the type of element being iterated over. The mutable iterator is invariant in its element type.
+// */
+// public interface MutableIterable<out T> : Iterable<T> {
+// /**
+// * Returns an iterator over the elements of this sequence that supports removing elements during iteration.
+// */
+// override fun iterator(): MutableIterator<T>
+// }
+//
+// /**
+// * A generic collection of elements. Methods in this interface support only read-only access to the collection;
+// * read/write access is supported through the [MutableCollection] interface.
+// * @param E the type of elements contained in the collection. The collection is covariant in its element type.
+// */
+// public interface Collection<out E> : Iterable<E> {
+// // Query Operations
+// /**
+// * Returns the size of the collection.
+// */
+// public val size: Int
+//
+// /**
+// * Returns `true` if the collection is empty (contains no elements), `false` otherwise.
+// */
+// public fun isEmpty(): Boolean
+//
+// /**
+// * Checks if the specified element is contained in this collection.
+// */
+// public operator fun contains(element: @UnsafeVariance E): Boolean
+//
+// override fun iterator(): Iterator<E>
+//
+// // Bulk Operations
+// /**
+// * Checks if all elements in the specified collection are contained in this collection.
+// */
+// public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
+// }
+//
+// /**
+// * A generic collection of elements that supports adding and removing elements.
+// *
+// * @param E the type of elements contained in the collection. The mutable collection is invariant in its element type.
+// */
+// public interface MutableCollection<E> : Collection<E>, MutableIterable<E> {
+// // Query Operations
+// override fun iterator(): MutableIterator<E>
+//
+// // Modification Operations
+// /**
+// * Adds the specified element to the collection.
+// *
+// * @return `true` if the element has been added, `false` if the collection does not support duplicates
+// * and the element is already contained in the collection.
+// */
+// public fun add(element: E): Boolean
+//
+// /**
+// * Removes a single instance of the specified element from this
+// * collection, if it is present.
+// *
+// * @return `true` if the element has been successfully removed; `false` if it was not present in the collection.
+// */
+// public fun remove(element: E): Boolean
+//
+// // Bulk Modification Operations
+// /**
+// * Adds all of the elements of the specified collection to this collection.
+// *
+// * @return `true` if any of the specified elements was added to the collection, `false` if the collection was not modified.
+// */
+// public fun addAll(elements: Collection<E>): Boolean
+//
+// /**
+// * Removes all of this collection's elements that are also contained in the specified collection.
+// *
+// * @return `true` if any of the specified elements was removed from the collection, `false` if the collection was not modified.
+// */
+// public fun removeAll(elements: Collection<E>): Boolean
+//
+// /**
+// * Retains only the elements in this collection that are contained in the specified collection.
+// *
+// * @return `true` if any element was removed from the collection, `false` if the collection was not modified.
+// */
+// public fun retainAll(elements: Collection<E>): Boolean
+//
+// /**
+// * Removes all elements from this collection.
+// */
+// public fun clear(): Unit
+// }
+//
+// /**
+// * A generic ordered collection of elements. Methods in this interface support only read-only access to the list;
+// * read/write access is supported through the [MutableList] interface.
+// * @param E the type of elements contained in the list. The list is covariant in its element type.
+// */
+// public interface List<out E> : Collection<E> {
+// // Query Operations
+//
+// override val size: Int
+// override fun isEmpty(): Boolean
+// override fun contains(element: @UnsafeVariance E): Boolean
+// override fun iterator(): Iterator<E>
+//
+// // Bulk Operations
+// override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
+//
+// // Positional Access Operations
+// /**
+// * Returns the element at the specified index in the list.
+// */
+// public operator fun get(index: Int): E
+//
+// // Search Operations
+// /**
+// * Returns the index of the first occurrence of the specified element in the list, or -1 if the specified
+// * element is not contained in the list.
+// */
+// public fun indexOf(element: @UnsafeVariance E): Int
+//
+// /**
+// * Returns the index of the last occurrence of the specified element in the list, or -1 if the specified
+// * element is not contained in the list.
+// */
+// public fun lastIndexOf(element: @UnsafeVariance E): Int
+//
+// // List Iterators
+// /**
+// * Returns a list iterator over the elements in this list (in proper sequence).
+// */
+// public fun listIterator(): ListIterator<E>
+//
+// /**
+// * Returns a list iterator over the elements in this list (in proper sequence), starting at the specified [index].
+// */
+// public fun listIterator(index: Int): ListIterator<E>
+//
+// // View
+// /**
+// * Returns a view of the portion of this list between the specified [fromIndex] (inclusive) and [toIndex] (exclusive).
+// * The returned list is backed by this list, so non-structural changes in the returned list are reflected in this list, and vice-versa.
+// *
+// * Structural changes in the base list make the behavior of the view undefined.
+// */
+// public fun subList(fromIndex: Int, toIndex: Int): List<E>
+// }
+//
+// // etc
+// """.trimMargin(),
+// platform = Platform.common.toString(),
+// configuration = testConfiguration,
+// prependPackage = false,
+// pluginsOverrides = listOf(IgnoreCommonBuiltInsPlugin())
+// ) {
+// with((this / "kotlin.collections" / "List" / "contains").cast<DFunction>()) {
+// documentation.size equals 1
+//
+// }
+// }
+// }
+
+ @Test
+ fun `should inherit docs in case of diamond inheritance`() {
+ inlineModelTest(
+ """
+ public interface Collection2<out E> {
+ /**
+ * Returns `true` if the collection is empty (contains no elements), `false` otherwise.
+ */
+ public fun isEmpty(): Boolean
+
+ /**
+ * Checks if the specified element is contained in this collection.
+ */
+ public operator fun contains(element: @UnsafeVariance E): Boolean
+ }
+
+ public interface MutableCollection2<E> : Collection2<E>, MutableIterable2<E>
+
+
+ public interface List2<out E> : Collection2<E> {
+ override fun isEmpty(): Boolean
+ override fun contains(element: @UnsafeVariance E): Boolean
+ }
+
+ public interface MutableList2<E> : List2<E>, MutableCollection2<E>
+
+ public class AbstractMutableList2<E> : MutableList2<E> {
+ protected constructor()
+
+ // From List
+
+ override fun isEmpty(): Boolean = size == 0
+ public override fun contains(element: E): Boolean = indexOf(element) != -1
+ }
+ public class ArrayDeque2<E> : AbstractMutableList2<E> {
+ override fun isEmpty(): Boolean = size == 0
+ public override fun contains(element: E): Boolean = indexOf(element) != -1
+
+ }
+ """.trimMargin()
+ ) {
+ with((this / "inheritors" / "ArrayDeque2" / "isEmpty").cast<DFunction>()) {
+ documentation.size equals 1
+ }
+ with((this / "inheritors" / "ArrayDeque2" / "contains").cast<DFunction>()) {
+ documentation.size equals 1
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt
new file mode 100644
index 00000000..ff706c5e
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt
@@ -0,0 +1,491 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo
+import org.jetbrains.dokka.links.*
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.doc.Param
+import org.jetbrains.dokka.model.doc.Text
+import utils.AbstractModelTest
+import utils.assertContains
+import utils.assertNotNull
+import utils.name
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ analysisPlatform = Platform.jvm.toString()
+ classpath += jvmStdlibPath!!
+ documentedVisibilities = setOf(
+ DokkaConfiguration.Visibility.PUBLIC,
+ DokkaConfiguration.Visibility.PRIVATE,
+ DokkaConfiguration.Visibility.PROTECTED,
+ DokkaConfiguration.Visibility.PACKAGE,
+ )
+ }
+ }
+ }
+
+ @Test
+ fun function() {
+ inlineModelTest(
+ """
+ |class Test {
+ | /**
+ | * Summary for Function
+ | * @param name is String parameter
+ | * @param value is int parameter
+ | */
+ | public void fn(String name, int value) {}
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "Test").cast<DClass>()) {
+ name equals "Test"
+ children counts 2 // default constructor and function
+ with((this / "fn").cast<DFunction>()) {
+ name equals "fn"
+ val params = parameters.map { it.documentation.values.first().children.first() as Param }
+ params.map { it.firstMemberOfType<Text>().body } equals listOf(
+ "is String parameter",
+ "is int parameter"
+ )
+ }
+ }
+ }
+ }
+
+ @Test fun allImplementedInterfacesInJava() {
+ inlineModelTest(
+ """
+ |interface Highest { }
+ |interface Lower extends Highest { }
+ |class Extendable { }
+ |class Tested extends Extendable implements Lower { }
+ """, configuration = configuration){
+ with((this / "java" / "Tested").cast<DClass>()){
+ extra[ImplementedInterfaces]?.interfaces?.entries?.single()?.value?.map { it.dri.sureClassNames }?.sorted() equals listOf("Highest", "Lower").sorted()
+ }
+ }
+ }
+
+ @Test fun multipleClassInheritanceWithInterface() {
+ inlineModelTest(
+ """
+ |interface Highest { }
+ |interface Lower extends Highest { }
+ |class Extendable { }
+ |class Tested extends Extendable implements Lower { }
+ """, configuration = configuration){
+ with((this / "java" / "Tested").cast<DClass>()) {
+ supertypes.entries.single().value.map { it.typeConstructor.dri.sureClassNames to it.kind }.sortedBy { it.first } equals listOf("Extendable" to JavaClassKindTypes.CLASS, "Lower" to JavaClassKindTypes.INTERFACE)
+ }
+ }
+ }
+
+ @Test
+ fun superClass() {
+ inlineModelTest(
+ """
+ |public class Foo extends Exception implements Cloneable {}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "Foo").cast<DClass>()) {
+ val sups = listOf("Exception", "Cloneable")
+ assertTrue(
+ sups.all { s -> supertypes.values.flatten().any { it.typeConstructor.dri.classNames == s } })
+ "Foo must extend ${sups.joinToString(", ")}"
+ }
+ }
+ }
+
+ @Test
+ fun arrayType() {
+ inlineModelTest(
+ """
+ |class Test {
+ | public String[] arrayToString(int[] data) {
+ | return null;
+ | }
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "Test").cast<DClass>()) {
+ name equals "Test"
+ children counts 2 // default constructor and function
+
+ with((this / "arrayToString").cast<DFunction>()) {
+ name equals "arrayToString"
+ type.name equals "Array"
+ with(parameters.firstOrNull().assertNotNull("parameters")) {
+ name equals "data"
+ type.name equals "Array"
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun typeParameter() {
+ inlineModelTest(
+ """
+ |class Foo<T extends Comparable<T>> {
+ | public <E> E foo();
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "Foo").cast<DClass>()) {
+ generics counts 1
+ generics[0].dri.classNames equals "Foo"
+ (functions[0].type as? TypeParameter)?.dri?.run {
+ packageName equals "java"
+ name equals "Foo"
+ callable?.name equals "foo"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun typeParameterIntoDifferentClasses2596() {
+ inlineModelTest(
+ """
+ |class GenericDocument { }
+ |public interface DocumentClassFactory<T> {
+ | String getSchemaName();
+ | GenericDocument toGenericDocument(T document);
+ | T fromGenericDocument(GenericDocument genericDoc);
+ |}
+ |
+ |public final class DocumentClassFactoryRegistry {
+ | public <T> DocumentClassFactory<T> getOrCreateFactory(T documentClass) {
+ | return null;
+ | }
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "DocumentClassFactory").cast<DInterface>()) {
+ generics counts 1
+ generics[0].dri.classNames equals "DocumentClassFactory"
+ }
+ with((this / "java" / "DocumentClassFactoryRegistry").cast<DClass>()) {
+ functions.forEach {
+ (it.type as GenericTypeConstructor).dri.classNames equals "DocumentClassFactory"
+ ((it.type as GenericTypeConstructor).projections[0] as TypeParameter).dri.classNames equals "DocumentClassFactoryRegistry"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun constructors() {
+ inlineModelTest(
+ """
+ |class Test {
+ | public Test() {}
+ |
+ | public Test(String s) {}
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "Test").cast<DClass>()) {
+ name equals "Test"
+
+ constructors counts 2
+ constructors.forEach { it.name equals "Test" }
+ constructors.find { it.parameters.isEmpty() }.assertNotNull("Test()")
+
+ with(constructors.find { it.parameters.isNotEmpty() }.assertNotNull("Test(String)")) {
+ parameters.firstOrNull()?.type?.name equals "String"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun innerClass() {
+ inlineModelTest(
+ """
+ |class InnerClass {
+ | public class D {}
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "InnerClass").cast<DClass>()) {
+ children counts 2 // default constructor and inner class
+ with((this / "D").cast<DClass>()) {
+ name equals "D"
+ children counts 1 // default constructor
+ }
+ }
+ }
+ }
+
+ @Test
+ fun varargs() {
+ inlineModelTest(
+ """
+ |class Foo {
+ | public void bar(String... x);
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "Foo").cast<DClass>()) {
+ name equals "Foo"
+ children counts 2 // default constructor and function
+
+ with((this / "bar").cast<DFunction>()) {
+ name equals "bar"
+ with(parameters.firstOrNull().assertNotNull("parameter")) {
+ name equals "x"
+ type.name equals "Array"
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun fields() {
+ inlineModelTest(
+ """
+ |class Test {
+ | public int i;
+ | public static final String s;
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "Test").cast<DClass>()) {
+ children counts 3 // default constructor + 2 props
+
+ with((this / "i").cast<DProperty>()) {
+ getter equals null
+ setter equals null
+ }
+
+ with((this / "s").cast<DProperty>()) {
+ getter equals null
+ setter equals null
+ }
+ }
+ }
+ }
+
+ @Test
+ fun staticMethod() {
+ inlineModelTest(
+ """
+ |class C {
+ | public static void foo() {}
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "C" / "foo").cast<DFunction>()) {
+ with(extra[AdditionalModifiers]!!.content.entries.single().value.assertNotNull("AdditionalModifiers")) {
+ this counts 1
+ first() equals ExtraModifiers.JavaOnlyModifiers.Static
+ }
+ }
+ }
+ }
+
+ @Test
+ fun throwsList() {
+ inlineModelTest(
+ """
+ |class C {
+ | public void foo() throws java.io.IOException, ArithmeticException {}
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "C" / "foo").cast<DFunction>()) {
+ with(extra[CheckedExceptions]?.exceptions?.entries?.single()?.value.assertNotNull("CheckedExceptions")) {
+ this counts 2
+ first().packageName equals "java.io"
+ first().classNames equals "IOException"
+ get(1).packageName equals "java.lang"
+ get(1).classNames equals "ArithmeticException"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun annotatedAnnotation() {
+ inlineModelTest(
+ """
+ |import java.lang.annotation.*;
+ |
+ |@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
+ |public @interface Attribute {
+ | String value() default "";
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "Attribute").cast<DAnnotation>()) {
+ with(extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ with(single()) {
+ dri.classNames equals "Target"
+ (params["value"].assertNotNull("value") as ArrayValue).value equals listOf(
+ EnumValue("ElementType.FIELD", DRI("java.lang.annotation", "ElementType")),
+ EnumValue("ElementType.TYPE", DRI("java.lang.annotation", "ElementType")),
+ EnumValue("ElementType.METHOD", DRI("java.lang.annotation", "ElementType"))
+ )
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun javaLangObject() {
+ inlineModelTest(
+ """
+ |class Test {
+ | public Object fn() { return null; }
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "Test" / "fn").cast<DFunction>()) {
+ assertTrue(type is JavaObject)
+ }
+ }
+ }
+
+ @Test
+ fun enumValues() {
+ inlineModelTest(
+ """
+ |enum E {
+ | Foo
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "E").cast<DEnum>()) {
+ name equals "E"
+ entries counts 1
+ with((this / "Foo").cast<DEnumEntry>()) {
+ name equals "Foo"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun inheritorLinks() {
+ inlineModelTest(
+ """
+ |public class InheritorLinks {
+ | public static class Foo {}
+ |
+ | public static class Bar extends Foo {}
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "InheritorLinks").cast<DClass>()) {
+ val dri = (this / "Bar").assertNotNull("Foo dri").dri
+ with((this / "Foo").cast<DClass>()) {
+ with(extra[InheritorsInfo].assertNotNull("InheritorsInfo")) {
+ with(value.values.flatten().distinct()) {
+ this counts 1
+ first() equals dri
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `retention should work with static import`() {
+ inlineModelTest(
+ """
+ |import java.lang.annotation.Retention;
+ |import java.lang.annotation.RetentionPolicy;
+ |import static java.lang.annotation.RetentionPolicy.RUNTIME;
+ |
+ |@Retention(RUNTIME)
+ |public @interface JsonClass {
+ |};
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "JsonClass").cast<DAnnotation>()) {
+ val annotation = extra[Annotations]?.directAnnotations?.entries
+ ?.firstOrNull()?.value //First sourceset
+ ?.firstOrNull()
+
+ val expectedDri = DRI("java.lang.annotation", "Retention", null, PointingToDeclaration)
+ val expectedParams = "value" to EnumValue(
+ "RUNTIME",
+ DRI(
+ "java.lang.annotation",
+ "RetentionPolicy.RUNTIME",
+ null,
+ PointingToDeclaration,
+ DRIExtraContainer().also { it[EnumEntryDRIExtra] = EnumEntryDRIExtra }.encode()
+ )
+ )
+
+ assertEquals(expectedDri, annotation?.dri)
+ assertEquals(expectedParams.first, annotation?.params?.entries?.first()?.key)
+ assertEquals(expectedParams.second, annotation?.params?.entries?.first()?.value)
+ }
+ }
+ }
+
+ @Test
+ fun variances() {
+ inlineModelTest(
+ """
+ |public class Foo {
+ | public void superBound(java.util.List<? super String> param) {}
+ | public void extendsBound(java.util.List<? extends String> param) {}
+ | public void unbounded(java.util.List<?> param) {}
+ |}
+ """, configuration = configuration
+ ) {
+ with((this / "java" / "Foo").cast<DClass>()) {
+ val functionNames = functions.map { it.name }
+ assertContains(functionNames, "superBound")
+ assertContains(functionNames, "extendsBound")
+ assertContains(functionNames, "unbounded")
+
+ for (function in functions) {
+ val param = function.parameters.single()
+ val type = param.type as GenericTypeConstructor
+ val variance = type.projections.single()
+
+ when (function.name) {
+ "superBound" -> {
+ assertTrue(variance is Contravariance<*>)
+ val bound = variance.inner
+ assertEquals((bound as GenericTypeConstructor).dri.classNames, "String")
+ }
+ "extendsBound" -> {
+ assertTrue(variance is Covariance<*>)
+ val bound = variance.inner
+ assertEquals((bound as GenericTypeConstructor).dri.classNames, "String")
+ }
+ "unbounded" -> {
+ assertTrue(variance is Covariance<*>)
+ val bound = variance.inner
+ assertTrue(bound is JavaObject)
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/MultiLanguageInheritanceTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/MultiLanguageInheritanceTest.kt
new file mode 100644
index 00000000..9b646f24
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/MultiLanguageInheritanceTest.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model
+
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.PointingToDeclaration
+import org.jetbrains.dokka.model.dfs
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.withDescendants
+import org.jetbrains.dokka.utilities.firstIsInstanceOrNull
+import translators.documentationOf
+import utils.docs
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class MultiLanguageInheritanceTest : BaseAbstractTest() {
+ val configuration = dokkaConfiguration {
+ suppressObviousFunctions = false
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/main/kotlin")
+ }
+ }
+ }
+
+ @Test
+ fun `from java to kotlin`() {
+ testInline(
+ """
+ |/src/main/kotlin/sample/Parent.java
+ |package sample;
+ |
+ |/**
+ | * Sample description from parent
+ | */
+ |public class Parent {
+ | /**
+ | * parent function docs
+ | * @see java.lang.String for details
+ | */
+ | public void parentFunction(){
+ | }
+ |}
+ |
+ |/src/main/kotlin/sample/Child.kt
+ |package sample
+ |public class Child : Parent() {
+ | override fun parentFunction(){
+ |
+ | }
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ val function = module.packages.flatMap { it.classlikes }
+ .find { it.name == "Child" }?.functions?.find { it.name == "parentFunction" }
+ val seeTag = function?.documentation?.values?.first()?.children?.firstIsInstanceOrNull<See>()
+
+ assertEquals("", module.documentationOf("Child"))
+ assertEquals("parent function docs", module.documentationOf("Child", "parentFunction"))
+ assertEquals("for details", (seeTag?.root?.dfs { it is Text } as Text).body)
+ assertEquals("java.lang.String", seeTag.name)
+ }
+ }
+ }
+
+ @Test
+ fun `from kotlin to java`() {
+ testInline(
+ """
+ |/src/main/kotlin/sample/ParentInKotlin.kt
+ |package sample
+ |
+ |/**
+ | * Sample description from parent
+ | */
+ |public open class ParentInKotlin {
+ | /**
+ | * parent `function docs`
+ | *
+ | * ```
+ | * code block
+ | * ```
+ | * @see java.lang.String for details
+ | */
+ | public open fun parentFun(){
+ |
+ | }
+ |}
+ |
+ |
+ |/src/main/kotlin/sample/ChildInJava.java
+ |package sample;
+ |public class ChildInJava extends ParentInKotlin {
+ | @Override
+ | public void parentFun() {
+ | super.parentFun();
+ | }
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ val function = module.packages.flatMap { it.classlikes }
+ .find { it.name == "ChildInJava" }?.functions?.find { it.name == "parentFun" }
+ val seeTag = function?.documentation?.values?.first()?.children?.firstIsInstanceOrNull<See>()
+
+ val expectedDocs = CustomDocTag(
+ children = listOf(
+ P(
+ listOf(
+ Text("parent "),
+ CodeInline(
+ listOf(Text("function docs"))
+ )
+ )
+ ),
+ CodeBlock(
+ listOf(Text("code block"))
+ )
+
+ ),
+ params = emptyMap(),
+ name = "MARKDOWN_FILE"
+ )
+
+ assertEquals("", module.documentationOf("ChildInJava"))
+ assertEquals(expectedDocs, function?.docs()?.firstIsInstanceOrNull<Description>()?.root)
+ assertEquals("for details", (seeTag?.root?.dfs { it is Text } as Text).body)
+ assertEquals("java.lang.String", seeTag.name)
+ }
+ }
+ }
+
+ @Test
+ fun `inherit doc on method`() {
+ testInline(
+ """
+ |/src/main/kotlin/sample/ParentInKotlin.kt
+ |package sample
+ |
+ |/**
+ | * Sample description from parent
+ | */
+ |public open class ParentInKotlin {
+ | /**
+ | * parent `function docs` with a link to [defaultString][java.lang.String]
+ | *
+ | * ```
+ | * code block
+ | * ```
+ | */
+ | public open fun parentFun(){
+ |
+ | }
+ |}
+ |
+ |
+ |/src/main/kotlin/sample/ChildInJava.java
+ |package sample;
+ |public class ChildInJava extends ParentInKotlin {
+ | /**
+ | * {@inheritDoc}
+ | */
+ | @Override
+ | public void parentFun() {
+ | super.parentFun();
+ | }
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ val function = module.packages.flatMap { it.classlikes }
+ .find { it.name == "ChildInJava" }?.functions?.find { it.name == "parentFun" }
+
+ val expectedDocs = CustomDocTag(
+ children = listOf(
+ P(
+ listOf(
+ P(
+ listOf(
+ Text("parent "),
+ CodeInline(
+ listOf(Text("function docs"))
+ ),
+ Text(" with a link to "),
+ DocumentationLink(
+ DRI("java.lang", "String", null, PointingToDeclaration),
+ listOf(Text("defaultString")),
+ params = mapOf("href" to "[java.lang.String]")
+ )
+ )
+ ),
+ CodeBlock(
+ listOf(Text("code block"))
+ )
+ )
+ )
+ ),
+ params = emptyMap(),
+ name = "MARKDOWN_FILE"
+ )
+
+ assertEquals("", module.documentationOf("ChildInJava"))
+ assertEquals(expectedDocs, function?.docs()?.firstIsInstanceOrNull<Description>()?.root)
+ }
+ }
+ }
+
+ @Test
+ fun `inline inherit doc on method`() {
+ testInline(
+ """
+ |/src/main/kotlin/sample/ParentInKotlin.kt
+ |package sample
+ |
+ |/**
+ | * Sample description from parent
+ | */
+ |public open class ParentInKotlin {
+ | /**
+ | * parent function docs
+ | * @see java.lang.String string
+ | */
+ | public open fun parentFun(){
+ |
+ | }
+ |}
+ |
+ |
+ |/src/main/kotlin/sample/ChildInJava.java
+ |package sample;
+ |public class ChildInJava extends ParentInKotlin {
+ | /**
+ | * Start {@inheritDoc} end
+ | */
+ | @Override
+ | public void parentFun() {
+ | super.parentFun();
+ | }
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ val function = module.packages.flatMap { it.classlikes }
+ .find { it.name == "ChildInJava" }?.functions?.find { it.name == "parentFun" }?.documentation?.values?.first()?.children?.first()
+ assertEquals("", module.documentationOf("ChildInJava"))
+ assertEquals("Start parent function docs end", function?.root?.withDescendants()?.filter { it is Text }?.toList()?.joinToString("") { (it as Text).body })
+ }
+ }
+ }
+
+ @Test
+ fun `inherit doc on multiple throws`() {
+ testInline(
+ """
+ |/src/main/kotlin/sample/ParentInKotlin.kt
+ |package sample
+ |
+ |/**
+ | * Sample description from parent
+ | */
+ |public open class ParentInKotlin {
+ | /**
+ | * parent function docs
+ | * @throws java.lang.RuntimeException runtime
+ | * @throws java.lang.Exception exception
+ | */
+ | public open fun parentFun(){
+ |
+ | }
+ |}
+ |
+ |
+ |/src/main/kotlin/sample/ChildInJava.java
+ |package sample;
+ |public class ChildInJava extends ParentInKotlin {
+ | /**
+ | * Start {@inheritDoc} end
+ | * @throws java.lang.RuntimeException Testing {@inheritDoc}
+ | */
+ | @Override
+ | public void parentFun() {
+ | super.parentFun();
+ | }
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ val function = module.packages.flatMap { it.classlikes }
+ .find { it.name == "ChildInJava" }?.functions?.find { it.name == "parentFun" }
+ val docs = function?.documentation?.values?.first()?.children?.first()
+ val throwsTag = function?.documentation?.values?.first()?.children?.firstIsInstanceOrNull<Throws>()
+
+ assertEquals("", module.documentationOf("ChildInJava"))
+ assertEquals("Start parent function docs end", docs?.root?.withDescendants()?.filter { it is Text }?.toList()?.joinToString("") { (it as Text).body })
+ assertEquals("Testing runtime", throwsTag?.root?.withDescendants()?.filter { it is Text }?.toList()?.joinToString("") { (it as Text).body })
+ assertEquals("RuntimeException", throwsTag?.exceptionAddress?.classNames)
+ }
+ }
+ }
+
+ @Test
+ fun `inherit doc on params`() {
+ testInline(
+ """
+ |/src/main/kotlin/sample/ParentInKotlin.kt
+ |package sample
+ |
+ |/**
+ | * Sample description from parent
+ | */
+ |public open class ParentInKotlin {
+ | /**
+ | * parent function docs
+ | * @param fst first docs
+ | * @param snd second docs
+ | */
+ | public open fun parentFun(fst: String, snd: Int){
+ |
+ | }
+ |}
+ |
+ |
+ |/src/main/kotlin/sample/ChildInJava.java
+ |package sample;
+ |
+ |import org.jetbrains.annotations.NotNull;
+ |
+ |public class ChildInJava extends ParentInKotlin {
+ | /**
+ | * @param fst start {@inheritDoc} end
+ | * @param snd start {@inheritDoc} end
+ | */
+ | @Override
+ | public void parentFun(@NotNull String fst, int snd) {
+ | super.parentFun();
+ | }
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ val function = module.packages.flatMap { it.classlikes }
+ .find { it.name == "ChildInJava" }?.functions?.find { it.name == "parentFun" }
+ val params = function?.documentation?.values?.first()?.children?.filterIsInstance<Param>()
+
+ val fst = params?.first { it.name == "fst" }
+ val snd = params?.first { it.name == "snd" }
+
+ assertEquals("", module.documentationOf("ChildInJava"))
+ assertEquals("", module.documentationOf("ChildInJava", "parentFun"))
+ assertEquals("start first docs end", fst?.root?.withDescendants()?.filter { it is Text }?.toList()?.joinToString("") { (it as Text).body })
+ assertEquals("start second docs end", snd?.root?.withDescendants()?.filter { it is Text }?.toList()?.joinToString("") { (it as Text).body })
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/ObjectTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/ObjectTest.kt
new file mode 100644
index 00000000..009b406e
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/ObjectTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model
+
+import org.jetbrains.dokka.model.AdditionalModifiers
+import org.jetbrains.dokka.model.DObject
+import org.jetbrains.dokka.model.ExtraModifiers
+import utils.AbstractModelTest
+import kotlin.test.Test
+
+class ObjectTest : AbstractModelTest("/src/main/kotlin/objects/Test.kt", "objects") {
+
+ @Test
+ fun emptyObject() {
+ inlineModelTest(
+ """
+ |object Obj {}
+ """.trimIndent()
+ ) {
+ with((this / "objects" / "Obj").cast<DObject>()) {
+ name equals "Obj"
+ children counts 3
+ }
+ }
+ }
+
+ @Test
+ fun `data object class`() {
+ inlineModelTest(
+ """
+ |data object KotlinDataObject {}
+ """.trimIndent()
+ ) {
+ with((this / "objects" / "KotlinDataObject").cast<DObject>()) {
+ name equals "KotlinDataObject"
+ extra[AdditionalModifiers]?.content?.values?.single()
+ ?.single() equals ExtraModifiers.KotlinOnlyModifiers.Data
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/PackagesTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/PackagesTest.kt
new file mode 100644
index 00000000..b32f214d
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/PackagesTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model
+
+import org.jetbrains.dokka.model.DPackage
+import utils.AbstractModelTest
+import kotlin.test.Test
+
+class PackagesTest : AbstractModelTest("/src/main/kotlin/packages/Test.kt", "packages") {
+
+ @Test
+ fun rootPackage() {
+ inlineModelTest(
+ """
+ |
+ """.trimIndent(),
+ prependPackage = false,
+ configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/main/kotlin")
+ displayName = "JVM"
+ }
+ }
+ }
+ ) {
+ with((this / "[root]").cast<DPackage>()) {
+ packageName equals ""
+ children counts 0
+ }
+ }
+ }
+
+ @Test
+ fun simpleNamePackage() {
+ inlineModelTest(
+ """
+ |package simple
+ """.trimIndent(),
+ prependPackage = false
+ ) {
+ with((this / "simple").cast<DPackage>()) {
+ packageName equals "simple"
+ children counts 0
+ }
+ }
+ }
+
+ @Test
+ fun dottedNamePackage() {
+ inlineModelTest(
+ """
+ |package dot.name
+ """.trimIndent(),
+ prependPackage = false
+ ) {
+ with((this / "dot.name").cast<DPackage>()) {
+ packageName equals "dot.name"
+ children counts 0
+ }
+ }
+
+ }
+
+ @Test
+ fun multipleFiles() {
+ inlineModelTest(
+ """
+ |package dot.name
+ |/src/main/kotlin/packages/Test2.kt
+ |package simple
+ """.trimIndent(),
+ prependPackage = false
+ ) {
+ children counts 2
+ with((this / "dot.name").cast<DPackage>()) {
+ packageName equals "dot.name"
+ children counts 0
+ }
+ with((this / "simple").cast<DPackage>()) {
+ packageName equals "simple"
+ children counts 0
+ }
+ }
+ }
+
+ @Test
+ fun multipleFilesSamePackage() {
+ inlineModelTest(
+ """
+ |package simple
+ |/src/main/kotlin/packages/Test2.kt
+ |package simple
+ """.trimIndent(),
+ prependPackage = false
+ ) {
+ children counts 1
+ with((this / "simple").cast<DPackage>()) {
+ packageName equals "simple"
+ children counts 0
+ }
+ }
+ }
+
+ @Test
+ fun classAtPackageLevel() {
+ inlineModelTest(
+ """
+ |package simple.name
+ |
+ |class Foo {}
+ """.trimIndent(),
+ prependPackage = false
+ ) {
+ with((this / "simple.name").cast<DPackage>()) {
+ packageName equals "simple.name"
+ children counts 1
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/PropertyTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/PropertyTest.kt
new file mode 100644
index 00000000..92dc56de
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/PropertyTest.kt
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model
+
+import org.jetbrains.dokka.model.*
+import utils.AbstractModelTest
+import utils.assertNotNull
+import utils.name
+import kotlin.test.Test
+
+class PropertyTest : AbstractModelTest("/src/main/kotlin/property/Test.kt", "property") {
+
+ @Test
+ fun valueProperty() {
+ inlineModelTest(
+ """
+ |val property = "test""""
+ ) {
+ with((this / "property" / "property").cast<DProperty>()) {
+ name equals "property"
+ children counts 0
+ with(getter.assertNotNull("Getter")) {
+ type.name equals "String"
+ }
+ type.name equals "String"
+ }
+ }
+ }
+
+ @Test
+ fun variableProperty() {
+ inlineModelTest(
+ """
+ |var property = "test"
+ """
+ ) {
+ with((this / "property" / "property").cast<DProperty>()) {
+ name equals "property"
+ children counts 0
+ setter.assertNotNull("Setter")
+ with(getter.assertNotNull("Getter")) {
+ type.name equals "String"
+ }
+ type.name equals "String"
+ }
+ }
+ }
+
+ @Test
+ fun valuePropertyWithGetter() {
+ inlineModelTest(
+ """
+ |val property: String
+ | get() = "test"
+ """
+ ) {
+ with((this / "property" / "property").cast<DProperty>()) {
+ name equals "property"
+ children counts 0
+ with(getter.assertNotNull("Getter")) {
+ type.name equals "String"
+ }
+ type.name equals "String"
+ }
+ }
+ }
+
+ @Test
+ fun variablePropertyWithAccessors() {
+ inlineModelTest(
+ """
+ |var property: String
+ | get() = "test"
+ | set(value) {}
+ """
+ ) {
+ with((this / "property" / "property").cast<DProperty>()) {
+ name equals "property"
+ children counts 0
+ setter.assertNotNull("Setter")
+ with(getter.assertNotNull("Getter")) {
+ type.name equals "String"
+ }
+ visibility.values allEquals KotlinVisibility.Public
+ }
+ }
+ }
+
+ @Test
+ fun propertyWithReceiver() {
+ inlineModelTest(
+ """
+ |val String.property: Int
+ | get() = size() * 2
+ """
+ ) {
+ with((this / "property" / "property").cast<DProperty>()) {
+ name equals "property"
+ children counts 0
+ with(receiver.assertNotNull("property receiver")) {
+ name equals null
+ type.name equals "String"
+ }
+ with(getter.assertNotNull("Getter")) {
+ type.name equals "Int"
+ }
+ visibility.values allEquals KotlinVisibility.Public
+ }
+ }
+ }
+
+ @Test
+ fun propertyOverride() {
+ inlineModelTest(
+ """
+ |open class Foo() {
+ | open val property: Int get() = 0
+ |}
+ |class Bar(): Foo() {
+ | override val property: Int get() = 1
+ |}
+ """
+ ) {
+ with((this / "property").cast<DPackage>()) {
+ with((this / "Foo" / "property").cast<DProperty>()) {
+ dri.classNames equals "Foo"
+ name equals "property"
+ children counts 0
+ with(getter.assertNotNull("Getter")) {
+ type.name equals "Int"
+ }
+ }
+ with((this / "Bar" / "property").cast<DProperty>()) {
+ dri.classNames equals "Bar"
+ name equals "property"
+ children counts 0
+ with(getter.assertNotNull("Getter")) {
+ type.name equals "Int"
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun propertyInherited() {
+ inlineModelTest(
+ """
+ |open class Foo() {
+ | open val property: Int get() = 0
+ |}
+ |class Bar(): Foo()
+ """
+ ) {
+ with((this / "property").cast<DPackage>()) {
+ with((this / "Bar" / "property").cast<DProperty>()) {
+ dri.classNames equals "Foo"
+ name equals "property"
+ children counts 0
+ with(getter.assertNotNull("Getter")) {
+ type.name equals "Int"
+ }
+ extra[InheritedMember]?.inheritedFrom?.values?.single()?.run {
+ classNames equals "Foo"
+ callable equals null
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun sinceKotlin() {
+ inlineModelTest(
+ """
+ |/**
+ | * Quite useful [String]
+ | */
+ |@SinceKotlin("1.1")
+ |val prop: String = "1.1 rulezz"
+ """
+ ) {
+ with((this / "property" / "prop").cast<DProperty>()) {
+ with(extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 1
+ with(first()) {
+ dri.classNames equals "SinceKotlin"
+ params.entries counts 1
+ (params["version"].assertNotNull("version") as StringValue).value equals "1.1"
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun annotatedProperty() {
+ inlineModelTest(
+ """
+ |@Strictfp var property = "test"
+ """,
+ configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath = listOfNotNull(jvmStdlibPath)
+ }
+ }
+ }
+ ) {
+ with((this / "property" / "property").cast<DProperty>()) {
+ with(extra[Annotations]!!.directAnnotations.entries.single().value.assertNotNull("Annotations")) {
+ this counts 1
+ with(first()) {
+ dri.classNames equals "Strictfp"
+ params.entries counts 0
+ }
+ }
+ }
+ }
+ }
+
+ @Test fun genericTopLevelExtensionProperty(){
+ inlineModelTest(
+ """ | val <T : Number> List<T>.sampleProperty: T
+ | get() { TODO() }
+ """.trimIndent()
+ ){
+ with((this / "property" / "sampleProperty").cast<DProperty>()) {
+ name equals "sampleProperty"
+ with(receiver.assertNotNull("Property receiver")) {
+ type.name equals "List"
+ }
+ with(getter.assertNotNull("Getter")) {
+ type.name equals "T"
+ }
+ setter equals null
+ generics counts 1
+ generics.forEach {
+ it.name equals "T"
+ it.bounds.first().name equals "Number"
+ }
+ visibility.values allEquals KotlinVisibility.Public
+ }
+ }
+ }
+
+ @Test fun genericExtensionPropertyInClass(){
+ inlineModelTest(
+ """ | package test
+ | class XD<T> {
+ | var List<T>.sampleProperty: T
+ | get() { TODO() }
+ | set(value) { TODO() }
+ | }
+ """.trimIndent()
+ ){
+ with((this / "property" / "XD" / "sampleProperty").cast<DProperty>()) {
+ name equals "sampleProperty"
+ children counts 0
+ with(receiver.assertNotNull("Property receiver")) {
+ type.name equals "List"
+ }
+ with(getter.assertNotNull("Getter")) {
+ type.name equals "T"
+ }
+ with(setter.assertNotNull("Setter")){
+ type.name equals "Unit"
+ }
+ generics counts 0
+ visibility.values allEquals KotlinVisibility.Public
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsForParametersTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsForParametersTest.kt
new file mode 100644
index 00000000..9800006b
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsForParametersTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model.annotations
+
+import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.annotations
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.utilities.cast
+import utils.AbstractModelTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class JavaAnnotationsForParametersTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
+
+ @Test
+ fun `function with deprecated parameter`() {
+ inlineModelTest(
+ """
+ |public class Test {
+ | public void fn(@Deprecated String name) {}
+ |}
+ """.trimIndent()
+ ) {
+ with((this / "java" / "Test").cast<DClass>()) {
+ with((this / "fn").cast<DFunction>()) {
+ val dri =
+ parameters.first().extra[Annotations]?.directAnnotations?.flatMap { it.value }?.map { it.dri }
+ assertEquals(listOf(DRI("java.lang", "Deprecated")), dri)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `function with parameter that has custom annotation`() {
+ inlineModelTest(
+ """
+ |@Retention(RetentionPolicy.RUNTIME)
+ |@Target(ElementType.PARAMETER)
+ |public @interface Hello {
+ | public String bar() default "";
+ |}
+ |public class Test {
+ | public void foo(@Hello(bar = "baz") String arg){ }
+ |}
+ """.trimIndent()
+ ) {
+ with((this / "java" / "Test").cast<DClass>()) {
+ with((this / "foo").cast<DFunction>()) {
+ val annotations =
+ parameters.first().extra[Annotations]?.directAnnotations?.flatMap { it.value }
+ val driOfHello = DRI("java", "Hello")
+ val annotationsValues = annotations?.flatMap { it.params.values }?.map { it.toString() }?.toList()
+
+ assertEquals(listOf(driOfHello), annotations?.map { it.dri })
+ assertEquals(listOf("baz"), annotationsValues)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `function with annotated generic parameter`() {
+ inlineModelTest(
+ """
+ |@Retention(RetentionPolicy.RUNTIME)
+ |@Target(ElementType.TYPE_PARAMETER)
+ |@interface Hello {
+ | public String bar() default "";
+ |}
+ |public class Test {
+ | public <@Hello(bar = "baz") T> List<T> foo() {
+ | return null;
+ | }
+ |}
+ """.trimIndent()
+ ) {
+ with((this / "java" / "Test").cast<DClass>()) {
+ with((this / "foo").cast<DFunction>()) {
+ val annotations = generics.first().extra[Annotations]?.directAnnotations?.flatMap { it.value }
+ val driOfHello = DRI("java", "Hello")
+ val annotationsValues = annotations?.flatMap { it.params.values }?.map { it.toString() }?.toList()
+
+ assertEquals(listOf(driOfHello), annotations?.map { it.dri })
+ assertEquals(listOf("baz"), annotationsValues)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `function with generic parameter that has annotated bounds`() {
+ inlineModelTest(
+ """
+ |@Retention(RetentionPolicy.RUNTIME)
+ |@Target({ElementType.TYPE_USE})
+ |@interface Hello {
+ | public String bar() default "";
+ |}
+ |public class Test {
+ | public <T extends @Hello(bar = "baz") String> List<T> foo() {
+ | return null;
+ | }
+ |}
+ """.trimIndent()
+ ) {
+ with((this / "java" / "Test").cast<DClass>()) {
+ with((this / "foo").cast<DFunction>()) {
+ val annotations = ((generics.first().bounds.first() as Nullable).inner as GenericTypeConstructor)
+ .extra[Annotations]?.directAnnotations?.flatMap { it.value }
+ val driOfHello = DRI("java", "Hello")
+ val annotationsValues = annotations?.flatMap { it.params.values }?.map { it.toString() }?.toList()
+
+ assertEquals(listOf(driOfHello), annotations?.map { it.dri })
+ assertEquals(listOf("baz"), annotationsValues)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `type parameter annotations should be visible even if type declaration has none`() {
+ inlineModelTest(
+ """
+ |@Retention(RetentionPolicy.RUNTIME)
+ |@Target(ElementType.PARAMETER)
+ |public @interface Hello {
+ | public String bar() default "";
+ |}
+ |public class Test {
+ | public <T> void foo(java.util.List<@Hello T> param) {}
+ |}
+ """.trimIndent()
+ ) {
+ with((this / "java" / "Test").cast<DClass>()) {
+ with((this / "foo").cast<DFunction>()) {
+ val paramAnnotations = parameters.first()
+ .type.cast<GenericTypeConstructor>()
+ .projections.first().cast<TypeParameter>()
+ .annotations()
+ .values
+ .flatten()
+
+ assertEquals(1, paramAnnotations.size)
+ assertEquals(DRI("java", "Hello"), paramAnnotations[0].dri)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `type parameter annotations should not be propagated from resolved type`() {
+ inlineModelTest(
+ """
+ |@Retention(RetentionPolicy.RUNTIME)
+ |@Target(ElementType.PARAMETER)
+ |public @interface Hello {
+ | public String bar() default "";
+ |}
+ |public class Test {
+ | public <@Hello T> void foo(java.util.List<T> param) {}
+ |}
+ """.trimIndent()
+ ) {
+ with((this / "java" / "Test").cast<DClass>()) {
+ with((this / "foo").cast<DFunction>()) {
+ val paramAnnotations = parameters.first()
+ .type.cast<GenericTypeConstructor>()
+ .projections.first().cast<TypeParameter>()
+ .annotations()
+
+ assertTrue(paramAnnotations.isEmpty())
+ }
+ }
+ }
+ }
+}
+
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsTest.kt
new file mode 100644
index 00000000..daab7dc9
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/JavaAnnotationsTest.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model.annotations
+
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.*
+import translators.findClasslike
+import kotlin.test.*
+
+class JavaAnnotationsTest : BaseAbstractTest() {
+
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/main/java")
+ }
+ }
+ }
+
+ @Test // see https://github.com/Kotlin/dokka/issues/2350
+ fun `should hande array used as annotation param value`() {
+ testInline(
+ """
+ |/src/main/java/annotation/TestClass.java
+ |package annotation;
+ |public class TestClass {
+ | @SimpleAnnotation(clazz = String[].class)
+ | public boolean simpleAnnotation() {
+ | return false;
+ | }
+ |}
+ |
+ |/src/main/java/annotation/SimpleAnnotation.java
+ |package annotation;
+ |@Retention(RetentionPolicy.RUNTIME)
+ |@Target(ElementType.METHOD)
+ |public @interface SimpleAnnotation {
+ | Class<?> clazz();
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesTransformationStage = { module ->
+ val testClass = module.findClasslike("annotation", "TestClass") as DClass
+ assertNotNull(testClass)
+
+ val annotatedFunction = testClass.functions.single { it.name == "simpleAnnotation" }
+ val annotation =
+ annotatedFunction.extra[Annotations]?.directAnnotations?.entries?.single()?.value?.single()
+ assertNotNull(annotation) { "Expected to find an annotation on simpleAnnotation function, found none" }
+ assertEquals("annotation", annotation.dri.packageName)
+ assertEquals("SimpleAnnotation", annotation.dri.classNames)
+ assertEquals(1, annotation.params.size)
+
+ val param = annotation.params.values.single()
+ assertTrue(param is ClassValue)
+ // should probably be Array instead
+ // String matches parsing of Kotlin sources as of now
+ assertEquals("String", param.className)
+ assertEquals("java.lang", param.classDRI.packageName)
+ assertEquals("String", param.classDRI.classNames)
+ }
+ }
+ }
+
+ @Test // see https://github.com/Kotlin/dokka/issues/2551
+ fun `should hande annotation used within annotation params with class param value`() {
+ testInline(
+ """
+ |/src/main/java/annotation/TestClass.java
+ |package annotation;
+ |public class TestClass {
+ | @XmlElementRefs({
+ | @XmlElementRef(name = "NotOffered", namespace = "http://www.gaeb.de/GAEB_DA_XML/DA86/3.3", type = JAXBElement.class, required = false)
+ | })
+ | public List<JAXBElement<Object>> content;
+ |}
+ |
+ |/src/main/java/annotation/XmlElementRefs.java
+ |package annotation;
+ |public @interface XmlElementRefs {
+ | XmlElementRef[] value();
+ |}
+ |
+ |/src/main/java/annotation/XmlElementRef.java
+ |package annotation;
+ |public @interface XmlElementRef {
+ | String name();
+ |
+ | String namespace();
+ |
+ | boolean required();
+ |
+ | Class<JAXBElement> type();
+ |}
+ |
+ |/src/main/java/annotation/JAXBElement.java
+ |package annotation;
+ |public class JAXBElement<T> {
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesTransformationStage = { module ->
+ val testClass = module.findClasslike("annotation", "TestClass") as DClass
+ assertNotNull(testClass)
+
+ val contentField = testClass.properties.find { it.name == "content" }
+ assertNotNull(contentField)
+
+ val annotation = contentField.extra[Annotations]?.directAnnotations?.entries?.single()?.value?.single()
+ assertNotNull(annotation) { "Expected to find an annotation on content field, found none" }
+ assertEquals("XmlElementRefs", annotation.dri.classNames)
+ assertEquals(1, annotation.params.size)
+
+ val arrayParam = annotation.params.values.single()
+ assertTrue(arrayParam is ArrayValue, "Expected single annotation param to be array")
+ assertEquals(1, arrayParam.value.size)
+
+ val arrayParamValue = arrayParam.value.single()
+ assertTrue(arrayParamValue is AnnotationValue)
+
+ val arrayParamAnnotationValue = arrayParamValue.annotation
+ assertEquals(4, arrayParamAnnotationValue.params.size)
+ assertEquals("XmlElementRef", arrayParamAnnotationValue.dri.classNames)
+
+ val annotationParams = arrayParamAnnotationValue.params.values.toList()
+
+ val nameParam = annotationParams[0]
+ assertTrue(nameParam is StringValue)
+ assertEquals("NotOffered", nameParam.value)
+
+ val namespaceParam = annotationParams[1]
+ assertTrue(namespaceParam is StringValue)
+ assertEquals("http://www.gaeb.de/GAEB_DA_XML/DA86/3.3", namespaceParam.value)
+
+ val typeParam = annotationParams[2]
+ assertTrue(typeParam is ClassValue)
+ assertEquals("JAXBElement", typeParam.className)
+ assertEquals("annotation", typeParam.classDRI.packageName)
+ assertEquals("JAXBElement", typeParam.classDRI.classNames)
+
+ val requiredParam = annotationParams[3]
+ assertTrue(requiredParam is BooleanValue)
+ assertFalse(requiredParam.value)
+ }
+ }
+ }
+
+ @Test // see https://github.com/Kotlin/dokka/issues/2509
+ fun `should handle generic class in annotation`() {
+ testInline(
+ """
+ |/src/main/java/annotation/Breaking.java
+ |package annotation;
+ |public class Breaking<Y> {
+ |}
+ |
+ |/src/main/java/annotation/TestAnnotate.java
+ |package annotation;
+ |public @interface TestAnnotate {
+ | Class<?> value();
+ |}
+ |
+ |/src/main/java/annotation/TestClass.java
+ |package annotation;
+ |@TestAnnotate(Breaking.class)
+ |public class TestClass {
+ |}
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesTransformationStage = { module ->
+ val testClass = module.findClasslike("annotation", "TestClass") as DClass
+ assertNotNull(testClass)
+
+ val annotation = testClass.extra[Annotations]?.directAnnotations?.entries?.single()?.value?.single()
+ assertNotNull(annotation) { "Expected to find an annotation on TestClass, found none" }
+
+ assertEquals("TestAnnotate", annotation.dri.classNames)
+ assertEquals(1, annotation.params.size)
+
+ val valueParameter = annotation.params.values.single()
+ assertTrue(valueParameter is ClassValue)
+
+ assertEquals("Breaking", valueParameter.className)
+
+ assertEquals("annotation", valueParameter.classDRI.packageName)
+ assertEquals("Breaking", valueParameter.classDRI.classNames)
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/KotlinAnnotationsForParametersTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/KotlinAnnotationsForParametersTest.kt
new file mode 100644
index 00000000..e3b17818
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/annotations/KotlinAnnotationsForParametersTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package model.annotations
+
+import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.annotations
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.utilities.cast
+import utils.AbstractModelTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class KotlinAnnotationsForParametersTest : AbstractModelTest("/src/main/kotlin/annotations/Test.kt", "annotations") {
+ @Test
+ fun `generic receiver with annotations`() {
+ inlineModelTest(
+ """
+ |@Target(AnnotationTarget.TYPE_PARAMETER)
+ |annotation class Hello(val bar: String)
+ |fun <@Hello("abc") T> foo(arg: String): List<T> = TODO()
+ """.trimIndent()
+ ) {
+ with((this / "annotations" / "foo").cast<DFunction>()) {
+ val annotations = generics.first().extra[Annotations]?.directAnnotations?.flatMap { it.value }
+ val driOfHello = DRI("annotations", "Hello")
+ val annotationsValues = annotations?.flatMap { it.params.values }?.map { it.toString() }?.toList()
+
+ assertEquals(listOf(driOfHello), annotations?.map { it.dri })
+ assertEquals(listOf("abc"), annotationsValues)
+ }
+ }
+ }
+
+ @Test
+ fun `generic receiver with annotated bounds`() {
+ inlineModelTest(
+ """
+ |@Target(AnnotationTarget.TYPE_PARAMETER)
+ |annotation class Hello(val bar: String)
+ |fun <T: @Hello("abc") String> foo(arg: String): List<T> = TODO()
+ """.trimIndent()
+ ) {
+ with((this / "annotations" / "foo").cast<DFunction>()) {
+ val annotations = (generics.first().bounds.first() as GenericTypeConstructor)
+ .extra[Annotations]?.directAnnotations?.flatMap { it.value }
+ val driOfHello = DRI("annotations", "Hello")
+ val annotationsValues = annotations?.flatMap { it.params.values }?.map { it.toString() }?.toList()
+
+ assertEquals(listOf(driOfHello), annotations?.map { it.dri })
+ assertEquals(listOf("abc"), annotationsValues)
+ }
+ }
+ }
+
+ @Test
+ fun `type parameter annotations should be visible even if type declaration has none`() {
+ inlineModelTest(
+ """
+ |@Target(AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.TYPE)
+ |annotation class Hello
+ |
+ |fun <T> foo(param: List<@Hello T>) {}
+ """.trimIndent()
+ ) {
+ with((this / "annotations" / "foo").cast<DFunction>()) {
+ val paramAnnotations = parameters.first()
+ .type.cast<GenericTypeConstructor>()
+ .projections
+ .first().cast<Invariance<TypeParameter>>()
+ .inner.cast<TypeParameter>()
+ .annotations()
+ .values
+ .flatten()
+
+ assertEquals(1, paramAnnotations.size)
+ assertEquals(DRI("annotations", "Hello"), paramAnnotations[0].dri)
+ }
+ }
+ }
+
+ @Test
+ fun `type parameter annotations should not be propagated from resolved type`() {
+ inlineModelTest(
+ """
+ |@Target(AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.TYPE)
+ |annotation class Hello
+ |
+ |fun <@Hello T> foo(param: List<T>) {}
+ """.trimIndent()
+ ) {
+ with((this / "annotations" / "foo").cast<DFunction>()) {
+ val paramAnnotations = parameters.first()
+ .type.cast<GenericTypeConstructor>()
+ .projections.first().cast<Invariance<TypeParameter>>()
+ .inner.cast<TypeParameter>()
+ .annotations()
+
+ assertTrue(paramAnnotations.isEmpty())
+ }
+ }
+ }
+}