diff options
Diffstat (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/model')
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()) + } + } + } +} |