aboutsummaryrefslogtreecommitdiff
path: root/dokka-subprojects/plugin-base/src/test/kotlin/signatures
diff options
context:
space:
mode:
authorIgnat Beresnev <ignat.beresnev@jetbrains.com>2023-11-10 11:46:54 +0100
committerGitHub <noreply@github.com>2023-11-10 11:46:54 +0100
commit8e5c63d035ef44a269b8c43430f43f5c8eebfb63 (patch)
tree1b915207b2b9f61951ddbf0ff2e687efd053d555 /dokka-subprojects/plugin-base/src/test/kotlin/signatures
parenta44efd4ba0c2e4ab921ff75e0f53fc9335aa79db (diff)
downloaddokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.gz
dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.bz2
dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.zip
Restructure the project to utilize included builds (#3174)
* Refactor and simplify artifact publishing * Update Gradle to 8.4 * Refactor and simplify convention plugins and build scripts Fixes #3132 --------- Co-authored-by: Adam <897017+aSemy@users.noreply.github.com> Co-authored-by: Oleg Yukhnevich <whyoleg@gmail.com>
Diffstat (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/signatures')
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/signatures/AbstractRenderingTest.kt65
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/signatures/DivergentSignatureTest.kt73
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/signatures/FunctionalTypeConstructorsSignatureTest.kt312
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/signatures/InheritedAccessorsSignatureTest.kt461
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/signatures/ObviousTypeSkippingTest.kt206
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/signatures/RawHtmlRenderingTest.kt70
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/signatures/SignatureTest.kt1035
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/signatures/VarianceSignatureTest.kt108
8 files changed, 2330 insertions, 0 deletions
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/signatures/AbstractRenderingTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/AbstractRenderingTest.kt
new file mode 100644
index 00000000..4c4bbc4c
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/AbstractRenderingTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package signatures
+
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Element
+import org.jsoup.select.Elements
+import utils.TestOutputWriterPlugin
+import java.nio.file.Path
+import java.nio.file.Paths
+
+abstract class AbstractRenderingTest : BaseAbstractTest() {
+ val testDataDir: Path = getTestDataDir("multiplatform/basicMultiplatformTest").toAbsolutePath()
+
+ val configuration = dokkaConfiguration {
+ moduleName = "example"
+ sourceSets {
+ val common = sourceSet {
+ name = "common"
+ displayName = "common"
+ analysisPlatform = "common"
+ sourceRoots = listOf(Paths.get("$testDataDir/commonMain/kotlin").toString())
+ }
+ val jvmAndJsSecondCommonMain = sourceSet {
+ name = "jvmAndJsSecondCommonMain"
+ displayName = "jvmAndJsSecondCommonMain"
+ analysisPlatform = "common"
+ dependentSourceSets = setOf(common.value.sourceSetID)
+ sourceRoots = listOf(Paths.get("$testDataDir/jvmAndJsSecondCommonMain/kotlin").toString())
+ }
+ sourceSet {
+ name = "js"
+ displayName = "js"
+ analysisPlatform = "js"
+ dependentSourceSets = setOf(common.value.sourceSetID, jvmAndJsSecondCommonMain.value.sourceSetID)
+ sourceRoots = listOf(Paths.get("$testDataDir/jsMain/kotlin").toString())
+ }
+ sourceSet {
+ name = "jvm"
+ displayName = "jvm"
+ analysisPlatform = "jvm"
+ dependentSourceSets = setOf(common.value.sourceSetID, jvmAndJsSecondCommonMain.value.sourceSetID)
+ sourceRoots = listOf(Paths.get("$testDataDir/jvmMain/kotlin").toString())
+ }
+ }
+ }
+
+ fun TestOutputWriterPlugin.renderedContent(path: String): Element = writer.contents.getValue(path)
+ .let { Jsoup.parse(it) }.select("#content").single()
+
+ fun TestOutputWriterPlugin.renderedDivergentContent(path: String): Elements =
+ renderedContent(path).select("div.divergent-group")
+
+ fun TestOutputWriterPlugin.renderedSourceDependentContent(path: String): Elements =
+ renderedContent(path).select("div.sourceset-dependent-content")
+
+ val Element.brief: String
+ get() = children().select("p").text()
+
+ val Element.rawBrief: String
+ get() = children().select("p").html()
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/signatures/DivergentSignatureTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/DivergentSignatureTest.kt
new file mode 100644
index 00000000..509dd6e7
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/DivergentSignatureTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package signatures
+
+import utils.TestOutputWriterPlugin
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+
+class DivergentSignatureTest : AbstractRenderingTest() {
+
+ @Test
+ fun `group { common + jvm + js }`() {
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testFromData(
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val content = writerPlugin.renderedSourceDependentContent("example/example/-clock/get-time.html")
+
+ assertEquals(3, content.count())
+ val sourceSets = listOf("example/common", "example/js", "example/jvm")
+ sourceSets.forEach {
+ assertEquals("", content.select("[data-togglable=$it]").single().brief)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `group { common + jvm }, group { js }`() {
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testFromData(
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val content = writerPlugin.renderedSourceDependentContent("example/example/-clock/get-times-in-millis.html")
+
+ assertEquals(3, content.count())
+ assertEquals("Time in minis", content.select("[data-togglable=example/common]").single().brief)
+ assertEquals("Time in minis", content.select("[data-togglable=example/jvm]").single().brief)
+ assertEquals("JS implementation of getTimeInMillis", content.select("[data-togglable=example/js]").single().brief)
+ }
+ }
+ }
+
+ @Test
+ fun `group { js }, group { jvm }, group { js }`() {
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testFromData(
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val content = writerPlugin.renderedSourceDependentContent("example/example/-clock/get-year.html")
+ assertEquals(3, content.count())
+ assertEquals("JVM custom kdoc", content.select("[data-togglable=example/jvm]").single().brief)
+ assertEquals("JS custom kdoc", content.select("[data-togglable=example/js]").single().brief)
+ assertEquals("", content.select("[data-togglable=example/common]").single().brief)
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/signatures/FunctionalTypeConstructorsSignatureTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/FunctionalTypeConstructorsSignatureTest.kt
new file mode 100644
index 00000000..13d1947f
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/FunctionalTypeConstructorsSignatureTest.kt
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package signatures
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.jdk
+import utils.A
+import utils.Span
+import utils.TestOutputWriterPlugin
+import utils.match
+import kotlin.test.Ignore
+import kotlin.test.Test
+
+class FunctionalTypeConstructorsSignatureTest : BaseAbstractTest() {
+ private val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath = listOf(commonStdlibPath!!, jvmStdlibPath!!)
+ externalDocumentationLinks = listOf(
+ stdlibExternalDocumentationLink,
+ DokkaConfiguration.ExternalDocumentationLink.Companion.jdk(8)
+ )
+ }
+ }
+ }
+
+ private val jvmConfiguration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath = listOf(jvmStdlibPath ?: throw IllegalStateException("JVM stdlib is not found"))
+ externalDocumentationLinks = listOf(
+ stdlibExternalDocumentationLink,
+ DokkaConfiguration.ExternalDocumentationLink.Companion.jdk(8)
+ )
+ }
+ }
+ }
+
+ fun source(signature: String) =
+ """
+ |/src/main/kotlin/test/Test.kt
+ |package example
+ |
+ | $signature
+ """.trimIndent()
+
+ @Test
+ fun `kotlin normal function`() {
+ val source = source("val nF: Function1<Int, String> = { _ -> \"\" }")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "val ", A("nF"), ": (", A("Int"), ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `kotlin syntactic sugar function`() {
+ val source = source("val nF: (Int) -> String = { _ -> \"\" }")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "val ", A("nF"), ": (", A("Int"), ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `kotlin syntactic sugar extension function`() {
+ val source = source("val nF: Boolean.(Int) -> String = { _ -> \"\" }")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "val ", A("nF"), ": ", A("Boolean"), ".(", A("Int"), ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `kotlin syntactic sugar function with param name`() {
+ val source = source("val nF: (param: Int) -> String = { _ -> \"\" }")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "val ", A("nF"), ": (param: ", A("Int"), ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `kotlin syntactic sugar function with param name of generic and functional type`() {
+ val source = source("""
+ | @Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
+ | @MustBeDocumented
+ | annotation class Fancy
+ |
+ | fun <T> f(): (param1: T, param2: @Fancy ()->Unit) -> String "
+ """.trimIndent())
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source, configuration, pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").lastSignature().match(
+ "fun <", A("T"), "> ",
+ A("f"), "(): (param1:", A("T"),
+ ", param2: ", Span("@", A("Fancy")), " () -> ", A("Unit"),
+ ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+ @Ignore // Add coroutines on classpath and get proper import
+ @Test
+ fun `kotlin normal suspendable function`() {
+ val source = source("val nF: SuspendFunction1<Int, String> = { _ -> \"\" }")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "val ", A("nF"), ": suspend (", A("Int"), ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `kotlin syntactic sugar suspendable function`() {
+ val source = source("val nF: suspend (Int) -> String = { _ -> \"\" }")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "val ", A("nF"), ": suspend (", A("Int"), ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `kotlin syntactic sugar suspendable extension function`() {
+ val source = source("val nF: suspend Boolean.(Int) -> String = { _ -> \"\" }")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "val ", A("nF"), ": suspend ", A("Boolean"), ".(", A("Int"), ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `kotlin syntactic sugar suspendable function with param name`() {
+ val source = source("val nF: suspend (param: Int) -> String = { _ -> \"\" }")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "val ", A("nF"), ": suspend (param: ", A("Int"), ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `kotlin syntactic sugar suspendable fancy function with param name`() {
+ val source =
+ source("val nF: suspend (param1: suspend Boolean.(param2: List<Int>) -> Boolean) -> String = { _ -> \"\" }")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "val ",
+ A("nF"),
+ ": suspend (param1: suspend",
+ A("Boolean"),
+ ".(param2: ",
+ A("List"),
+ "<",
+ A("Int"),
+ ">) -> ",
+ A("Boolean"),
+ ") -> ",
+ A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `java with java function`() {
+ val source = """
+ |/src/main/kotlin/test/JavaClass.java
+ |package example
+ |
+ |public class JavaClass {
+ | public java.util.function.Function<Integer, String> javaFunction = null;
+ |}
+ """.trimIndent()
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-java-class/index.html").lastSignature().match(
+ "open var ", A("javaFunction"), ": (", A("Integer"), ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `java with kotlin function`() {
+ val source = """
+ |/src/main/kotlin/test/JavaClass.java
+ |package example
+ |
+ |public class JavaClass {
+ | public kotlin.jvm.functions.Function1<Integer, String> kotlinFunction = null;
+ |}
+ """.trimIndent()
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ jvmConfiguration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-java-class/index.html").lastSignature().match(
+ "open var ", A("kotlinFunction"), ": (", A("Integer"), ") -> ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/signatures/InheritedAccessorsSignatureTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/InheritedAccessorsSignatureTest.kt
new file mode 100644
index 00000000..b5e2a9c3
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/InheritedAccessorsSignatureTest.kt
@@ -0,0 +1,461 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package signatures
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import utils.A
+import utils.Span
+import utils.TestOutputWriterPlugin
+import utils.match
+import utils.OnlyDescriptors
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class InheritedAccessorsSignatureTest : BaseAbstractTest() {
+
+ private val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath = listOf(
+ commonStdlibPath ?: throw IllegalStateException("Common stdlib is not found"),
+ jvmStdlibPath ?: throw IllegalStateException("JVM stdlib is not found")
+ )
+ externalDocumentationLinks = listOf(stdlibExternalDocumentationLink)
+ }
+ }
+ }
+
+ @OnlyDescriptors("'var' expected but found: 'open var'")
+ @Test
+ fun `should collapse accessor functions inherited from java into the property`() {
+ val writerPlugin = TestOutputWriterPlugin()
+ testInline(
+ """
+ |/src/test/A.java
+ |package test;
+ |public class A {
+ | private int a = 1;
+ | public int getA() { return a; }
+ | public void setA(int a) { this.a = a; }
+ |}
+ |
+ |/src/test/B.kt
+ |package test
+ |class B : A {}
+ """.trimIndent(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/test/-b/index.html").let { kotlinClassContent ->
+ val signatures = kotlinClassContent.signature().toList()
+ assertEquals(
+ 3, signatures.size,
+ "Expected 3 signatures: class signature, constructor and property"
+ )
+
+ val property = signatures[2]
+ property.match(
+ "var ", A("a"), ":", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+
+ writerPlugin.writer.renderedContent("root/test/-a/index.html").let { javaClassContent ->
+ val signatures = javaClassContent.signature().toList()
+ assertEquals(
+ 3, signatures.size,
+ "Expected 3 signatures: class signature, default constructor and property"
+ )
+
+ val property = signatures[2]
+ property.match(
+ "open var ", A("a"), ":", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+ }
+
+ @OnlyDescriptors("'var' expected but found: 'open var'")
+ @Test
+ fun `should render as val if inherited java property has no setter`() {
+ val writerPlugin = TestOutputWriterPlugin()
+ testInline(
+ """
+ |/src/test/A.java
+ |package test;
+ |public class A {
+ | private int a = 1;
+ | public int getA() { return a; }
+ |}
+ |
+ |/src/test/B.kt
+ |package test
+ |class B : A {}
+ """.trimIndent(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/test/-b/index.html").let { kotlinClassContent ->
+ val signatures = kotlinClassContent.signature().toList()
+ assertEquals(3, signatures.size, "Expected 3 signatures: class signature, constructor and property")
+
+ val property = signatures[2]
+ property.match(
+ "val ", A("a"), ":", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+
+ writerPlugin.writer.renderedContent("root/test/-a/index.html").let { javaClassContent ->
+ val signatures = javaClassContent.signature().toList()
+ assertEquals(
+ 3,
+ signatures.size,
+ "Expected 3 signatures: class signature, default constructor and property"
+ )
+
+ val property = signatures[2]
+ property.match(
+ "open val ", A("a"), ":", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `should keep inherited java setter as a regular function due to inaccessible property`() {
+ val writerPlugin = TestOutputWriterPlugin()
+ testInline(
+ """
+ |/src/test/A.java
+ |package test;
+ |public class A {
+ | private int a = 1;
+ | public void setA(int a) { this.a = a; }
+ |}
+ |
+ |/src/test/B.kt
+ |package test
+ |class B : A {}
+ """.trimIndent(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/test/-b/index.html").let { kotlinClassContent ->
+ val signatures = kotlinClassContent.signature().toList()
+ assertEquals(3, signatures.size, "Expected 3 signatures: class signature, constructor and setter")
+
+ val setterFunction = signatures[2]
+ setterFunction.match(
+ "open fun ", A("setA"), "(", Parameters(
+ Parameter("a: ", A("Int"))
+ ), ")",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+
+ writerPlugin.writer.renderedContent("root/test/-a/index.html").let { javaClassContent ->
+ val signatures = javaClassContent.signature().toList()
+ assertEquals(
+ 3,
+ signatures.size,
+ "Expected 3 signatures: class signature, default constructor and setter"
+ )
+
+ val setterFunction = signatures[2]
+ setterFunction.match(
+ "open fun ", A("setA"), "(", Parameters(
+ Parameter("a: ", A("Int"))
+ ), ")",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+ }
+
+ @OnlyDescriptors("'var' expected but found: 'open var'")
+ @Test
+ fun `should keep inherited java accessor lookalikes if underlying function is public`() {
+ val writerPlugin = TestOutputWriterPlugin()
+ testInline(
+ """
+ |/src/test/A.java
+ |package test;
+ |public class A {
+ | public int a = 1;
+ | public int getA() { return a; }
+ | public void setA(int a) { this.a = a; }
+ |}
+ |
+ |/src/test/B.kt
+ |package test
+ |class B : A {}
+ """.trimIndent(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val signatures = writerPlugin.writer.renderedContent("root/test/-b/index.html").signature().toList()
+ assertEquals(
+ 5, signatures.size,
+ "Expected 5 signatures: class signature, constructor, property and two accessor lookalikes"
+ )
+
+ val getterLookalikeFunction = signatures[3]
+ getterLookalikeFunction.match(
+ "open fun ", A("getA"), "():", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+
+ val setterLookalikeFunction = signatures[4]
+ setterLookalikeFunction.match(
+ "open fun ", A("setA"), "(", Parameters(
+ Parameter("a: ", A("Int"))
+ ), ")",
+ ignoreSpanWithTokenStyle = true
+ )
+
+ val property = signatures[2]
+ property.match(
+ "var ", A("a"), ":", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `should keep kotlin property with no accessors when java inherits kotlin a var`() {
+ val writerPlugin = TestOutputWriterPlugin()
+ testInline(
+ """
+ |/src/test/JavaClass.java
+ |package test;
+ |public class JavaClass extends KotlinClass {}
+ |
+ |/src/test/KotlinClass.kt
+ |package test
+ |open class KotlinClass {
+ | var variable: String = "s"
+ |}
+ """.trimIndent(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/test/-java-class/index.html").let { kotlinClassContent ->
+ val signatures = kotlinClassContent.signature().toList()
+ assertEquals(
+ 3,
+ signatures.size,
+ "Expected to find 3 signatures: class, default constructor and property"
+ )
+
+ val property = signatures[2]
+ property.match(
+ "open var ", A("variable"), ": ", Span("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `kotlin property with compute get and set`() {
+ val writerPlugin = TestOutputWriterPlugin()
+ testInline(
+ """
+ |/src/test/JavaClass.java
+ |package test;
+ |public class JavaClass extends KotlinClass {}
+ |
+ |/src/test/KotlinClass.kt
+ |package test
+ |open class KotlinClass {
+ | var variable: String
+ | get() = "asd"
+ | set(value) {}
+ |}
+ """.trimIndent(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/test/-kotlin-class/index.html").let { kotlinClassContent ->
+ val signatures = kotlinClassContent.signature().toList()
+ assertEquals(3, signatures.size, "Expected to find 3 signatures: class, constructor and property")
+
+ val property = signatures[2]
+ property.match(
+ "var ", A("variable"), ": ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+
+ // it's actually unclear how it should react in this situation. It should most likely not
+ // break the abstraction and display it as a simple variable just like can be seen from Kotlin,
+ // test added to control changes
+ writerPlugin.writer.renderedContent("root/test/-java-class/index.html").let { javaClassContent ->
+ val signatures = javaClassContent.signature().toList()
+ assertEquals(
+ 4,
+ signatures.size,
+ "Expected to find 4 signatures: class, default constructor and two accessors"
+ )
+
+ val getter = signatures[2]
+ getter.match(
+ "fun ", A("getVariable"), "(): ", Span("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+
+ val setter = signatures[3]
+ setter.match(
+ "fun ", A("setVariable"), "(", Parameters(
+ Parameter("value: ", Span("String"))
+ ), ")",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+ }
+
+ @OnlyDescriptors("'var' expected but found: 'open var'")
+ @Test
+ fun `inherited property should inherit getter's visibility`() {
+ val configWithProtectedVisibility = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath = listOf(
+ commonStdlibPath ?: throw IllegalStateException("Common stdlib is not found"),
+ jvmStdlibPath ?: throw IllegalStateException("JVM stdlib is not found")
+ )
+ externalDocumentationLinks = listOf(stdlibExternalDocumentationLink)
+ documentedVisibilities = setOf(
+ DokkaConfiguration.Visibility.PUBLIC,
+ DokkaConfiguration.Visibility.PROTECTED
+ )
+ }
+ }
+ }
+
+ val writerPlugin = TestOutputWriterPlugin()
+ testInline(
+ """
+ |/src/test/JavaClass.java
+ |package test;
+ |public class JavaClass {
+ | private int protectedGetterAndProtectedSetter = 0;
+ |
+ | protected int getProtectedGetterAndProtectedSetter() {
+ | return protectedGetterAndProtectedSetter;
+ | }
+ |
+ | protected void setProtectedGetterAndProtectedSetter(int protectedGetterAndProtectedSetter) {
+ | this.protectedGetterAndProtectedSetter = protectedGetterAndProtectedSetter;
+ | }
+ |}
+ |
+ |/src/test/KotlinClass.kt
+ |package test
+ |open class KotlinClass : JavaClass() { }
+ """.trimIndent(),
+ configWithProtectedVisibility,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/test/-kotlin-class/index.html").let { kotlinClassContent ->
+ val signatures = kotlinClassContent.signature().toList()
+ assertEquals(3, signatures.size, "Expected 3 signatures: class signature, constructor and property")
+
+ val property = signatures[2]
+ property.match(
+ "protected var ", A("protectedGetterAndProtectedSetter"), ":", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+
+ writerPlugin.writer.renderedContent("root/test/-java-class/index.html").let { javaClassContent ->
+ val signatures = javaClassContent.signature().toList()
+ assertEquals(
+ 3,
+ signatures.size,
+ "Expected 3 signatures: class signature, default constructor and property"
+ )
+
+ val property = signatures[2]
+ property.match(
+ "protected open var ", A("protectedGetterAndProtectedSetter"), ":", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+ }
+
+ @OnlyDescriptors("'var' expected but found: 'open var'")
+ @Test
+ fun `should resolve protected java property as protected`() {
+ val configWithProtectedVisibility = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath = listOf(
+ commonStdlibPath ?: throw IllegalStateException("Common stdlib is not found"),
+ jvmStdlibPath ?: throw IllegalStateException("JVM stdlib is not found")
+ )
+ externalDocumentationLinks = listOf(stdlibExternalDocumentationLink)
+ documentedVisibilities = setOf(
+ DokkaConfiguration.Visibility.PUBLIC,
+ DokkaConfiguration.Visibility.PROTECTED
+ )
+ }
+ }
+ }
+
+ val writerPlugin = TestOutputWriterPlugin()
+ testInline(
+ """
+ |/src/test/JavaClass.java
+ |package test;
+ |public class JavaClass {
+ | protected int protectedProperty = 0;
+ |}
+ |
+ |/src/test/KotlinClass.kt
+ |package test
+ |open class KotlinClass : JavaClass() { }
+ """.trimIndent(),
+ configWithProtectedVisibility,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/test/-kotlin-class/index.html").let { kotlinClassContent ->
+ val signatures = kotlinClassContent.signature().toList()
+ assertEquals(3, signatures.size, "Expected 2 signatures: class signature, constructor and property")
+
+ val property = signatures[2]
+ property.match(
+ "protected var ", A("protectedProperty"), ":", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/signatures/ObviousTypeSkippingTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/ObviousTypeSkippingTest.kt
new file mode 100644
index 00000000..71a0851b
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/ObviousTypeSkippingTest.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package signatures
+
+import matchers.content.assertNode
+import matchers.content.hasExactText
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.firstMemberOfType
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.testApi.logger.TestLogger
+import org.jetbrains.dokka.utilities.DokkaConsoleLogger
+import org.jetbrains.dokka.utilities.LoggingLevel
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+import kotlin.reflect.KClass
+
+class ObviousTypeSkippingTest : BaseAbstractTest(
+ logger = TestLogger(DokkaConsoleLogger(LoggingLevel.WARN))
+) {
+
+ private fun source(signature: String) =
+ """
+ |/src/test.kt
+ |package example
+ |
+ | $signature
+ """.trimIndent()
+
+ private val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ classpath = listOfNotNull(jvmStdlibPath)
+ }
+ }
+ }
+
+ companion object TestDataSources {
+ @JvmStatic
+ fun `run tests for obvious types omitting`() = listOf(
+ forFunction("fun underTest(): Int = 5", "fun underTest(): Int"),
+ forFunction("fun underTest() = 5", "fun underTest(): Int"),
+ forFunction("fun underTest() {}", "fun underTest()"),
+ forFunction("fun underTest() = println(6)", "fun underTest()"),
+ forFunction("fun underTest(): Unit = println(6)", "fun underTest()"),
+ forFunction("fun underTest(): Unit? = if (true) println(6) else null", "fun underTest(): Unit?"),
+ forFunction("fun underTest() = if (true) println(6) else null", "fun underTest(): Unit?"),
+ forFunction("fun underTest(): Any = if (true) 7 else true", "fun underTest(): Any"),
+ forFunction("fun underTest() = if (true) 7 else true", "fun underTest(): Any"),
+ forFunction("fun underTest(): Any? = if (true) 7 else (null as String?)", "fun underTest(): Any?"),
+ forFunction("fun underTest() = if (true) 7 else (null as String?)", "fun underTest(): Any?"),
+ forFunction("fun underTest(arg: Int) {}", "fun underTest(arg: Int)"),
+ forFunction("fun underTest(arg: Unit) {}", "fun underTest(arg: Unit)"),
+ forFunction("fun <T: Iterable<Any>> underTest(arg: T) {}", "fun <T : Iterable<Any>> underTest(arg: T)"),
+ forFunction("fun <T: Iterable<Any?>> underTest(arg: T) {}", "fun <T : Iterable<Any?>> underTest(arg: T)"),
+ forFunction("fun <T> underTest(arg: T) {}", "fun <T> underTest(arg: T)"),
+ forFunction("fun <T: Any> underTest(arg: T) {}", "fun <T : Any> underTest(arg: T)"),
+ forFunction("fun <T: Any?> underTest(arg: T) {}", "fun <T> underTest(arg: T)"),
+ forProperty("val underTest: Int = 5", "val underTest: Int = 5"),
+ forProperty("val underTest = 5", "val underTest: Int = 5"),
+ forProperty("val underTest: Unit = println(5)", "val underTest: Unit"),
+ forProperty("val underTest = println(5)", "val underTest: Unit"),
+ forProperty("val underTest: Unit? = if (true) println(5) else null", "val underTest: Unit?"),
+ forProperty("val underTest = if (true) println(5) else null", "val underTest: Unit?"),
+ forProperty("val underTest: Any = if (true) println(5) else 5", "val underTest: Any"),
+ forProperty("val underTest = if (true) println(5) else 5", "val underTest: Any"),
+ forExtension("fun <T: Iterable<Any>> T.underTest() {}", "fun <T : Iterable<Any>> T.underTest()"),
+ forExtension("fun <T: Iterable<Any?>> T.underTest() {}", "fun <T : Iterable<Any?>> T.underTest()"),
+ forExtension("fun <T: Iterable<Any?>?> T.underTest() {}", "fun <T : Iterable<Any?>?> T.underTest()"),
+ forExtension("fun <T: Any> T.underTest() {}", "fun <T : Any> T.underTest()"),
+ forExtension("fun <T: Any?> T.underTest() {}", "fun <T> T.underTest()"),
+ forExtension("fun <T> T.underTest() {}", "fun <T> T.underTest()"),
+ forClass("class Testable<T: Any>", "class Testable<T : Any>"),
+ forClass("class Testable<T: Any?>", "class Testable<T>"),
+ forClass("class Testable<T: Any?>(t: T)", "class Testable<T>(t: T)"),
+ forClass("class Testable<T>", "class Testable<T>"),
+ forClass("class Testable(butWhy: Unit)", "class Testable(butWhy: Unit)"),
+ forMethod("class Testable { fun underTest(): Int = 5 }", "fun underTest(): Int"),
+ forMethod("class Testable { fun underTest() = 5 }", "fun underTest(): Int"),
+ forMethod("class Testable { fun underTest() {} }", "fun underTest()"),
+ forMethod("class Testable { fun underTest() = println(6) }", "fun underTest()"),
+ forMethod("class Testable { fun underTest(): Unit = println(6) }", "fun underTest()"),
+ forMethod(
+ "class Testable { fun underTest(): Unit? = if (true) println(6) else null }",
+ "fun underTest(): Unit?"
+ ),
+ forClassProperty("class Testable { val underTest: Unit = println(5) }", "val underTest: Unit"),
+ forClassProperty("class Testable { val underTest = println(5) }", "val underTest: Unit"),
+ forClassProperty(
+ "class Testable { val underTest: Unit? = if (true) println(5) else null }",
+ "val underTest: Unit?"
+ ),
+ forClassProperty(
+ "class Testable { val underTest = if (true) println(5) else null }",
+ "val underTest: Unit?"
+ ),
+ forClassProperty(
+ "class Testable { val underTest: Any = if (true) println(5) else 5 }",
+ "val underTest: Any"
+ ),
+ forClassProperty("class Testable { val underTest = if (true) println(5) else 5 }", "val underTest: Any"),
+ )
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource
+ fun `run tests for obvious types omitting`(testData: TestData) {
+ val (codeFragment, expectedSignature, placesToTest) = testData
+ testInline(
+ query = source(codeFragment),
+ configuration = configuration
+ ) {
+ pagesTransformationStage = { root ->
+ placesToTest.forEach { place ->
+ try {
+ when (place) {
+ is OnOwnPage ->
+ root.firstMemberOfType<ContentPage> { it.name == place.name }.content
+ .firstMemberOfType<ContentGroup> { it.dci.kind == ContentKind.Symbol }
+ .assertNode { hasExactText(expectedSignature) }
+ is OnParentPage ->
+ root.firstMemberOfType<ContentPage> {
+ place.pageType.isInstance(it) && (place.parentName.isNullOrBlank() || place.parentName == it.name)
+ }
+ .content
+ .firstMemberOfType<ContentGroup> {
+ it.dci.kind == place.section && (place.selfName.isNullOrBlank() ||
+ it.dci.dri.toString().contains(place.selfName))
+ }
+ .firstMemberOfType<ContentGroup> { it.dci.kind == ContentKind.Symbol }
+ .assertNode { hasExactText(expectedSignature) }
+ }
+ } catch (e: Throwable) {
+ logger.warn("$testData") // Because gradle has serious problem rendering custom test names
+ throw e
+ }
+ }
+ }
+ }
+ }
+
+}
+
+sealed class Place
+data class OnOwnPage(val name: String) : Place()
+data class OnParentPage(
+ val pageType: KClass<out ContentPage>,
+ val section: Kind,
+ val parentName: String? = null,
+ val selfName: String? = null
+) : Place()
+
+data class TestData(
+ val codeFragment: String,
+ val expectedSignature: String,
+ val placesToTest: Iterable<Place>
+) {
+ constructor(codeFragment: String, expectedSignature: String, vararg placesToTest: Place)
+ : this(codeFragment, expectedSignature, placesToTest.asIterable())
+
+ override fun toString() = "[code = \"$codeFragment\"]"
+}
+
+private fun forFunction(codeFragment: String, expectedSignature: String, functionName: String = "underTest") =
+ TestData(
+ codeFragment,
+ expectedSignature,
+ OnParentPage(PackagePageNode::class, ContentKind.Functions),
+ OnOwnPage(functionName)
+ )
+
+private fun forExtension(codeFragment: String, expectedSignature: String, functionName: String = "underTest") =
+ TestData(
+ codeFragment,
+ expectedSignature,
+ OnParentPage(PackagePageNode::class, ContentKind.Extensions),
+ OnOwnPage(functionName)
+ )
+private fun forMethod(
+ codeFragment: String,
+ expectedSignature: String,
+ functionName: String = "underTest",
+ className: String = "Testable"
+) =
+ TestData(
+ codeFragment,
+ expectedSignature,
+ OnParentPage(ClasslikePageNode::class, ContentKind.Functions, className, functionName),
+ OnOwnPage(functionName)
+ )
+
+private fun forProperty(codeFragment: String, expectedSignature: String) =
+ TestData(codeFragment, expectedSignature, OnParentPage(PackagePageNode::class, ContentKind.Properties))
+
+private fun forClassProperty(codeFragment: String, expectedSignature: String, className: String = "Testable") =
+ TestData(codeFragment, expectedSignature, OnParentPage(ClasslikePageNode::class, ContentKind.Properties, className))
+
+private fun forClass(codeFragment: String, expectedSignature: String, className: String = "Testable") =
+ TestData(
+ codeFragment,
+ expectedSignature,
+ OnParentPage(PackagePageNode::class, ContentKind.Classlikes),
+ OnOwnPage(className)
+ )
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/signatures/RawHtmlRenderingTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/RawHtmlRenderingTest.kt
new file mode 100644
index 00000000..c79d70fd
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/RawHtmlRenderingTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package signatures
+
+import org.jsoup.Jsoup
+import utils.TestOutputWriterPlugin
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class RawHtmlRenderingTest: AbstractRenderingTest() {
+ @Test
+ fun `work with raw html with inline comment`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testFromData(
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val content = writerPlugin.renderedSourceDependentContent("example/example/-html-test/test.html")
+ assertEquals(1, content.count())
+ assertEquals(content.select("[data-togglable=example/jvm]").single().rawBrief,"This is an example <!-- not visible --> of html")
+
+ val indexContent = writerPlugin.writer.contents.getValue("example/example/-html-test/index.html")
+ .let { Jsoup.parse(it) }
+ assertTrue(indexContent.select("div.brief").any { it.html().contains("This is an example <!-- not visible --> of html")})
+ }
+ }
+ }
+
+ @Test
+ fun `work with raw html`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testFromData(
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ //Module page
+ val content = writerPlugin.renderedContent("example/example/index.html").select("div.brief")
+ assertTrue(content.size > 0)
+ assertTrue(content.any { it.html().contains("<!-- this shouldn't be visible -->")})
+ }
+ }
+ }
+
+ @Test
+ fun `work with raw, visible html`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testFromData(
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val content = writerPlugin.renderedSourceDependentContent("example/example/-html-test/test-p.html")
+ assertEquals(1, content.count())
+ assertEquals(content.select("[data-togglable=example/jvm]").single().rawBrief, "This is an <b> documentation </b>")
+
+ val indexContent = writerPlugin.writer.contents.getValue("example/example/-html-test/index.html")
+ .let { Jsoup.parse(it) }
+ assertTrue(indexContent.select("div.brief").any { it.html().contains("This is an <b> documentation </b>")})
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/signatures/SignatureTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/SignatureTest.kt
new file mode 100644
index 00000000..80a043fe
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/SignatureTest.kt
@@ -0,0 +1,1035 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package signatures
+
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.DFunction
+import org.jetbrains.dokka.model.DefinitelyNonNullable
+import org.jetbrains.dokka.model.dfs
+import utils.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class SignatureTest : BaseAbstractTest() {
+ private val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath = listOf(
+ commonStdlibPath ?: throw IllegalStateException("Common stdlib is not found"),
+ jvmStdlibPath ?: throw IllegalStateException("JVM stdlib is not found")
+ )
+ externalDocumentationLinks = listOf(stdlibExternalDocumentationLink)
+ }
+ }
+ }
+
+ private val mppConfiguration = dokkaConfiguration {
+ moduleName = "test"
+ sourceSets {
+ sourceSet {
+ name = "common"
+ sourceRoots = listOf("src/main/kotlin/common/Test.kt")
+ classpath = listOf(commonStdlibPath!!)
+ externalDocumentationLinks = listOf(stdlibExternalDocumentationLink)
+ }
+ sourceSet {
+ name = "jvm"
+ dependentSourceSets = setOf(DokkaSourceSetID("test", "common"))
+ sourceRoots = listOf("src/main/kotlin/jvm/Test.kt")
+ classpath = listOf(
+ commonStdlibPath ?: throw IllegalStateException("Common stdlib is not found"),)
+ externalDocumentationLinks = listOf(stdlibExternalDocumentationLink)
+ }
+ }
+ }
+
+ fun source(signature: String) =
+ """
+ |/src/main/kotlin/test/Test.kt
+ |package example
+ |
+ | $signature
+ """.trimIndent()
+
+ @Test
+ fun `fun`() {
+ val source = source("fun simpleFun(): String = \"Celebrimbor\"")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ "fun ", A("simpleFun"), "(): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `open fun`() {
+ val source = source("open fun simpleFun(): String = \"Celebrimbor\"")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ "open fun ", A("simpleFun"), "(): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `open suspend fun`() {
+ val source = source("open suspend fun simpleFun(): String = \"Celebrimbor\"")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ "open suspend fun ", A("simpleFun"), "(): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with params`() {
+ val source = source("fun simpleFun(a: Int, b: Boolean, c: Any): String = \"Celebrimbor\"")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ "fun ", A("simpleFun"), "(", Parameters(
+ Parameter("a: ", A("Int"), ","),
+ Parameter("b: ", A("Boolean"), ","),
+ Parameter("c: ", A("Any")),
+ ), "): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with function param`() {
+ val source = source("fun simpleFun(a: (Int) -> String): String = \"Celebrimbor\"")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ "fun ", A("simpleFun"), "(", Parameters(
+ Parameter("a: (", A("Int"), ") -> ", A("String")),
+ ),"): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with generic param`() {
+ val source = source("fun <T> simpleFun(): T = \"Celebrimbor\" as T")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ "fun <", A("T"), "> ", A("simpleFun"), "(): ",
+ A("T"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with generic bounded param`() {
+ val source = source("fun <T : String> simpleFun(): T = \"Celebrimbor\" as T")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ "fun <", A("T"), " : ", A("String"), "> ", A("simpleFun"),
+ "(): ", A("T"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with definitely non-nullable types`() {
+ val source = source("fun <T> elvisLike(x: T, y: T & Any): T & Any = x ?: y")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ documentablesTransformationStage = {
+ val fn = (it.dfs { it.name == "elvisLike" } as? DFunction).assertNotNull("Function elvisLike")
+
+ assertTrue(fn.type is DefinitelyNonNullable)
+ assertTrue(fn.parameters[1].type is DefinitelyNonNullable)
+ }
+ renderingStage = { _, _ ->
+ val signature = writerPlugin.writer.renderedContent("root/example/elvis-like.html")
+ assertEquals(2, signature.select("a[href=\"https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/index.html\"]").size)
+ signature.firstSignature().match(
+ "fun <", A("T"), "> ", A("elvisLike"),
+ "(",
+ Span(
+ Span("x: ", A("T"), ", "),
+ Span("y: ", A("T"), " & ", A("Any"))
+ ),
+ "): ", A("T"), " & ", A("Any"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with keywords, params and generic bound`() {
+ val source = source("inline suspend fun <T : String> simpleFun(a: Int, b: String): T = \"Celebrimbor\" as T")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ "inline suspend fun <", A("T"), " : ", A("String"), "> ", A("simpleFun"), "(", Parameters(
+ Parameter("a: ", A("Int"), ","),
+ Parameter("b: ", A("String")),
+ ), "): ", A("T"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with vararg`() {
+ val source = source("fun simpleFun(vararg params: Int): Unit")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ "fun ", A("simpleFun"), "(", Parameters(
+ Parameter("vararg params: ", A("Int")),
+ ), ")",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `class with no supertype`() {
+ val source = source("class SimpleClass")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-simple-class/index.html").firstSignature().match(
+ "class ", A("SimpleClass"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `class with generic supertype`() {
+ val source = source("class InheritingClassFromGenericType<T : Number, R : CharSequence> : Comparable<T>, Collection<R>")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-inheriting-class-from-generic-type/index.html").firstSignature().match(
+ "class ", A("InheritingClassFromGenericType"), " <", A("T"), " : ", A("Number"), ", ", A("R"), " : ", A("CharSequence"),
+ "> : ", A("Comparable"), "<", A("T"), "> , ", A("Collection"), "<", A("R"), ">",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `functional interface`() {
+ val source = source("fun interface KRunnable")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-k-runnable/index.html").firstSignature().match(
+ "fun interface ", A("KRunnable"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with annotation`() {
+ val source = """
+ |/src/main/kotlin/test/Test.kt
+ |package example
+ |
+ | @MustBeDocumented()
+ | @Target(AnnotationTarget.FUNCTION)
+ | annotation class Marking
+ |
+ | @Marking()
+ | fun simpleFun(): String = "Celebrimbor"
+ """.trimIndent()
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ Div(
+ Div("@", A("Marking"))
+ ),
+ "fun ", A("simpleFun"),
+ "(): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `property with annotation`() {
+ val source = """
+ |/src/main/kotlin/test/Test.kt
+ |package example
+ |
+ | @MustBeDocumented()
+ | @Target(AnnotationTarget.FUNCTION)
+ | annotation class Marking
+ |
+ | @get:Marking()
+ | @set:Marking()
+ | var str: String
+ """.trimIndent()
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/str.html").firstSignature().match(
+ Div(
+ Div("@get:", A("Marking")),
+ Div("@set:", A("Marking"))
+ ),
+ "var ", A("str"),
+ ": ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with two annotations`() {
+ val source = """
+ |/src/main/kotlin/test/Test.kt
+ |package example
+ |
+ | @MustBeDocumented()
+ | @Target(AnnotationTarget.FUNCTION)
+ | annotation class Marking(val msg: String)
+ |
+ | @MustBeDocumented()
+ | @Target(AnnotationTarget.FUNCTION)
+ | annotation class Marking2(val int: Int)
+ |
+ | @Marking("Nenya")
+ | @Marking2(1)
+ | fun simpleFun(): String = "Celebrimbor"
+ """.trimIndent()
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html")
+ .firstSignature()
+ .match(
+ Div(
+ Div("@", A("Marking"), "(", Span("msg = ", Span("\"Nenya\"")), Wbr, ")"),
+ Div("@", A("Marking2"), "(", Span("int = ", Span("1")), Wbr, ")")
+ ),
+ "fun ", A("simpleFun"),
+ "(): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with annotation with array`() {
+ val source = """
+ |/src/main/kotlin/test/Test.kt
+ |package example
+ |
+ | @MustBeDocumented()
+ | @Target(AnnotationTarget.FUNCTION)
+ | annotation class Marking(val msg: Array<String>)
+ |
+ | @Marking(["Nenya", "Vilya", "Narya"])
+ | @Marking2(1)
+ | fun simpleFun(): String = "Celebrimbor"
+ """.trimIndent()
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ Div(
+ Div(
+ "@", A("Marking"), "(", Span(
+ "msg = [",
+ Span(Span("\"Nenya\""), ", "), Wbr,
+ Span(Span("\"Vilya\""), ", "), Wbr,
+ Span(Span("\"Narya\"")), Wbr, "]"
+ ), Wbr, ")"
+ )
+ ),
+ "fun ", A("simpleFun"),
+ "(): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `actual fun`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |expect fun simpleFun(): String
+ |
+ |/src/main/kotlin/jvm/Test.kt
+ |package example
+ |
+ |actual fun simpleFun(): String = "Celebrimbor"
+ |
+ """.trimMargin(),
+ mppConfiguration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val signatures = writerPlugin.writer.renderedContent("test/example/simple-fun.html").signature().toList()
+
+ signatures[0].match(
+ "expect fun ", A("simpleFun"),
+ "(): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ signatures[1].match(
+ "actual fun ", A("simpleFun"),
+ "(): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `actual property with a default value`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |expect val prop: Int
+ |
+ |/src/main/kotlin/jvm/Test.kt
+ |package example
+ |
+ |actual val prop: Int = 2
+ |
+ """.trimMargin(),
+ mppConfiguration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val signatures = writerPlugin.writer.renderedContent("test/example/prop.html").signature().toList()
+
+ signatures[0].match(
+ "expect val ", A("prop"),
+ ": ", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ signatures[1].match(
+ "actual val ", A("prop"),
+ ": ", A("Int"),
+ " = 2",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+ @Test
+ fun `actual typealias should have generic parameters and fully qualified name of the expansion type`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |expect class Array<T>
+ |
+ |/src/main/kotlin/jvm/Test.kt
+ |package example
+ |
+ |actual typealias Array<T> = kotlin.Array<T>
+ """.trimMargin(),
+ mppConfiguration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val signatures = writerPlugin.writer.renderedContent("test/example/-array/index.html").signature().toList()
+
+ signatures[0].match(
+ "expect class ", A("Array"), "<", A("T"), ">",
+ ignoreSpanWithTokenStyle = true
+ )
+ signatures[1].match(
+ "actual typealias ", A("Array"), "<", A("T"), "> = ", A("kotlin.Array"), "<", A("T"), ">",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `type with an actual typealias`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |expect class Foo
+ |
+ |/src/main/kotlin/jvm/Test.kt
+ |package example
+ |
+ |class Bar
+ |actual typealias Foo = Bar
+ |
+ """.trimMargin(),
+ mppConfiguration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val signatures = writerPlugin.writer.renderedContent("test/example/-foo/index.html").signature().toList()
+
+ signatures[0].match(
+ "expect class ", A("Foo"),
+ ignoreSpanWithTokenStyle = true
+ )
+ signatures[1].match(
+ "actual typealias ", A("Foo"), " = ", A("Bar"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `plain typealias of plain class`() {
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |typealias PlainTypealias = Int
+ |
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "typealias ", A("PlainTypealias"), " = ", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `plain typealias of plain class with annotation`() {
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |@MustBeDocumented
+ |@Target(AnnotationTarget.TYPEALIAS)
+ |annotation class SomeAnnotation
+ |
+ |@SomeAnnotation
+ |typealias PlainTypealias = Int
+ |
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ Div(
+ Div(
+ "@", A("SomeAnnotation")
+ )
+ ),
+ "typealias ", A("PlainTypealias"), " = ", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `plain typealias of generic class`() {
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |typealias PlainTypealias = Comparable<Int>
+ |
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "typealias ", A("PlainTypealias"), " = ", A("Comparable"),
+ "<", A("Int"), ">",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `typealias with generics params`() {
+
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |typealias GenericTypealias<T> = Comparable<T>
+ |
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "typealias ", A("GenericTypealias"), "<", A("T"), "> = ", A("Comparable"),
+ "<", A("T"), ">",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `typealias with generic params swapped`() {
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/kotlinAsJavaPlugin/Test.kt
+ |package kotlinAsJavaPlugin
+ |
+ |typealias XD<B, A> = Map<A, B>
+ |
+ |class ABC {
+ | fun someFun(xd: XD<Int, String>) = 1
+ |}
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/kotlinAsJavaPlugin/-a-b-c/some-fun.html").firstSignature()
+ .match(
+ "fun ", A("someFun"), "(", Parameters(
+ Parameter("xd: ", A("XD"), "<", A("Int"), ", ", A("String"), ">"),
+ ), "):", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @OnlyDescriptors("Order of constructors is different in K2")
+ @Test
+ fun `generic constructor params`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |class GenericClass<T>(val x: Int) {
+ | constructor(x: T) : this(1)
+ |
+ | constructor(x: Int, y: String) : this(1)
+ |
+ | constructor(x: Int, y: List<T>) : this(1)
+ |
+ | constructor(x: Boolean, y: Int, z: String) : this(1)
+ |
+ | constructor(x: List<Comparable<Lazy<T>>>?) : this(1)
+ |}
+ |
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-generic-class/-generic-class.html").signature().zip(
+ listOf(
+ arrayOf(
+ "constructor(",
+ Parameters(
+ Parameter("x: ", A("T"))
+ ),
+ ")",
+ ),
+ arrayOf(
+ "constructor(",
+ Parameters(
+ Parameter("x: ", A("Int"), ", "),
+ Parameter("y: ", A("String"))
+ ),
+ ")",
+ ),
+ arrayOf(
+ "constructor(",
+ Parameters(
+ Parameter("x: ", A("Int"), ", "),
+ Parameter("y: ", A("List"), "<", A("T"), ">")
+ ),
+ ")",
+ ),
+ arrayOf(
+ "constructor(",
+ Parameters(
+ Parameter("x: ", A("Boolean"), ", "),
+ Parameter("y: ", A("Int"), ", "),
+ Parameter("z:", A("String"))
+ ),
+ ")",
+ ),
+ arrayOf(
+ "constructor(",
+ Parameters(
+ Parameter("x: ", A("List"), "<", A("Comparable"), "<", A("Lazy"), "<", A("T"), ">>>?")
+ ),
+ ")",
+ ),
+ arrayOf(
+ "constructor(",
+ Parameters(
+ Parameter("x: ", A("Int"))
+ ),
+ ")",
+ ),
+ )
+ ).forEach {
+ it.first.match(*it.second, ignoreSpanWithTokenStyle = true)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `constructor has its own custom signature keyword in Constructor tab`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |class PrimaryConstructorClass(x: String) { }
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val constructorTabFirstElement =
+ writerPlugin.writer.renderedContent("root/example/-primary-constructor-class/index.html")
+ .tab("CONSTRUCTOR")
+ .first() ?: throw NoSuchElementException("No Constructors tab found or it is empty")
+
+ constructorTabFirstElement.firstSignature().match(
+ "constructor(", Parameters(Parameter("x: ", A("String"))), ")",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `primary constructor with properties check for all tokens`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/Test.kt
+ |package example
+ |
+ |class PrimaryConstructorClass<T>(val x: Int, var s: String) { }
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-primary-constructor-class/index.html").firstSignature().match(
+ // In `<T>` expression, an empty `<span class="token keyword"></span>` is present for some reason
+ Span("class "), A("PrimaryConstructorClass"), Span("<"), Span(), A("T"), Span(">"), Span("("), Parameters(
+ Parameter(Span("val "), "x", Span(": "), A("Int"), Span(",")),
+ Parameter(Span("var "), "s", Span(": "), A("String"))
+ ), Span(")"),
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `fun with default values`() {
+ val source = source("fun simpleFun(int: Int = 1, string: String = \"string\"): String = \"\"")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/simple-fun.html").firstSignature().match(
+ "fun", A("simpleFun"), "(", Parameters(
+ Parameter("int: ", A("Int"), " = 1,"),
+ Parameter("string: ", A("String"), " = \"string\"")
+ ), "): ", A("String"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `const val with default values`() {
+ val source = source("const val simpleVal = 1")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/index.html").firstSignature().match(
+ "const val ", A("simpleVal"), ": ", A("Int"), " = 1",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `should not expose enum constructor entry arguments`() {
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/common/EnumClass.kt
+ |package example
+ |
+ |enum class EnumClass(param: String = "Default") {
+ | EMPTY,
+ | WITH_ARG("arg")
+ |}
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val enumEntrySignatures = writerPlugin.writer.renderedContent("root/example/-enum-class/index.html")
+ .select("div[data-togglable=ENTRY] .table")
+ .single()
+ .signature()
+ .select("div.block")
+
+ enumEntrySignatures[0].match(
+ A("EMPTY"),
+ ignoreSpanWithTokenStyle = true
+ )
+
+ enumEntrySignatures[1].match(
+ A("WITH_ARG"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @OnlyDescriptors("'var' expected but found: 'open var'")
+ @Test
+ fun `java property without accessors should be var`() {
+ val writerPlugin = TestOutputWriterPlugin()
+ testInline(
+ """
+ |/src/test/JavaClass.java
+ |package test;
+ |public class JavaClass {
+ | public int property = 0;
+ |}
+ |
+ |/src/test/KotlinClass.kt
+ |package test
+ |open class KotlinClass : JavaClass() { }
+ """.trimIndent(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/test/-kotlin-class/index.html").let { kotlinClassContent ->
+ val signatures = kotlinClassContent.signature().toList()
+ assertEquals(3, signatures.size, "Expected 2 signatures: class signature, constructor and property")
+
+ val property = signatures[2]
+ property.match(
+ "var ", A("property"), ":", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+
+ writerPlugin.writer.renderedContent("root/test/-java-class/index.html").let { kotlinClassContent ->
+ val signatures = kotlinClassContent.signature().toList()
+ assertEquals(
+ 3,
+ signatures.size,
+ "Expected 3 signatures: class signature, default constructor and property"
+ )
+
+ val property = signatures[2]
+ property.match(
+ "open var ", A("property"), ":", A("Int"),
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/signatures/VarianceSignatureTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/VarianceSignatureTest.kt
new file mode 100644
index 00000000..0e8a8845
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/signatures/VarianceSignatureTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package signatures
+
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import utils.A
+import utils.TestOutputWriterPlugin
+import utils.match
+import kotlin.test.Test
+
+class VarianceSignatureTest : BaseAbstractTest() {
+ private val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ classpath = listOf(commonStdlibPath!!)
+ externalDocumentationLinks = listOf(stdlibExternalDocumentationLink)
+ }
+ }
+ }
+
+ fun source(signature: String) =
+ """
+ |/src/main/kotlin/test/Test.kt
+ |package example
+ |
+ | $signature
+ """.trimIndent()
+
+ @Test
+ fun `simple contravariance`() {
+ val source = source("class Generic<in T>")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-generic/index.html").firstSignature().match(
+ "class ", A("Generic"), "<in ", A("T"), ">",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `simple covariance`() {
+ val source = source("class Generic<out T>")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-generic/index.html").firstSignature().match(
+ "class ", A("Generic"), "<out ", A("T"), ">",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `simple invariance`() {
+ val source = source("class Generic<T>")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-generic/index.html").firstSignature().match(
+ "class ", A("Generic"), "<", A("T"), ">",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `covariance and bound`() {
+ val source = source("class Generic<out T : List<CharSequence>>")
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ source,
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ writerPlugin.writer.renderedContent("root/example/-generic/index.html").firstSignature().match(
+ "class ", A("Generic"), "<out ", A("T"), ":", A("List"), "<", A("CharSequence"), ">>",
+ ignoreSpanWithTokenStyle = true
+ )
+ }
+ }
+ }
+}
+