aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/base/src')
-rw-r--r--plugins/base/src/main/kotlin/DokkaBase.kt4
-rw-r--r--plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt228
-rw-r--r--plugins/base/src/main/kotlin/transformers/documentables/KotlinArrayDocumentableReplacerTransformer.kt63
-rw-r--r--plugins/base/src/test/kotlin/filter/KotlinArrayDocumentableReplacerTest.kt199
4 files changed, 494 insertions, 0 deletions
diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt
index c0e512c5..4c3f35db 100644
--- a/plugins/base/src/main/kotlin/DokkaBase.kt
+++ b/plugins/base/src/main/kotlin/DokkaBase.kt
@@ -86,6 +86,10 @@ class DokkaBase : DokkaPlugin() {
preMergeDocumentableTransformer providing ::InheritedEntriesDocumentableFilterTransformer
}
+ val kotlinArrayDocumentableReplacer by extending {
+ preMergeDocumentableTransformer providing ::KotlinArrayDocumentableReplacerTransformer
+ }
+
val emptyPackagesFilter by extending {
preMergeDocumentableTransformer providing ::EmptyPackagesFilterTransformer order {
after(
diff --git a/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt
new file mode 100644
index 00000000..f5ef8ed1
--- /dev/null
+++ b/plugins/base/src/main/kotlin/transformers/documentables/DocumentableReplacerTransformer.kt
@@ -0,0 +1,228 @@
+package org.jetbrains.dokka.base.transformers.documentables
+
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer
+
+abstract class DocumentableReplacerTransformer(val context: DokkaContext) :
+ PreMergeDocumentableTransformer {
+ override fun invoke(modules: List<DModule>): List<DModule> =
+ modules.map { module ->
+ val (documentable, wasChanged) = processModule(module)
+ documentable.takeIf { wasChanged } ?: module
+ }
+
+ protected open fun processModule(module: DModule): AnyWithChanges<DModule> {
+ val afterProcessing = module.packages.map { processPackage(it) }
+ val processedModule = module.takeIf { afterProcessing.none { it.changed } }
+ ?: module.copy(packages = afterProcessing.mapNotNull { it.target })
+ return AnyWithChanges(processedModule, afterProcessing.any { it.changed })
+ }
+
+ protected open fun processPackage(dPackage: DPackage): AnyWithChanges<DPackage> {
+ val classlikes = dPackage.classlikes.map { processClassLike(it) }
+ val typeAliases = dPackage.typealiases.map { processTypeAlias(it) }
+ val functions = dPackage.functions.map { processFunction(it) }
+ val properies = dPackage.properties.map { processProperty(it) }
+
+ val wasChanged = (classlikes + typeAliases + functions + properies).any { it.changed }
+ return (dPackage.takeIf { !wasChanged } ?: dPackage.copy(
+ classlikes = classlikes.mapNotNull { it.target },
+ typealiases = typeAliases.mapNotNull { it.target },
+ functions = functions.mapNotNull { it.target },
+ properties = properies.mapNotNull { it.target }
+ )).let { processedPackage -> AnyWithChanges(processedPackage, wasChanged) }
+ }
+
+ protected open fun processClassLike(classlike: DClasslike): AnyWithChanges<DClasslike> {
+ val functions = classlike.functions.map { processFunction(it) }
+ val classlikes = classlike.classlikes.map { processClassLike(it) }
+ val properties = classlike.properties.map { processProperty(it) }
+ val companion = (classlike as? WithCompanion)?.companion?.let { processClassLike(it) }
+
+ val wasClasslikeChanged = (functions + classlikes + properties).any { it.changed } || companion?.changed == true
+ return when (classlike) {
+ is DClass -> {
+ val constructors = classlike.constructors.map { processFunction(it) }
+ val generics = classlike.generics.map { processTypeParameter(it) }
+ val wasClassChange =
+ wasClasslikeChanged || constructors.any { it.changed } || generics.any { it.changed }
+ (classlike.takeIf { !wasClassChange } ?: classlike.copy(
+ functions = functions.mapNotNull { it.target },
+ classlikes = classlikes.mapNotNull { it.target },
+ properties = properties.mapNotNull { it.target },
+ constructors = constructors.mapNotNull { it.target },
+ generics = generics.mapNotNull { it.target },
+ companion = companion?.target as? DObject
+ )).let { AnyWithChanges(it, wasClassChange) }
+ }
+ is DInterface -> {
+ val generics = classlike.generics.map { processTypeParameter(it) }
+ val wasInterfaceChange = wasClasslikeChanged || generics.any { it.changed }
+ (classlike.takeIf { !wasInterfaceChange } ?: classlike.copy(
+ functions = functions.mapNotNull { it.target },
+ classlikes = classlikes.mapNotNull { it.target },
+ properties = properties.mapNotNull { it.target },
+ generics = generics.mapNotNull { it.target },
+ companion = companion?.target as? DObject
+ )).let { AnyWithChanges(it, wasClasslikeChanged) }
+ }
+ is DObject -> (classlike.takeIf { !wasClasslikeChanged } ?: classlike.copy(
+ functions = functions.mapNotNull { it.target },
+ classlikes = classlikes.mapNotNull { it.target },
+ properties = properties.mapNotNull { it.target },
+ )).let { AnyWithChanges(it, wasClasslikeChanged) }
+ is DAnnotation -> {
+ val constructors = classlike.constructors.map { processFunction(it) }
+ val generics = classlike.generics.map { processTypeParameter(it) }
+ val wasClassChange =
+ wasClasslikeChanged || constructors.any { it.changed } || generics.any { it.changed }
+ (classlike.takeIf { !wasClassChange } ?: classlike.copy(
+ functions = functions.mapNotNull { it.target },
+ classlikes = classlikes.mapNotNull { it.target },
+ properties = properties.mapNotNull { it.target },
+ constructors = constructors.mapNotNull { it.target },
+ generics = generics.mapNotNull { it.target },
+ companion = companion?.target as? DObject
+ )).let { AnyWithChanges(it, wasClassChange) }
+ }
+ is DEnum -> {
+ val constructors = classlike.constructors.map { processFunction(it) }
+ val entries = classlike.entries.map { processEnumEntry(it) }
+ val wasClassChange =
+ wasClasslikeChanged || (constructors + entries).any { it.changed }
+ (classlike.takeIf { !wasClassChange } ?: classlike.copy(
+ functions = functions.mapNotNull { it.target },
+ classlikes = classlikes.mapNotNull { it.target },
+ properties = properties.mapNotNull { it.target },
+ constructors = constructors.mapNotNull { it.target },
+ companion = companion?.target as? DObject,
+ entries = entries.mapNotNull { it.target }
+ )).let { AnyWithChanges(it, wasClassChange) }
+ }
+ }
+ }
+
+ protected open fun processEnumEntry(dEnumEntry: DEnumEntry): AnyWithChanges<DEnumEntry> {
+ val functions = dEnumEntry.functions.map { processFunction(it) }
+ val properties = dEnumEntry.properties.map { processProperty(it) }
+ val classlikes = dEnumEntry.classlikes.map { processClassLike(it) }
+
+ val wasChanged = (functions + properties + classlikes).any { it.changed }
+ return (dEnumEntry.takeIf { !wasChanged } ?: dEnumEntry.copy(
+ functions = functions.mapNotNull { it.target },
+ classlikes = classlikes.mapNotNull { it.target },
+ properties = properties.mapNotNull { it.target },
+ )).let { AnyWithChanges(it, wasChanged) }
+ }
+
+ protected fun processFunction(dFunction: DFunction): AnyWithChanges<DFunction> {
+ val type = processBound(dFunction.type)
+ val parameters = dFunction.parameters.map { processParameter(it) }
+ val receiver = dFunction.receiver?.let { processParameter(it) }
+ val generics = dFunction.generics.map { processTypeParameter(it) }
+
+ val wasChanged = parameters.any { it.changed } || receiver?.changed == true
+ || type.changed || generics.any { it.changed }
+ return (dFunction.takeIf { !wasChanged } ?: dFunction.copy(
+ type = type.target ?: dFunction.type,
+ parameters = parameters.mapNotNull { it.target },
+ receiver = receiver?.target,
+ generics = generics.mapNotNull { it.target },
+ )).let { AnyWithChanges(it, wasChanged) }
+ }
+
+ protected open fun processProperty(dProperty: DProperty): AnyWithChanges<DProperty> {
+ val getter = dProperty.getter?.let { processFunction(it) }
+ val setter = dProperty.setter?.let { processFunction(it) }
+ val type = processBound(dProperty.type)
+ val generics = dProperty.generics.map { processTypeParameter(it) }
+
+ val wasChanged = getter?.changed == true || setter?.changed == true
+ || type.changed || generics.any { it.changed }
+ return (dProperty.takeIf { !wasChanged } ?: dProperty.copy(
+ type = type.target ?: dProperty.type,
+ setter = setter?.target,
+ getter = getter?.target,
+ generics = generics.mapNotNull { it.target }
+ )).let { AnyWithChanges(it, wasChanged) }
+ }
+
+ protected open fun processParameter(dParameter: DParameter): AnyWithChanges<DParameter> {
+ val type = processBound(dParameter.type)
+
+ val wasChanged = type.changed
+ return (dParameter.takeIf { !wasChanged } ?: dParameter.copy(
+ type = type.target ?: dParameter.type,
+ )).let { AnyWithChanges(it, wasChanged) }
+ }
+
+ protected open fun processTypeParameter(dTypeParameter: DTypeParameter): AnyWithChanges<DTypeParameter> {
+ val bounds = dTypeParameter.bounds.map { processBound(it) }
+
+ val wasChanged = bounds.any { it.changed }
+ return (dTypeParameter.takeIf { !wasChanged } ?: dTypeParameter.copy(
+ bounds = bounds.mapIndexed { i, v -> v.target ?: dTypeParameter.bounds[i] }
+ )).let { AnyWithChanges(it, wasChanged) }
+ }
+
+ protected open fun processBound(bound: Bound) = when(bound) {
+ is GenericTypeConstructor -> processGenericTypeConstructor(bound)
+ is FunctionalTypeConstructor -> processFunctionalTypeConstructor(bound)
+ else -> AnyWithChanges(bound, false)
+ }
+
+ protected open fun processVariance(variance: Variance<*>): AnyWithChanges<Variance<*>> {
+ val bound = processBound(variance.inner)
+ if (!bound.changed)
+ return AnyWithChanges(variance, false)
+ return when (variance) {
+ is Covariance<*> -> AnyWithChanges(
+ Covariance(bound.target ?: variance.inner), true)
+ is Contravariance<*> -> AnyWithChanges(
+ Contravariance(bound.target ?: variance.inner), true)
+ is Invariance<*> -> AnyWithChanges(
+ Invariance(bound.target ?: variance.inner), true)
+ else -> AnyWithChanges(variance, false)
+ }
+ }
+
+ protected open fun processProjection(projection: Projection): AnyWithChanges<Projection> =
+ when (projection) {
+ is Bound -> processBound(projection)
+ is Variance<Bound> -> processVariance(projection)
+ else -> AnyWithChanges(projection, false)
+ }
+
+ protected open fun processGenericTypeConstructor(genericTypeConstructor: GenericTypeConstructor): AnyWithChanges<GenericTypeConstructor> {
+ val projections = genericTypeConstructor.projections.map { processProjection(it) }
+
+ val wasChanged = projections.any { it.changed }
+ return (genericTypeConstructor.takeIf { !wasChanged } ?: genericTypeConstructor.copy(
+ projections = projections.mapNotNull { it.target }
+ )).let { AnyWithChanges(it, wasChanged) }
+ }
+
+ protected open fun processFunctionalTypeConstructor(functionalTypeConstructor: FunctionalTypeConstructor): AnyWithChanges<FunctionalTypeConstructor> {
+ val projections = functionalTypeConstructor.projections.map { processProjection(it) }
+
+ val wasChanged = projections.any { it.changed }
+ return (functionalTypeConstructor.takeIf { !wasChanged } ?: functionalTypeConstructor.copy(
+ projections = projections.mapNotNull { it.target }
+ )).let { AnyWithChanges(it, wasChanged) }
+ }
+
+ protected open fun processTypeAlias(dTypeAlias: DTypeAlias): AnyWithChanges<DTypeAlias> {
+ val underlyingType = dTypeAlias.underlyingType.mapValues { processBound(it.value) }
+ val generics = dTypeAlias.generics.map { processTypeParameter(it) }
+
+ val wasChanged = underlyingType.any { it.value.changed } || generics.any { it.changed }
+ return (dTypeAlias.takeIf { !wasChanged } ?: dTypeAlias.copy(
+ underlyingType = underlyingType.mapValues { it.value.target ?: dTypeAlias.underlyingType.getValue(it.key) },
+ generics = generics.mapNotNull { it.target }
+ )).let { AnyWithChanges(it, wasChanged) }
+ }
+
+
+ protected data class AnyWithChanges<out T>(val target: T?, val changed: Boolean = false)
+} \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/transformers/documentables/KotlinArrayDocumentableReplacerTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/KotlinArrayDocumentableReplacerTransformer.kt
new file mode 100644
index 00000000..251422f4
--- /dev/null
+++ b/plugins/base/src/main/kotlin/transformers/documentables/KotlinArrayDocumentableReplacerTransformer.kt
@@ -0,0 +1,63 @@
+package org.jetbrains.dokka.base.transformers.documentables
+
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.plugability.DokkaContext
+
+class KotlinArrayDocumentableReplacerTransformer(context: DokkaContext):
+ DocumentableReplacerTransformer(context) {
+
+ private fun Documentable.isJVM() =
+ sourceSets.any{ it.analysisPlatform == Platform.jvm }
+
+ override fun processGenericTypeConstructor(genericTypeConstructor: GenericTypeConstructor): AnyWithChanges<GenericTypeConstructor> =
+ genericTypeConstructor.takeIf { genericTypeConstructor.dri == DRI("kotlin", "Array") }
+ ?.let {
+ with(it.projections.firstOrNull() as? Variance<Bound>) {
+ with(this?.inner as? GenericTypeConstructor) {
+ when (this?.dri) {
+ DRI("kotlin", "Int") ->
+ AnyWithChanges(
+ GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList()),
+ true)
+ DRI("kotlin", "Boolean") ->
+ AnyWithChanges(
+ GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()),
+ true)
+ DRI("kotlin", "Float") ->
+ AnyWithChanges(
+ GenericTypeConstructor(DRI("kotlin", "FloatArray"), emptyList()),
+ true)
+ DRI("kotlin", "Double") ->
+ AnyWithChanges(
+ GenericTypeConstructor(DRI("kotlin", "DoubleArray"), emptyList()),
+ true)
+ DRI("kotlin", "Long") ->
+ AnyWithChanges(
+ GenericTypeConstructor(DRI("kotlin", "LongArray"), emptyList()),
+ true)
+ DRI("kotlin", "Short") ->
+ AnyWithChanges(
+ GenericTypeConstructor(DRI("kotlin", "ShortArray"), emptyList()),
+ true)
+ DRI("kotlin", "Char") ->
+ AnyWithChanges(
+ GenericTypeConstructor(DRI("kotlin", "CharArray"), emptyList()),
+ true)
+ DRI("kotlin", "Byte") ->
+ AnyWithChanges(
+ GenericTypeConstructor(DRI("kotlin", "ByteArray"), emptyList()),
+ true)
+ else -> null
+ }
+ }
+ }
+ }
+ ?: super.processGenericTypeConstructor(genericTypeConstructor)
+
+ override fun processModule(module: DModule): AnyWithChanges<DModule> =
+ if(module.isJVM())
+ super.processModule(module)
+ else AnyWithChanges(module)
+} \ No newline at end of file
diff --git a/plugins/base/src/test/kotlin/filter/KotlinArrayDocumentableReplacerTest.kt b/plugins/base/src/test/kotlin/filter/KotlinArrayDocumentableReplacerTest.kt
new file mode 100644
index 00000000..b9b1dc1e
--- /dev/null
+++ b/plugins/base/src/test/kotlin/filter/KotlinArrayDocumentableReplacerTest.kt
@@ -0,0 +1,199 @@
+package filter
+
+import com.jetbrains.rd.util.firstOrNull
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+
+class KotlinArrayDocumentableReplacerTest : BaseAbstractTest() {
+ private val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/main/kotlin/basic/Test.kt")
+ }
+ }
+ }
+
+ @Test
+ fun `function with array type params`() {
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package example
+ |
+ |fun testFunction(param1: Array<Int>, param2: Array<Boolean>,
+ | param3: Array<Float>, param4: Array<Double>,
+ | param5: Array<Long>, param6: Array<Short>,
+ | param7: Array<Char>, param8: Array<Byte>) { }
+ |
+ |
+ |
+ """.trimMargin(),
+ configuration
+ ) {
+ preMergeDocumentablesTransformationStage = {
+ val params = it.firstOrNull()?.packages?.firstOrNull()?.functions?.firstOrNull()?.parameters
+
+ val typeArrayNames = listOf("IntArray", "BooleanArray", "FloatArray", "DoubleArray", "LongArray", "ShortArray",
+ "CharArray", "ByteArray")
+
+ Assertions.assertEquals(typeArrayNames.size, params?.size)
+ params?.forEachIndexed{ i, param ->
+ Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin", typeArrayNames[i]), emptyList()),
+ param.type)
+ }
+ }
+ }
+ }
+ @Test
+ fun `function with specific parameters of array type`() {
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package example
+ |
+ |fun testFunction(param1: Array<Array<Int>>, param2: (Array<Int>) -> Array<Int>) { }
+ |
+ |
+ |
+ """.trimMargin(),
+ configuration
+ ) {
+ preMergeDocumentablesTransformationStage = {
+ val params = it.firstOrNull()?.packages?.firstOrNull()?.functions?.firstOrNull()?.parameters
+ Assertions.assertEquals(
+ Invariance(GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList())),
+ (params?.firstOrNull()?.type as? GenericTypeConstructor)?.projections?.firstOrNull())
+ Assertions.assertEquals(
+ Invariance(GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList())),
+ (params?.get(1)?.type as? FunctionalTypeConstructor)?.projections?.get(0))
+ Assertions.assertEquals(
+ Invariance(GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList())),
+ (params?.get(1)?.type as? FunctionalTypeConstructor)?.projections?.get(1))
+ }
+ }
+ }
+ @Test
+ fun `property with array type`() {
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package example
+ |
+ |class MyTest {
+ | val isEmpty: Array<Boolean>
+ | get() = emptyList
+ | set(value) {
+ | field = value
+ | }
+ |}
+ |
+ |
+ """.trimMargin(),
+ configuration
+ ) {
+ preMergeDocumentablesTransformationStage = {
+ val myTestClass = it.firstOrNull()?.packages?.firstOrNull()?.classlikes?.firstOrNull()
+ val property = myTestClass?.properties?.firstOrNull()
+
+ Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()),
+ property?.type)
+ Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()),
+ property?.getter?.type)
+ Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin", "BooleanArray"), emptyList()),
+ property?.setter?.parameters?.firstOrNull()?.type)
+ }
+ }
+ }
+ @Test
+ fun `typealias with array type`() {
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package example
+ |
+ |typealias arr = Array<Int>
+ |
+ |
+ """.trimMargin(),
+ configuration
+ ) {
+ preMergeDocumentablesTransformationStage = {
+ val arrTypealias = it.firstOrNull()?.packages?.firstOrNull()?.typealiases?.firstOrNull()
+
+ Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList()),
+ arrTypealias?.underlyingType?.firstOrNull()?.value)
+ }
+ }
+ }
+ @Test
+ fun `generic fun and class`() {
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package example
+ |
+ |fun<T : Array<Int>> testFunction() { }
+ |class<T : Array<Int>> myTestClass{ }
+ |
+ |
+ """.trimMargin(),
+ configuration
+ ) {
+ preMergeDocumentablesTransformationStage = {
+ val testFun = it.firstOrNull()?.packages?.firstOrNull()?.functions?.firstOrNull()
+ val myTestClass = it.firstOrNull()?.packages?.firstOrNull()?.classlikes?.firstOrNull() as? DClass
+
+ Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin","IntArray"), emptyList()),
+ testFun?.generics?.firstOrNull()?.bounds?.firstOrNull())
+ Assertions.assertEquals(GenericTypeConstructor(DRI("kotlin","IntArray"), emptyList()),
+ myTestClass?.generics?.firstOrNull()?.bounds?.firstOrNull())
+ }
+ }
+ }
+ @Test
+ fun `no jvm source set`() {
+ val configurationWithNoJVM = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/main/kotlin/basic/Test.kt")
+ analysisPlatform = "jvm"
+ }
+ sourceSet {
+ sourceRoots = listOf("src/main/kotlin/basic/TestJS.kt")
+ analysisPlatform = "js"
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package example
+ |
+ |fun testFunction(param: Array<Int>)
+ |
+ |
+ |/src/main/kotlin/basic/TestJS.kt
+ |package example
+ |
+ |fun testFunction(param: Array<Int>)
+ """.trimMargin(),
+ configurationWithNoJVM
+ ) {
+ preMergeDocumentablesTransformationStage = {
+ val paramsJS = it[1].packages.firstOrNull()?.functions?.firstOrNull()?.parameters
+ Assertions.assertNotEquals(
+ GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList()),
+ paramsJS?.firstOrNull()?.type)
+
+ val paramsJVM = it.firstOrNull()?.packages?.firstOrNull()?.functions?.firstOrNull()?.parameters
+ Assertions.assertEquals(
+ GenericTypeConstructor(DRI("kotlin", "IntArray"), emptyList()),
+ paramsJVM?.firstOrNull()?.type)
+ }
+ }
+ }
+} \ No newline at end of file