/* * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package model import org.jetbrains.dokka.Platform import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo import org.jetbrains.dokka.model.DClass import org.jetbrains.dokka.model.DFunction import org.jetbrains.dokka.model.DInterface import org.jetbrains.dokka.model.doc.P import org.jetbrains.dokka.model.doc.Text import utils.AbstractModelTest import utils.assertNotNull import kotlin.test.Test import kotlin.test.assertTrue class InheritorsTest : AbstractModelTest("/src/main/kotlin/inheritors/Test.kt", "inheritors") { @Test fun simple() { inlineModelTest( """|interface A{} |class B() : A {} """.trimMargin(), ) { with((this / "inheritors" / "A").cast()) { val map = extra[InheritorsInfo].assertNotNull("InheritorsInfo").value with(map.keys.also { it counts 1 }.find { it.analysisPlatform == Platform.jvm }.assertNotNull("jvm key").let { map[it]!! } ) { this counts 1 first().classNames equals "B" } } } } @Test fun sealed() { inlineModelTest( """|sealed class A {} |class B() : A() {} |class C() : A() {} |class D() """.trimMargin(), ) { with((this / "inheritors" / "A").cast()) { val map = extra[InheritorsInfo].assertNotNull("InheritorsInfo").value with(map.keys.also { it counts 1 }.find { it.analysisPlatform == Platform.jvm }.assertNotNull("jvm key").let { map[it]!! } ) { this counts 2 mapNotNull { it.classNames }.sorted() equals listOf("B", "C") } } } } @Test fun multiplatform() { val configuration = dokkaConfiguration { sourceSets { val commonSourceSet = sourceSet { name = "common" sourceRoots = listOf("common/src/") analysisPlatform = "common" } sourceSet { name = "jvm" sourceRoots = listOf("jvm/src/") analysisPlatform = "jvm" dependentSourceSets = setOf(commonSourceSet.value.sourceSetID) } sourceSet { name = "js" sourceRoots = listOf("js/src/") analysisPlatform = "js" dependentSourceSets = setOf(commonSourceSet.value.sourceSetID) } } } testInline( """ |/common/src/main/kotlin/inheritors/Test.kt |package inheritors |interface A{} |/jvm/src/main/kotlin/inheritors/Test.kt |package inheritors |class B() : A {} |/js/src/main/kotlin/inheritors/Test.kt |package inheritors |class B() : A {} |class C() : A {} """.trimMargin(), configuration, cleanupOutput = false, ) { documentablesTransformationStage = { m -> with((m / "inheritors" / "A").cast()) { val map = extra[InheritorsInfo].assertNotNull("InheritorsInfo").value with(map.keys.also { it counts 2 }) { with(find { it.analysisPlatform == Platform.jvm }.assertNotNull("jvm key").let { map[it]!! }) { this counts 1 first().classNames equals "B" } with(find { it.analysisPlatform == Platform.js }.assertNotNull("js key").let { map[it]!! }) { this counts 2 val classes = listOf("B", "C") assertTrue(all { classes.contains(it.classNames) }, "One of subclasses missing in js" ) } } } } } } @Test fun `should inherit docs`() { val expectedDoc = listOf(P(listOf(Text("some text")))) inlineModelTest( """|interface A { | /** | * some text | */ | val a: Int | | /** | * some text | */ | fun b(): E |} |open class C |class B() : C(), A { | val a = 0 | override fun b(): E {} |} """.trimMargin(), platform = Platform.common.toString() ) { with((this / "inheritors" / "A").cast()) { with(this / "a") { val propDoc = this?.documentation?.values?.single()?.children?.first()?.children propDoc equals expectedDoc } with(this / "b") { val funDoc = this?.documentation?.values?.single()?.children?.first()?.children funDoc equals expectedDoc } } with((this / "inheritors" / "B").cast()) { with(this / "a") { val propDoc = this?.documentation?.values?.single()?.children?.first()?.children propDoc equals expectedDoc } } } } // TODO [beresnev] fix, needs access to analysis // class IgnoreCommonBuiltInsPlugin : DokkaPlugin() { // private val kotlinAnalysisPlugin by lazy { plugin() } // @Suppress("unused") // val stdLibKotlinAnalysis by extending { // kotlinAnalysisPlugin.kotlinAnalysis providing { ctx -> // ProjectKotlinAnalysis( // sourceSets = ctx.configuration.sourceSets, // logger = ctx.logger, // analysisConfiguration = DokkaAnalysisConfiguration(ignoreCommonBuiltIns = true) // ) // } override kotlinAnalysisPlugin.defaultKotlinAnalysis // } // // @OptIn(DokkaPluginApiPreview::class) // override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = // PluginApiPreviewAcknowledgement // } // @Test // fun `should inherit docs for stdLib #2638`() { // val testConfiguration = dokkaConfiguration { // suppressObviousFunctions = false // sourceSets { // sourceSet { // sourceRoots = listOf("src/") // analysisPlatform = "common" // languageVersion = "1.4" // } // } // } // // inlineModelTest( // """ // package kotlin.collections // // import kotlin.internal.PlatformDependent // // /** // * Classes that inherit from this interface can be represented as a sequence of elements that can // * be iterated over. // * @param T the type of element being iterated over. The iterator is covariant in its element type. // */ // public interface Iterable { // /** // * Returns an iterator over the elements of this object. // */ // public operator fun iterator(): Iterator // } // // /** // * Classes that inherit from this interface can be represented as a sequence of elements that can // * be iterated over and that supports removing elements during iteration. // * @param T the type of element being iterated over. The mutable iterator is invariant in its element type. // */ // public interface MutableIterable : Iterable { // /** // * Returns an iterator over the elements of this sequence that supports removing elements during iteration. // */ // override fun iterator(): MutableIterator // } // // /** // * A generic collection of elements. Methods in this interface support only read-only access to the collection; // * read/write access is supported through the [MutableCollection] interface. // * @param E the type of elements contained in the collection. The collection is covariant in its element type. // */ // public interface Collection : Iterable { // // Query Operations // /** // * Returns the size of the collection. // */ // public val size: Int // // /** // * Returns `true` if the collection is empty (contains no elements), `false` otherwise. // */ // public fun isEmpty(): Boolean // // /** // * Checks if the specified element is contained in this collection. // */ // public operator fun contains(element: @UnsafeVariance E): Boolean // // override fun iterator(): Iterator // // // Bulk Operations // /** // * Checks if all elements in the specified collection are contained in this collection. // */ // public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean // } // // /** // * A generic collection of elements that supports adding and removing elements. // * // * @param E the type of elements contained in the collection. The mutable collection is invariant in its element type. // */ // public interface MutableCollection : Collection, MutableIterable { // // Query Operations // override fun iterator(): MutableIterator // // // Modification Operations // /** // * Adds the specified element to the collection. // * // * @return `true` if the element has been added, `false` if the collection does not support duplicates // * and the element is already contained in the collection. // */ // public fun add(element: E): Boolean // // /** // * Removes a single instance of the specified element from this // * collection, if it is present. // * // * @return `true` if the element has been successfully removed; `false` if it was not present in the collection. // */ // public fun remove(element: E): Boolean // // // Bulk Modification Operations // /** // * Adds all of the elements of the specified collection to this collection. // * // * @return `true` if any of the specified elements was added to the collection, `false` if the collection was not modified. // */ // public fun addAll(elements: Collection): Boolean // // /** // * Removes all of this collection's elements that are also contained in the specified collection. // * // * @return `true` if any of the specified elements was removed from the collection, `false` if the collection was not modified. // */ // public fun removeAll(elements: Collection): Boolean // // /** // * Retains only the elements in this collection that are contained in the specified collection. // * // * @return `true` if any element was removed from the collection, `false` if the collection was not modified. // */ // public fun retainAll(elements: Collection): Boolean // // /** // * Removes all elements from this collection. // */ // public fun clear(): Unit // } // // /** // * A generic ordered collection of elements. Methods in this interface support only read-only access to the list; // * read/write access is supported through the [MutableList] interface. // * @param E the type of elements contained in the list. The list is covariant in its element type. // */ // public interface List : Collection { // // Query Operations // // override val size: Int // override fun isEmpty(): Boolean // override fun contains(element: @UnsafeVariance E): Boolean // override fun iterator(): Iterator // // // Bulk Operations // override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean // // // Positional Access Operations // /** // * Returns the element at the specified index in the list. // */ // public operator fun get(index: Int): E // // // Search Operations // /** // * Returns the index of the first occurrence of the specified element in the list, or -1 if the specified // * element is not contained in the list. // */ // public fun indexOf(element: @UnsafeVariance E): Int // // /** // * Returns the index of the last occurrence of the specified element in the list, or -1 if the specified // * element is not contained in the list. // */ // public fun lastIndexOf(element: @UnsafeVariance E): Int // // // List Iterators // /** // * Returns a list iterator over the elements in this list (in proper sequence). // */ // public fun listIterator(): ListIterator // // /** // * Returns a list iterator over the elements in this list (in proper sequence), starting at the specified [index]. // */ // public fun listIterator(index: Int): ListIterator // // // View // /** // * Returns a view of the portion of this list between the specified [fromIndex] (inclusive) and [toIndex] (exclusive). // * The returned list is backed by this list, so non-structural changes in the returned list are reflected in this list, and vice-versa. // * // * Structural changes in the base list make the behavior of the view undefined. // */ // public fun subList(fromIndex: Int, toIndex: Int): List // } // // // etc // """.trimMargin(), // platform = Platform.common.toString(), // configuration = testConfiguration, // prependPackage = false, // pluginsOverrides = listOf(IgnoreCommonBuiltInsPlugin()) // ) { // with((this / "kotlin.collections" / "List" / "contains").cast()) { // documentation.size equals 1 // // } // } // } @Test fun `should inherit docs in case of diamond inheritance`() { inlineModelTest( """ public interface Collection2 { /** * Returns `true` if the collection is empty (contains no elements), `false` otherwise. */ public fun isEmpty(): Boolean /** * Checks if the specified element is contained in this collection. */ public operator fun contains(element: @UnsafeVariance E): Boolean } public interface MutableCollection2 : Collection2, MutableIterable2 public interface List2 : Collection2 { override fun isEmpty(): Boolean override fun contains(element: @UnsafeVariance E): Boolean } public interface MutableList2 : List2, MutableCollection2 public class AbstractMutableList2 : MutableList2 { protected constructor() // From List override fun isEmpty(): Boolean = size == 0 public override fun contains(element: E): Boolean = indexOf(element) != -1 } public class ArrayDeque2 : AbstractMutableList2 { override fun isEmpty(): Boolean = size == 0 public override fun contains(element: E): Boolean = indexOf(element) != -1 } """.trimMargin() ) { with((this / "inheritors" / "ArrayDeque2" / "isEmpty").cast()) { documentation.size equals 1 } with((this / "inheritors" / "ArrayDeque2" / "contains").cast()) { documentation.size equals 1 } } } }