aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.idea/compiler.xml16
-rw-r--r--core/src/main/kotlin/model/properties/PropertyContainer.kt3
-rw-r--r--core/src/main/kotlin/pages/ContentNodes.kt4
-rw-r--r--plugins/base/src/main/kotlin/DokkaBase.kt3
-rw-r--r--plugins/base/src/main/kotlin/transformers/documentables/ExtensionExtractorTransformer.kt127
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt5
6 files changed, 156 insertions, 2 deletions
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 00000000..8144c3cf
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <wildcardResourcePatterns>
+ <entry name="!?*.java" />
+ <entry name="!?*.form" />
+ <entry name="!?*.class" />
+ <entry name="!?*.groovy" />
+ <entry name="!?*.scala" />
+ <entry name="!?*.flex" />
+ <entry name="!?*.kt" />
+ <entry name="!?*.clj" />
+ </wildcardResourcePatterns>
+ <bytecodeTargetLevel target="1.8" />
+ </component>
+</project> \ No newline at end of file
diff --git a/core/src/main/kotlin/model/properties/PropertyContainer.kt b/core/src/main/kotlin/model/properties/PropertyContainer.kt
index e1e0250e..6009bfe0 100644
--- a/core/src/main/kotlin/model/properties/PropertyContainer.kt
+++ b/core/src/main/kotlin/model/properties/PropertyContainer.kt
@@ -23,6 +23,9 @@ class PropertyContainer<C : Any> internal constructor(
}
}
+operator fun <D: Any> PropertyContainer<D>.plus(prop: ExtraProperty<D>?): PropertyContainer<D> =
+ if (prop == null) this else PropertyContainer(map + (prop.key to prop))
+
interface WithExtraProperties<C : Any> {
val extra: PropertyContainer<C>
diff --git a/core/src/main/kotlin/pages/ContentNodes.kt b/core/src/main/kotlin/pages/ContentNodes.kt
index dc23a082..4e4c0fca 100644
--- a/core/src/main/kotlin/pages/ContentNodes.kt
+++ b/core/src/main/kotlin/pages/ContentNodes.kt
@@ -214,11 +214,11 @@ interface Kind
enum class ContentKind : Kind {
Comment, Constructors, Functions, Parameters, Properties, Classlikes, Packages, Symbol, Sample, Main, BriefComment,
- Empty, Source, TypeAliases, Cover, Inheritors, SourceSetDependantHint, Annotations;
+ Empty, Source, TypeAliases, Cover, Inheritors, SourceSetDependantHint, Extensions, Annotations;
companion object {
private val platformTagged =
- setOf(Constructors, Functions, Properties, Classlikes, Packages, Source, TypeAliases, Inheritors)
+ setOf(Constructors, Functions, Properties, Classlikes, Packages, Source, TypeAliases, Inheritors, Extensions)
fun shouldBePlatformTagged(kind: Kind): Boolean = kind in platformTagged
}
diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt
index f1b0d969..580b3f6b 100644
--- a/plugins/base/src/main/kotlin/DokkaBase.kt
+++ b/plugins/base/src/main/kotlin/DokkaBase.kt
@@ -102,6 +102,9 @@ class DokkaBase : DokkaPlugin() {
CoreExtensions.documentableTransformer with ReportUndocumentedTransformer()
}
+ val extensionsExtractor by extending {
+ CoreExtensions.documentableTransformer with ExtensionExtractorTransformer()
+ }
val documentableToPageTranslator by extending(isFallback = true) {
CoreExtensions.documentableToPageTranslator providing { ctx ->
diff --git a/plugins/base/src/main/kotlin/transformers/documentables/ExtensionExtractorTransformer.kt b/plugins/base/src/main/kotlin/transformers/documentables/ExtensionExtractorTransformer.kt
new file mode 100644
index 00000000..2da25d4b
--- /dev/null
+++ b/plugins/base/src/main/kotlin/transformers/documentables/ExtensionExtractorTransformer.kt
@@ -0,0 +1,127 @@
+package org.jetbrains.dokka.base.transformers.documentables
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.ClosedReceiveChannelException
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.toList
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.DriOfAny
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.properties.ExtraProperty
+import org.jetbrains.dokka.model.properties.MergeStrategy
+import org.jetbrains.dokka.model.properties.plus
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer
+
+
+class ExtensionExtractorTransformer : DocumentableTransformer {
+ override fun invoke(original: DModule, context: DokkaContext): DModule = runBlocking(Dispatchers.Default) {
+ val channel = Channel<Pair<DRI, Callable>>(10)
+ launch {
+ coroutineScope {
+ original.packages.forEach { launch { collectExtensions(it, channel) } }
+ }
+ channel.close()
+ }
+ val extensionMap = channel.consumeAsFlow().toList().toMultiMap()
+
+ val newPackages = original.packages.map { async { it.addExtensionInformation(extensionMap) } }
+ original.copy(packages = newPackages.awaitAll())
+ }
+}
+
+private suspend fun <T : Documentable> T.addExtensionInformation(
+ extensionMap: Map<DRI, List<Callable>>
+): T = coroutineScope {
+ val newClasslikes = (this@addExtensionInformation as? WithScope)
+ ?.classlikes
+ ?.map { async { it.addExtensionInformation(extensionMap) } }
+ .orEmpty()
+
+ @Suppress("UNCHECKED_CAST")
+ when (this@addExtensionInformation) {
+ is DPackage -> {
+ val newTypealiases = typealiases.map { async { it.addExtensionInformation(extensionMap) } }
+ copy(classlikes = newClasslikes.awaitAll(), typealiases = newTypealiases.awaitAll())
+ }
+ is DClass -> copy(classlikes = newClasslikes.awaitAll(), extra = extra + extensionMap.find(dri))
+ is DEnum -> copy(classlikes = newClasslikes.awaitAll(), extra = extra + extensionMap.find(dri))
+ is DInterface -> copy(classlikes = newClasslikes.awaitAll(), extra = extra + extensionMap.find(dri))
+ is DObject -> copy(classlikes = newClasslikes.awaitAll(), extra = extra + extensionMap.find(dri))
+ is DAnnotation -> copy(classlikes = newClasslikes.awaitAll(), extra = extra + extensionMap.find(dri))
+ is DTypeAlias -> copy(extra = extra + extensionMap.find(dri))
+ else -> throw IllegalStateException(
+ "${this@addExtensionInformation::class.simpleName} is not expected to have extensions"
+ )
+ } as T
+}
+
+private fun Map<DRI, List<Callable>>.find(dri: DRI) = get(dri)?.toSet()?.let(::CallableExtensions)
+
+private suspend fun collectExtensions(
+ documentable: Documentable,
+ channel: SendChannel<Pair<DRI, Callable>>
+): Unit = coroutineScope {
+ if (documentable is WithScope) {
+ documentable.classlikes.forEach {
+ launch { collectExtensions(it, channel) }
+ }
+
+ if (documentable is DObject || documentable is DPackage) {
+ (documentable.properties.asSequence() + documentable.functions.asSequence())
+ .flatMap(Callable::asPairsWithReceiverDRIs)
+ .forEach { channel.send(it) }
+ }
+ }
+}
+
+
+private fun Callable.asPairsWithReceiverDRIs(): Sequence<Pair<DRI, Callable>> =
+ receiver?.type?.let(::findReceiverDRIs).orEmpty().map { it to this }
+
+// In normal cases we return at max one DRI, but sometimes receiver type can be bound by more than one type constructor
+// for example `fun <T> T.example() where T: A, T: B` is extension of both types A and B
+// Note: in some cases returning empty sequence doesn't mean that we cannot determine the DRI but only that we don't
+// care about it since there is nowhere to put documentation of given extension.
+private fun Callable.findReceiverDRIs(bound: Bound): Sequence<DRI> = when (bound) {
+ is Nullable -> findReceiverDRIs(bound.inner)
+ is OtherParameter ->
+ if (this is DFunction && bound.declarationDRI == this.dri)
+ generics.find { it.name == bound.name }?.bounds?.asSequence()?.flatMap(::findReceiverDRIs).orEmpty()
+ else
+ emptySequence()
+ is TypeConstructor -> sequenceOf(bound.dri)
+ is PrimitiveJavaType -> emptySequence()
+ is Void -> emptySequence()
+ is JavaObject -> sequenceOf(DriOfAny)
+ is Dynamic -> sequenceOf(DriOfAny)
+ is UnresolvedBound -> emptySequence()
+}
+
+private fun <T, U> Iterable<Pair<T, U>>.toMultiMap(): Map<T, List<U>> =
+ groupBy(Pair<T, *>::first, Pair<*, U>::second)
+
+data class CallableExtensions(val extensions: Set<Callable>) : ExtraProperty<Documentable> {
+ companion object Key : ExtraProperty.Key<Documentable, CallableExtensions> {
+ override fun mergeStrategyFor(left: CallableExtensions, right: CallableExtensions) =
+ MergeStrategy.Replace(CallableExtensions(left.extensions + right.extensions))
+ }
+
+ override val key = Key
+}
+
+//TODO IMPORTANT remove this terrible hack after updating to 1.4-M3
+fun <T : Any> ReceiveChannel<T>.consumeAsFlow(): Flow<T> = flow {
+ try {
+ while (true) {
+ emit(receive())
+ }
+ } catch (_: ClosedReceiveChannelException) {
+ // cool and good
+ }
+}.flowOn(Dispatchers.Default) \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
index c94b1814..21a91f2c 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
@@ -2,6 +2,7 @@ package org.jetbrains.dokka.base.translators.documentables
import org.jetbrains.dokka.DokkaConfiguration
import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions
import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo
import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder
@@ -231,6 +232,10 @@ open class DefaultPageCreator(
}
}
+contentForScope(c, c.dri, c.sourceSets)
+
+ @Suppress("UNCHECKED_CAST")
+ val extensions = (c as WithExtraProperties<DClasslike>).extra[CallableExtensions]?.extensions?.filterIsInstance<Documentable>()
+ divergentBlock("Extensions", extensions.orEmpty(), ContentKind.Extensions, mainExtra + SimpleAttr.header("Extensions"))
}
}