diff options
11 files changed, 240 insertions, 37 deletions
diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index f2adcbc1..4af12991 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -9,15 +9,16 @@ import org.jetbrains.dokka.base.resolvers.LocationProviderFactory import org.jetbrains.dokka.base.signatures.KotlinSignatureProvider import org.jetbrains.dokka.base.signatures.SignatureProvider import org.jetbrains.dokka.base.transformers.documentables.DefaultDocumentableMerger +import org.jetbrains.dokka.base.transformers.documentables.InheritorsExtractorTransformer import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter import org.jetbrains.dokka.base.transformers.pages.merger.FallbackPageMergerStrategy import org.jetbrains.dokka.base.transformers.pages.merger.PageMerger import org.jetbrains.dokka.base.transformers.pages.merger.PageMergerStrategy import org.jetbrains.dokka.base.transformers.pages.merger.SameMethodNamePageMergerStrategy -import org.jetbrains.dokka.base.translators.psi.DefaultPsiToDocumentableTranslator import org.jetbrains.dokka.base.translators.descriptors.DefaultDescriptorToDocumentableTranslator import org.jetbrains.dokka.base.translators.documentables.DefaultDocumentableToPageTranslator +import org.jetbrains.dokka.base.translators.psi.DefaultPsiToDocumentableTranslator import org.jetbrains.dokka.plugability.DokkaPlugin class DokkaBase : DokkaPlugin() { @@ -45,6 +46,10 @@ class DokkaBase : DokkaPlugin() { } } + val inheritorsExtractor by extending { + CoreExtensions.documentableTransformer with InheritorsExtractorTransformer() + } + val documentableToPageTranslator by extending(isFallback = true) { CoreExtensions.documentableToPageTranslator providing { ctx -> DefaultDocumentableToPageTranslator( diff --git a/plugins/base/src/main/kotlin/transformers/documentables/InheritorsExtractorTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/InheritorsExtractorTransformer.kt new file mode 100644 index 00000000..e372ad9c --- /dev/null +++ b/plugins/base/src/main/kotlin/transformers/documentables/InheritorsExtractorTransformer.kt @@ -0,0 +1,73 @@ +package org.jetbrains.dokka.base.transformers.documentables + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.properties.ExtraProperty +import org.jetbrains.dokka.model.properties.MergeStrategy +import org.jetbrains.dokka.pages.PlatformData +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer + +class InheritorsExtractorTransformer : DocumentableTransformer { + override fun invoke(original: DModule, context: DokkaContext): DModule = + original.generateInheritanceMap().let { inheritanceMap -> original.appendInheritors(inheritanceMap) as DModule } + + private fun <T : Documentable> T.appendInheritors(inheritanceMap: Map<PlatformData, Map<DRI, List<DRI>>>): Documentable = + InheritorsInfo(PlatformDependent(inheritanceMap.getForDRI(dri))).let { info -> + when (this) { + is DModule -> copy(packages = packages.map { it.appendInheritors(inheritanceMap) as DPackage }) + is DPackage -> copy(classlikes = classlikes.map { it.appendInheritors(inheritanceMap) as DClasslike }) + is DClass -> copy( + extra = extra + info, + classlikes = classlikes.map { it.appendInheritors(inheritanceMap) as DClasslike }) + is DEnum -> copy( + extra = extra + info, + classlikes = classlikes.map { it.appendInheritors(inheritanceMap) as DClasslike }) + is DInterface -> copy( + extra = extra + info, + classlikes = classlikes.map { it.appendInheritors(inheritanceMap) as DClasslike }) + is DObject -> copy(classlikes = classlikes.map { it.appendInheritors(inheritanceMap) as DClasslike }) + is DAnnotation -> copy(classlikes = classlikes.map { it.appendInheritors(inheritanceMap) as DClasslike }) + else -> this + } + } + + private fun Map<PlatformData, Map<DRI, List<DRI>>>.getForDRI(dri: DRI) = + PlatformDependent(map { (v, k) -> + v to k[dri] + }.map { (k, v) -> k to v.orEmpty() }.toMap()) + + private fun DModule.generateInheritanceMap() = + getInheritanceEntriesRec().filterNot { it.second.isEmpty() }.groupBy({ it.first }) { it.second } + .map { (k, v) -> + k to v.flatMap { p -> p.groupBy({ it.first }) { it.second }.toList() } + .groupBy({ it.first }) { it.second }.map { (k2, v2) -> k2 to v2.flatten() }.toMap() + }.toMap() + + private fun <T : Documentable> T.getInheritanceEntriesRec(): List<Pair<PlatformData, List<Pair<DRI, DRI>>>> = + this.toInheritanceEntries() + children.flatMap { it.getInheritanceEntriesRec() } + + private fun <T : Documentable> T.toInheritanceEntries() = + (this as? WithSupertypes)?.let { + it.supertypes.map.map { (k, v) -> k to v.map { it to dri } } + }.orEmpty() + +} + +class InheritorsInfo(val value: PlatformDependent<List<DRI>>) : ExtraProperty<Documentable> { + companion object : ExtraProperty.Key<Documentable, InheritorsInfo> { + override fun mergeStrategyFor(left: InheritorsInfo, right: InheritorsInfo): MergeStrategy<Documentable> = + MergeStrategy.Replace( + InheritorsInfo( + PlatformDependent( + (left.value.map.entries.toList() + right.value.map.entries.toList()) + .groupBy({ it.key }) { it.value } + .map { (k, v) -> k to v.flatten() }.toMap() + ) + ) + ) + } + + override val key: ExtraProperty.Key<Documentable, *> = InheritorsInfo +} + diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index a0b5a072..eba56625 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -1,12 +1,14 @@ package org.jetbrains.dokka.base.translators.documentables import org.jetbrains.dokka.base.signatures.SignatureProvider +import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.DFunction import org.jetbrains.dokka.model.doc.Property import org.jetbrains.dokka.model.doc.TagWrapper +import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.utilities.DokkaLogger @@ -85,6 +87,15 @@ open class DefaultPageCreator( text(it.briefDocumentation()) } } + (s as? WithExtraProperties<Documentable>)?.let { it.extra[InheritorsInfo] }?.let { inheritors -> + val map = inheritors.value.map + text("Subclasses:") + platformDependentHint(dri = s.dri, platformData = map.keys) { + map.keys.forEach { pd -> + linkTable(map[pd].orEmpty(), ContentKind.Classlikes, setOf(pd)) + } + } + } } protected open fun contentForClasslike(c: DClasslike) = contentBuilder.contentFor(c) { diff --git a/plugins/base/src/test/kotlin/issues/IssuesTest.kt b/plugins/base/src/test/kotlin/issues/IssuesTest.kt index e34c610a..1ad7a97b 100644 --- a/plugins/base/src/test/kotlin/issues/IssuesTest.kt +++ b/plugins/base/src/test/kotlin/issues/IssuesTest.kt @@ -4,6 +4,7 @@ import org.jetbrains.dokka.model.DClass import org.jetbrains.dokka.model.DFunction import org.junit.Test import utils.AbstractModelTest +import utils.name class IssuesTest : AbstractModelTest("/src/main/kotlin/issues/Test.kt", "issues") { @@ -35,14 +36,14 @@ class IssuesTest : AbstractModelTest("/src/main/kotlin/issues/Test.kt", "issues" ) { with((this / "issues" / "Test").cast<DClass>()) { // passes - (this / "working").cast<DFunction>().type.constructorFqName equals "kotlin.String" - (this / "doSomething").cast<DFunction>().type.constructorFqName equals "kotlin.String" + (this / "working").cast<DFunction>().type.name equals "String" + (this / "doSomething").cast<DFunction>().type.name equals "String" // fails - (this / "brokenGenerics").cast<DFunction>().type.constructorFqName equals "kotlin.collections.List" - (this / "brokenApply").cast<DFunction>().type.constructorFqName equals "issues.Test" - (this / "brokenRun").cast<DFunction>().type.constructorFqName equals "issues.Test" - (this / "brokenLet").cast<DFunction>().type.constructorFqName equals "issues.Test" + (this / "brokenGenerics").cast<DFunction>().type.name equals "List" + (this / "brokenApply").cast<DFunction>().type.name equals "Test" + (this / "brokenRun").cast<DFunction>().type.name equals "Test" + (this / "brokenLet").cast<DFunction>().type.name equals "Test" } } } diff --git a/plugins/base/src/test/kotlin/model/ClassesTest.kt b/plugins/base/src/test/kotlin/model/ClassesTest.kt index d627dab4..4855056c 100644 --- a/plugins/base/src/test/kotlin/model/ClassesTest.kt +++ b/plugins/base/src/test/kotlin/model/ClassesTest.kt @@ -6,6 +6,7 @@ import org.jetbrains.dokka.model.DFunction import org.junit.Test import utils.AbstractModelTest import utils.assertNotNull +import utils.name import utils.supers @@ -54,7 +55,7 @@ class ClassesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "class parameters counts 1 with(parameters.firstOrNull().assertNotNull("Constructor parameter")) { name equals "name" - type.constructorFqName equals "kotlin.String" + type.name equals "String" } } @@ -76,7 +77,7 @@ class ClassesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "class children counts 5 with((this / "fn").cast<DFunction>()) { - type.constructorFqName equals "kotlin.Unit" + type.name equals "Unit" parameters counts 0 visibility.values allEquals KotlinVisibility.Public } @@ -132,7 +133,7 @@ class ClassesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "class with((this / "foo").cast<DFunction>()) { name equals "foo" parameters counts 0 - type.constructorFqName equals "kotlin.Unit" + type.name equals "Unit" } } } @@ -172,7 +173,7 @@ class ClassesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "class ) { with((this / "classes" / "Klass").cast<DClass>()) { name equals "Klass" - modifier equals KotlinModifier.Sealed + modifier equals Sealed } } } @@ -356,7 +357,7 @@ class ClassesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "class parameters counts 1 with(parameters.firstOrNull() notNull "Constructor parameter") { name equals "s" - type.constructorFqName equals "kotlin.String" + type.name equals "String" } } } diff --git a/plugins/base/src/test/kotlin/model/FunctionsTest.kt b/plugins/base/src/test/kotlin/model/FunctionsTest.kt index f3a7adc7..97d6b237 100644 --- a/plugins/base/src/test/kotlin/model/FunctionsTest.kt +++ b/plugins/base/src/test/kotlin/model/FunctionsTest.kt @@ -19,7 +19,7 @@ class FunctionTest : AbstractModelTest("/src/main/kotlin/function/Test.kt", "fun ) { with((this / "function" / "fn").cast<DFunction>()) { name equals "fn" - type.constructorFqName equals "kotlin.Unit" + type.name equals "Unit" this.children.assertCount(0, "Function children: ") } } @@ -55,7 +55,7 @@ class FunctionTest : AbstractModelTest("/src/main/kotlin/function/Test.kt", "fun with(fn2) { name equals "fn" parameters.assertCount(1) - parameters.first().type.constructorFqName equals "kotlin.Int" + parameters.first().type.name equals "Int" } } } @@ -94,7 +94,7 @@ class FunctionTest : AbstractModelTest("/src/main/kotlin/function/Test.kt", "fun name equals "fn" parameters counts 1 receiver.assertNotNull("fn(Int) receiver") - parameters.first().type.constructorFqName equals "kotlin.Int" + parameters.first().type.name equals "Int" } } } @@ -121,11 +121,11 @@ class FunctionTest : AbstractModelTest("/src/main/kotlin/function/Test.kt", "fun parameters counts 1 parameters.firstOrNull().assertNotNull("Parameter: ").also { it.name equals "x" - it.type.constructorFqName equals "kotlin.Int" + it.type.name equals "Int" it.comments() equals "parameter" } - type.assertNotNull("Return type: ").constructorFqName equals "kotlin.Unit" + type.assertNotNull("Return type: ").name equals "Unit" } } } diff --git a/plugins/base/src/test/kotlin/model/InheritorsTest.kt b/plugins/base/src/test/kotlin/model/InheritorsTest.kt new file mode 100644 index 00000000..b3d7bf92 --- /dev/null +++ b/plugins/base/src/test/kotlin/model/InheritorsTest.kt @@ -0,0 +1,94 @@ +package model + +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.transformers.documentables.InheritorsExtractorTransformer +import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo +import org.jetbrains.dokka.model.DInterface +import org.jetbrains.dokka.model.DPackage +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.junit.Test +import utils.AbstractModelTest +import utils.assertNotNull + +class InheritorsTest : AbstractModelTest("/src/main/kotlin/inheritors/Test.kt", "inheritors") { + + object InheritorsPlugin : DokkaPlugin() { + val inheritors by extending { + CoreExtensions.documentableTransformer with InheritorsExtractorTransformer() + } + } + + @Test + fun simple() { + inlineModelTest( + """|interface A{} + |class B() : A {} + """.trimMargin(), + pluginsOverrides = listOf(InheritorsPlugin) + ) { + with((this / "inheritors" / "A").cast<DInterface>()) { + val map = extra[InheritorsInfo].assertNotNull("InheritorsInfo").value.map + with(map.keys.also { it counts 1 }.find { it.platformType == Platform.jvm }.assertNotNull("jvm key").let { map[it]!! } + ) { + this counts 1 + first().classNames equals "B" + } + } + } + } + + @Test + fun multiplatform() { + val configuration = dokkaConfiguration { + passes { + pass { + sourceRoots = listOf("common/src/", "jvm/src/") + analysisPlatform = "jvm" + targets = listOf("jvm") + } + pass { + sourceRoots = listOf("common/src/", "js/src/") + analysisPlatform = "js" + targets = listOf("js") + } + } + } + + 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, + pluginOverrides = listOf(InheritorsPlugin) + ) { + documentablesTransformationStage = { m -> + with((m / "inheritors" / "A").cast<DInterface>()) { + val map = extra[InheritorsInfo].assertNotNull("InheritorsInfo").value.map + with(map.keys.also { it counts 2 }) { + with(find { it.platformType == Platform.jvm }.assertNotNull("jvm key").let { map[it]!! }) { + this counts 1 + first().classNames equals "B" + } + with(find { it.platformType == Platform.js }.assertNotNull("js key").let { map[it]!! }) { + this counts 2 + val classes = listOf("B", "C") + assert(all{ classes.contains(it.classNames) }){"One of subclasses missing in js"} + } + } + + } + } + } + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/model/JavaTest.kt b/plugins/base/src/test/kotlin/model/JavaTest.kt index c2545e7a..1e33d6fd 100644 --- a/plugins/base/src/test/kotlin/model/JavaTest.kt +++ b/plugins/base/src/test/kotlin/model/JavaTest.kt @@ -7,6 +7,7 @@ import org.junit.Assert.assertTrue import org.junit.Test import utils.AbstractModelTest import utils.assertNotNull +import utils.name class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { @@ -134,10 +135,10 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { with((this / "arrayToString").cast<DFunction>()) { name equals "arrayToString" - type.constructorFqName equals "java.lang.String[]" + type.name equals "Array" with(parameters.firstOrNull().assertNotNull("parameters")) { name equals "data" - type.constructorFqName equals "int[]" + type.name equals "Array" } } } @@ -196,7 +197,7 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { constructors.find { it.parameters.isNullOrEmpty() }.assertNotNull("Test()") with(constructors.find { it.parameters.isNotEmpty() }.assertNotNull("Test(String)")) { - parameters.firstOrNull()?.type?.constructorFqName equals "java.lang.String" + parameters.firstOrNull()?.type?.name equals "String" } } } @@ -238,7 +239,7 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { name equals "bar" with(parameters.firstOrNull().assertNotNull("parameter")) { name equals "x" - type.constructorFqName equals "java.lang.String..." + type.name equals "Array" } } } diff --git a/plugins/base/src/test/kotlin/model/PropertyTest.kt b/plugins/base/src/test/kotlin/model/PropertyTest.kt index d12e292b..4af4ee5a 100644 --- a/plugins/base/src/test/kotlin/model/PropertyTest.kt +++ b/plugins/base/src/test/kotlin/model/PropertyTest.kt @@ -6,6 +6,7 @@ import org.jetbrains.dokka.model.DProperty import org.junit.Test import utils.AbstractModelTest import utils.assertNotNull +import utils.name class PropertyTest : AbstractModelTest("/src/main/kotlin/property/Test.kt", "property") { @@ -19,9 +20,9 @@ class PropertyTest : AbstractModelTest("/src/main/kotlin/property/Test.kt", "pro name equals "property" children counts 0 with(getter.assertNotNull("Getter")) { - type.constructorFqName equals "kotlin.String" + type.name equals "String" } - type.constructorFqName equals "kotlin.String" + type.name equals "String" } } } @@ -38,9 +39,9 @@ class PropertyTest : AbstractModelTest("/src/main/kotlin/property/Test.kt", "pro children counts 0 setter.assertNotNull("Setter") with(getter.assertNotNull("Getter")) { - type.constructorFqName equals "kotlin.String" + type.name equals "String" } - type.constructorFqName equals "kotlin.String" + type.name equals "String" } } } @@ -57,9 +58,9 @@ class PropertyTest : AbstractModelTest("/src/main/kotlin/property/Test.kt", "pro name equals "property" children counts 0 with(getter.assertNotNull("Getter")) { - type.constructorFqName equals "kotlin.String" + type.name equals "String" } - type.constructorFqName equals "kotlin.String" + type.name equals "String" } } } @@ -78,7 +79,7 @@ class PropertyTest : AbstractModelTest("/src/main/kotlin/property/Test.kt", "pro children counts 0 setter.assertNotNull("Setter") with(getter.assertNotNull("Getter")) { - type.constructorFqName equals "kotlin.String" + type.name equals "String" } visibility.values allEquals KotlinVisibility.Public } @@ -98,10 +99,10 @@ class PropertyTest : AbstractModelTest("/src/main/kotlin/property/Test.kt", "pro children counts 0 with(receiver.assertNotNull("property receiver")) { name equals null - type.constructorFqName equals "kotlin.String" + type.name equals "String" } with(getter.assertNotNull("Getter")) { - type.constructorFqName equals "kotlin.Int" + type.name equals "Int" } visibility.values allEquals KotlinVisibility.Public } @@ -125,14 +126,14 @@ class PropertyTest : AbstractModelTest("/src/main/kotlin/property/Test.kt", "pro name equals "property" children counts 0 with(getter.assertNotNull("Getter")) { - type.constructorFqName equals "kotlin.Int" + type.name equals "Int" } } with((this / "Bar" / "property").cast<DProperty>()) { name equals "property" children counts 0 with(getter.assertNotNull("Getter")) { - type.constructorFqName equals "kotlin.Int" + type.name equals "Int" } } } diff --git a/plugins/base/src/test/kotlin/utils/ModelUtils.kt b/plugins/base/src/test/kotlin/utils/ModelUtils.kt index 1e6f64c6..69c4f0d2 100644 --- a/plugins/base/src/test/kotlin/utils/ModelUtils.kt +++ b/plugins/base/src/test/kotlin/utils/ModelUtils.kt @@ -1,5 +1,6 @@ package utils +import org.jetbrains.dokka.plugability.DokkaPlugin import org.jetbrains.dokka.model.DModule abstract class AbstractModelTest(val path: String? = null, val pkg: String) : ModelDSL(), AssertDSL { @@ -9,6 +10,8 @@ abstract class AbstractModelTest(val path: String? = null, val pkg: String) : Mo platform: String = "jvm", targetList: List<String> = listOf("jvm"), prependPackage: Boolean = true, + cleanupOutput: Boolean = true, + pluginsOverrides: List<DokkaPlugin> = emptyList(), block: DModule.() -> Unit ) { val configuration = dokkaConfiguration { @@ -20,9 +23,14 @@ abstract class AbstractModelTest(val path: String? = null, val pkg: String) : Mo } } } - val prepend = path.let { p -> p?.let { "|$it\n" } ?: "" } + if(prependPackage) "|package $pkg" else "" + val prepend = path.let { p -> p?.let { "|$it\n" } ?: "" } + if (prependPackage) "|package $pkg" else "" - testInline(("$prepend\n$query").trim().trimIndent(), configuration) { + testInline( + query = ("$prepend\n$query").trim().trimIndent(), + configuration = configuration, + cleanupOutput = cleanupOutput, + pluginOverrides = pluginsOverrides + ) { documentablesTransformationStage = block } } diff --git a/plugins/base/src/test/kotlin/utils/TestUtils.kt b/plugins/base/src/test/kotlin/utils/TestUtils.kt index bf86c1b1..68ab7120 100644 --- a/plugins/base/src/test/kotlin/utils/TestUtils.kt +++ b/plugins/base/src/test/kotlin/utils/TestUtils.kt @@ -1,9 +1,9 @@ package utils -import org.jetbrains.dokka.model.DClass -import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.doc.* import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import kotlin.collections.orEmpty @DslMarker annotation class TestDSL @@ -61,4 +61,12 @@ fun <T> T?.assertNotNull(name: String = ""): T = this ?: throw AssertionError("$ fun <T : Documentable> T?.docs() = this?.documentation.orEmpty().values.flatMap { it.children } val DClass.supers - get() = supertypes.flatMap{it.component2()}
\ No newline at end of file + get() = supertypes.flatMap { it.component2() } + +val Bound.name: String? + get() = when (this) { + is Nullable -> inner.name + is OtherParameter -> name + is PrimitiveJavaType -> name + is TypeConstructor -> dri.classNames + }
\ No newline at end of file |