aboutsummaryrefslogtreecommitdiff
path: root/dokka-subprojects/plugin-base/src/test/kotlin/transformers
diff options
context:
space:
mode:
Diffstat (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/transformers')
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/AbstractContextModuleAndPackageDocumentationReaderTest.kt27
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt484
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest1.kt187
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest3.kt61
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/DivisionSwitchTest.kt126
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/InheritedEntriesDocumentableFilterTransfromerTest.kt162
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/InvalidContentModuleAndPackageDocumentationReaderTest.kt100
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt386
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerFunctionalTest.kt137
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerUnitTest.kt260
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/ObviousAndInheritedFunctionsDocumentableFilterTest.kt229
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt927
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/SourceLinkTransformerTest.kt131
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressTagFilterTest.kt211
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressedByConfigurationDocumentableFilterTransformerTest.kt193
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/transformers/isExceptionTest.kt147
16 files changed, 3768 insertions, 0 deletions
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/AbstractContextModuleAndPackageDocumentationReaderTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/AbstractContextModuleAndPackageDocumentationReaderTest.kt
new file mode 100644
index 00000000..8ce9360f
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/AbstractContextModuleAndPackageDocumentationReaderTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.model.SourceSetDependent
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.model.doc.Text
+import org.jetbrains.dokka.model.withDescendants
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
+
+abstract class AbstractContextModuleAndPackageDocumentationReaderTest {
+ @TempDir
+ protected lateinit var temporaryDirectory: Path
+
+
+ companion object {
+ val SourceSetDependent<DocumentationNode>.texts: List<String>
+ get() = values.flatMap { it.withDescendants() }
+ .flatMap { it.children }
+ .flatMap { it.children }
+ .mapNotNull { it as? Text }
+ .map { it.body }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt
new file mode 100644
index 00000000..1387c0e0
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt
@@ -0,0 +1,484 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import matchers.content.*
+import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.pages.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class CommentsToContentConverterTest {
+ private val converter = DocTagToContentConverter()
+
+ private fun executeTest(
+ docTag: DocTag,
+ match: ContentMatcherBuilder<ContentComposite>.() -> Unit,
+ ) {
+ val dci = DCI(
+ setOf(
+ DRI("kotlin", "Any")
+ ),
+ ContentKind.Comment
+ )
+ converter.buildContent(
+ Li(
+ listOf(
+ docTag
+ )
+ ),
+ dci,
+ emptySet()
+ ).single().assertNode(match)
+ }
+
+ @Test
+ fun `simple text`() {
+ val docTag = P(listOf(Text("This is simple test of string Next line")))
+ executeTest(docTag) {
+ group { +"This is simple test of string Next line" }
+ }
+ }
+
+ @Test
+ fun `simple text with new line`() {
+ val docTag = P(
+ listOf(
+ Text("This is simple test of string"),
+ Br,
+ Text("Next line")
+ )
+ )
+ executeTest(docTag) {
+ group {
+ +"This is simple test of string"
+ node<ContentBreakLine>()
+ +"Next line"
+ }
+ }
+ }
+
+ @Test
+ fun `paragraphs`() {
+ val docTag = P(
+ listOf(
+ P(listOf(Text("Paragraph number one"))),
+ P(listOf(Text("Paragraph"), Br, Text("number two")))
+ )
+ )
+ executeTest(docTag) {
+ group {
+ group { +"Paragraph number one" }
+ group {
+ +"Paragraph"
+ node<ContentBreakLine>()
+ +"number two"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `unordered list with empty lines`() {
+ val docTag = Ul(
+ listOf(
+ Li(listOf(P(listOf(Text("list item 1 continue 1"))))),
+ Li(listOf(P(listOf(Text("list item 2"), Br, Text("continue 2")))))
+ )
+ )
+ executeTest(docTag) {
+ node<ContentList> {
+ group {
+ +"list item 1 continue 1"
+ }
+ group {
+ +"list item 2"
+ node<ContentBreakLine>()
+ +"continue 2"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `nested list`() {
+ val docTag = P(
+ listOf(
+ Ul(
+ listOf(
+ Li(listOf(P(listOf(Text("Outer first Outer next line"))))),
+ Li(listOf(P(listOf(Text("Outer second"))))),
+ Ul(
+ listOf(
+ Li(listOf(P(listOf(Text("Middle first Middle next line"))))),
+ Li(listOf(P(listOf(Text("Middle second"))))),
+ Ul(
+ listOf(
+ Li(listOf(P(listOf(Text("Inner first Inner next line")))))
+ )
+ ),
+ Li(listOf(P(listOf(Text("Middle third")))))
+ )
+ ),
+ Li(listOf(P(listOf(Text("Outer third")))))
+ )
+ ),
+ P(listOf(Text("New paragraph")))
+ )
+ )
+ executeTest(docTag) {
+ group {
+ node<ContentList> {
+ group { +"Outer first Outer next line" }
+ group { +"Outer second" }
+ node<ContentList> {
+ group { +"Middle first Middle next line" }
+ group { +"Middle second" }
+ node<ContentList> {
+ group { +"Inner first Inner next line" }
+ }
+ group { +"Middle third" }
+ }
+ group { +"Outer third" }
+ }
+ group { +"New paragraph" }
+ }
+ }
+ }
+
+ @Test
+ fun `header and paragraphs`() {
+ val docTag = P(
+ listOf(
+ H1(listOf(Text("Header 1"))),
+ P(listOf(Text("Following text"))),
+ P(listOf(Text("New paragraph")))
+ )
+ )
+ executeTest(docTag) {
+ group {
+ header(1) { +"Header 1" }
+ group { +"Following text" }
+ group { +"New paragraph" }
+ }
+ }
+ }
+
+ @Test
+ fun `header levels`() {
+ val docTag = P(
+ listOf(
+ H1(listOf(Text("Header 1"))),
+ P(listOf(Text("Text 1"))),
+ H2(listOf(Text("Header 2"))),
+ P(listOf(Text("Text 2"))),
+ H3(listOf(Text("Header 3"))),
+ P(listOf(Text("Text 3"))),
+ H4(listOf(Text("Header 4"))),
+ P(listOf(Text("Text 4"))),
+ H5(listOf(Text("Header 5"))),
+ P(listOf(Text("Text 5"))),
+ H6(listOf(Text("Header 6"))),
+ P(listOf(Text("Text 6")))
+ )
+ )
+ executeTest(docTag) {
+ group {
+ header(1) { +"Header 1" }
+ group { +"Text 1" }
+ header(2) { +"Header 2" }
+ group { +"Text 2" }
+ header(3) { +"Header 3" }
+ group { +"Text 3" }
+ header(4) { +"Header 4" }
+ group { +"Text 4" }
+ header(5) { +"Header 5" }
+ group { +"Text 5" }
+ header(6) { +"Header 6" }
+ group { +"Text 6" }
+ }
+ }
+ }
+
+ @Test
+ fun `block quotes`() {
+ val docTag = P(
+ listOf(
+ BlockQuote(
+ listOf(
+ P(
+ listOf(
+ Text("Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.")
+ )
+ )
+ )
+ ),
+ P(listOf(Text("Quote break."))),
+ BlockQuote(
+ listOf(
+ P(listOf(Text("Quote")))
+ )
+ )
+ )
+ )
+ executeTest(docTag) {
+ group {
+ group {
+ group {
+ +"Blockquotes are very handy in email to emulate reply text. This line is part of the same quote."
+ }
+ }
+ group { +"Quote break." }
+ group {
+ group {
+ +"Quote"
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `nested block quotes`() {
+ val docTag = P(
+ listOf(
+ BlockQuote(
+ listOf(
+ P(listOf(Text("text 1 text 2"))),
+ BlockQuote(
+ listOf(
+ P(listOf(Text("text 3 text 4")))
+ )
+ ),
+ P(listOf(Text("text 5")))
+ )
+ ),
+ P(listOf(Text("Quote break."))),
+ BlockQuote(
+ listOf(
+ P(listOf(Text("Quote")))
+ )
+ )
+ )
+ )
+ executeTest(docTag) {
+ group {
+ group {
+ group { +"text 1 text 2" }
+ group {
+ group { +"text 3 text 4" }
+ }
+ group { +"text 5" }
+ }
+ group { +"Quote break." }
+ group {
+ group { +"Quote" }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `multiline code`() {
+ val docTag = P(
+ listOf(
+ CodeBlock(
+ listOf(
+ Text("val x: Int = 0"), Br,
+ Text("val y: String = \"Text\""), Br, Br,
+ Text(" val z: Boolean = true"), Br,
+ Text("for(i in 0..10) {"), Br,
+ Text(" println(i)"), Br,
+ Text("}")
+ ),
+ mapOf("lang" to "kotlin")
+ ),
+ P(listOf(Text("Sample text")))
+ )
+ )
+ executeTest(docTag) {
+ group {
+ node<ContentCodeBlock> {
+ +"val x: Int = 0"
+ node<ContentBreakLine>()
+ +"val y: String = \"Text\""
+ node<ContentBreakLine>()
+ node<ContentBreakLine>()
+ +" val z: Boolean = true"
+ node<ContentBreakLine>()
+ +"for(i in 0..10) {"
+ node<ContentBreakLine>()
+ +" println(i)"
+ node<ContentBreakLine>()
+ +"}"
+ }
+ group { +"Sample text" }
+ }
+ }
+ }
+
+ @Test
+ fun `inline link`() {
+ val docTag = P(
+ listOf(
+ A(
+ listOf(Text("I'm an inline-style link")),
+ mapOf("href" to "https://www.google.com")
+ )
+ )
+ )
+ executeTest(docTag) {
+ group {
+ link {
+ +"I'm an inline-style link"
+ check {
+ assertEquals(
+ (this as? ContentResolvedLink)?.address ?: error("Link should be resolved"),
+ "https://www.google.com"
+ )
+ }
+ }
+ }
+ }
+ }
+
+
+ @Test
+ fun `ordered list`() {
+ val docTag =
+ Ol(
+ listOf(
+ Li(
+ listOf(
+ P(listOf(Text("test1"))),
+ P(listOf(Text("test2"))),
+ )
+ ),
+ Li(
+ listOf(
+ P(listOf(Text("test3"))),
+ P(listOf(Text("test4"))),
+ )
+ )
+ )
+ )
+ executeTest(docTag) {
+ node<ContentList> {
+ group {
+ +"test1"
+ +"test2"
+ }
+ group {
+ +"test3"
+ +"test4"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `nested ordered list`() {
+ val docTag = P(
+ listOf(
+ Ol(
+ listOf(
+ Li(listOf(P(listOf(Text("Outer first Outer next line"))))),
+ Li(listOf(P(listOf(Text("Outer second"))))),
+ Ol(
+ listOf(
+ Li(listOf(P(listOf(Text("Middle first Middle next line"))))),
+ Li(listOf(P(listOf(Text("Middle second"))))),
+ Ol(
+ listOf(
+ Li(listOf(P(listOf(Text("Inner first Inner next line")))))
+ ),
+ mapOf("start" to "1")
+ ),
+ Li(listOf(P(listOf(Text("Middle third")))))
+ ),
+ mapOf("start" to "1")
+ ),
+ Li(listOf(P(listOf(Text("Outer third")))))
+ ),
+ mapOf("start" to "1")
+ ),
+ P(listOf(Text("New paragraph")))
+ )
+ )
+ executeTest(docTag) {
+ group {
+ node<ContentList> {
+ group { +"Outer first Outer next line" }
+ group { +"Outer second" }
+ node<ContentList> {
+ group { +"Middle first Middle next line" }
+ group { +"Middle second" }
+ node<ContentList> {
+ +"Inner first Inner next line"
+ }
+ group { +"Middle third" }
+ }
+ group { +"Outer third" }
+ }
+ group {
+ +"New paragraph"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `description list`() {
+ val docTag =
+ Dl(
+ listOf(
+ Dt(
+ listOf(
+ Text("description list can have...")
+ )
+ ),
+ Dt(
+ listOf(
+ Text("... two consecutive description terms")
+ )
+ ),
+ Dd(
+ listOf(
+ Text("and usually has some sort of a description, like this one")
+ )
+ )
+ )
+ )
+
+ executeTest(docTag) {
+ composite<ContentList> {
+ check {
+ assertTrue(style.contains(ListStyle.DescriptionList), "Expected DL style")
+ }
+ group {
+ check {
+ assertTrue(style.contains(ListStyle.DescriptionTerm), "Expected DT style")
+ }
+ +"description list can have..."
+ }
+ group {
+ check {
+ assertTrue(style.contains(ListStyle.DescriptionTerm), "Expected DT style")
+ }
+ +"... two consecutive description terms"
+ }
+ group {
+ check {
+ assertTrue(style.contains(ListStyle.DescriptionDetails), "Expected DD style")
+ }
+ +"and usually has some sort of a description, like this one"
+ }
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest1.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest1.kt
new file mode 100644
index 00000000..dfb3eff1
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest1.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.testApi.logger.TestLogger
+import org.jetbrains.dokka.utilities.DokkaConsoleLogger
+import org.jetbrains.dokka.utilities.LoggingLevel
+import testApi.testRunner.TestDokkaConfigurationBuilder
+import testApi.testRunner.dModule
+import testApi.testRunner.dPackage
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+class ContextModuleAndPackageDocumentationReaderTest1 : AbstractContextModuleAndPackageDocumentationReaderTest() {
+
+
+ private val includeSourceSetA by lazy { temporaryDirectory.resolve("includeA.md").toFile() }
+ private val includeSourceSetB by lazy { temporaryDirectory.resolve("includeB.md").toFile() }
+
+ @BeforeTest
+ fun materializeIncludes() {
+ includeSourceSetA.writeText(
+ """
+ # Module moduleA
+ This is moduleA
+
+ # Package sample.a
+ This is package sample.a\r\n
+
+ # Package noise.b
+ This will just add some noise
+ """.trimIndent().replace("\n", "\r\n")
+ )
+
+ includeSourceSetB.writeText(
+ """
+ # Module moduleB
+ This is moduleB
+
+ # Package sample.b
+ This is package sample.b
+
+ # Package noise.b
+ This will just add some more noise
+ """.trimIndent()
+ )
+ }
+
+ private val configurationBuilder = TestDokkaConfigurationBuilder().apply {
+ moduleName = "moduleA"
+ }
+
+ private val sourceSetA by configurationBuilder.sourceSet {
+ name = "sourceSetA"
+ includes = listOf(includeSourceSetA.canonicalPath)
+ }
+
+
+ private val sourceSetB by configurationBuilder.sourceSet {
+ name = "sourceSetB"
+ includes = listOf(includeSourceSetB.canonicalPath)
+ }
+
+
+ private val sourceSetB2 by configurationBuilder.sourceSet {
+ name = "sourceSetB2"
+ includes = emptyList()
+ }
+
+
+ private val context by lazy {
+ DokkaContext.create(
+ configuration = configurationBuilder.build(),
+ logger = TestLogger(DokkaConsoleLogger(LoggingLevel.DEBUG)),
+ pluginOverrides = emptyList()
+ )
+ }
+
+ private val reader by lazy { context.plugin<InternalKotlinAnalysisPlugin>().querySingle { moduleAndPackageDocumentationReader } }
+
+ @Test
+ fun `assert moduleA with sourceSetA`() {
+ val documentation = reader.read(dModule(name = "moduleA", sourceSets = setOf(sourceSetA)))
+ assertEquals(
+ 1, documentation.keys.size,
+ "Expected moduleA only containing documentation in a single source set"
+ )
+ assertEquals(
+ "sourceSetA", documentation.keys.single().sourceSetID.sourceSetName,
+ "Expected moduleA documentation coming from sourceSetA"
+ )
+
+ assertEquals(
+ "This is moduleA", documentation.texts.single(),
+ "Expected moduleA documentation being present"
+ )
+ }
+
+ @Test
+ fun `assert moduleA with no source sets`() {
+ val documentation = reader.read(dModule("moduleA"))
+ assertEquals(
+ emptyMap(), documentation,
+ "Expected no documentation received for module not declaring a matching sourceSet"
+ )
+ }
+
+ @Test
+ fun `assert moduleA with unknown source set`() {
+ assertFailsWith<IllegalStateException>(
+ "Expected no documentation received for module with unknown sourceSet"
+ ) {
+ reader.read(
+ dModule("moduleA", sourceSets = setOf(configurationBuilder.unattachedSourceSet { name = "unknown" }))
+ )
+ }
+ }
+
+ @Test
+ fun `assert moduleA with all sourceSets`() {
+ val documentation = reader.read(dModule("moduleA", sourceSets = setOf(sourceSetA, sourceSetB, sourceSetB2)))
+ assertEquals(1, documentation.entries.size, "Expected only one entry from sourceSetA")
+ assertEquals(sourceSetA, documentation.keys.single(), "Expected only one entry from sourceSetA")
+ assertEquals("This is moduleA", documentation.texts.single())
+ }
+
+ @Test
+ fun `assert moduleB with sourceSetB and sourceSetB2`() {
+ val documentation = reader.read(dModule("moduleB", sourceSets = setOf(sourceSetB, sourceSetB2)))
+ assertEquals(1, documentation.keys.size, "Expected only one entry from sourceSetB")
+ assertEquals(sourceSetB, documentation.keys.single(), "Expected only one entry from sourceSetB")
+ assertEquals("This is moduleB", documentation.texts.single())
+ }
+
+ @Test
+ fun `assert sample_A in sourceSetA`() {
+ val documentation = reader.read(dPackage(DRI("sample.a"), sourceSets = setOf(sourceSetA)))
+ assertEquals(1, documentation.keys.size, "Expected only one entry from sourceSetA")
+ assertEquals(sourceSetA, documentation.keys.single(), "Expected only one entry from sourceSetA")
+ assertEquals("This is package sample.a\\r\\n", documentation.texts.single())
+ }
+
+ @Test
+ fun `assert sample_a_sub in sourceSetA`() {
+ val documentation = reader.read(dPackage(DRI("sample.a.sub"), sourceSets = setOf(sourceSetA)))
+ assertEquals(
+ emptyMap<DokkaSourceSet, DocumentationNode>(), documentation,
+ "Expected no documentation found for different package"
+ )
+ }
+
+ @Test
+ fun `assert sample_a in sourceSetB`() {
+ val documentation = reader.read(dPackage(DRI("sample.a"), sourceSets = setOf(sourceSetB)))
+ assertEquals(
+ emptyMap<DokkaSourceSet, DocumentationNode>(), documentation,
+ "Expected no documentation found for different sourceSet"
+ )
+ }
+
+ @Test
+ fun `assert sample_b in sourceSetB`() {
+ val documentation = reader.read(dPackage(DRI("sample.b"), sourceSets = setOf(sourceSetB)))
+ assertEquals(1, documentation.keys.size, "Expected only one entry from sourceSetB")
+ assertEquals(sourceSetB, documentation.keys.single(), "Expected only one entry from sourceSetB")
+ assertEquals("This is package sample.b", documentation.texts.single())
+ }
+
+ @Test
+ fun `assert sample_b in sourceSetB and sourceSetB2`() {
+ val documentation = reader.read(dPackage(DRI("sample.b"), sourceSets = setOf(sourceSetB, sourceSetB2)))
+ assertEquals(1, documentation.keys.size, "Expected only one entry from sourceSetB")
+ assertEquals(sourceSetB, documentation.keys.single(), "Expected only one entry from sourceSetB")
+ assertEquals("This is package sample.b", documentation.texts.single())
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest3.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest3.kt
new file mode 100644
index 00000000..ebd5b7eb
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest3.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.utilities.DokkaConsoleLogger
+import org.jetbrains.dokka.utilities.LoggingLevel
+import testApi.testRunner.TestDokkaConfigurationBuilder
+import testApi.testRunner.dPackage
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class ContextModuleAndPackageDocumentationReaderTest3 : AbstractContextModuleAndPackageDocumentationReaderTest() {
+
+ private val include by lazy { temporaryDirectory.resolve("include.md").toFile() }
+
+ @BeforeTest
+ fun materializeInclude() {
+ include.writeText(
+ """
+ # Package
+ This is the root package
+
+ # Package [root]
+ This is also the root package
+ """.trimIndent()
+ )
+ }
+
+ private val configurationBuilder = TestDokkaConfigurationBuilder()
+
+ private val sourceSet by configurationBuilder.sourceSet {
+ includes = listOf(include.canonicalPath)
+ }
+
+ private val context by lazy {
+ DokkaContext.create(
+ configuration = configurationBuilder.build(),
+ logger = DokkaConsoleLogger(LoggingLevel.DEBUG),
+ pluginOverrides = emptyList()
+ )
+ }
+
+ private val reader by lazy { context.plugin<InternalKotlinAnalysisPlugin>().querySingle { moduleAndPackageDocumentationReader } }
+
+
+ @Test
+ fun `root package is matched by empty string and the root keyword`() {
+ val documentation = reader.read(dPackage(DRI(""), sourceSets = setOf(sourceSet)))
+ assertEquals(
+ listOf("This is the root package", "This is also the root package"), documentation.texts
+ )
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/DivisionSwitchTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/DivisionSwitchTest.kt
new file mode 100644
index 00000000..fec5fc47
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/DivisionSwitchTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.PluginConfigurationImpl
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.dfs
+import org.jetbrains.dokka.pages.ClasslikePageNode
+import org.jetbrains.dokka.pages.ContentHeader
+import org.jetbrains.dokka.pages.ContentNode
+import org.jetbrains.dokka.pages.ContentText
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+class DivisionSwitchTest : BaseAbstractTest() {
+
+ private val query = """
+ |/src/source0.kt
+ package package0
+ /**
+ * Documentation for ClassA
+ */
+ class ClassA {
+ val A: String = "A"
+ fun a() {}
+ fun b() {}
+ }
+
+ /src/source1.kt
+ package package0
+ /**
+ * Documentation for ClassB
+ */
+ class ClassB : ClassA() {
+ val B: String = "B"
+ fun d() {}
+ fun e() {}
+ }
+ """.trimMargin()
+
+ private fun configuration(switchOn: Boolean) = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ suppressObviousFunctions = false
+ pluginsConfigurations.add(
+ PluginConfigurationImpl(
+ DokkaBase::class.qualifiedName!!,
+ DokkaConfiguration.SerializationFormat.JSON,
+ """{ "separateInheritedMembers": $switchOn }""",
+ )
+ )
+ }
+
+ private fun testClassB(switchOn: Boolean, operation: (ClasslikePageNode) -> Unit) {
+ testInline(
+ query,
+ configuration(switchOn),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classB = root.dfs { it.name == "ClassB" } as? ClasslikePageNode
+ assertNotNull(classB, "Tested class not found!")
+ operation(classB)
+ }
+ }
+ }
+
+ private fun ClasslikePageNode.findSectionWithName(name: String) : ContentNode? {
+ var sectionHeader: ContentHeader? = null
+ return content.dfs { node ->
+ node.children.filterIsInstance<ContentHeader>().any { header ->
+ header.children.firstOrNull { it is ContentText && it.text == name }?.also { sectionHeader = header } != null
+ }
+ }?.children?.dropWhile { child -> child != sectionHeader }?.drop(1)?.firstOrNull()
+ }
+
+ @Test
+ fun `should not split inherited and regular methods`() {
+ testClassB(false) { classB ->
+ val functions = classB.findSectionWithName("Functions")
+ assertNotNull(functions, "Functions not found!")
+ assertEquals(7, functions.children.size, "Incorrect number of functions found")
+ }
+ }
+
+ @Test
+ fun `should not split inherited and regular properties`() {
+ testClassB(false) { classB ->
+ val properties = classB.findSectionWithName("Properties")
+ assertNotNull(properties, "Properties not found!")
+ assertEquals(2, properties.children.size, "Incorrect number of properties found")
+ }
+ }
+
+ @Test
+ fun `should split inherited and regular methods`() {
+ testClassB(true) { classB ->
+ val functions = classB.findSectionWithName("Functions")
+ val inheritedFunctions = classB.findSectionWithName("Inherited functions")
+ assertNotNull(functions, "Functions not found!")
+ assertEquals(2, functions.children.size, "Incorrect number of functions found")
+ assertNotNull(inheritedFunctions, "Inherited functions not found!")
+ assertEquals(5, inheritedFunctions.children.size, "Incorrect number of inherited functions found")
+ }
+ }
+
+ @Test
+ fun `should split inherited and regular properties`() {
+ testClassB(true) { classB ->
+ val properties = classB.findSectionWithName("Properties")
+ assertNotNull(properties, "Properties not found!")
+ assertEquals(1, properties.children.size, "Incorrect number of properties found")
+ val inheritedProperties = classB.findSectionWithName("Inherited properties")
+ assertNotNull(inheritedProperties, "Inherited properties not found!")
+ assertEquals(1, inheritedProperties.children.size, "Incorrect number of inherited properties found")
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InheritedEntriesDocumentableFilterTransfromerTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InheritedEntriesDocumentableFilterTransfromerTest.kt
new file mode 100644
index 00000000..c07dd5b8
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InheritedEntriesDocumentableFilterTransfromerTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.DEnum
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class InheritedEntriesDocumentableFilterTransformerTest : BaseAbstractTest() {
+ val suppressingInheritedConfiguration = dokkaConfiguration {
+ suppressInheritedMembers = true
+ suppressObviousFunctions = false
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ }
+ }
+ }
+
+ val nonSuppressingInheritedConfiguration = dokkaConfiguration {
+ suppressObviousFunctions = false
+ suppressInheritedMembers = false
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ }
+ }
+ }
+
+
+ @Test
+ fun `should suppress toString, equals and hashcode but keep custom ones`() {
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ data class Suppressed(val x: String) {
+ override fun toString(): String {
+ return "custom"
+ }
+ }
+ """.trimIndent(),
+ suppressingInheritedConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(listOf("toString", "copy", "component1").sorted(), functions.map { it.name }.sorted())
+ }
+ }
+ }
+
+ @Test
+ fun `should suppress toString, equals and hashcode`() {
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ data class Suppressed(val x: String)
+ """.trimIndent(),
+ suppressingInheritedConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(listOf("copy", "component1").sorted(), functions.map { it.name }.sorted())
+ }
+ }
+ }
+
+ @Test
+ fun `should also suppress properites`(){
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ open class Parent {
+ val parentValue = "String"
+ }
+
+ class Child : Parent {
+
+ }
+ """.trimIndent(),
+ suppressingInheritedConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val properties = modules.flatMap { it.packages }.flatMap { it.classlikes }.first { it.name == "Child" }.properties
+ assertEquals(0, properties.size)
+ }
+ }
+ }
+
+ @Test
+ fun `should not suppress properites if config says so`(){
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ open class Parent {
+ val parentValue = "String"
+ }
+
+ class Child : Parent {
+
+ }
+ """.trimIndent(),
+ nonSuppressingInheritedConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val properties = modules.flatMap { it.packages }.flatMap { it.classlikes }.first { it.name == "Child" }.properties
+ assertEquals(listOf("parentValue"), properties.map { it.name })
+ }
+ }
+ }
+
+ @Test
+ fun `should work with enum entries`(){
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ enum class Suppressed {
+ ENTRY_SUPPRESSED
+ }
+ """.trimIndent(),
+ suppressingInheritedConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val entry = (modules.flatMap { it.packages }.flatMap { it.classlikes }.first { it.name == "Suppressed" } as DEnum).entries.first()
+ assertEquals(emptyList(), entry.properties)
+ assertEquals(emptyList(), entry.functions)
+ assertEquals(emptyList(), entry.classlikes)
+ }
+ }
+ }
+
+ @Test
+ fun `should work with enum entries when not suppressing`(){
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ enum class Suppressed {
+ ENTRY_SUPPRESSED;
+ class A
+ }
+ """.trimIndent(),
+ nonSuppressingInheritedConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val entry = (modules.flatMap { it.packages }.flatMap { it.classlikes }.first { it.name == "Suppressed" } as DEnum).entries.first()
+ assertEquals(listOf("name", "ordinal"), entry.properties.map { it.name })
+ assertTrue(entry.functions.map { it.name }.containsAll(listOf("compareTo", "equals", "hashCode", "toString")))
+ assertEquals(emptyList(), entry.classlikes)
+ }
+ }
+ }
+}
+
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InvalidContentModuleAndPackageDocumentationReaderTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InvalidContentModuleAndPackageDocumentationReaderTest.kt
new file mode 100644
index 00000000..ca7536d4
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InvalidContentModuleAndPackageDocumentationReaderTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.utilities.DokkaConsoleLogger
+import org.jetbrains.dokka.utilities.LoggingLevel
+import testApi.testRunner.TestDokkaConfigurationBuilder
+import testApi.testRunner.dModule
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+
+class InvalidContentModuleAndPackageDocumentationReaderTest : AbstractContextModuleAndPackageDocumentationReaderTest() {
+
+ private val includeA by lazy { temporaryDirectory.resolve("includeA.md").toFile() }
+ private val includeB by lazy { temporaryDirectory.resolve("includeB.md").toFile() }
+
+ @BeforeTest
+ fun materializeInclude() {
+ includeA.writeText(
+ """
+ Invalid random stuff
+
+ # Module moduleA
+ Simple stuff
+ """.trimIndent()
+ )
+ includeB.writeText(
+ """
+ # Module moduleB
+ ###
+ """.trimIndent()
+ )
+ }
+
+ private val configurationBuilderA = TestDokkaConfigurationBuilder().apply {
+ moduleName = "moduleA"
+ }
+ private val configurationBuilderB = TestDokkaConfigurationBuilder().apply {
+ moduleName = "moduleB"
+ }
+
+ private val sourceSetA by configurationBuilderA.sourceSet {
+ includes = listOf(includeA.canonicalPath)
+ }
+
+ private val sourceSetB by configurationBuilderB.sourceSet {
+ includes = listOf(includeB.canonicalPath)
+ }
+
+ private val contextA by lazy {
+ DokkaContext.create(
+ configuration = configurationBuilderA.build(),
+ logger = DokkaConsoleLogger(LoggingLevel.DEBUG),
+ pluginOverrides = emptyList()
+ )
+ }
+ private val contextB by lazy {
+ DokkaContext.create(
+ configuration = configurationBuilderB.build(),
+ logger = DokkaConsoleLogger(LoggingLevel.DEBUG),
+ pluginOverrides = emptyList()
+ )
+ }
+
+ private val readerA by lazy { contextA.plugin<InternalKotlinAnalysisPlugin>().querySingle { moduleAndPackageDocumentationReader } }
+ private val readerB by lazy { contextB.plugin<InternalKotlinAnalysisPlugin>().querySingle { moduleAndPackageDocumentationReader } }
+
+
+ @Test
+ fun `parsing should fail with a message when documentation is in not proper format`() {
+ val exception =
+ runCatching { readerA.read(dModule(name = "moduleA", sourceSets = setOf(sourceSetA))) }.exceptionOrNull()
+ assertEquals(
+ "Unexpected classifier: \"Invalid\", expected either \"Module\" or \"Package\". \n" +
+ "For more information consult the specification: https://kotlinlang.org/docs/dokka-module-and-package-docs.html",
+ exception?.message
+ )
+ }
+
+ @Test
+ fun `parsing should fail with a message where it encountered error and why`() {
+ val exception =
+ runCatching { readerB.read(dModule(name = "moduleB", sourceSets = setOf(sourceSetB))) }.exceptionOrNull()?.message!!
+
+ //I don't want to assert whole message since it contains a path to a temporary folder
+ assertTrue(exception.contains("Wrong AST Tree. Header does not contain expected content in "))
+ assertTrue(exception.contains("includeB.md"))
+ assertTrue(exception.contains("element starts from offset 0 and ends 3: ###"))
+ }
+}
+
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt
new file mode 100644
index 00000000..18e42e47
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.PluginConfigurationImpl
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.childrenOfType
+import org.jetbrains.dokka.model.dfs
+import org.jetbrains.dokka.model.firstChildOfType
+import org.jetbrains.dokka.pages.*
+import utils.assertNotNull
+import utils.findSectionWithName
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() {
+
+ @Suppress("UNUSED_VARIABLE")
+ private fun configuration(switchOn: Boolean) = dokkaConfiguration {
+ sourceSets {
+ val common = sourceSet {
+ name = "common"
+ displayName = "common"
+ analysisPlatform = "common"
+ sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt")
+ }
+ val js = sourceSet {
+ name = "js"
+ displayName = "js"
+ analysisPlatform = "js"
+ dependentSourceSets = setOf(common.value.sourceSetID)
+ sourceRoots = listOf("src/jsMain/kotlin/pageMerger/Test.kt")
+ }
+ val jvm = sourceSet {
+ name = "jvm"
+ displayName = "jvm"
+ analysisPlatform = "jvm"
+ sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt")
+ }
+ }
+ pluginsConfigurations.add(
+ PluginConfigurationImpl(
+ DokkaBase::class.qualifiedName!!,
+ DokkaConfiguration.SerializationFormat.JSON,
+ """{ "mergeImplicitExpectActualDeclarations": $switchOn }""",
+ )
+ )
+ }
+
+ private fun ContentNode.findTabWithType(type: TabbedContentType): ContentNode? = dfs { node ->
+ node.children.filterIsInstance<ContentGroup>().any { gr ->
+ gr.extra[TabbedContentTypeExtra]?.value == type
+ }
+ }
+
+ @Test
+ fun `should merge fun`() {
+ testInline(
+ """
+
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | fun method1(): String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | fun method1(): Int
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val functions = classPage.findSectionWithName("Functions").assertNotNull("Functions")
+ val method1 = functions.children.singleOrNull().assertNotNull("method1")
+
+ assertEquals(
+ 2,
+ method1.firstChildOfType<ContentDivergentGroup>().childrenOfType<ContentDivergentInstance>().size,
+ "Incorrect number of divergent instances found"
+ )
+
+ val methodPage = root.dfs { it.name == "method1" } as? MemberPageNode
+ assertNotNull(methodPage, "Tested method not found!")
+
+ val divergentGroup = methodPage.content.dfs { it is ContentDivergentGroup } as? ContentDivergentGroup
+
+ assertEquals(
+ 2,
+ divergentGroup?.childrenOfType<ContentDivergentInstance>()?.size,
+ "Incorrect number of divergent instances found in method page"
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `should merge class and typealias`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class A {
+ | fun method1(): String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |typealias A = String
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "A" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val platformHintedContent = classPage.content.dfs { it is PlatformHintedContent }.assertNotNull("platformHintedContent")
+ assertEquals(2, platformHintedContent.sourceSets.size)
+
+ platformHintedContent.dfs { it is ContentText && it.text == "class " }.assertNotNull("class keyword")
+ platformHintedContent.dfs { it is ContentText && it.text == "typealias " }.assertNotNull("typealias keyword")
+ }
+ }
+ }
+ @Test
+ fun `should merge method and prop`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | fun method1(): String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | val prop1: Int
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val props = classPage.findSectionWithName("Properties").assertNotNull("Properties")
+ props.children.singleOrNull().assertNotNull("prop1")
+
+ val functions = classPage.findSectionWithName("Functions").assertNotNull("Functions")
+ functions.children.singleOrNull().assertNotNull("method1")
+ }
+ }
+ }
+
+ @Test
+ fun `should merge prop`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | val prop1: String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | val prop1: Int
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val props = classPage.findSectionWithName("Properties").assertNotNull("Properties")
+ val prop1 = props.children.singleOrNull().assertNotNull("prop1")
+
+ assertEquals(
+ 2,
+ prop1.firstChildOfType<ContentDivergentGroup>().children.size,
+ "Incorrect number of divergent instances found"
+ )
+
+ val propPage = root.dfs { it.name == "prop1" } as? MemberPageNode
+ assertNotNull(propPage, "Tested method not found!")
+
+ val divergentGroup = propPage.content.dfs { it is ContentDivergentGroup } as? ContentDivergentGroup
+
+ assertEquals(
+ 2,
+ divergentGroup?.childrenOfType<ContentDivergentInstance>()?.size,
+ "Incorrect number of divergent instances found in method page"
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `should merge enum and class`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | val prop1: String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |enum class classA {
+ | ENTRY
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val entries = classPage.content.findTabWithType(BasicTabbedContentType.ENTRY).assertNotNull("Entries")
+ entries.children.singleOrNull().assertNotNull("ENTRY")
+
+ val props = classPage.findSectionWithName("Properties").assertNotNull("Properties")
+ assertEquals(
+ 3,
+ props.children.size,
+ "Incorrect number of properties found in method page"
+ )
+ }
+ }
+ }
+
+ fun PageNode.childrenRec(): List<PageNode> = listOf(this) + children.flatMap { it.childrenRec() }
+
+ @Test
+ fun `should merge enum entries`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |enum class classA {
+ | SMTH;
+ | fun method1(): Int
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |enum class classA {
+ | SMTH;
+ | fun method1(): Int
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "SMTH" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val functions = classPage.findSectionWithName("Functions").assertNotNull("Functions")
+ val method1 = functions.children.single { it.sourceSets.size == 2 && it.dci.dri.singleOrNull()?.callable?.name == "method1" }
+ .assertNotNull("method1")
+
+ assertEquals(
+ 2,
+ method1.firstChildOfType<ContentDivergentGroup>().childrenOfType<ContentDivergentInstance>().size,
+ "Incorrect number of divergent instances found"
+ )
+ }
+ }
+ }
+
+ /**
+ * There is a case when a property and fun from different source sets
+ * have the same name so pages have the same urls respectively.
+ */
+ @Test
+ fun `should no merge prop and method with the same name`() {
+ testInline(
+ """
+ |/src/jvmMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | fun merged():String
+ |}
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |class classA {
+ | val merged:String
+ |}
+ |
+ """.trimMargin(),
+ configuration(true),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val allChildren = root.childrenRec().filterIsInstance<MemberPageNode>()
+
+ assertEquals(
+ 1,
+ allChildren.filter { it.name == "merged" }.size,
+ "Incorrect number of fun pages"
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `should always merge constructor`() {
+ testInline(
+ """
+ |/src/commonMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |expect class classA(a: Int)
+ |
+ |/src/jsMain/kotlin/pageMerger/Test.kt
+ |package pageMerger
+ |
+ |actual class classA(a: Int)
+ """.trimMargin(),
+ configuration(false),
+ cleanupOutput = true
+ ) {
+ pagesTransformationStage = { root ->
+ val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode
+ assertNotNull(classPage, "Tested class not found!")
+
+ val constructors = classPage.findSectionWithName("Constructors").assertNotNull("Constructors")
+
+ assertEquals(
+ 1,
+ constructors.children.size,
+ "Incorrect number of constructors"
+ )
+
+ val platformHinted = constructors.dfs { it is PlatformHintedContent } as? PlatformHintedContent
+
+ assertEquals(
+ 2,
+ platformHinted?.sourceSets?.size,
+ "Incorrect number of source sets"
+ )
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerFunctionalTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerFunctionalTest.kt
new file mode 100644
index 00000000..54f0120a
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerFunctionalTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.junit.jupiter.api.io.TempDir
+import transformers.AbstractContextModuleAndPackageDocumentationReaderTest.Companion.texts
+import utils.OnlyDescriptorsMPP
+import java.nio.file.Path
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class ModuleAndPackageDocumentationTransformerFunctionalTest : BaseAbstractTest() {
+
+ @OnlyDescriptorsMPP("#3238")
+ @Test
+ fun `multiplatform project`(@TempDir tempDir: Path) {
+ val include = tempDir.resolve("include.md").toFile()
+ include.writeText(
+ """
+ # Module moduleA
+ This is moduleA
+
+ # Package
+ This is the root package
+
+ # Package [root]
+ This is also the root package
+
+ # Package common
+ This is the common package
+
+ # Package jvm
+ This is the jvm package
+
+ # Package js
+ This is the js package
+ """.trimIndent()
+ )
+ val configuration = dokkaConfiguration {
+ moduleName = "moduleA"
+ sourceSets {
+ sourceSet {
+ name = "commonMain"
+ displayName = "common"
+ analysisPlatform = "common"
+ sourceRoots = listOf("src/commonMain/kotlin")
+ includes = listOf(include.canonicalPath)
+ }
+ sourceSet {
+ name = "jsMain"
+ displayName = "js"
+ analysisPlatform = "js"
+ sourceRoots = listOf("src/jsMain/kotlin")
+ dependentSourceSets = setOf(DokkaSourceSetID("moduleA", "commonMain"))
+ includes = listOf(include.canonicalPath)
+ }
+ sourceSet {
+ name = "jvmMain"
+ displayName = "jvm"
+ analysisPlatform = "jvm"
+ sourceRoots = listOf("src/jvmMain/kotlin")
+ dependentSourceSets = setOf(DokkaSourceSetID("moduleA", "commonMain"))
+ includes = listOf(include.canonicalPath)
+ }
+ }
+ }
+
+ testInline(
+ """
+ /src/commonMain/kotlin/common/CommonApi.kt
+ package common
+ val commonApi = "common"
+
+ /src/jsMain/kotlin/js/JsApi.kt
+ package js
+ val jsApi = "js"
+
+ /src/jvmMain/kotlin/jvm/JvmApi.kt
+ package jvm
+ val jvmApi = "jvm"
+
+ /src/commonMain/kotlin/CommonRoot.kt
+ val commonRoot = "commonRoot"
+
+ /src/jsMain/kotlin/JsRoot.kt
+ val jsRoot = "jsRoot"
+
+ /src/jvmMain/kotlin/JvmRoot.kt
+ val jvmRoot = "jvmRoot"
+ """.trimIndent(),
+ configuration
+ ) {
+ this.documentablesMergingStage = { module ->
+ val packageNames = module.packages.map { it.dri.packageName ?: "NULL" }
+ assertEquals(
+ listOf("", "common", "js", "jvm").sorted(), packageNames.sorted(),
+ "Expected all packages to be present"
+ )
+
+ /* Assert module documentation */
+ assertEquals(3, module.documentation.keys.size, "Expected all three source sets")
+ assertEquals("This is moduleA", module.documentation.texts.distinct().joinToString())
+
+ /* Assert root package */
+ val rootPackage = module.packages.single { it.dri.packageName == "" }
+ assertEquals(3, rootPackage.documentation.keys.size, "Expected all three source sets")
+ assertEquals(
+ listOf("This is the root package", "This is also the root package"),
+ rootPackage.documentation.texts.distinct()
+ )
+
+ /* Assert common package */
+ val commonPackage = module.packages.single { it.dri.packageName == "common" }
+ assertEquals(3, commonPackage.documentation.keys.size, "Expected all three source sets")
+ assertEquals("This is the common package", commonPackage.documentation.texts.distinct().joinToString())
+
+ /* Assert js package */
+ val jsPackage = module.packages.single { it.dri.packageName == "js" }
+ assertEquals(
+ "This is the js package",
+ jsPackage.documentation.texts.joinToString()
+ )
+
+ /* Assert the jvm package */
+ val jvmPackage = module.packages.single { it.dri.packageName == "jvm" }
+ assertEquals(
+ "This is the jvm package",
+ jvmPackage.documentation.texts.joinToString()
+ )
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerUnitTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerUnitTest.kt
new file mode 100644
index 00000000..a54b6c68
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerUnitTest.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.kotlin.internal.ModuleAndPackageDocumentationReader
+import org.jetbrains.dokka.analysis.markdown.jb.MARKDOWN_ELEMENT_FILE_NAME
+import org.jetbrains.dokka.base.transformers.documentables.ModuleAndPackageDocumentationTransformer
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.model.DPackage
+import org.jetbrains.dokka.model.SourceSetDependent
+import org.jetbrains.dokka.model.doc.CustomDocTag
+import org.jetbrains.dokka.model.doc.Description
+import org.jetbrains.dokka.model.doc.DocumentationNode
+import org.jetbrains.dokka.model.doc.Text
+import testApi.testRunner.dPackage
+import testApi.testRunner.sourceSet
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+
+class ModuleAndPackageDocumentationTransformerUnitTest {
+
+ @Test
+ fun `empty list of modules`() {
+ val transformer = ModuleAndPackageDocumentationTransformer(
+ object : ModuleAndPackageDocumentationReader {
+ override fun read(module: DModule): SourceSetDependent<DocumentationNode> = throw NotImplementedError()
+ override fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> = throw NotImplementedError()
+ override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode = throw NotImplementedError()
+ }
+ )
+
+ assertEquals(
+ emptyList<DModule>(), transformer(emptyList()),
+ )
+ }
+
+ @Test
+ fun `single module documentation`() {
+ val transformer = ModuleAndPackageDocumentationTransformer(
+ object : ModuleAndPackageDocumentationReader {
+ override fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> = throw NotImplementedError()
+ override fun read(module: DModule): SourceSetDependent<DocumentationNode> {
+ return module.sourceSets.associateWith { sourceSet ->
+ documentationNode("doc" + sourceSet.displayName)
+ }
+ }
+ override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode = throw NotImplementedError()
+ }
+ )
+
+ val result = transformer(
+ listOf(
+ DModule(
+ "ModuleName",
+ documentation = emptyMap(),
+ packages = emptyList(),
+ sourceSets = setOf(
+ sourceSet("A"),
+ sourceSet("B")
+ )
+ )
+ )
+ )
+
+ assertEquals(
+ DModule(
+ "ModuleName",
+ documentation = mapOf(
+ sourceSet("A") to documentationNode("docA"),
+ sourceSet("B") to documentationNode("docB")
+ ),
+ sourceSets = setOf(sourceSet("A"), sourceSet("B")),
+ packages = emptyList()
+ ),
+ result.single()
+ )
+
+ }
+
+ @Test
+ fun `merges with already existing module documentation`() {
+ val transformer = ModuleAndPackageDocumentationTransformer(
+ object : ModuleAndPackageDocumentationReader {
+ override fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> = throw NotImplementedError()
+ override fun read(module: DModule): SourceSetDependent<DocumentationNode> {
+ /* Only add documentation for first source set */
+ return module.sourceSets.take(1).associateWith { sourceSet ->
+ documentationNode("doc" + sourceSet.displayName)
+ }
+ }
+ override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode = throw NotImplementedError()
+ }
+ )
+
+ val result = transformer(
+ listOf(
+ DModule(
+ "MyModule",
+ documentation = mapOf(
+ sourceSet("A") to documentationNode("pre-existing:A"),
+ sourceSet("B") to documentationNode("pre-existing:B")
+ ),
+ sourceSets = setOf(sourceSet("A"), sourceSet("B")),
+ packages = emptyList()
+ )
+ )
+ )
+
+ assertEquals(
+ DModule(
+ "MyModule",
+ documentation = mapOf(
+ /* Expect previous documentation and newly attached one */
+ sourceSet("A") to documentationNode("pre-existing:A", "docA"),
+ /* Only first source set will get documentation attached */
+ sourceSet("B") to documentationNode("pre-existing:B")
+ ),
+ sourceSets = setOf(sourceSet("A"), sourceSet("B")),
+ packages = emptyList()
+ ),
+ result.single()
+ )
+ }
+
+ @Test
+ fun `package documentation`() {
+ val transformer = ModuleAndPackageDocumentationTransformer(
+ object : ModuleAndPackageDocumentationReader {
+ override fun read(module: DModule): SourceSetDependent<DocumentationNode> = emptyMap()
+ override fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> {
+ /* Only attach documentation to packages with 'attach' */
+ if ("attach" !in pkg.dri.packageName.orEmpty()) return emptyMap()
+ /* Only attach documentation to two source sets */
+ return pkg.sourceSets.take(2).associateWith { sourceSet ->
+ documentationNode("doc:${sourceSet.displayName}:${pkg.dri.packageName}")
+ }
+ }
+ override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode = throw NotImplementedError()
+ }
+ )
+
+ val result = transformer(
+ listOf(
+ DModule(
+ "MyModule",
+ documentation = emptyMap(),
+ sourceSets = emptySet(),
+ packages = listOf(
+ dPackage(
+ dri = DRI("com.sample"),
+ documentation = mapOf(
+ sourceSet("A") to documentationNode("pre-existing:A:com.sample")
+ ),
+ sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")),
+ ),
+ dPackage(
+ dri = DRI("com.attach"),
+ documentation = mapOf(
+ sourceSet("A") to documentationNode("pre-existing:A:com.attach")
+ ),
+ sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C"))
+ ),
+ dPackage(
+ dri = DRI("com.attach.sub"),
+ documentation = mapOf(
+ sourceSet("A") to documentationNode("pre-existing:A:com.attach.sub"),
+ sourceSet("B") to documentationNode("pre-existing:B:com.attach.sub"),
+ sourceSet("C") to documentationNode("pre-existing:C:com.attach.sub")
+ ),
+ sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")),
+ )
+ )
+ )
+ )
+ )
+
+ result.single().packages.forEach { pkg ->
+ assertEquals(
+ setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")), pkg.sourceSets,
+ "Expected source sets A, B, C for package ${pkg.dri.packageName}"
+ )
+ }
+
+ val comSample = result.single().packages.single { it.dri.packageName == "com.sample" }
+ assertEquals(
+ mapOf(sourceSet("A") to documentationNode("pre-existing:A:com.sample")),
+ comSample.documentation,
+ "Expected no documentation added to package 'com.sample' because of wrong package"
+ )
+
+ val comAttach = result.single().packages.single { it.dri.packageName == "com.attach" }
+ assertEquals(
+ mapOf(
+ sourceSet("A") to documentationNode("pre-existing:A:com.attach", "doc:A:com.attach"),
+ sourceSet("B") to documentationNode("doc:B:com.attach")
+ ),
+ comAttach.documentation,
+ "Expected documentation added to source sets A and B"
+ )
+
+ assertEquals(
+ DModule(
+ "MyModule",
+ documentation = emptyMap(),
+ sourceSets = emptySet(),
+ packages = listOf(
+ dPackage(
+ dri = DRI("com.sample"),
+ documentation = mapOf(
+ /* No documentation added, since in wrong package */
+ sourceSet("A") to documentationNode("pre-existing:A:com.sample")
+ ),
+ sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")),
+
+ ),
+ dPackage(
+ dri = DRI("com.attach"),
+ documentation = mapOf(
+ /* Documentation added */
+ sourceSet("A") to documentationNode("pre-existing:A:com.attach", "doc:A:com.attach"),
+ sourceSet("B") to documentationNode("doc:B:com.attach")
+ ),
+ sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")),
+ ),
+ dPackage(
+ dri = DRI("com.attach.sub"),
+ documentation = mapOf(
+ /* Documentation added */
+ sourceSet("A") to documentationNode(
+ "pre-existing:A:com.attach.sub",
+ "doc:A:com.attach.sub"
+ ),
+ /* Documentation added */
+ sourceSet("B") to documentationNode(
+ "pre-existing:B:com.attach.sub",
+ "doc:B:com.attach.sub"
+ ),
+ /* No documentation added, since in wrong source set */
+ sourceSet("C") to documentationNode("pre-existing:C:com.attach.sub")
+ ),
+ sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")),
+ )
+ )
+ ), result.single()
+ )
+ }
+
+
+ private fun documentationNode(vararg texts: String): DocumentationNode {
+ return DocumentationNode(
+ texts.toList()
+ .map { Description(CustomDocTag(listOf(Text(it)), name = MARKDOWN_ELEMENT_FILE_NAME)) })
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ObviousAndInheritedFunctionsDocumentableFilterTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ObviousAndInheritedFunctionsDocumentableFilterTest.kt
new file mode 100644
index 00000000..d035948f
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ObviousAndInheritedFunctionsDocumentableFilterTest.kt
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+import testApi.testRunner.dokkaConfiguration
+import kotlin.test.assertEquals
+
+class ObviousAndInheritedFunctionsDocumentableFilterTest : BaseAbstractTest() {
+ companion object {
+ @JvmStatic
+ fun suppressingObviousConfiguration() = listOf(dokkaConfiguration {
+ suppressInheritedMembers = false
+ suppressObviousFunctions = true
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ }
+ }
+ })
+
+ @JvmStatic
+ fun nonSuppressingObviousConfiguration() = listOf(dokkaConfiguration {
+ suppressObviousFunctions = false
+ suppressInheritedMembers = false
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ }
+ }
+ })
+
+ @JvmStatic
+ fun suppressingInheritedConfiguration() = listOf(dokkaConfiguration {
+ suppressInheritedMembers = true
+ suppressObviousFunctions = false
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ }
+ }
+ })
+
+ @JvmStatic
+ fun nonSuppressingInheritedConfiguration() = listOf(dokkaConfiguration {
+ suppressObviousFunctions = false
+ suppressInheritedMembers = false
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ }
+ }
+ })
+ }
+
+
+ @ParameterizedTest
+ @MethodSource(value = ["suppressingObviousConfiguration"])
+ fun `should suppress toString, equals and hashcode`(suppressingConfiguration: DokkaConfigurationImpl) {
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ data class Suppressed(val x: String)
+ """.trimIndent(),
+ suppressingConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(0, functions.size)
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = ["suppressingObviousConfiguration", "suppressingInheritedConfiguration"])
+ fun `should suppress toString, equals and hashcode for interface`(suppressingConfiguration: DokkaConfigurationImpl) {
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ interface Suppressed
+ """.trimIndent(),
+ suppressingConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(0, functions.size)
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = ["suppressingObviousConfiguration", "suppressingInheritedConfiguration"])
+ fun `should suppress toString, equals and hashcode in Java`(suppressingConfiguration: DokkaConfigurationImpl) {
+ testInline(
+ """
+ /src/suppressed/Suppressed.java
+ package suppressed;
+ public class Suppressed {
+ }
+ """.trimIndent(),
+ suppressingConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(0, functions.size)
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = ["suppressingObviousConfiguration"])
+ fun `should suppress toString, equals and hashcode but keep custom ones`(suppressingConfiguration: DokkaConfigurationImpl) {
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ data class Suppressed(val x: String) {
+ override fun toString(): String {
+ return "custom"
+ }
+ }
+ """.trimIndent(),
+ suppressingConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(listOf("toString"), functions.map { it.name })
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = ["suppressingObviousConfiguration", "suppressingInheritedConfiguration"])
+ fun `should suppress toString, equals and hashcode but keep custom ones in Java`(suppressingConfiguration: DokkaConfigurationImpl) {
+ testInline(
+ """
+ /src/suppressed/Suppressed.java
+ package suppressed;
+ public class Suppressed {
+ @Override
+ public String toString() {
+ return "";
+ }
+ }
+ """.trimIndent(),
+ suppressingConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(listOf("toString"), functions.map { it.name })
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = ["nonSuppressingObviousConfiguration", "nonSuppressingInheritedConfiguration"])
+ fun `should not suppress toString, equals and hashcode if custom config is provided`(nonSuppressingConfiguration: DokkaConfigurationImpl) {
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ data class Suppressed(val x: String)
+ """.trimIndent(),
+ nonSuppressingConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(
+ listOf("copy", "equals", "toString", "component1", "hashCode").sorted(),
+ functions.map { it.name }.sorted()
+ )
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = ["nonSuppressingObviousConfiguration", "nonSuppressingInheritedConfiguration"])
+ fun `not should suppress toString, equals and hashcode for interface if custom config is provided`(nonSuppressingConfiguration: DokkaConfigurationImpl) {
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ interface Suppressed
+ """.trimIndent(),
+ nonSuppressingConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ assertEquals(listOf("equals", "hashCode", "toString").sorted(), functions.map { it.name }.sorted())
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = ["nonSuppressingObviousConfiguration", "nonSuppressingInheritedConfiguration"])
+ fun `should not suppress toString, equals and hashcode if custom config is provided in Java`(nonSuppressingConfiguration: DokkaConfigurationImpl) {
+ testInline(
+ """
+ /src/suppressed/Suppressed.java
+ package suppressed;
+ public class Suppressed {
+ }
+ """.trimIndent(),
+ nonSuppressingConfiguration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ //I would normally just assert names but this would make it JDK dependent, so this is better
+ assertEquals(
+ 5,
+ setOf(
+ "equals",
+ "hashCode",
+ "toString",
+ "notify",
+ "notifyAll"
+ ).intersect(functions.map { it.name }).size
+ )
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt
new file mode 100644
index 00000000..c824e690
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt
@@ -0,0 +1,927 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaDefaults
+import org.jetbrains.dokka.PackageOptionsImpl
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+
+class ReportUndocumentedTransformerTest : BaseAbstractTest() {
+ @Test
+ fun `undocumented class gets reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |class X
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport(Regex("init"))
+ assertSingleUndocumentedReport(Regex("""sample/X/"""))
+ }
+ }
+ }
+
+ @Test
+ fun `undocumented non-public class does not get reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |internal class X
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport()
+ }
+ }
+ }
+
+ @Test
+ fun `undocumented function gets reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |/** Documented */
+ |class X {
+ | fun x()
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertSingleUndocumentedReport(Regex("X"))
+ assertSingleUndocumentedReport(Regex("X/x"))
+ }
+ }
+ }
+
+ @Test
+ fun `undocumented property gets reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |/** Documented */
+ |class X {
+ | val x: Int = 0
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertSingleUndocumentedReport(Regex("X"))
+ assertSingleUndocumentedReport(Regex("X/x"))
+ }
+ }
+ }
+
+ @Test
+ fun `undocumented primary constructor does not get reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |/** Documented */
+ |class X(private val x: Int) {
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport()
+ }
+ }
+ }
+
+ @Test
+ fun `data class component functions do not get reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |/** Documented */
+ |data class X(val x: Int) {
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport(Regex("component"))
+ assertNumberOfUndocumentedReports(1)
+ }
+ }
+ }
+
+ @Ignore
+ @Test
+ fun `undocumented secondary constructor gets reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |/** Documented */
+ |class X {
+ | constructor(unit: Unit) : this()
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertSingleUndocumentedReport(Regex("X"))
+ assertSingleUndocumentedReport(Regex("X.*init.*Unit"))
+ }
+ }
+ }
+
+ @Test
+ fun `undocumented inherited function does not get reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |/** Documented */
+ |open class A {
+ | fun a() = Unit
+ |}
+ |
+ |/** Documented */
+ |class B : A()
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport(Regex("B"))
+ assertSingleUndocumentedReport(Regex("A.*a"))
+ }
+ }
+ }
+
+ @Test
+ fun `undocumented inherited property does not get reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |/** Documented */
+ |open class A {
+ | val a = Unit
+ |}
+ |
+ |/** Documented */
+ |class B : A()
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport(Regex("B"))
+ assertSingleUndocumentedReport(Regex("A.*a"))
+ }
+ }
+ }
+
+ @Test
+ fun `overridden function does not get reported when super is documented`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |import kotlin.Exception
+ |
+ |/** Documented */
+ |open class A {
+ | /** Documented */
+ | fun a() = Unit
+ |}
+ |
+ |/** Documented */
+ |class B : A() {
+ | override fun a() = throw Exception()
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport()
+ }
+ }
+ }
+
+ @Test
+ fun `overridden property does not get reported when super is documented`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |import kotlin.Exception
+ |
+ |/** Documented */
+ |open class A {
+ | /** Documented */
+ | open val a = 0
+ |}
+ |
+ |/** Documented */
+ |class B : A() {
+ | override val a = 1
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport()
+ }
+ }
+ }
+
+ @Test
+ fun `report disabled by source set`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = false
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |class X
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport()
+ }
+ }
+ }
+
+ @Test
+ fun `report enabled by package configuration`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ perPackageOptions += packageOptions(
+ matchingRegex = "sample.*",
+ reportUndocumented = true,
+ )
+ reportUndocumented = false
+ sourceRoots = listOf("src/main/kotlin/Test.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/Test.kt
+ |package sample
+ |
+ |class X
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertSingleUndocumentedReport(Regex("X"))
+ }
+ }
+ }
+
+ @Test
+ fun `report enabled by more specific package configuration`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ perPackageOptions += packageOptions(
+ matchingRegex = "sample.*",
+ reportUndocumented = false,
+ )
+ perPackageOptions += packageOptions(
+ matchingRegex = "sample.enabled.*",
+ reportUndocumented = true,
+ )
+ reportUndocumented = false
+ sourceRoots = listOf("src/main/kotlin/")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/sample/disabled/Disabled.kt
+ |package sample.disabled
+ |class Disabled
+ |
+ |/src/main/kotlin/sample/enabled/Enabled.kt
+ |package sample.enabled
+ |class Enabled
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertSingleUndocumentedReport(Regex("Enabled"))
+ assertNumberOfUndocumentedReports(1)
+ }
+ }
+ }
+
+ @Test
+ fun `report disabled by more specific package configuration`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ perPackageOptions += packageOptions(
+ matchingRegex = "sample.*",
+ reportUndocumented = true,
+ )
+ perPackageOptions += packageOptions(
+ matchingRegex = "sample.disabled.*",
+ reportUndocumented = false,
+ )
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/kotlin/")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/sample/disabled/Disabled.kt
+ |package sample.disabled
+ |class Disabled
+ |
+ |/src/main/kotlin/sample/enabled/Enabled.kt
+ |package sample.enabled
+ |class Enabled
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertSingleUndocumentedReport(Regex("Enabled"))
+ assertNumberOfUndocumentedReports(1)
+ }
+ }
+ }
+
+ @Test
+ fun `multiplatform undocumented class gets reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ val commonMain by sourceSet {
+ reportUndocumented = true
+ analysisPlatform = Platform.common.toString()
+ name = "commonMain"
+ displayName = "commonMain"
+ sourceRoots = listOf("src/commonMain/kotlin")
+ }
+
+ sourceSet {
+ reportUndocumented = true
+ analysisPlatform = Platform.jvm.toString()
+ name = "jvmMain"
+ displayName = "jvmMain"
+ sourceRoots = listOf("src/jvmMain/kotlin")
+ dependentSourceSets = setOf(commonMain.sourceSetID)
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/commonMain/kotlin/sample/Common.kt
+ |package sample
+ |expect class X
+ |
+ |/src/jvmMain/kotlin/sample/JvmMain.kt
+ |package sample
+ |actual class X
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNumberOfUndocumentedReports(2, Regex("X"))
+ assertSingleUndocumentedReport(Regex("X.*jvmMain"))
+ assertSingleUndocumentedReport(Regex("X.*commonMain"))
+ }
+ }
+ }
+
+ @Test
+ fun `multiplatform undocumented class does not get reported if expect is documented`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ val commonMain by sourceSet {
+ reportUndocumented = true
+ analysisPlatform = Platform.common.toString()
+ name = "commonMain"
+ displayName = "commonMain"
+ sourceRoots = listOf("src/commonMain/kotlin")
+ }
+
+ sourceSet {
+ reportUndocumented = true
+ analysisPlatform = Platform.jvm.toString()
+ name = "jvmMain"
+ displayName = "jvmMain"
+ sourceRoots = listOf("src/jvmMain/kotlin")
+ dependentSourceSets = setOf(commonMain.sourceSetID)
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/commonMain/kotlin/sample/Common.kt
+ |package sample
+ |/** Documented */
+ |expect class X
+ |
+ |/src/jvmMain/kotlin/sample/JvmMain.kt
+ |package sample
+ |actual class X
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNumberOfUndocumentedReports(0)
+ }
+ }
+ }
+
+ @Test
+ fun `multiplatform undocumented function gets reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ val commonMain by sourceSet {
+ reportUndocumented = true
+ analysisPlatform = Platform.common.toString()
+ name = "commonMain"
+ displayName = "commonMain"
+ sourceRoots = listOf("src/commonMain/kotlin")
+ }
+
+ sourceSet {
+ reportUndocumented = true
+ analysisPlatform = Platform.jvm.toString()
+ name = "jvmMain"
+ displayName = "jvmMain"
+ sourceRoots = listOf("src/jvmMain/kotlin")
+ dependentSourceSets = setOf(commonMain.sourceSetID)
+ }
+
+ sourceSet {
+ reportUndocumented = true
+ analysisPlatform = Platform.native.toString()
+ name = "macosMain"
+ displayName = "macosMain"
+ sourceRoots = listOf("src/macosMain/kotlin")
+ dependentSourceSets = setOf(commonMain.sourceSetID)
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/commonMain/kotlin/sample/Common.kt
+ |package sample
+ |expect fun x()
+ |
+ |/src/macosMain/kotlin/sample/MacosMain.kt
+ |package sample
+ |/** Documented */
+ |actual fun x() = Unit
+ |
+ |/src/jvmMain/kotlin/sample/JvmMain.kt
+ |package sample
+ |actual fun x() = Unit
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNumberOfUndocumentedReports(2)
+ assertSingleUndocumentedReport(Regex("x.*commonMain"))
+ assertSingleUndocumentedReport(Regex("x.*jvmMain"))
+ }
+ }
+ }
+
+ @Test
+ fun `java undocumented class gets reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/java")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/java/sample/Test.java
+ |package sample
+ |public class Test { }
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport(Regex("init"))
+ assertSingleUndocumentedReport(Regex("""Test"""))
+ assertNumberOfUndocumentedReports(1)
+ }
+ }
+ }
+
+ @Test
+ fun `java undocumented non-public class does not get reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/java")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/java/sample/Test.java
+ |package sample
+ |class Test { }
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport()
+ }
+ }
+ }
+
+ @Test
+ fun `java undocumented constructor does not get reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/java")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/java/sample/Test.java
+ |package sample
+ |/** Documented */
+ |public class Test {
+ | public Test() {
+ | }
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport()
+ }
+ }
+ }
+
+ @Test
+ fun `java undocumented method gets reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/java")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/java/sample/X.java
+ |package sample
+ |/** Documented */
+ |public class X {
+ | public void x { }
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertSingleUndocumentedReport(Regex("X"))
+ assertSingleUndocumentedReport(Regex("X.*x"))
+ assertNumberOfUndocumentedReports(1)
+ }
+ }
+ }
+
+ @Test
+ fun `java undocumented property gets reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/java")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/java/sample/X.java
+ |package sample
+ |/** Documented */
+ |public class X {
+ | public int x = 0;
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertSingleUndocumentedReport(Regex("X"))
+ assertSingleUndocumentedReport(Regex("X.*x"))
+ assertNumberOfUndocumentedReports(1)
+ }
+ }
+ }
+
+ @Test
+ fun `java undocumented inherited method gets reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/java")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/java/sample/Super.java
+ |package sample
+ |/** Documented */
+ |public class Super {
+ | public void x() {}
+ |}
+ |
+ |/src/main/java/sample/X.java
+ |package sample
+ |/** Documented */
+ |public class X extends Super {
+ | public void x() {}
+ |}
+ |
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertSingleUndocumentedReport(Regex("X"))
+ assertSingleUndocumentedReport(Regex("X.*x"))
+ assertSingleUndocumentedReport(Regex("Super.*x"))
+ assertNumberOfUndocumentedReports(2)
+ }
+ }
+ }
+
+ @Test
+ fun `java documented inherited method does not get reported`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/java")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/java/sample/Super.java
+ |package sample
+ |/** Documented */
+ |public class Super {
+ | /** Documented */
+ | public void x() {}
+ |}
+ |
+ |/src/main/java/sample/X.java
+ |package sample
+ |/** Documented */
+ |public class X extends Super {
+ |
+ |}
+ |
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport()
+ }
+ }
+ }
+
+ @Test
+ fun `java overridden function does not get reported when super is documented`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ reportUndocumented = true
+ sourceRoots = listOf("src/main/java")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/java/sample/Super.java
+ |package sample
+ |/** Documented */
+ |public class Super {
+ | /** Documented */
+ | public void x() {}
+ |}
+ |
+ |/src/main/java/sample/X.java
+ |package sample
+ |/** Documented */
+ |public class X extends Super {
+ | @Override
+ | public void x() {}
+ |}
+ |
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = {
+ assertNoUndocumentedReport()
+ }
+ }
+ }
+
+ private fun assertNumberOfUndocumentedReports(expectedReports: Int, regex: Regex = Regex(".")) {
+ val reports = logger.warnMessages
+ .filter { it.startsWith("Undocumented:") }
+ val matchingReports = reports
+ .filter { it.contains(regex) }
+
+ assertEquals(
+ expectedReports, matchingReports.size,
+ "Expected $expectedReports report of documented code ($regex).\n" +
+ "Found matching reports: $matchingReports\n" +
+ "Found reports: $reports"
+ )
+ }
+
+ private fun assertSingleUndocumentedReport(regex: Regex) {
+ assertNumberOfUndocumentedReports(1, regex)
+ }
+
+ private fun assertNoUndocumentedReport(regex: Regex) {
+ assertNumberOfUndocumentedReports(0, regex)
+ }
+
+ private fun assertNoUndocumentedReport() {
+ assertNoUndocumentedReport(Regex("."))
+ }
+
+ private fun packageOptions(
+ matchingRegex: String,
+ reportUndocumented: Boolean?,
+ includeNonPublic: Boolean = true,
+ skipDeprecated: Boolean = false,
+ suppress: Boolean = false,
+ documentedVisibilities: Set<DokkaConfiguration.Visibility> = DokkaDefaults.documentedVisibilities
+ ) = PackageOptionsImpl(
+ matchingRegex = matchingRegex,
+ reportUndocumented = reportUndocumented,
+ includeNonPublic = includeNonPublic,
+ documentedVisibilities = documentedVisibilities,
+ skipDeprecated = skipDeprecated,
+ suppress = suppress
+ )
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SourceLinkTransformerTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SourceLinkTransformerTest.kt
new file mode 100644
index 00000000..87424120
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SourceLinkTransformerTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.DokkaSourceSetID
+import org.jetbrains.dokka.SourceLinkDefinitionImpl
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jsoup.nodes.Element
+import signatures.renderedContent
+import utils.TestOutputWriterPlugin
+import java.net.URL
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class SourceLinkTransformerTest : BaseAbstractTest() {
+
+ private fun Element.getSourceLink() = select(".symbol .floating-right")
+ .select("a[href]")
+ .attr("href")
+
+ @Test
+ fun `source link should lead to name`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ sourceLinks = listOf(
+ SourceLinkDefinitionImpl(
+ localDirectory = "src/main/kotlin",
+ remoteUrl = URL("https://github.com/user/repo/tree/master/src/main/kotlin"),
+ remoteLineSuffix = "#L"
+ )
+ )
+ }
+ }
+ }
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/basic/Deprecated.kt
+ |package testpackage
+ |
+ |/**
+ |* Marks the annotated declaration as deprecated. ...
+ |*/
+ |@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS)
+ |@MustBeDocumented
+ |public annotation class Deprecated(
+ | val message: String,
+ | val replaceWith: ReplaceWith = ReplaceWith(""),
+ | val level: DeprecationLevel = DeprecationLevel.WARNING
+ |)
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val page = writerPlugin.writer.renderedContent("root/testpackage/-deprecated/index.html")
+ val sourceLink = page.getSourceLink()
+
+ assertEquals(
+ "https://github.com/user/repo/tree/master/src/main/kotlin/basic/Deprecated.kt#L8",
+ sourceLink
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `source link should be for actual typealias`() {
+ 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!!)
+ externalDocumentationLinks = listOf(stdlibExternalDocumentationLink)
+ sourceLinks = listOf(
+ SourceLinkDefinitionImpl(
+ localDirectory = "src/main/kotlin",
+ remoteUrl = URL("https://github.com/user/repo/tree/master/src/main/kotlin"),
+ remoteLineSuffix = "#L"
+ )
+ )
+ }
+ }
+ }
+
+ 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 page = writerPlugin.writer.renderedContent("test/example/-foo/index.html")
+ val sourceLink = page.getSourceLink()
+
+ assertEquals(
+ "https://github.com/user/repo/tree/master/src/main/kotlin/jvm/Test.kt#L4",
+ sourceLink
+ )
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressTagFilterTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressTagFilterTest.kt
new file mode 100644
index 00000000..5392a028
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressTagFilterTest.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.DEnum
+import org.jetbrains.dokka.model.WithCompanion
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+
+class SuppressTagFilterTest : BaseAbstractTest() {
+ private val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ }
+ }
+ }
+
+ @Test
+ fun `should filter classes with suppress tag`() {
+ testInline(
+ """
+ |/src/suppressed/NotSuppressed.kt
+ |/**
+ | * sample docs
+ |*/
+ |class NotSuppressed
+ |/src/suppressed/Suppressed.kt
+ |/**
+ | * sample docs
+ | * @suppress
+ |*/
+ |class Suppressed
+ """.trimIndent(), configuration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ assertEquals(
+ "NotSuppressed",
+ modules.flatMap { it.packages }.flatMap { it.classlikes }.singleOrNull()?.name
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `should filter functions with suppress tag`() {
+ testInline(
+ """
+ |/src/suppressed/Suppressed.kt
+ |class Suppressed {
+ | /**
+ | * sample docs
+ | * @suppress
+ | */
+ | fun suppressedFun(){ }
+ |}
+ """.trimIndent(), configuration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ assertNull(modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions }
+ .firstOrNull { it.name == "suppressedFun" })
+ }
+ }
+ }
+
+ @Test
+ fun `should filter top level functions`() {
+ testInline(
+ """
+ |/src/suppressed/Suppressed.kt
+ |/**
+ | * sample docs
+ | * @suppress
+ | */
+ |fun suppressedFun(){ }
+ |
+ |/**
+ | * Sample
+ | */
+ |fun notSuppressedFun() { }
+ """.trimIndent(), configuration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ assertNull(modules.flatMap { it.packages }.flatMap { it.functions }
+ .firstOrNull { it.name == "suppressedFun" })
+ }
+ }
+ }
+
+ @Test
+ fun `should filter setter`() {
+ testInline(
+ """
+ |/src/suppressed/Suppressed.kt
+ |var property: Int
+ |/** @suppress */
+ |private set
+ """.trimIndent(), configuration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val prop = modules.flatMap { it.packages }.flatMap { it.properties }
+ .find { it.name == "property" }
+ assertNotNull(prop)
+ assertNotNull(prop.getter)
+ assertNull(prop.setter)
+ }
+ }
+ }
+
+ @Test
+ fun `should filter top level type aliases`() {
+ testInline(
+ """
+ |/src/suppressed/suppressed.kt
+ |/**
+ | * sample docs
+ | * @suppress
+ | */
+ |typealias suppressedTypeAlias = String
+ |
+ |/**
+ | * Sample
+ | */
+ |typealias notSuppressedTypeAlias = String
+ """.trimIndent(), configuration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ assertNull(modules.flatMap { it.packages }.flatMap { it.typealiases }
+ .firstOrNull { it.name == "suppressedTypeAlias" })
+ assertNotNull(modules.flatMap { it.packages }.flatMap { it.typealiases }
+ .firstOrNull { it.name == "notSuppressedTypeAlias" })
+ }
+ }
+ }
+
+ @Test
+ fun `should filter companion object`() {
+ testInline(
+ """
+ |/src/suppressed/Suppressed.kt
+ |class Suppressed {
+ |/**
+ | * @suppress
+ | */
+ |companion object {
+ | val x = 1
+ |}
+ |}
+ """.trimIndent(), configuration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ assertNull((modules.flatMap { it.packages }.flatMap { it.classlikes }
+ .firstOrNull { it.name == "Suppressed" } as? WithCompanion)?.companion)
+ }
+ }
+ }
+
+ @Test
+ fun `should suppress inner classlike`() {
+ testInline(
+ """
+ |/src/suppressed/Testing.kt
+ |class Testing {
+ | /**
+ | * Sample
+ | * @suppress
+ | */
+ | inner class Suppressed {
+ | val x = 1
+ | }
+ |}
+ """.trimIndent(), configuration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val testingClass = modules.flatMap { it.packages }.flatMap { it.classlikes }.single()
+ assertNull(testingClass.classlikes.firstOrNull())
+ }
+ }
+ }
+
+ @Test
+ fun `should suppress enum entry`() {
+ testInline(
+ """
+ |/src/suppressed/Testing.kt
+ |enum class Testing {
+ | /**
+ | * Sample
+ | * @suppress
+ | */
+ | SUPPRESSED,
+ |
+ | /**
+ | * Not suppressed
+ | */
+ | NOT_SUPPRESSED
+ |}
+ """.trimIndent(), configuration
+ ) {
+ preMergeDocumentablesTransformationStage = { modules ->
+ val testingClass = modules.flatMap { it.packages }.flatMap { it.classlikes }.single() as DEnum
+ assertEquals(listOf("NOT_SUPPRESSED"), testingClass.entries.map { it.name })
+ }
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressedByConfigurationDocumentableFilterTransformerTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressedByConfigurationDocumentableFilterTransformerTest.kt
new file mode 100644
index 00000000..f946a885
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressedByConfigurationDocumentableFilterTransformerTest.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.DokkaDefaults
+import org.jetbrains.dokka.PackageOptionsImpl
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.links.DRI
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class SuppressedByConfigurationDocumentableFilterTransformerTest : BaseAbstractTest() {
+
+ @Test
+ fun `class filtered by package options`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ perPackageOptions = listOf(
+ packageOptions(matchingRegex = "suppressed.*", suppress = true),
+ packageOptions(matchingRegex = "default.*", suppress = false)
+ )
+ }
+ }
+ }
+
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ class Suppressed
+
+ /src/default/Default.kt
+ package default
+ class Default.kt
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ assertEquals(1, module.children.size, "Expected just a single package in module")
+ assertEquals(1, module.packages.size, "Expected just a single package in module")
+
+ val pkg = module.packages.single()
+ assertEquals("default", pkg.dri.packageName, "Expected 'default' package in module")
+ assertEquals(1, pkg.children.size, "Expected just a single child in 'default' package")
+ assertEquals(1, pkg.classlikes.size, "Expected just a single child in 'default' package")
+
+ val classlike = pkg.classlikes.single()
+ assertEquals(DRI("default", "Default"), classlike.dri, "Expected 'Default' class in 'default' package")
+ }
+ }
+ }
+
+ @Test
+ fun `class filtered by more specific package options`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ perPackageOptions = listOf(
+ packageOptions(matchingRegex = "parent.some.*", suppress = false),
+ packageOptions(matchingRegex = "parent.some.suppressed.*", suppress = true),
+
+ packageOptions(matchingRegex = "parent.other.*", suppress = true),
+ packageOptions(matchingRegex = "parent.other.default.*", suppress = false)
+ )
+ }
+ }
+ }
+
+ testInline(
+ """
+ /src/parent/some/Some.kt
+ package parent.some
+ class Some
+
+ /src/parent/some/suppressed/Suppressed.kt
+ package parent.some.suppressed
+ class Suppressed
+
+ /src/parent/other/Other.kt
+ package parent.other
+ class Other
+
+ /src/parent/other/default/Default.kt
+ package parent.other.default
+ class Default
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ assertEquals(2, module.packages.size, "Expected two packages in module")
+ assertEquals(
+ listOf(DRI("parent.some"), DRI("parent.other.default")).sortedBy { it.packageName },
+ module.packages.map { it.dri }.sortedBy { it.packageName },
+ "Expected 'parent.some' and 'parent.other.default' packages to be not suppressed"
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `class filtered by parent file path`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ suppressedFiles = listOf("src/suppressed")
+ }
+ }
+ }
+
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ class Suppressed
+
+ /src/default/Default.kt
+ package default
+ class Default.kt
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ assertEquals(1, module.children.size, "Expected just a single package in module")
+ assertEquals(1, module.packages.size, "Expected just a single package in module")
+
+ val pkg = module.packages.single()
+ assertEquals("default", pkg.dri.packageName, "Expected 'default' package in module")
+ assertEquals(1, pkg.children.size, "Expected just a single child in 'default' package")
+ assertEquals(1, pkg.classlikes.size, "Expected just a single child in 'default' package")
+
+ val classlike = pkg.classlikes.single()
+ assertEquals(DRI("default", "Default"), classlike.dri, "Expected 'Default' class in 'default' package")
+ }
+ }
+ }
+
+ @Test
+ fun `class filtered by exact file path`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src")
+ suppressedFiles = listOf("src/suppressed/Suppressed.kt")
+ }
+ }
+ }
+
+ testInline(
+ """
+ /src/suppressed/Suppressed.kt
+ package suppressed
+ class Suppressed
+
+ /src/default/Default.kt
+ package default
+ class Default.kt
+ """.trimIndent(),
+ configuration
+ ) {
+ documentablesMergingStage = { module ->
+ assertEquals(1, module.children.size, "Expected just a single package in module")
+ assertEquals(1, module.packages.size, "Expected just a single package in module")
+
+ val pkg = module.packages.single()
+ assertEquals("default", pkg.dri.packageName, "Expected 'default' package in module")
+ assertEquals(1, pkg.children.size, "Expected just a single child in 'default' package")
+ assertEquals(1, pkg.classlikes.size, "Expected just a single child in 'default' package")
+
+ val classlike = pkg.classlikes.single()
+ assertEquals(DRI("default", "Default"), classlike.dri, "Expected 'Default' class in 'default' package")
+ }
+ }
+ }
+
+ private fun packageOptions(
+ matchingRegex: String,
+ suppress: Boolean
+ ) = PackageOptionsImpl(
+ matchingRegex = matchingRegex,
+ suppress = suppress,
+ includeNonPublic = true,
+ documentedVisibilities = DokkaDefaults.documentedVisibilities,
+ reportUndocumented = false,
+ skipDeprecated = false
+ )
+
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/isExceptionTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/isExceptionTest.kt
new file mode 100644
index 00000000..a387c60d
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/isExceptionTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package transformers
+
+import org.jetbrains.dokka.base.transformers.documentables.isException
+import org.jetbrains.dokka.model.DClass
+import org.jetbrains.dokka.model.DTypeAlias
+import utils.AbstractModelTest
+import kotlin.test.Test
+
+class IsExceptionKotlinTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "classes") {
+ @Test
+ fun `isException should work for kotlin exception`(){
+ inlineModelTest(
+ """
+ |class ExampleException(): Exception()"""
+ ) {
+ with((this / "classes" / "ExampleException").cast<DClass>()) {
+ name equals "ExampleException"
+ isException equals true
+ }
+ }
+ }
+
+ @Test
+ fun `isException should work for java exceptions`(){
+ inlineModelTest(
+ """
+ |class ExampleException(): java.lang.Exception()"""
+ ) {
+ with((this / "classes" / "ExampleException").cast<DClass>()) {
+ name equals "ExampleException"
+ isException equals true
+ }
+ }
+ }
+
+ @Test
+ fun `isException should work for RuntimeException`(){
+ inlineModelTest(
+ """
+ |class ExampleException(reason: String): RuntimeException(reason)"""
+ ) {
+ with((this / "classes" / "ExampleException").cast<DClass>()) {
+ name equals "ExampleException"
+ isException equals true
+ }
+ }
+ }
+
+ @Test
+ fun `isException should work if exception is typealiased`(){
+ inlineModelTest(
+ """
+ |typealias ExampleException = java.lang.Exception"""
+ ) {
+ with((this / "classes" / "ExampleException").cast<DTypeAlias>()) {
+ name equals "ExampleException"
+ isException equals true
+ }
+ }
+ }
+
+ @Test
+ fun `isException should work if exception is extending a typaliased class`(){
+ inlineModelTest(
+ """
+ |class ExampleException(): Exception()
+ |typealias ExampleExceptionAlias = ExampleException"""
+ ) {
+ with((this / "classes" / "ExampleExceptionAlias").cast<DTypeAlias>()) {
+ name equals "ExampleExceptionAlias"
+ isException equals true
+ }
+ }
+ }
+
+ @Test
+ fun `isException should return false for a basic class`(){
+ inlineModelTest(
+ """
+ |class NotAnException(): Serializable"""
+ ) {
+ with((this / "classes" / "NotAnException").cast<DClass>()) {
+ name equals "NotAnException"
+ isException equals false
+ }
+ }
+ }
+
+ @Test
+ fun `isException should return false for a typealias`(){
+ inlineModelTest(
+ """
+ |typealias NotAnException = Serializable"""
+ ) {
+ with((this / "classes" / "NotAnException").cast<DTypeAlias>()) {
+ name equals "NotAnException"
+ isException equals false
+ }
+ }
+ }
+}
+
+class IsExceptionJavaTest: AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
+ @Test
+ fun `isException should work for java exceptions`(){
+ inlineModelTest(
+ """
+ |public class ExampleException extends java.lang.Exception { }"""
+ ) {
+ with((this / "java" / "ExampleException").cast<DClass>()) {
+ name equals "ExampleException"
+ isException equals true
+ }
+ }
+ }
+
+ @Test
+ fun `isException should work for RuntimeException`(){
+ inlineModelTest(
+ """
+ |public class ExampleException extends java.lang.RuntimeException"""
+ ) {
+ with((this / "java" / "ExampleException").cast<DClass>()) {
+ name equals "ExampleException"
+ isException equals true
+ }
+ }
+ }
+
+ @Test
+ fun `isException should return false for a basic class`(){
+ inlineModelTest(
+ """
+ |public class NotAnException extends Serializable"""
+ ) {
+ with((this / "java" / "NotAnException").cast<DClass>()) {
+ name equals "NotAnException"
+ isException equals false
+ }
+ }
+ }
+}
+